@design.estate/wcctools 6.2.0 → 7.0.0
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/changelog.md +21 -0
- package/dist_shell/bundle.js +4 -4
- package/dist_shell/bundle.js.map +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/capture/classes.capturebrowser.d.ts +65 -0
- package/dist_ts/capture/classes.capturebrowser.js +251 -0
- package/dist_ts/capture/classes.captureservice.d.ts +39 -0
- package/dist_ts/capture/classes.captureservice.js +119 -0
- package/dist_ts/capture/classes.screencast.d.ts +47 -0
- package/dist_ts/capture/classes.screencast.js +127 -0
- package/dist_ts/capture/errors.d.ts +14 -0
- package/dist_ts/capture/errors.js +23 -0
- package/dist_ts/capture/images.d.ts +3 -0
- package/dist_ts/capture/images.js +58 -0
- package/dist_ts/capture/index.d.ts +4 -0
- package/dist_ts/capture/index.js +5 -0
- package/dist_ts/capture/interaction.d.ts +11 -0
- package/dist_ts/capture/interaction.js +138 -0
- package/dist_ts/capture/navigationguard.d.ts +44 -0
- package/dist_ts/capture/navigationguard.js +129 -0
- package/dist_ts/capture/previewpage.d.ts +29 -0
- package/dist_ts/capture/previewpage.js +96 -0
- package/dist_ts/capture/request.d.ts +69 -0
- package/dist_ts/capture/request.js +239 -0
- package/dist_ts/classes.devserver.d.ts +25 -9
- package/dist_ts/classes.devserver.js +45 -14
- package/dist_ts/classes.hostpolicy.d.ts +63 -0
- package/dist_ts/classes.hostpolicy.js +171 -0
- package/dist_ts/cli.js +25 -3
- package/dist_ts/cli.screenshot.d.ts +6 -0
- package/dist_ts/cli.screenshot.js +89 -0
- package/dist_ts/plugins.d.ts +6 -2
- package/dist_ts/plugins.js +8 -3
- package/dist_ts_interfaces/capture.d.ts +151 -0
- package/dist_ts_interfaces/capture.js +2 -0
- package/dist_ts_interfaces/index.d.ts +1 -0
- package/dist_ts_interfaces/index.js +2 -1
- package/dist_ts_interfaces/requests.d.ts +24 -0
- package/dist_ts_shell/elements/wcc-recording-panel.d.ts +9 -0
- package/dist_ts_shell/elements/wcc-recording-panel.js +12 -1
- package/dist_ts_shell/services/framesampler.service.d.ts +23 -0
- package/dist_ts_shell/services/framesampler.service.js +101 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +2 -1
- package/readme.md +104 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/capture/classes.capturebrowser.ts +290 -0
- package/ts/capture/classes.captureservice.ts +152 -0
- package/ts/capture/classes.screencast.ts +152 -0
- package/ts/capture/errors.ts +25 -0
- package/ts/capture/images.ts +61 -0
- package/ts/capture/index.ts +4 -0
- package/ts/capture/interaction.ts +146 -0
- package/ts/capture/navigationguard.ts +147 -0
- package/ts/capture/previewpage.ts +130 -0
- package/ts/capture/request.ts +304 -0
- package/ts/classes.devserver.ts +55 -13
- package/ts/classes.hostpolicy.ts +201 -0
- package/ts/cli.screenshot.ts +95 -0
- package/ts/cli.ts +25 -2
- package/ts/plugins.ts +9 -2
- package/ts_interfaces/capture.ts +144 -0
- package/ts_interfaces/index.ts +1 -0
- package/ts_interfaces/requests.ts +39 -0
- package/ts_shell/elements/wcc-recording-panel.ts +12 -0
- package/ts_shell/services/framesampler.service.ts +112 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { toWccImage } from './images.js';
|
|
3
|
+
/**
|
|
4
|
+
* Records a page's screencast (Chrome DevTools Protocol `Page.startScreencast`): JPEG frames the
|
|
5
|
+
* browser sends whenever the page paints, kept at a bounded rate and count. The page's last
|
|
6
|
+
* painted frame is always kept.
|
|
7
|
+
*/
|
|
8
|
+
export class WccScreencastRecorder {
|
|
9
|
+
page;
|
|
10
|
+
options;
|
|
11
|
+
session = null;
|
|
12
|
+
startedAt = 0;
|
|
13
|
+
kept = [];
|
|
14
|
+
/** The newest frame that arrived too soon after the last kept one. */
|
|
15
|
+
latest = null;
|
|
16
|
+
stopped = false;
|
|
17
|
+
ackFailure = null;
|
|
18
|
+
minFrameGapMs;
|
|
19
|
+
maxKeptFrames;
|
|
20
|
+
constructor(page, options) {
|
|
21
|
+
this.page = page;
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.minFrameGapMs = 1000 / (options.maxFps ?? 10);
|
|
24
|
+
this.maxKeptFrames = options.maxKeptFrames ?? 200;
|
|
25
|
+
}
|
|
26
|
+
/** The time the recording started, as `Date.now()` read it. */
|
|
27
|
+
getStartedAt() {
|
|
28
|
+
return this.startedAt;
|
|
29
|
+
}
|
|
30
|
+
async start() {
|
|
31
|
+
const session = await this.page.createCDPSession();
|
|
32
|
+
this.session = session;
|
|
33
|
+
session.on('Page.screencastFrame', this.onFrame);
|
|
34
|
+
this.startedAt = Date.now();
|
|
35
|
+
await session.send('Page.startScreencast', {
|
|
36
|
+
format: 'jpeg',
|
|
37
|
+
quality: this.options.quality,
|
|
38
|
+
maxWidth: this.options.maxWidth,
|
|
39
|
+
maxHeight: this.options.maxHeight,
|
|
40
|
+
everyNthFrame: 1,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Stops the screencast, detaches from the page and answers the kept frames in time order. */
|
|
44
|
+
async stop() {
|
|
45
|
+
const session = this.session;
|
|
46
|
+
this.session = null;
|
|
47
|
+
if (session && !this.stopped) {
|
|
48
|
+
this.stopped = true;
|
|
49
|
+
session.off('Page.screencastFrame', this.onFrame);
|
|
50
|
+
if (!session.detached) {
|
|
51
|
+
try {
|
|
52
|
+
await session.send('Page.stopScreencast');
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
await session.detach();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (this.ackFailure) {
|
|
60
|
+
throw this.ackFailure;
|
|
61
|
+
}
|
|
62
|
+
if (this.latest && this.latest.timestampMs > (this.kept.at(-1)?.timestampMs ?? -1)) {
|
|
63
|
+
this.kept.push(this.latest);
|
|
64
|
+
this.latest = null;
|
|
65
|
+
}
|
|
66
|
+
return this.kept.map((frameArg) => ({
|
|
67
|
+
...toWccImage(Buffer.from(frameArg.dataBase64, 'base64'), 'jpeg'),
|
|
68
|
+
timestampMs: frameArg.timestampMs,
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
onFrame = (eventArg) => {
|
|
72
|
+
const session = this.session;
|
|
73
|
+
if (!session) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// Chrome sends the next frame only once this one is acknowledged
|
|
77
|
+
session.send('Page.screencastFrameAck', { sessionId: eventArg.sessionId }).catch((error) => {
|
|
78
|
+
if (!this.stopped) {
|
|
79
|
+
this.ackFailure ??= error;
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
const paintedAt = eventArg.metadata.timestamp === undefined ? Date.now() : eventArg.metadata.timestamp * 1000;
|
|
83
|
+
const frame = {
|
|
84
|
+
timestampMs: Math.max(0, Math.round(paintedAt - this.startedAt)),
|
|
85
|
+
dataBase64: eventArg.data,
|
|
86
|
+
};
|
|
87
|
+
const lastKept = this.kept.at(-1);
|
|
88
|
+
if (lastKept && frame.timestampMs - lastKept.timestampMs < this.minFrameGapMs) {
|
|
89
|
+
this.latest = frame;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.latest = null;
|
|
93
|
+
this.kept.push(frame);
|
|
94
|
+
if (this.kept.length > this.maxKeptFrames) {
|
|
95
|
+
// Thin out evenly: drop every other frame, keeping the first
|
|
96
|
+
for (let index = this.kept.length - 1; index > 0; index -= 2) {
|
|
97
|
+
this.kept.splice(index, 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Picks at most `maxFramesArg` frames spread evenly over the time the frames cover: for each of
|
|
104
|
+
* that many points in time, the frame on screen at that point. The first and the last frame are
|
|
105
|
+
* always among them.
|
|
106
|
+
*/
|
|
107
|
+
export const selectFrames = (framesArg, maxFramesArg) => {
|
|
108
|
+
if (framesArg.length <= maxFramesArg) {
|
|
109
|
+
return [...framesArg];
|
|
110
|
+
}
|
|
111
|
+
if (maxFramesArg === 1) {
|
|
112
|
+
return [framesArg[framesArg.length - 1]];
|
|
113
|
+
}
|
|
114
|
+
const first = framesArg[0].timestampMs;
|
|
115
|
+
const span = framesArg[framesArg.length - 1].timestampMs - first;
|
|
116
|
+
const picked = new Set();
|
|
117
|
+
let frameIndex = 0;
|
|
118
|
+
for (let point = 0; point < maxFramesArg; point++) {
|
|
119
|
+
const time = point === maxFramesArg - 1 ? Number.POSITIVE_INFINITY : first + (span * point) / (maxFramesArg - 1);
|
|
120
|
+
while (frameIndex + 1 < framesArg.length && framesArg[frameIndex + 1].timestampMs <= time) {
|
|
121
|
+
frameIndex++;
|
|
122
|
+
}
|
|
123
|
+
picked.add(frameIndex);
|
|
124
|
+
}
|
|
125
|
+
return [...picked].sort((a, b) => a - b).map((indexArg) => framesArg[indexArg]);
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zY3JlZW5jYXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY2FwdHVyZS9jbGFzc2VzLnNjcmVlbmNhc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQXVCekM7Ozs7R0FJRztBQUNILE1BQU0sT0FBTyxxQkFBcUI7SUFZYjtJQUNBO0lBWlgsT0FBTyxHQUF1QixJQUFJLENBQUM7SUFDbkMsU0FBUyxHQUFHLENBQUMsQ0FBQztJQUNMLElBQUksR0FBdUIsRUFBRSxDQUFDO0lBQy9DLHNFQUFzRTtJQUM5RCxNQUFNLEdBQTRCLElBQUksQ0FBQztJQUN2QyxPQUFPLEdBQUcsS0FBSyxDQUFDO0lBQ2hCLFVBQVUsR0FBWSxJQUFJLENBQUM7SUFDbEIsYUFBYSxDQUFTO0lBQ3RCLGFBQWEsQ0FBUztJQUV2QyxZQUNtQixJQUFXLEVBQ1gsT0FBOEI7UUFEOUIsU0FBSSxHQUFKLElBQUksQ0FBTztRQUNYLFlBQU8sR0FBUCxPQUFPLENBQXVCO1FBRS9DLElBQUksQ0FBQyxhQUFhLEdBQUcsSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLE1BQU0sSUFBSSxFQUFFLENBQUMsQ0FBQztRQUNuRCxJQUFJLENBQUMsYUFBYSxHQUFHLE9BQU8sQ0FBQyxhQUFhLElBQUksR0FBRyxDQUFDO0lBQ3BELENBQUM7SUFFRCwrREFBK0Q7SUFDeEQsWUFBWTtRQUNqQixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUM7SUFDeEIsQ0FBQztJQUVNLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ25ELElBQUksQ0FBQyxPQUFPLEdBQUcsT0FBTyxDQUFDO1FBQ3ZCLE9BQU8sQ0FBQyxFQUFFLENBQUMsc0JB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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* A capture that failed: the typed API answers it as a typed error with this message, the CLI
|
|
4
|
+
* prints it and exits with 1.
|
|
5
|
+
*/
|
|
6
|
+
export declare class WccCaptureError extends plugins.typedrequest.TypedResponseError {
|
|
7
|
+
constructor(messageArg: string);
|
|
8
|
+
}
|
|
9
|
+
/** A capture request that is invalid as given; the CLI exits with 2. */
|
|
10
|
+
export declare class WccCaptureRequestError extends WccCaptureError {
|
|
11
|
+
constructor(messageArg: string);
|
|
12
|
+
}
|
|
13
|
+
/** The message of any thrown value. */
|
|
14
|
+
export declare const getErrorMessage: (errorArg: unknown) => string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
/**
|
|
3
|
+
* A capture that failed: the typed API answers it as a typed error with this message, the CLI
|
|
4
|
+
* prints it and exits with 1.
|
|
5
|
+
*/
|
|
6
|
+
export class WccCaptureError extends plugins.typedrequest.TypedResponseError {
|
|
7
|
+
constructor(messageArg) {
|
|
8
|
+
super(messageArg);
|
|
9
|
+
this.name = 'WccCaptureError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** A capture request that is invalid as given; the CLI exits with 2. */
|
|
13
|
+
export class WccCaptureRequestError extends WccCaptureError {
|
|
14
|
+
constructor(messageArg) {
|
|
15
|
+
super(messageArg);
|
|
16
|
+
this.name = 'WccCaptureRequestError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** The message of any thrown value. */
|
|
20
|
+
export const getErrorMessage = (errorArg) => {
|
|
21
|
+
return errorArg instanceof Error ? errorArg.message : String(errorArg);
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY2FwdHVyZS9lcnJvcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFFekM7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLGVBQWdCLFNBQVEsT0FBTyxDQUFDLFlBQVksQ0FBQyxrQkFBa0I7SUFDMUUsWUFBWSxVQUFrQjtRQUM1QixLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDbEIsSUFBSSxDQUFDLElBQUksR0FBRyxpQkFBaUIsQ0FBQztJQUNoQyxDQUFDO0NBQ0Y7QUFFRCx3RUFBd0U7QUFDeEUsTUFBTSxPQUFPLHNCQUF1QixTQUFRLGVBQWU7SUFDekQsWUFBWSxVQUFrQjtRQUM1QixLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDbEIsSUFBSSxDQUFDLElBQUksR0FBRyx3QkFBd0IsQ0FBQztJQUN2QyxDQUFDO0NBQ0Y7QUFFRCx1Q0FBdUM7QUFDdkMsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLENBQUMsUUFBaUIsRUFBVSxFQUFFO0lBQzNELE9BQU8sUUFBUSxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQ3pFLENBQUMsQ0FBQyJ9
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { WccCaptureError } from './errors.js';
|
|
3
|
+
const pngSignature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
4
|
+
/** The pixel size of a PNG, from its IHDR chunk. */
|
|
5
|
+
const readPngSize = (bytesArg) => {
|
|
6
|
+
if (bytesArg.length < 24 || pngSignature.some((byteArg, indexArg) => bytesArg[indexArg] !== byteArg)) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const view = new DataView(bytesArg.buffer, bytesArg.byteOffset, bytesArg.byteLength);
|
|
10
|
+
return { width: view.getUint32(16), height: view.getUint32(20) };
|
|
11
|
+
};
|
|
12
|
+
/** The pixel size of a JPEG, from its first start-of-frame segment. */
|
|
13
|
+
const readJpegSize = (bytesArg) => {
|
|
14
|
+
if (bytesArg.length < 4 || bytesArg[0] !== 0xff || bytesArg[1] !== 0xd8) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const view = new DataView(bytesArg.buffer, bytesArg.byteOffset, bytesArg.byteLength);
|
|
18
|
+
let offset = 2;
|
|
19
|
+
while (offset + 4 <= bytesArg.length) {
|
|
20
|
+
if (bytesArg[offset] !== 0xff) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const marker = bytesArg[offset + 1];
|
|
24
|
+
// Markers without a length: fill bytes, TEM and RSTn
|
|
25
|
+
if (marker === 0xff) {
|
|
26
|
+
offset += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
|
|
30
|
+
offset += 2;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const length = view.getUint16(offset + 2);
|
|
34
|
+
const isStartOfFrame = marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
|
|
35
|
+
if (isStartOfFrame) {
|
|
36
|
+
if (offset + 9 > bytesArg.length) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return { width: view.getUint16(offset + 7), height: view.getUint16(offset + 5) };
|
|
40
|
+
}
|
|
41
|
+
offset += 2 + length;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
};
|
|
45
|
+
/** An encoded image with its pixel size read from its own header. */
|
|
46
|
+
export const toWccImage = (bytesArg, formatArg) => {
|
|
47
|
+
const size = formatArg === 'png' ? readPngSize(bytesArg) : readJpegSize(bytesArg);
|
|
48
|
+
if (!size) {
|
|
49
|
+
throw new WccCaptureError(`wcctools: the browser returned no valid ${formatArg.toUpperCase()} image.`);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
mimeType: formatArg === 'png' ? 'image/png' : 'image/jpeg',
|
|
53
|
+
width: size.width,
|
|
54
|
+
height: size.height,
|
|
55
|
+
dataBase64: Buffer.from(bytesArg.buffer, bytesArg.byteOffset, bytesArg.byteLength).toString('base64'),
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW1hZ2VzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY2FwdHVyZS9pbWFnZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUU5QyxNQUFNLFlBQVksR0FBRyxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztBQUV0RSxvREFBb0Q7QUFDcEQsTUFBTSxXQUFXLEdBQUcsQ0FBQyxRQUFvQixFQUE0QyxFQUFFO0lBQ3JGLElBQUksUUFBUSxDQUFDLE1BQU0sR0FBRyxFQUFFLElBQUksWUFBWSxDQUFDLElBQUksQ0FBQyxDQUFDLE9BQU8sRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsS0FBSyxPQUFPLENBQUMsRUFBRSxDQUFDO1FBQ3JHLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUNELE1BQU0sSUFBSSxHQUFHLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxNQUFNLEVBQUUsUUFBUSxDQUFDLFVBQVUsRUFBRSxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDckYsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUM7QUFDbkUsQ0FBQyxDQUFDO0FBRUYsdUVBQXVFO0FBQ3ZFLE1BQU0sWUFBWSxHQUFHLENBQUMsUUFBb0IsRUFBNEMsRUFBRTtJQUN0RixJQUFJLFFBQVEsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLFFBQVEsQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLElBQUksUUFBUSxDQUFDLENB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
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export * from './classes.captureservice.js';
|
|
2
|
+
export * from './classes.capturebrowser.js';
|
|
3
|
+
export * from './errors.js';
|
|
4
|
+
export { captureLimits, captureViewportWidths, resolveScreenshotRequest, resolveInteractionRequest } from './request.js';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9jYXB0dXJlL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsNkJBQTZCLENBQUM7QUFDNUMsY0FBYyw2QkFBNkIsQ0FBQztBQUM1QyxjQUFjLGFBQWEsQ0FBQztBQUM1QixPQUFPLEVBQUUsYUFBYSxFQUFFLHFCQUFxQixFQUFFLHdCQUF3QixFQUFFLHlCQUF5QixFQUFFLE1BQU0sY0FBYyxDQUFDIn0=
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import type { IWccRenderedPreview } from './previewpage.js';
|
|
3
|
+
import { type IWccResolvedInteractionRequest } from './request.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs the steps on a rendered preview while recording its screencast, observes for a while after
|
|
6
|
+
* the last one and answers the steps' results and a bounded selection of the frames. A failed step
|
|
7
|
+
* ends the script; an abort ends it with the signal's reason. The page stays on the preview: a step
|
|
8
|
+
* that starts a navigation fails with the blocked navigation and ends the script; a navigation
|
|
9
|
+
* blocked during the observation fails the last step.
|
|
10
|
+
*/
|
|
11
|
+
export declare const runInteraction: (previewArg: IWccRenderedPreview, requestArg: IWccResolvedInteractionRequest, abortSignalArg: AbortSignal) => Promise<Omit<plugins.interfaces.IWccInteractionResult, "selection" | "finalImage">>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { getErrorMessage } from './errors.js';
|
|
3
|
+
import { WccScreencastRecorder, selectFrames } from './classes.screencast.js';
|
|
4
|
+
import { formatBlockedNavigation } from './navigationguard.js';
|
|
5
|
+
import { captureLimits } from './request.js';
|
|
6
|
+
/** A selector matched in the document and in every open shadow root below it. */
|
|
7
|
+
const piercing = (selectorArg) => `pierce/${selectorArg}`;
|
|
8
|
+
/** Resolves after `msArg`, or rejects with the signal's reason once it aborts; never leaves its timer behind. */
|
|
9
|
+
const observe = (msArg, abortSignalArg) => {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
if (abortSignalArg.aborted) {
|
|
12
|
+
reject(abortSignalArg.reason);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const onAbort = () => {
|
|
16
|
+
clearTimeout(timer);
|
|
17
|
+
reject(abortSignalArg.reason);
|
|
18
|
+
};
|
|
19
|
+
const timer = setTimeout(() => {
|
|
20
|
+
abortSignalArg.removeEventListener('abort', onAbort);
|
|
21
|
+
resolve();
|
|
22
|
+
}, msArg);
|
|
23
|
+
abortSignalArg.addEventListener('abort', onAbort, { once: true });
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
/** A step as its error messages name it: its action and its selector or key. */
|
|
27
|
+
const describeStep = (stepArg) => {
|
|
28
|
+
switch (stepArg.action) {
|
|
29
|
+
case 'press':
|
|
30
|
+
return stepArg.selector === undefined ? `press "${stepArg.key}"` : `press "${stepArg.key}" on "${stepArg.selector}"`;
|
|
31
|
+
case 'wait':
|
|
32
|
+
return `wait ${stepArg.ms} ms`;
|
|
33
|
+
default:
|
|
34
|
+
return `${stepArg.action} "${stepArg.selector}"`;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const runStep = async (pageArg, stepArg, abortSignalArg) => {
|
|
38
|
+
const timeout = captureLimits.stepTimeoutMs;
|
|
39
|
+
switch (stepArg.action) {
|
|
40
|
+
case 'click':
|
|
41
|
+
await pageArg.locator(piercing(stepArg.selector)).setTimeout(timeout).click({ signal: abortSignalArg });
|
|
42
|
+
return;
|
|
43
|
+
case 'hover':
|
|
44
|
+
await pageArg.locator(piercing(stepArg.selector)).setTimeout(timeout).hover({ signal: abortSignalArg });
|
|
45
|
+
return;
|
|
46
|
+
case 'fill':
|
|
47
|
+
await pageArg.locator(piercing(stepArg.selector)).setTimeout(timeout).fill(stepArg.text, { signal: abortSignalArg });
|
|
48
|
+
return;
|
|
49
|
+
case 'press': {
|
|
50
|
+
if (stepArg.selector !== undefined) {
|
|
51
|
+
const handle = await pageArg.locator(piercing(stepArg.selector)).setTimeout(timeout).waitHandle({ signal: abortSignalArg });
|
|
52
|
+
try {
|
|
53
|
+
await handle.focus();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await handle.dispose();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Puppeteer refuses keys it does not know with an error naming the key
|
|
60
|
+
await pageArg.keyboard.press(stepArg.key);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
case 'waitFor': {
|
|
64
|
+
const handle = await pageArg.waitForSelector(piercing(stepArg.selector), {
|
|
65
|
+
timeout,
|
|
66
|
+
signal: abortSignalArg,
|
|
67
|
+
...(stepArg.state === 'hidden' ? { hidden: true } : { visible: true }),
|
|
68
|
+
});
|
|
69
|
+
await handle?.dispose();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
case 'wait':
|
|
73
|
+
await observe(stepArg.ms, abortSignalArg);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Runs the steps on a rendered preview while recording its screencast, observes for a while after
|
|
79
|
+
* the last one and answers the steps' results and a bounded selection of the frames. A failed step
|
|
80
|
+
* ends the script; an abort ends it with the signal's reason. The page stays on the preview: a step
|
|
81
|
+
* that starts a navigation fails with the blocked navigation and ends the script; a navigation
|
|
82
|
+
* blocked during the observation fails the last step.
|
|
83
|
+
*/
|
|
84
|
+
export const runInteraction = async (previewArg, requestArg, abortSignalArg) => {
|
|
85
|
+
const { page, navigationGuard } = previewArg;
|
|
86
|
+
const recorder = new WccScreencastRecorder(page, {
|
|
87
|
+
quality: requestArg.frameQuality,
|
|
88
|
+
maxWidth: requestArg.view.width * requestArg.view.scale,
|
|
89
|
+
maxHeight: requestArg.view.height * requestArg.view.scale,
|
|
90
|
+
});
|
|
91
|
+
const steps = [];
|
|
92
|
+
let frames;
|
|
93
|
+
await recorder.start();
|
|
94
|
+
try {
|
|
95
|
+
const startedAt = recorder.getStartedAt();
|
|
96
|
+
for (const [index, step] of requestArg.steps.entries()) {
|
|
97
|
+
const startMs = Date.now() - startedAt;
|
|
98
|
+
let error = null;
|
|
99
|
+
try {
|
|
100
|
+
await runStep(page, step, abortSignalArg);
|
|
101
|
+
}
|
|
102
|
+
catch (stepError) {
|
|
103
|
+
abortSignalArg.throwIfAborted();
|
|
104
|
+
error = getErrorMessage(stepError);
|
|
105
|
+
}
|
|
106
|
+
const blockedUrl = await navigationGuard.takeBlockedNavigation();
|
|
107
|
+
if (blockedUrl !== null) {
|
|
108
|
+
error = formatBlockedNavigation(blockedUrl);
|
|
109
|
+
}
|
|
110
|
+
const endMs = Date.now() - startedAt;
|
|
111
|
+
if (error === null) {
|
|
112
|
+
steps.push({ index, ok: true, startMs, endMs });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
steps.push({ index, ok: false, error: `${describeStep(step)}: ${error}`, startMs, endMs });
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
await observe(requestArg.observeMs, abortSignalArg);
|
|
119
|
+
const lastStep = steps.at(-1);
|
|
120
|
+
if (lastStep === undefined) {
|
|
121
|
+
await navigationGuard.assertStayed('being observed');
|
|
122
|
+
}
|
|
123
|
+
else if (lastStep.ok) {
|
|
124
|
+
const blockedUrl = await navigationGuard.takeBlockedNavigation();
|
|
125
|
+
if (blockedUrl !== null) {
|
|
126
|
+
// Started after the last step ended: by that step, or by the demo on its own
|
|
127
|
+
lastStep.ok = false;
|
|
128
|
+
lastStep.error = `${describeStep(requestArg.steps[lastStep.index])}: ${formatBlockedNavigation(blockedUrl)}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
frames = await recorder.stop();
|
|
134
|
+
}
|
|
135
|
+
const completed = steps.length === requestArg.steps.length && steps.every((stepArg) => stepArg.ok);
|
|
136
|
+
return { frames: selectFrames(frames, requestArg.maxFrames), steps, completed };
|
|
137
|
+
};
|
|
138
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJhY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9jYXB0dXJlL2ludGVyYWN0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDOUMsT0FBTyxFQUFFLHFCQUFxQixFQUFFLFlBQVksRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQzlFLE9BQU8sRUFBRSx1QkFBdUIsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBRS9ELE9BQU8sRUFBRSxhQUFhLEVBQXVDLE1BQU0sY0FBYyxDQUFDO0FBS2xGLGlGQUFpRjtBQUNqRixNQUFNLFFBQVEsR0FBRyxDQUFDLFdBQW1CLEVBQUUsRUFBRSxDQUFDLFVBQVUsV0FBVyxFQUFFLENBQUM7QUFFbEUsaUhBQWlIO0FBQ2pILE1BQU0sT0FBTyxHQUFHLENBQUMsS0FBYSxFQUFFLGNBQTJCLEVBQWlCLEVBQUU7SUFDNUUsT0FBTyxJQUFJLE9BQU8sQ0FBTyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtRQUMzQyxJQUFJLGNBQWMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUMzQixNQUFNLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQzlCLE9BQU87UUFDVCxDQUFDO1FBQ0QsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQ25CLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNwQixNQUFNLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2hDLENBQUMsQ0FBQztRQUNGLE1BQU0sS0FBSyxHQUFHLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDNUIsY0FBYyxDQUFDLG1CQUFtQixDQUFDLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQztZQUNyRCxPQUFPLEVBQUUsQ0FBQztRQUNaLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztRQUNWLGNBQWMsQ0FBQyxnQkFBZ0IsQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFDcEUsQ0FBQyxDQUFDLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRixnRkFBZ0Y7QUFDaEYsTUFBTSxZQUFZLEdBQUcsQ0FBQyxPQUE0QixFQUFVLEVBQUU7SUFDNUQsUUFBUSxPQUFPLENBQUMsTUFBTSxFQUFFLENBQUM7UUFDdkIsS0FBSyxPQUFPO1lBQ1YsT0FBTyxPQUFPLENBQUMsUUFBUSxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUMsVUFBVSxPQUFPLENBQUMsR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUFDLFVBQVUsT0FBTyxDQUFDLEdBQUcsU0FBUyxPQUFPLENBQUMsUUFBUSxHQUFHLENBQUM7UUFDdkgsS0FBSyxNQUFNO1lBQ1QsT0FBTyxRQUFRLE9BQU8sQ0FBQyxFQUFFLEtBQUssQ0FBQztRQUNqQztZQUNFLE9BQU8sR0FBRyxPQUFPLENBQUMsTUFBTSxLQUFLLE9BQU8sQ0FBQyxRQUFRLEdBQUcsQ0FBQztJQUNyRCxDQUFDO0FBQ0gsQ0FBQyxDQUFDO0FBRUYsTUFBTSxPQUFPLEdBQUcsS0FBSyxFQUFFLE9BQWMsRUFBRSxPQUE0QixFQUFFLGNBQTJCLEVBQWlCLEVBQUU7SUFDakgsTUFBTSxPQUFPLEdBQUcsYUFBYSxDQUFDLGFBQWEsQ0FBQztJQUM1QyxRQUFRLE9BQU8sQ0FBQyxNQUFNLEVBQUUsQ0FBQztRQUN2QixLQUFLLE9BQU87WUFDVixNQUFNLE9BQU8sQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxLQUFLLENBQUMsRUFBRSxNQUFNLEVBQUUsY0FBYyxFQUFFLENBQUMsQ0FBQztZQUN4RyxPQUFPO1FBQ1QsS0FBSyxPQUFPO1lBQ1YsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsS0FBSyxDQUFDLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7WUFDeEcsT0FBTztRQUNULEtBQUssTUFBTTtZQUNULE1BQU0sT0FBTyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7WUFDckgsT0FBTztRQUNULEtBQUssT0FBTyxDQUFDLENBQUMsQ0FBQztZQUNiLElBQUksT0FBTyxDQUFDLFFBQVEsS0FBSyxTQUFTLEVBQUUsQ0FBQztnQkFDbkMsTUFBTSxNQUFNLEdBQUcsTUFBTSxPQUFPLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUMsVUFBVSxDQUFDLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxDQUFDLENBQUM7Z0JBQzVILElBQUksQ0FBQztvQkFDSCxNQUFNLE1BQU0sQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDdkIsQ0FBQzt3QkFBUyxDQUFDO29CQUNULE1BQU0sTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUN6QixDQUFDO1lBQ0gsQ0FBQztZQUNELHVFQUF1RTtZQUN2RSxNQUFNLE9BQU8sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUE4QyxDQUFDLENBQUM7WUFDckYsT0FBTztRQUNULENBQUM7UUFDRCxLQUFLLFNBQVMsQ0FBQyxDQUFDLENBQUM7WUFDZixNQUFNLE1BQU0sR0FBRyxNQUFNLE9BQU8sQ0FBQyxlQUFlLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsRUFBRTtnQkFDdkUsT0FBTztnQkFDUCxNQUFNLEVBQUUsY0FBYztnQkFDdEIsR0FBRyxDQUFDLE9BQU8sQ0FBQyxLQUFLLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUM7YUFDdkUsQ0FBQyxDQUFDO1lBQ0gsTUFBTSxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7WUFDeEIsT0FBTztRQUNULENBQUM7UUFDRCxLQUFLLE1BQU07WUFDVCxNQUFNLE9BQU8sQ0FBQyxPQUFPLENBQUMsRUFBRSxFQUFFLGNBQWMsQ0FBQyxDQUFDO1lBQzFDLE9BQU87SUFDWCxDQUFDO0FBQ0gsQ0FBQyxDQUFDO0FBRUY7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLEtBQUssRUFDakMsVUFBK0IsRUFDL0IsVUFBMEMsRUFDMUMsY0FBMkIsRUFDMEQsRUFBRTtJQUN2RixNQUFNLEVBQUUsSUFBSSxFQUFFLGVBQWUsRUFBRSxHQUFHLFVBQVUsQ0FBQztJQUM3QyxNQUFNLFFBQVEsR0FBRyxJQUFJLHFCQUFxQixDQUFDLElBQUksRUFBRTtRQUMvQyxPQUFPLEVBQUUsVUFBVSxDQUFDLFlBQVk7UUFDaEMsUUFBUSxFQUFFLFVBQVUsQ0FBQyxJQUFJLENBQUMsS0FBSyxHQUFHLFVBQVUsQ0FBQyxJQUFJLENBQUMsS0FBSztRQUN2RCxTQUFTLEVBQUUsVUFBVSxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FBQyxLQUFLO0tBQzFELENBQUMsQ0FBQztJQUNILE1BQU0sS0FBSyxHQUFtRCxFQUFFLENBQUM7SUFDakUsSUFBSSxNQUEyQyxDQUFDO0lBQ2hELE1BQU0sUUFBUSxDQUFDLEtBQUssRUFBRSxDQUFDO0lBQ3ZCLElBQUksQ0FBQztRQUNILE1BQU0sU0FBUyxHQUFHLFFBQVEsQ0FBQyxZQUFZLEVBQUUsQ0FBQztRQUMxQyxLQUFLLE1BQU0sQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLElBQUksVUFBVSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsRUFBRSxDQUFDO1lBQ3ZELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxTQUFTLENBQUM7WUFDdkMsSUFBSSxLQUFLLEdBQWtCLElBQUksQ0FBQztZQUNoQyxJQUFJLENBQUM7Z0JBQ0gsTUFBTSxPQUFPLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxjQUFjLENBQUMsQ0FBQztZQUM1QyxDQUFDO1lBQUMsT0FBTyxTQUFTLEVBQUUsQ0FBQztnQkFDbkIsY0FBYyxDQUFDLGNBQWMsRUFBRSxDQUFDO2dCQUNoQyxLQUFLLEdBQUcsZUFBZSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQ3JDLENBQUM7WUFDRCxNQUFNLFVBQVUsR0FBRyxNQUFNLGVBQWUsQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1lBQ2pFLElBQUksVUFBVSxLQUFLLElBQUksRUFBRSxDQUFDO2dCQUN4QixLQUFLLEdBQUcsdUJBQXVCLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDOUMsQ0FBQztZQUNELE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxTQUFTLENBQUM7WUFDckMsSUFBSSxLQUFLLEtBQUssSUFBSSxFQUFFLENBQUM7Z0JBQ25CLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztnQkFDaEQsU0FBUztZQUNYLENBQUM7WUFDRCxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxLQUFLLEtBQUssRUFBRSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDO1lBQzNGLE1BQU07UUFDUixDQUFDO1FBQ0QsTUFBTSxPQUFPLENBQUMsVUFBVSxDQUFDLFNBQVMsRUFBRSxjQUFjLENBQUMsQ0FBQztRQUNwRCxNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDOUIsSUFBSSxRQUFRLEtBQUssU0FBUyxFQUFFLENBQUM7WUFDM0IsTUFBTSxlQUFlLENBQUMsWUFBWSxDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDdkQsQ0FBQzthQUFNLElBQUksUUFBUSxDQUFDLEVBQUUsRUFBRSxDQUFDO1lBQ3ZCLE1BQU0sVUFBVSxHQUFHLE1BQU0sZUFBZSxDQUFDLHFCQUFxQixFQUFFLENBQUM7WUFDakUsSUFBSSxVQUFVLEtBQUssSUFBSSxFQUFFLENBQUM7Z0JBQ3hCLDZFQUE2RTtnQkFDN0UsUUFBUSxDQUFDLEVBQUUsR0FBRyxLQUFLLENBQUM7Z0JBQ3BCLFFBQVEsQ0FBQyxLQUFLLEdBQUcsR0FBRyxZQUFZLENBQUMsVUFBVSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyx1QkFBdUIsQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDO1lBQy9HLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztZQUFTLENBQUM7UUFDVCxNQUFNLEdBQUcsTUFBTSxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7SUFDakMsQ0FBQztJQUNELE1BQU0sU0FBUyxHQUFHLEtBQUssQ0FBQyxNQUFNLEtBQUssVUFBVSxDQUFDLEtBQUssQ0FBQyxNQUFNLElBQUksS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQ25HLE9BQU8sRUFBRSxNQUFNLEVBQUUsWUFBWSxDQUFDLE1BQU0sRUFBRSxVQUFVLENBQUMsU0FBUyxDQUFDLEVBQUUsS0FBSyxFQUFFLFNBQVMsRUFBRSxDQUFDO0FBQ2xGLENBQUMsQ0FBQyJ9
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
type TPage = plugins.smartbrowser.puppeteer.Page;
|
|
3
|
+
/** The message of a navigation the capture blocked. */
|
|
4
|
+
export declare const formatBlockedNavigation: (urlArg: string) => string;
|
|
5
|
+
/**
|
|
6
|
+
* Keeps a capture page on the preview. Its main frame loads the preview document once; after that,
|
|
7
|
+
* the page script above cancels every navigation that would replace the document before it starts.
|
|
8
|
+
* Request interception backs it up: a main-frame navigation request the page could not cancel is
|
|
9
|
+
* aborted before it leaves. The preview's history holds the preview alone, so there is no earlier
|
|
10
|
+
* document to go back to. Subframes and all other requests are left alone.
|
|
11
|
+
*
|
|
12
|
+
* `takeBlockedNavigation()` answers the navigation blocked since its last call, so each step of an
|
|
13
|
+
* interaction learns about the navigation it started.
|
|
14
|
+
*/
|
|
15
|
+
export declare class WccNavigationGuard {
|
|
16
|
+
private readonly page;
|
|
17
|
+
/** Navigation requests aborted by the backstop since the last `takeBlockedNavigation()`. */
|
|
18
|
+
private abortedUrls;
|
|
19
|
+
private interceptionFailure;
|
|
20
|
+
private previewUrl;
|
|
21
|
+
private constructor();
|
|
22
|
+
/**
|
|
23
|
+
* Installs the page script and intercepts the page's requests; the page's first main-frame
|
|
24
|
+
* navigation may load `previewUrlArg` only. Install before loading the preview.
|
|
25
|
+
*/
|
|
26
|
+
static install(pageArg: TPage, previewUrlArg: string): Promise<WccNavigationGuard>;
|
|
27
|
+
/** Drops the history entries before the loaded preview (the blank page it opened as), so going back leaves nothing. */
|
|
28
|
+
forgetEarlierHistory(): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Lets the page run the tasks queued so far (an animation frame, then a task), then answers the
|
|
31
|
+
* URL of the first navigation blocked since the last call, by the page script or the backstop,
|
|
32
|
+
* or null when none was.
|
|
33
|
+
*/
|
|
34
|
+
takeBlockedNavigation(): Promise<string | null>;
|
|
35
|
+
/** Throws when the page tried to leave the preview while `whileArg` (e.g. "rendering"). */
|
|
36
|
+
assertStayed(whileArg: string): Promise<void>;
|
|
37
|
+
private decide;
|
|
38
|
+
/**
|
|
39
|
+
* Keeps the first failure of an interception decision. Puppeteer itself tolerates a request
|
|
40
|
+
* that ended or a page that closed meanwhile; what reaches here fails the capture.
|
|
41
|
+
*/
|
|
42
|
+
private settle;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { WccCaptureError, getErrorMessage } from './errors.js';
|
|
3
|
+
/** The message of a navigation the capture blocked. */
|
|
4
|
+
export const formatBlockedNavigation = (urlArg) => {
|
|
5
|
+
return `navigation to ${urlArg} blocked: a capture stays on the rendered preview`;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Runs in the main frame before the preview's scripts, so its `navigate` listener is the first:
|
|
9
|
+
* cancels each navigation that would replace the document (a link, a form submission, a script
|
|
10
|
+
* assigning `location`, a reload, also to the same URL, to `about:blank` or a `blob:` URL) as it
|
|
11
|
+
* starts, and records its URL. Same-document navigations (`history.replaceState`, fragments) and
|
|
12
|
+
* downloads pass. Link clicks and scripts start their navigation while the step that caused it
|
|
13
|
+
* runs; form submissions in a task right after.
|
|
14
|
+
*/
|
|
15
|
+
const cancelLeavingNavigations = () => {
|
|
16
|
+
if (window !== window.top) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const cancelled = [];
|
|
20
|
+
window.wccCancelledNavigations = cancelled;
|
|
21
|
+
navigation.addEventListener('navigate', (eventArg) => {
|
|
22
|
+
if (eventArg.destination.sameDocument || eventArg.downloadRequest !== null || !eventArg.cancelable) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
eventArg.preventDefault();
|
|
26
|
+
cancelled.push(eventArg.destination.url);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Keeps a capture page on the preview. Its main frame loads the preview document once; after that,
|
|
31
|
+
* the page script above cancels every navigation that would replace the document before it starts.
|
|
32
|
+
* Request interception backs it up: a main-frame navigation request the page could not cancel is
|
|
33
|
+
* aborted before it leaves. The preview's history holds the preview alone, so there is no earlier
|
|
34
|
+
* document to go back to. Subframes and all other requests are left alone.
|
|
35
|
+
*
|
|
36
|
+
* `takeBlockedNavigation()` answers the navigation blocked since its last call, so each step of an
|
|
37
|
+
* interaction learns about the navigation it started.
|
|
38
|
+
*/
|
|
39
|
+
export class WccNavigationGuard {
|
|
40
|
+
page;
|
|
41
|
+
/** Navigation requests aborted by the backstop since the last `takeBlockedNavigation()`. */
|
|
42
|
+
abortedUrls = [];
|
|
43
|
+
interceptionFailure = null;
|
|
44
|
+
previewUrl;
|
|
45
|
+
constructor(page, previewUrlArg) {
|
|
46
|
+
this.page = page;
|
|
47
|
+
this.previewUrl = new URL(previewUrlArg).href;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Installs the page script and intercepts the page's requests; the page's first main-frame
|
|
51
|
+
* navigation may load `previewUrlArg` only. Install before loading the preview.
|
|
52
|
+
*/
|
|
53
|
+
static async install(pageArg, previewUrlArg) {
|
|
54
|
+
const guard = new WccNavigationGuard(pageArg, previewUrlArg);
|
|
55
|
+
pageArg.on('request', (requestArg) => guard.decide(requestArg));
|
|
56
|
+
await pageArg.setRequestInterception(true);
|
|
57
|
+
await pageArg.evaluateOnNewDocument(cancelLeavingNavigations);
|
|
58
|
+
return guard;
|
|
59
|
+
}
|
|
60
|
+
/** Drops the history entries before the loaded preview (the blank page it opened as), so going back leaves nothing. */
|
|
61
|
+
async forgetEarlierHistory() {
|
|
62
|
+
const session = await this.page.createCDPSession();
|
|
63
|
+
try {
|
|
64
|
+
await session.send('Page.resetNavigationHistory');
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
await session.detach();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Lets the page run the tasks queued so far (an animation frame, then a task), then answers the
|
|
72
|
+
* URL of the first navigation blocked since the last call, by the page script or the backstop,
|
|
73
|
+
* or null when none was.
|
|
74
|
+
*/
|
|
75
|
+
async takeBlockedNavigation() {
|
|
76
|
+
const cancelledUrl = await this.page.evaluate(() => {
|
|
77
|
+
return new Promise((resolve) => {
|
|
78
|
+
requestAnimationFrame(() => {
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
const cancelled = window.wccCancelledNavigations ?? [];
|
|
81
|
+
resolve(cancelled[0] ?? null);
|
|
82
|
+
cancelled.length = 0;
|
|
83
|
+
}, 0);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
if (this.interceptionFailure !== null) {
|
|
88
|
+
throw new WccCaptureError(`wcctools: the capture failed to handle a request of the preview: ${getErrorMessage(this.interceptionFailure)}`);
|
|
89
|
+
}
|
|
90
|
+
const abortedUrl = this.abortedUrls[0] ?? null;
|
|
91
|
+
this.abortedUrls = [];
|
|
92
|
+
return cancelledUrl ?? abortedUrl;
|
|
93
|
+
}
|
|
94
|
+
/** Throws when the page tried to leave the preview while `whileArg` (e.g. "rendering"). */
|
|
95
|
+
async assertStayed(whileArg) {
|
|
96
|
+
const blockedUrl = await this.takeBlockedNavigation();
|
|
97
|
+
if (blockedUrl !== null) {
|
|
98
|
+
throw new WccCaptureError(`wcctools: the demo tried to navigate away while ${whileArg}; ${formatBlockedNavigation(blockedUrl)}.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
decide(requestArg) {
|
|
102
|
+
if (requestArg.isInterceptResolutionHandled()) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!requestArg.isNavigationRequest() || requestArg.frame() !== this.page.mainFrame()) {
|
|
106
|
+
this.settle(requestArg.continue());
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (this.previewUrl !== null && requestArg.url() === this.previewUrl) {
|
|
110
|
+
// The preview document's own load, once
|
|
111
|
+
this.previewUrl = null;
|
|
112
|
+
this.settle(requestArg.continue());
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
this.abortedUrls.push(requestArg.url());
|
|
116
|
+
// `aborted` drops the navigation without committing an error page, so the preview stays
|
|
117
|
+
this.settle(requestArg.abort('aborted'));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Keeps the first failure of an interception decision. Puppeteer itself tolerates a request
|
|
121
|
+
* that ended or a page that closed meanwhile; what reaches here fails the capture.
|
|
122
|
+
*/
|
|
123
|
+
settle(decisionArg) {
|
|
124
|
+
decisionArg.catch((errorArg) => {
|
|
125
|
+
this.interceptionFailure ??= errorArg;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmF2aWdhdGlvbmd1YXJkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvY2FwdHVyZS9uYXZpZ2F0aW9uZ3VhcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxlQUFlLENBQUM7QUFDekMsT0FBTyxFQUFFLGVBQWUsRUFBRSxlQUFlLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFLL0QsdURBQXVEO0FBQ3ZELE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLENBQUMsTUFBYyxFQUFVLEVBQUU7SUFDaEUsT0FBTyxpQkFBaUIsTUFBTSxtREFBbUQsQ0FBQztBQUNwRixDQUFDLENBQUM7QUFRRjs7Ozs7OztHQU9HO0FBQ0gsTUFBTSx3QkFBd0IsR0FBRyxHQUFHLEVBQUU7SUFDcEMsSUFBSSxNQUFNLEtBQUssTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQzFCLE9BQU87SUFDVCxDQUFDO0lBQ0QsTUFBTSxTQUFTLEdBQWEsRUFBRSxDQUFDO0lBQzlCLE1BQStDLENBQUMsdUJBQXVCLEdBQUcsU0FBUyxDQUFDO0lBQ3JGLFVBQVUsQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLEVBQUUsQ0FBQyxRQUFRLEVBQUUsRUFBRTtRQUNuRCxJQUFJLFFBQVEsQ0FBQyxXQUFXLENBQUMsWUFBWSxJQUFJLFFBQVEsQ0FBQyxlQUFlLEtBQUssSUFBSSxJQUFJLENBQUMsUUFBUSxDQUFDLFVBQVUsRUFBRSxDQUFDO1lBQ25HLE9BQU87UUFDVCxDQUFDO1FBQ0QsUUFBUSxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQzFCLFNBQVMsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUMzQyxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQztBQUVGOzs7Ozs7Ozs7R0FTRztBQUNILE1BQU0sT0FBTyxrQkFBa0I7SUFPVjtJQU5uQiw0RkFBNEY7SUFDcEYsV0FBVyxHQUFh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
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import { WccNavigationGuard } from './navigationguard.js';
|
|
3
|
+
import { type IWccResolvedImageOptions, type IWccResolvedView } from './request.js';
|
|
4
|
+
type TBrowserContext = plugins.smartbrowser.puppeteer.BrowserContext;
|
|
5
|
+
type TPage = plugins.smartbrowser.puppeteer.Page;
|
|
6
|
+
/** A preview document in the capture browser that rendered one demo. */
|
|
7
|
+
export interface IWccRenderedPreview {
|
|
8
|
+
page: TPage;
|
|
9
|
+
/** Keeps the page on the preview and knows the navigation it blocked. */
|
|
10
|
+
navigationGuard: WccNavigationGuard;
|
|
11
|
+
selection: plugins.interfaces.IWccSelection;
|
|
12
|
+
/** What the demo rendered: an element demo or a page. */
|
|
13
|
+
type: 'element' | 'page';
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Opens the preview route of the dev server at `originArg` in the context, waits for the preview
|
|
17
|
+
* runtime to install its bridge (it does once it rendered the route), resolves the subject against
|
|
18
|
+
* the catalog manifest and renders it. Waits only on the preview's own signals: the bridge, the
|
|
19
|
+
* render's completed updates and the document's fonts. The page stays on the preview: every
|
|
20
|
+
* later navigation of its main frame is blocked, and one blocked while rendering fails the capture.
|
|
21
|
+
*/
|
|
22
|
+
export declare const renderPreview: (contextArg: TBrowserContext, originArg: string, viewArg: IWccResolvedView, subjectArg: plugins.interfaces.IWccCaptureSubject) => Promise<IWccRenderedPreview>;
|
|
23
|
+
/** Screenshots a rendered preview as framed. */
|
|
24
|
+
export declare const screenshotPreview: (previewArg: IWccRenderedPreview, viewArg: IWccResolvedView, optionsArg: IWccResolvedImageOptions) => Promise<{
|
|
25
|
+
framing: plugins.interfaces.TWccCaptureFraming;
|
|
26
|
+
image: plugins.interfaces.IWccImage;
|
|
27
|
+
truncated: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
export {};
|