@lullabot/playwright-testing 0.1.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/README.md +61 -0
- package/bin/github-a11y-summary +5 -0
- package/bin/github-failure-summary +8 -0
- package/lib/accessibility-baseline-file.d.ts +47 -0
- package/lib/accessibility-baseline-file.js +205 -0
- package/lib/accessibility-baseline.d.ts +19 -0
- package/lib/accessibility-baseline.js +38 -0
- package/lib/accessible-screenshot.d.ts +181 -0
- package/lib/accessible-screenshot.js +519 -0
- package/lib/focus.d.ts +14 -0
- package/lib/focus.js +28 -0
- package/lib/fonts.d.ts +18 -0
- package/lib/fonts.js +24 -0
- package/lib/frames.d.ts +7 -0
- package/lib/frames.js +27 -0
- package/lib/github/a11y-summary.d.ts +55 -0
- package/lib/github/a11y-summary.js +383 -0
- package/lib/github/attachments.d.ts +98 -0
- package/lib/github/attachments.js +297 -0
- package/lib/github/failure-summary.d.ts +144 -0
- package/lib/github/failure-summary.js +567 -0
- package/lib/github/index.d.ts +6 -0
- package/lib/github/index.js +35 -0
- package/lib/github/report-paths.d.ts +38 -0
- package/lib/github/report-paths.js +200 -0
- package/lib/hover.d.ts +13 -0
- package/lib/hover.js +61 -0
- package/lib/images.d.ts +118 -0
- package/lib/images.js +260 -0
- package/lib/index.d.ts +13 -0
- package/lib/index.js +29 -0
- package/lib/interaction-states.d.ts +22 -0
- package/lib/interaction-states.js +75 -0
- package/lib/mock/index.d.ts +1 -0
- package/lib/mock/index.js +5 -0
- package/lib/mock/youtube.d.ts +5 -0
- package/lib/mock/youtube.js +38 -0
- package/lib/pseudo-state.d.ts +17 -0
- package/lib/pseudo-state.js +50 -0
- package/lib/videos.d.ts +134 -0
- package/lib/videos.js +349 -0
- package/lib/visualdiff.d.ts +154 -0
- package/lib/visualdiff.js +197 -0
- package/package.json +47 -0
- package/src/accessibility-baseline-file.test.ts +181 -0
- package/src/accessibility-baseline-file.ts +208 -0
- package/src/accessibility-baseline.test.ts +601 -0
- package/src/accessibility-baseline.ts +50 -0
- package/src/accessible-screenshot.test.ts +597 -0
- package/src/accessible-screenshot.ts +809 -0
- package/src/focus.test.ts +34 -0
- package/src/focus.ts +27 -0
- package/src/fonts.test.ts +17 -0
- package/src/fonts.ts +23 -0
- package/src/frames.test.ts +75 -0
- package/src/frames.ts +26 -0
- package/src/github/a11y-summary.test.ts +439 -0
- package/src/github/a11y-summary.ts +421 -0
- package/src/github/attachments.test.ts +248 -0
- package/src/github/attachments.ts +328 -0
- package/src/github/failure-summary.test.ts +636 -0
- package/src/github/failure-summary.ts +720 -0
- package/src/github/index.test.ts +24 -0
- package/src/github/index.ts +35 -0
- package/src/github/report-paths.test.ts +222 -0
- package/src/github/report-paths.ts +208 -0
- package/src/hover.test.ts +76 -0
- package/src/hover.ts +64 -0
- package/src/images.test.ts +355 -0
- package/src/images.ts +299 -0
- package/src/index.ts +13 -0
- package/src/interaction-states.test.ts +48 -0
- package/src/interaction-states.ts +94 -0
- package/src/mock/index.ts +1 -0
- package/src/mock/youtube.test.ts +38 -0
- package/src/mock/youtube.ts +39 -0
- package/src/pseudo-state.test.ts +83 -0
- package/src/pseudo-state.ts +69 -0
- package/src/videos.test.ts +637 -0
- package/src/videos.ts +389 -0
- package/src/visualdiff.test.ts +452 -0
- package/src/visualdiff.ts +381 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./focus.js"), exports);
|
|
18
|
+
__exportStar(require("./accessibility-baseline.js"), exports);
|
|
19
|
+
__exportStar(require("./accessibility-baseline-file.js"), exports);
|
|
20
|
+
__exportStar(require("./accessible-screenshot.js"), exports);
|
|
21
|
+
__exportStar(require("./fonts.js"), exports);
|
|
22
|
+
__exportStar(require("./frames.js"), exports);
|
|
23
|
+
__exportStar(require("./hover.js"), exports);
|
|
24
|
+
__exportStar(require("./images.js"), exports);
|
|
25
|
+
__exportStar(require("./interaction-states.js"), exports);
|
|
26
|
+
__exportStar(require("./pseudo-state.js"), exports);
|
|
27
|
+
__exportStar(require("./videos.js"), exports);
|
|
28
|
+
__exportStar(require("./visualdiff.js"), exports);
|
|
29
|
+
__exportStar(require("./mock/index.js"), exports);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Locator } from '@playwright/test';
|
|
2
|
+
/** Real browser interaction states supported consistently by Playwright. */
|
|
3
|
+
export type InteractionState = 'hover' | 'focus';
|
|
4
|
+
/** A locator and the real browser interaction states to apply to it. */
|
|
5
|
+
export interface ScreenshotInteractionState {
|
|
6
|
+
locator: Locator;
|
|
7
|
+
states: InteractionState[];
|
|
8
|
+
}
|
|
9
|
+
export interface InteractionStateCleanupOptions {
|
|
10
|
+
/** Move the pointer away from the hovered locator during cleanup. */
|
|
11
|
+
clearHover?: () => Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
/** Validate that the requested states are possible with real browser input. */
|
|
14
|
+
export declare function validateInteractionStates(interactionStates: ScreenshotInteractionState[]): void;
|
|
15
|
+
/**
|
|
16
|
+
* Apply real hover and focus, returning an idempotent cleanup function.
|
|
17
|
+
*
|
|
18
|
+
* Hover is applied before focus so focusing cannot disturb the pointer. If an
|
|
19
|
+
* interaction fails, any state already applied is cleaned before the original
|
|
20
|
+
* error is rethrown.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyInteractionStates(interactionStates: ScreenshotInteractionState[], cleanupOptions?: InteractionStateCleanupOptions): Promise<() => Promise<void>>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateInteractionStates = validateInteractionStates;
|
|
4
|
+
exports.applyInteractionStates = applyInteractionStates;
|
|
5
|
+
/** Validate that the requested states are possible with real browser input. */
|
|
6
|
+
function validateInteractionStates(interactionStates) {
|
|
7
|
+
const hoverTargets = interactionStates.filter(({ states }) => states.includes('hover'));
|
|
8
|
+
const focusTargets = interactionStates.filter(({ states }) => states.includes('focus'));
|
|
9
|
+
if (hoverTargets.length > 1) {
|
|
10
|
+
throw new Error('interactionStates can hover at most one locator at a time.');
|
|
11
|
+
}
|
|
12
|
+
if (focusTargets.length > 1) {
|
|
13
|
+
throw new Error('interactionStates can focus at most one locator at a time.');
|
|
14
|
+
}
|
|
15
|
+
for (const { states } of interactionStates) {
|
|
16
|
+
if (new Set(states).size !== states.length) {
|
|
17
|
+
throw new Error('interactionStates cannot repeat a state for the same locator.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Apply real hover and focus, returning an idempotent cleanup function.
|
|
23
|
+
*
|
|
24
|
+
* Hover is applied before focus so focusing cannot disturb the pointer. If an
|
|
25
|
+
* interaction fails, any state already applied is cleaned before the original
|
|
26
|
+
* error is rethrown.
|
|
27
|
+
*/
|
|
28
|
+
async function applyInteractionStates(interactionStates, cleanupOptions = {}) {
|
|
29
|
+
validateInteractionStates(interactionStates);
|
|
30
|
+
const hoverTarget = interactionStates.find(({ states }) => states.includes('hover'))?.locator;
|
|
31
|
+
const focusTarget = interactionStates.find(({ states }) => states.includes('focus'))?.locator;
|
|
32
|
+
let hoverApplied = false;
|
|
33
|
+
let focusApplied = false;
|
|
34
|
+
let cleaned = false;
|
|
35
|
+
const cleanup = async () => {
|
|
36
|
+
if (cleaned) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
cleaned = true;
|
|
40
|
+
try {
|
|
41
|
+
if (focusApplied) {
|
|
42
|
+
await focusTarget?.blur();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
if (hoverApplied) {
|
|
47
|
+
await cleanupOptions.clearHover?.();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
try {
|
|
52
|
+
if (hoverTarget) {
|
|
53
|
+
// Mark the state first: Playwright can move the pointer and then fail
|
|
54
|
+
// while waiting for actionability, and that partial effect still needs
|
|
55
|
+
// cleanup.
|
|
56
|
+
hoverApplied = true;
|
|
57
|
+
await hoverTarget.hover();
|
|
58
|
+
}
|
|
59
|
+
if (focusTarget) {
|
|
60
|
+
focusApplied = true;
|
|
61
|
+
await focusTarget.focus();
|
|
62
|
+
}
|
|
63
|
+
return cleanup;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
try {
|
|
67
|
+
await cleanup();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Preserve the interaction failure. Cleanup is best effort on this path;
|
|
71
|
+
// callers cannot act on a cleanup error if applying the state failed.
|
|
72
|
+
}
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { YoutubeMock } from './youtube.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.YoutubeMock = void 0;
|
|
4
|
+
var youtube_js_1 = require("./youtube.js");
|
|
5
|
+
Object.defineProperty(exports, "YoutubeMock", { enumerable: true, get: function () { return youtube_js_1.YoutubeMock; } });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.YoutubeMock = void 0;
|
|
4
|
+
class YoutubeMock {
|
|
5
|
+
async mock(page) {
|
|
6
|
+
await page.route(/www\.youtube\.com/i, async (route) => {
|
|
7
|
+
await route.fulfill({
|
|
8
|
+
contentType: 'text/html',
|
|
9
|
+
body: `
|
|
10
|
+
<html>
|
|
11
|
+
<head>
|
|
12
|
+
<title>Youtube Video Mock</title>
|
|
13
|
+
<style>
|
|
14
|
+
body {
|
|
15
|
+
color: white;
|
|
16
|
+
background-color: darkred;
|
|
17
|
+
}
|
|
18
|
+
div {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 50%;
|
|
21
|
+
left: 50%;
|
|
22
|
+
margin: 0;
|
|
23
|
+
transform: translate(-50%, -50%);
|
|
24
|
+
font-size: xxx-large;
|
|
25
|
+
text-align: center;
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
28
|
+
</head>
|
|
29
|
+
<body>
|
|
30
|
+
<div>Youtube Video Mock</div>
|
|
31
|
+
</body>
|
|
32
|
+
</html>
|
|
33
|
+
`,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.YoutubeMock = YoutubeMock;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Page } from '@playwright/test';
|
|
2
|
+
/** Pseudo-classes supported by Chromium's CSS.forcePseudoState command. */
|
|
3
|
+
export type ForcedPseudoClass = 'active' | 'focus' | 'focus-visible' | 'focus-within' | 'hover' | 'target';
|
|
4
|
+
/** A CSS selector and the pseudo-classes to force on its first match. */
|
|
5
|
+
export interface ForcedPseudoState {
|
|
6
|
+
selector: string;
|
|
7
|
+
pseudoClasses: ForcedPseudoClass[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Force CSS pseudo-classes on the first element matching a selector.
|
|
11
|
+
*
|
|
12
|
+
* This uses Chromium's DevTools Protocol and therefore only works in Chromium
|
|
13
|
+
* projects. The state is synthetic: moving the pointer or blurring the active
|
|
14
|
+
* element does not clear it. Call the returned idempotent cleanup function when
|
|
15
|
+
* the screenshot and accessibility scan are complete.
|
|
16
|
+
*/
|
|
17
|
+
export declare function forcePseudoState(page: Page, selector: string, pseudoClasses: ForcedPseudoClass[]): Promise<() => Promise<void>>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.forcePseudoState = forcePseudoState;
|
|
4
|
+
/**
|
|
5
|
+
* Force CSS pseudo-classes on the first element matching a selector.
|
|
6
|
+
*
|
|
7
|
+
* This uses Chromium's DevTools Protocol and therefore only works in Chromium
|
|
8
|
+
* projects. The state is synthetic: moving the pointer or blurring the active
|
|
9
|
+
* element does not clear it. Call the returned idempotent cleanup function when
|
|
10
|
+
* the screenshot and accessibility scan are complete.
|
|
11
|
+
*/
|
|
12
|
+
async function forcePseudoState(page, selector, pseudoClasses) {
|
|
13
|
+
let session;
|
|
14
|
+
try {
|
|
15
|
+
session = await page.context().newCDPSession(page);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error('forcePseudoState() requires a Chromium browser project.', { cause: error });
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
await session.send('DOM.enable');
|
|
22
|
+
await session.send('CSS.enable');
|
|
23
|
+
const { root } = await session.send('DOM.getDocument');
|
|
24
|
+
const { nodeId } = await session.send('DOM.querySelector', {
|
|
25
|
+
nodeId: root.nodeId,
|
|
26
|
+
selector,
|
|
27
|
+
});
|
|
28
|
+
if (!nodeId) {
|
|
29
|
+
throw new Error(`forcePseudoState() could not find an element matching "${selector}".`);
|
|
30
|
+
}
|
|
31
|
+
await session.send('CSS.forcePseudoState', { nodeId, forcedPseudoClasses: pseudoClasses });
|
|
32
|
+
let cleared = false;
|
|
33
|
+
return async () => {
|
|
34
|
+
if (cleared) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
cleared = true;
|
|
38
|
+
try {
|
|
39
|
+
await session.send('CSS.forcePseudoState', { nodeId, forcedPseudoClasses: [] });
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await session.detach();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
await session.detach();
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/lib/videos.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Page } from "@playwright/test";
|
|
2
|
+
/**
|
|
3
|
+
* Settle every <video> on the page so a screenshot of it is reproducible.
|
|
4
|
+
*
|
|
5
|
+
* A `<video>` is the one replaced element nothing else in the screenshot path
|
|
6
|
+
* covers. waitForImages() only looks at `img`, and Playwright's
|
|
7
|
+
* `animations: 'disabled'` only fast-forwards CSS transitions and Web
|
|
8
|
+
* Animations -- it does not touch media playback. That leaves two separate ways
|
|
9
|
+
* for a video to break a visual comparison:
|
|
10
|
+
*
|
|
11
|
+
* 1. **The frame is captured before it exists.** A full-page screenshot rasters
|
|
12
|
+
* the whole document, including regions that were never in the viewport, so
|
|
13
|
+
* it will happily capture a video that is still loading. The region paints as
|
|
14
|
+
* an empty box on one capture and as the video on the next, which Playwright
|
|
15
|
+
* reports as "Failed to take two consecutive stable screenshots" -- the run
|
|
16
|
+
* burns its whole stability window and fails even though the image it ended
|
|
17
|
+
* up with was correct. When the video is slower still, the captures agree on
|
|
18
|
+
* the empty box, which is worse: it passes stability and bakes a blank
|
|
19
|
+
* rectangle into the baseline if snapshots are being regenerated.
|
|
20
|
+
* 2. **The frame is captured while the video is playing.** An `autoplay muted
|
|
21
|
+
* loop` video renders whatever moment the shutter caught. Chromium only
|
|
22
|
+
* autoplays a muted video while it is on screen, so whether such a baseline
|
|
23
|
+
* is reproducible at all currently rests on nothing more principled than
|
|
24
|
+
* whether an earlier scroll happened to bring the video into the viewport.
|
|
25
|
+
*
|
|
26
|
+
* Both are fixed together: pin playback first, bring the video on screen so it
|
|
27
|
+
* both loads and composites, wait for a decodable frame, and rewind.
|
|
28
|
+
*
|
|
29
|
+
* Note this is deliberately *not* built on `requestVideoFrameCallback()`, which
|
|
30
|
+
* looks like the right signal and is not: it only fires when a frame is
|
|
31
|
+
* presented, and a paused off-screen video never presents one. Measured against
|
|
32
|
+
* a real page it never fired at all -- every call sat there until its timeout,
|
|
33
|
+
* adding the full timeout to each capture and reporting nothing.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* Settle every visible video, in the browser.
|
|
37
|
+
*
|
|
38
|
+
* This runs in the browser via `page.evaluate()`, which serializes the function
|
|
39
|
+
* source, so it must stay self-contained and reference nothing else in this
|
|
40
|
+
* module. It is exported separately from waitForVideos() so it can be tested
|
|
41
|
+
* directly.
|
|
42
|
+
*
|
|
43
|
+
* The order of operations matters and is not the obvious one:
|
|
44
|
+
*
|
|
45
|
+
* - Playback is pinned -- `autoplay` cleared and the element paused -- *before*
|
|
46
|
+
* the video is scrolled into view, because bringing a muted `autoplay` video
|
|
47
|
+
* on screen is exactly what starts it: measured on a real page, scrolling to
|
|
48
|
+
* it first and pausing after left `currentTime` at 0.6s and climbing, while
|
|
49
|
+
* pinning first held it at 0 through the same scroll. Pinning first turns the
|
|
50
|
+
* scroll into a pure repaint.
|
|
51
|
+
* - The video is scrolled into view *before* its readiness is polled, because
|
|
52
|
+
* for a lazily loaded video the scroll is what starts the load. Polling first
|
|
53
|
+
* would sit there watching a `preload="none"` element that has been told not
|
|
54
|
+
* to fetch anything, burn the whole timeout, and only then scroll it on
|
|
55
|
+
* screen -- leaving the capture racing a load that had just begun.
|
|
56
|
+
*
|
|
57
|
+
* Videos are handled in serial: each one is scrolled into view in turn, and
|
|
58
|
+
* doing that concurrently would just mean the last one wins. Every scroll is
|
|
59
|
+
* undone -- the window offset and any scrollable ancestor `scrollIntoView()`
|
|
60
|
+
* moved -- so this composes with callers that care where the page is left;
|
|
61
|
+
* waitForImages(), in particular, does its own careful scroll back to the top.
|
|
62
|
+
*
|
|
63
|
+
* Playback state is not restored here, because the whole point is to leave the
|
|
64
|
+
* videos pinned at their first frame for the capture. It is recorded on each
|
|
65
|
+
* element so restorePlayback() can put it back afterwards.
|
|
66
|
+
*
|
|
67
|
+
* A video that never produces a frame is not an error. A page that legitimately
|
|
68
|
+
* references a missing or undecodable video should still be screenshotted and
|
|
69
|
+
* still have its accessibility checked, and the comparison is what fails. The
|
|
70
|
+
* wait is not silent either: the sources that never became ready are returned
|
|
71
|
+
* so the caller can report them.
|
|
72
|
+
*
|
|
73
|
+
* @param options.timeoutMs How long to wait for each video to become ready.
|
|
74
|
+
* @param options.pollMs How long to sleep between readiness checks.
|
|
75
|
+
* @param options.paintTimeoutMs How long to wait on a frame or a seek that may
|
|
76
|
+
* never arrive.
|
|
77
|
+
* @returns The sources of the videos that never became ready, and whether the
|
|
78
|
+
* page was scrolled at all.
|
|
79
|
+
*/
|
|
80
|
+
export declare function settleVideos(options: {
|
|
81
|
+
timeoutMs: number;
|
|
82
|
+
pollMs?: number;
|
|
83
|
+
paintTimeoutMs?: number;
|
|
84
|
+
}): Promise<{
|
|
85
|
+
notReady: string[];
|
|
86
|
+
scrolled: boolean;
|
|
87
|
+
}>;
|
|
88
|
+
/**
|
|
89
|
+
* Put back the playback state settleVideos() recorded, in the browser.
|
|
90
|
+
*
|
|
91
|
+
* Like settleVideos() this is serialized into the page by `page.evaluate()`, so
|
|
92
|
+
* it must stay self-contained, and it is exported separately from
|
|
93
|
+
* restoreVideoPlayback() so it can be tested directly.
|
|
94
|
+
*
|
|
95
|
+
* Videos that were not settled are left alone, and the recorded state is
|
|
96
|
+
* cleared as it is applied so a second call is a no-op rather than a rewind of
|
|
97
|
+
* whatever the test did in between.
|
|
98
|
+
*/
|
|
99
|
+
export declare function restorePlayback(): void;
|
|
100
|
+
/**
|
|
101
|
+
* Wait for every visible video to be ready, composited, and paused at its
|
|
102
|
+
* first frame.
|
|
103
|
+
*
|
|
104
|
+
* See settleVideos() for what "ready" means, why playback is pinned before the
|
|
105
|
+
* video is scrolled into view, why the readiness poll comes after that scroll,
|
|
106
|
+
* and why this is not built on `requestVideoFrameCallback()`.
|
|
107
|
+
*
|
|
108
|
+
* This runs in every frame, not just the main one: media embeds commonly render
|
|
109
|
+
* inside iframes, and a
|
|
110
|
+
* `<video>` in one of those is no less able to break a comparison.
|
|
111
|
+
*
|
|
112
|
+
* @param page
|
|
113
|
+
* @param options A timeout or settling options.
|
|
114
|
+
* @returns The sources of the videos that never became ready. Empty when they
|
|
115
|
+
* all did.
|
|
116
|
+
*/
|
|
117
|
+
export interface WaitForVideosOptions {
|
|
118
|
+
/** How long to wait for each video to produce a frame. */
|
|
119
|
+
timeoutMs?: number;
|
|
120
|
+
/** Optional adapter hook run after video scrolling has been restored. */
|
|
121
|
+
afterScroll?: (page: Page) => Promise<void>;
|
|
122
|
+
}
|
|
123
|
+
export declare function waitForVideos(page: Page, options?: number | WaitForVideosOptions): Promise<string[]>;
|
|
124
|
+
/**
|
|
125
|
+
* Restore the playback state waitForVideos() pinned.
|
|
126
|
+
*
|
|
127
|
+
* Settling a video means pausing it, clearing `autoplay` and rewinding it, and
|
|
128
|
+
* a test that takes a screenshot has not asked for any of that to outlive the
|
|
129
|
+
* capture -- it may well go on to assert that the video is playing. Call this
|
|
130
|
+
* once the screenshot is taken to hand the page back as it was found.
|
|
131
|
+
*
|
|
132
|
+
* @param page
|
|
133
|
+
*/
|
|
134
|
+
export declare function restoreVideoPlayback(page: Page): Promise<void>;
|