shots: feature rotate, per-language frame fallback, layout-aware capture selectors
This commit is contained in:
parent
2e1c7cdc3c
commit
61b5fa0623
15 changed files with 355 additions and 17 deletions
|
|
@ -24,6 +24,7 @@ import type { ShotSpec, ShotsCapture } from '../lib/config';
|
|||
import { log, warn } from '../lib/log';
|
||||
import { type AuthPlan, expiredHint, landedOnLogin, maskSecrets, safeUrl } from './auth';
|
||||
import { shotsFor } from './devices';
|
||||
import { applySelectors, candidates, probe } from './selectors';
|
||||
import type { FrameDims } from './lib';
|
||||
|
||||
type Device = 'phone' | 'tablet';
|
||||
|
|
@ -166,6 +167,7 @@ export async function captureWeb(browser: any, cap: ShotsCapture, jobs: WebJob[]
|
|||
if (!expired.has(safeUrl(s.page.url()))) { expired.add(safeUrl(s.page.url())); warn('shots', `${j.lang}/${j.device}/${j.name}: ${mask(expiredHint(s.page.url()))}`); }
|
||||
throw new Error(`landed on the sign-in page ${safeUrl(s.page.url())}`);
|
||||
}
|
||||
await applySelectors(s.page, j.spec, { device: j.device, w: o.dims[j.device].w, h: o.dims[j.device].h }, waitTimeout); // waitSelector, click: a miss names the selector and the size
|
||||
if (j.spec.waitFor) await s.page.waitForFunction(j.spec.waitFor, null, { timeout: waitTimeout, polling: 50 });
|
||||
t.waitFor = Date.now() - t0; t0 = Date.now();
|
||||
for (const js of j.spec.eval ?? []) await s.page.evaluate(js);
|
||||
|
|
@ -245,3 +247,32 @@ export function writeCaptureState(root: string, report: CaptureReport, cache: Ca
|
|||
writeFileSync(cachePath(root), JSON.stringify(Object.fromEntries(Object.entries(cache).sort()), null, 1) + '\n');
|
||||
writeFileSync(join(root, '.storebound', 'capture-report.json'), JSON.stringify(report, null, 1) + '\n');
|
||||
}
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/** `shots capture --dry --probe`: opens each page (no screenshot), runs the shot's waitFor, and reports per size which
|
||||
* selector candidates match. Returns the log lines; `missed` counts shots where no candidate of a list matched. */
|
||||
export async function probeSelectors(browser: any, jobs: WebJob[], o: { dims: Record<Device, FrameDims>; auth?: AuthPlan; timeoutMs?: number }): Promise<{ lines: string[]; missed: number }> {
|
||||
const lines: string[] = []; let missed = 0;
|
||||
for (const j of jobs) {
|
||||
const lists = [['waitSelector', candidates(j.spec.waitSelector, j.device)], ...(Array.isArray(j.spec.click) && j.spec.click.some(c => typeof c !== 'string') ? j.spec.click : j.spec.click === undefined ? [] : [j.spec.click])
|
||||
.map((c, i) => [`click[${i}]`, candidates(c as any, j.device)] as const)].filter(([, l]) => l.length) as [string, string[]][];
|
||||
if (!lists.length) continue;
|
||||
const d = o.dims[j.device];
|
||||
const c = await browser.newContext({ viewport: { width: d.w, height: d.h }, deviceScaleFactor: 1, hasTouch: true, isMobile: true, serviceWorkers: 'block', locale: j.lang, ...(o.auth?.contextOptions ?? {}) });
|
||||
try {
|
||||
if (o.auth) await o.auth.prepare(c);
|
||||
const page = await c.newPage();
|
||||
await page.goto(j.url, { waitUntil: 'domcontentloaded', timeout: 90000 });
|
||||
if (j.spec.waitFor) await page.waitForFunction(j.spec.waitFor, null, { timeout: o.timeoutMs ?? 20000, polling: 50 }).catch(() => lines.push(`${j.frame} ${d.w}x${d.h}: waitFor did not become true in ${(o.timeoutMs ?? 20000) / 1000}s`));
|
||||
for (const [what, list] of lists) {
|
||||
await page.waitForTimeout(500);
|
||||
const res = await probe(page, list);
|
||||
const hit = res.find(r => r.matches > 0);
|
||||
if (!hit) missed++;
|
||||
lines.push(`${j.frame} ${j.device} ${d.w}x${d.h} ${what}: ${hit ? `ok via ${hit.selector}` : 'MISS'} (${res.map(r => `${r.selector} -> ${r.matches < 0 ? 'invalid' : r.matches}`).join('; ')})`);
|
||||
}
|
||||
} catch (e) { missed++; lines.push(`${j.frame}: probe failed: ${(e as Error).message.split('\n')[0]!.slice(0, 200)}`); } finally { await c.close(); }
|
||||
}
|
||||
return { lines, missed };
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue