@crouton-kit/capture 1.3.19 → 1.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/capture +159 -45
- package/package.json +1 -1
- package/src/cdp/args.ts +8 -1
- package/src/cdp/color-scheme.ts +47 -0
- package/src/cdp/commands/measure/index.ts +1 -1
- package/src/cdp/commands/measure/snap.ts +13 -6
- package/src/cdp/commands/page/click.ts +32 -4
- package/src/cdp/commands/page/index.ts +1 -1
- package/src/cdp/commands/page/scroll.ts +6 -4
- package/src/cdp/commands/page/shot.ts +33 -9
- package/src/cdp/commands/page/type.ts +6 -4
- package/src/cdp/measure/collectors/styles.ts +67 -43
- package/src/cdp/measure/snapshot.ts +26 -13
- package/src/cdp/measure/types.ts +4 -0
- package/src/cdp/types.ts +2 -0
|
@@ -199,28 +199,30 @@ export class SnapshotCaptureTimeout extends Error {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
/** Bounds one snapshot CDP request and labels the phase that owns it. */
|
|
202
|
-
function boundedClient(client: CDPClient, phase: string, partialPath: string, onTimeout?: () => void): CDPClient {
|
|
202
|
+
function boundedClient(client: CDPClient, phase: string, partialPath: string, onTimeout?: (error: SnapshotCaptureTimeout) => void): CDPClient {
|
|
203
203
|
const bounded = Object.create(client) as CDPClient;
|
|
204
204
|
bounded.send = (method: string, params: Record<string, unknown> = {}, timeout = 60_000, sessionId?: string): Promise<unknown> => {
|
|
205
205
|
const timeoutMs = Math.min(timeout, SNAPSHOT_REQUEST_TIMEOUT_MS);
|
|
206
206
|
let timer: NodeJS.Timeout | undefined;
|
|
207
207
|
let timedOut = false;
|
|
208
|
-
const markTimedOut = (): void => {
|
|
208
|
+
const markTimedOut = (error: SnapshotCaptureTimeout): void => {
|
|
209
209
|
if (timedOut) return;
|
|
210
210
|
timedOut = true;
|
|
211
|
-
onTimeout?.();
|
|
211
|
+
onTimeout?.(error);
|
|
212
212
|
};
|
|
213
213
|
const deadline = new Promise<never>((_, reject) => {
|
|
214
214
|
timer = setTimeout(() => {
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
const error = new SnapshotCaptureTimeout(phase, method, timeoutMs, partialPath);
|
|
216
|
+
markTimedOut(error);
|
|
217
|
+
reject(error);
|
|
217
218
|
}, timeoutMs);
|
|
218
219
|
});
|
|
219
220
|
const request = Promise.resolve().then(() => client.send(method, params, timeoutMs, sessionId)).catch((error: unknown) => {
|
|
220
221
|
if (error instanceof SnapshotCaptureTimeout) throw error;
|
|
221
222
|
if (error instanceof Error && error.message.startsWith('CDP request timeout')) {
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
const timeoutError = new SnapshotCaptureTimeout(phase, method, timeoutMs, partialPath);
|
|
224
|
+
markTimedOut(timeoutError);
|
|
225
|
+
throw timeoutError;
|
|
224
226
|
}
|
|
225
227
|
throw error;
|
|
226
228
|
});
|
|
@@ -250,6 +252,7 @@ export async function captureSnapshotSubstrate(options: CaptureSnapshotOptions):
|
|
|
250
252
|
const pixels = options.pixels ?? false;
|
|
251
253
|
const state = options.state ?? [];
|
|
252
254
|
const viewport = options.viewport ?? null;
|
|
255
|
+
const colorScheme = options.colorScheme;
|
|
253
256
|
// Apply the shared artifact-string cap once at the source. URL contents
|
|
254
257
|
// otherwise survive unchanged in meta.json, the result, and collector context.
|
|
255
258
|
const url = options.url ? sanitizeString(options.url) : null;
|
|
@@ -386,25 +389,34 @@ export async function captureSnapshotSubstrate(options: CaptureSnapshotOptions):
|
|
|
386
389
|
|
|
387
390
|
const startedAt = Date.now();
|
|
388
391
|
let requestTimeouts = 0;
|
|
392
|
+
let timeoutError: SnapshotCaptureTimeout | undefined;
|
|
389
393
|
const collectorWriter = makeCollectorWriter(ctx.write);
|
|
390
394
|
try {
|
|
391
|
-
// Await the collector itself, not a wall-clock race around it.
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
// connection collector starts.
|
|
395
|
+
// Await the collector itself, not a wall-clock race around it. The
|
|
396
|
+
// collector owns its cleanup, and timeout handling below decides whether
|
|
397
|
+
// this capture can safely move on to another shared-connection collector.
|
|
395
398
|
await collector.fn({
|
|
396
399
|
...ctx,
|
|
397
|
-
client: boundedClient(client, `collector:${collector.name}`, dir, () => {
|
|
400
|
+
client: boundedClient(client, `collector:${collector.name}`, dir, (error) => {
|
|
401
|
+
requestTimeouts += 1;
|
|
402
|
+
timeoutError ??= error;
|
|
403
|
+
}),
|
|
398
404
|
write: collectorWriter.writer,
|
|
399
405
|
});
|
|
406
|
+
// A styles timeout may be caught per-record by the provenance collector,
|
|
407
|
+
// but its browser operation can still belong to this shared connection.
|
|
408
|
+
// End this capture instead of letting another collector issue commands
|
|
409
|
+
// under that outstanding operation.
|
|
410
|
+
if (collector.name === 'styles' && timeoutError) throw timeoutError;
|
|
400
411
|
const ms = Date.now() - startedAt;
|
|
401
412
|
collectorWriter.commit();
|
|
402
413
|
return requestTimeouts > 0
|
|
403
414
|
? { name: collector.name, status: 'degraded', ms, requestTimeouts }
|
|
404
415
|
: { name: collector.name, status: 'ok', ms };
|
|
405
|
-
} catch {
|
|
416
|
+
} catch (error) {
|
|
406
417
|
const ms = Date.now() - startedAt;
|
|
407
418
|
collectorWriter.discard();
|
|
419
|
+
if (collector.name === 'styles' && error instanceof SnapshotCaptureTimeout) throw error;
|
|
408
420
|
return { name: collector.name, status: 'error', ms, ...(requestTimeouts > 0 ? { requestTimeouts } : {}) };
|
|
409
421
|
}
|
|
410
422
|
};
|
|
@@ -483,6 +495,7 @@ export async function captureSnapshotSubstrate(options: CaptureSnapshotOptions):
|
|
|
483
495
|
captureUnsettled,
|
|
484
496
|
pixels,
|
|
485
497
|
states: state.map((spec) => sanitizeString(spec)),
|
|
498
|
+
...(colorScheme ? { colorScheme } : {}),
|
|
486
499
|
unstableRegionCount: unstableRegions.length,
|
|
487
500
|
...(freezeAnimations ? { animationsRestored: animationsRestored ?? false } : {}),
|
|
488
501
|
// #8: the browser-wide `Animation.setPlaybackRate({playbackRate:0})`
|
package/src/cdp/measure/types.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface CaptureSnapshotOptions {
|
|
|
53
53
|
/** `--state <state[:selector]>` (repeatable); default `[]`. */
|
|
54
54
|
readonly state?: readonly string[];
|
|
55
55
|
readonly viewport?: string | null;
|
|
56
|
+
/** Transient `prefers-color-scheme` media override used while capturing. */
|
|
57
|
+
readonly colorScheme?: 'dark' | 'light';
|
|
56
58
|
/**
|
|
57
59
|
* Poll interval for the settledness loop; default {@link DEFAULT_POLL_INTERVAL_MS}
|
|
58
60
|
* (100ms). Not part of the CLI surface — a test-only override so the
|
|
@@ -109,6 +111,8 @@ export interface SnapshotMeta extends SnapMeta {
|
|
|
109
111
|
readonly captureUnsettled: boolean;
|
|
110
112
|
readonly pixels: boolean;
|
|
111
113
|
readonly states: readonly string[];
|
|
114
|
+
/** Present only when this snapshot applied a transient color-scheme override. */
|
|
115
|
+
readonly colorScheme?: 'dark' | 'light';
|
|
112
116
|
readonly unstableRegionCount: number;
|
|
113
117
|
/** Present only when `freezeAnimations` is true — whether every animation `freezeAnimationsBeforeCapture` paused was confirmed resumed to its pre-freeze `playState` (I-6). `false` means restoration could not be guaranteed (origin capture failed, or the restore call itself threw) — never omitted to imply a clean restore. */
|
|
114
118
|
readonly animationsRestored?: boolean;
|
package/src/cdp/types.ts
CHANGED
|
@@ -38,6 +38,8 @@ export interface ParsedArgs {
|
|
|
38
38
|
into?: string;
|
|
39
39
|
noScreenshot?: boolean;
|
|
40
40
|
viewport?: string;
|
|
41
|
+
/** `page shot|measure snap --color-scheme dark|light` — transient media override. */
|
|
42
|
+
colorScheme?: string;
|
|
41
43
|
fullPage?: boolean;
|
|
42
44
|
help?: boolean;
|
|
43
45
|
/** `--all` — boolean scope-widener (per-leaf semantics). */
|