@lullabot/playwright-testing 0.1.0 → 1.0.1

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.
Files changed (68) hide show
  1. package/bin/github-a11y-summary +2 -2
  2. package/bin/github-failure-summary +4 -4
  3. package/lib/accessibility-baseline-file.d.ts +6 -6
  4. package/lib/accessibility-baseline-file.js +29 -23
  5. package/lib/accessibility-baseline.js +5 -3
  6. package/lib/accessible-screenshot.d.ts +4 -4
  7. package/lib/accessible-screenshot.js +88 -76
  8. package/lib/focus.js +4 -1
  9. package/lib/frames.js +4 -4
  10. package/lib/github/a11y-summary.js +74 -79
  11. package/lib/github/attachments.d.ts +1 -1
  12. package/lib/github/attachments.js +36 -34
  13. package/lib/github/failure-summary.d.ts +4 -4
  14. package/lib/github/failure-summary.js +139 -107
  15. package/lib/github/index.d.ts +6 -6
  16. package/lib/github/report-paths.js +17 -9
  17. package/lib/hover.js +17 -17
  18. package/lib/images.js +15 -12
  19. package/lib/index.d.ts +13 -13
  20. package/lib/interaction-states.d.ts +2 -2
  21. package/lib/interaction-states.js +7 -7
  22. package/lib/mock/index.d.ts +1 -1
  23. package/lib/mock/youtube.d.ts +1 -1
  24. package/lib/mock/youtube.js +1 -1
  25. package/lib/pseudo-state.d.ts +2 -2
  26. package/lib/pseudo-state.js +15 -7
  27. package/lib/videos.js +21 -10
  28. package/lib/visualdiff.d.ts +4 -4
  29. package/lib/visualdiff.js +35 -27
  30. package/package.json +3 -3
  31. package/src/accessibility-baseline-file.test.ts +190 -154
  32. package/src/accessibility-baseline-file.ts +107 -83
  33. package/src/accessibility-baseline.test.ts +633 -433
  34. package/src/accessibility-baseline.ts +41 -21
  35. package/src/accessible-screenshot.test.ts +551 -423
  36. package/src/accessible-screenshot.ts +334 -245
  37. package/src/focus.test.ts +23 -23
  38. package/src/focus.ts +7 -2
  39. package/src/fonts.test.ts +13 -13
  40. package/src/fonts.ts +1 -1
  41. package/src/frames.test.ts +10 -8
  42. package/src/frames.ts +5 -5
  43. package/src/github/a11y-summary.test.ts +436 -332
  44. package/src/github/a11y-summary.ts +189 -179
  45. package/src/github/attachments.test.ts +228 -167
  46. package/src/github/attachments.ts +144 -121
  47. package/src/github/failure-summary.test.ts +659 -445
  48. package/src/github/failure-summary.ts +376 -274
  49. package/src/github/index.test.ts +14 -14
  50. package/src/github/index.ts +6 -6
  51. package/src/github/report-paths.test.ts +211 -156
  52. package/src/github/report-paths.ts +75 -61
  53. package/src/hover.test.ts +50 -46
  54. package/src/hover.ts +18 -18
  55. package/src/images.test.ts +251 -206
  56. package/src/images.ts +52 -43
  57. package/src/index.ts +13 -13
  58. package/src/interaction-states.test.ts +72 -42
  59. package/src/interaction-states.ts +43 -29
  60. package/src/mock/index.ts +1 -1
  61. package/src/mock/youtube.test.ts +26 -26
  62. package/src/mock/youtube.ts +4 -6
  63. package/src/pseudo-state.test.ts +58 -51
  64. package/src/pseudo-state.ts +32 -27
  65. package/src/videos.test.ts +559 -399
  66. package/src/videos.ts +68 -44
  67. package/src/visualdiff.test.ts +416 -315
  68. package/src/visualdiff.ts +140 -99
package/src/images.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {Page} from "@playwright/test";
1
+ import { Page } from "@playwright/test";
2
2
 
3
3
  export interface WaitForImagesOptions {
4
4
  /** How long to wait for visible images to decode. */
@@ -36,13 +36,15 @@ export async function waitForImages(
36
36
  for (const l of await locators.all()) {
37
37
  // Ensure images are connected to the DOM before trying to scroll to them.
38
38
  // https://github.com/microsoft/playwright/issues/23758
39
- if (await l.evaluate(image => image.isConnected)) {
39
+ if (await l.evaluate((image) => image.isConnected)) {
40
40
  await l.scrollIntoViewIfNeeded();
41
41
  }
42
42
  }
43
43
 
44
44
  // Make sure all images have loaded.
45
- const promises = (await locators.all()).map(locator => locator.evaluate(settleImage));
45
+ const promises = (await locators.all()).map((locator) =>
46
+ locator.evaluate(settleImage),
47
+ );
46
48
  await Promise.all(promises);
47
49
 
48
50
  // The wait above treats an errored image as "loaded". Decode each visible
@@ -59,16 +61,16 @@ export async function waitForImages(
59
61
  window.scroll({
60
62
  top: 0,
61
63
  left: 0,
62
- behavior: 'instant',
63
- })
64
+ behavior: "instant",
65
+ }),
64
66
  );
65
67
 
66
68
  // window.scroll is async and doesn't return a promise, so wait until the
67
69
  // browser confirms we are at the top again.
68
- const scrollState = {forced: false};
69
- await page.waitForFunction(state => {
70
+ const scrollState = { forced: false };
71
+ await page.waitForFunction((state) => {
70
72
  if (window.scrollY !== 0 && !state.forced) {
71
- window.scroll({top: 0, left: 0, behavior: 'instant'});
73
+ window.scroll({ top: 0, left: 0, behavior: "instant" });
72
74
  state.forced = true;
73
75
  }
74
76
  return window.scrollY === 0;
@@ -111,7 +113,7 @@ export function settleImage(image: HTMLImageElement): void | Promise<void> {
111
113
  if ((image.width <= 1 && image.height <= 1) || image.complete) {
112
114
  return;
113
115
  }
114
- return new Promise<void>(resolve => {
116
+ return new Promise<void>((resolve) => {
115
117
  const settled = () => {
116
118
  image.removeEventListener("load", settled);
117
119
  image.removeEventListener("error", settled);
@@ -131,7 +133,7 @@ export async function waitForAllImages(
131
133
  page: Page,
132
134
  options: WaitForImagesOptions = {},
133
135
  ): Promise<void> {
134
- await waitForImages(page, 'img:visible', options);
136
+ await waitForImages(page, "img:visible", options);
135
137
  }
136
138
 
137
139
  /**
@@ -167,14 +169,12 @@ export async function waitForAllImages(
167
169
  * rewritten and re-requested. Defaults to false.
168
170
  * @returns The URLs of the images that never decoded. Empty when they all did.
169
171
  */
170
- export async function decodeVisibleImages(
171
- options: {
172
- timeoutMs: number,
173
- pollMs?: number,
174
- reloadIntervalMs?: number,
175
- recoverErroredImages?: boolean,
176
- }
177
- ): Promise<string[]> {
172
+ export async function decodeVisibleImages(options: {
173
+ timeoutMs: number;
174
+ pollMs?: number;
175
+ reloadIntervalMs?: number;
176
+ recoverErroredImages?: boolean;
177
+ }): Promise<string[]> {
178
178
  const timeoutMs = options.timeoutMs;
179
179
  const pollMs = options.pollMs ?? 250;
180
180
  const reloadIntervalMs = options.reloadIntervalMs ?? 2000;
@@ -204,7 +204,7 @@ export async function decodeVisibleImages(
204
204
  // one fetched, instead of re-running srcset selection.
205
205
  const picture = img.closest("picture");
206
206
  if (picture) {
207
- picture.querySelectorAll("source").forEach(source => source.remove());
207
+ picture.querySelectorAll("source").forEach((source) => source.remove());
208
208
  }
209
209
  img.removeAttribute("srcset");
210
210
  img.src = url.href;
@@ -220,31 +220,37 @@ export async function decodeVisibleImages(
220
220
  const candidates = Array.from(document.images).filter(isCandidate);
221
221
  // Collect the verdicts positionally rather than pushing as each check
222
222
  // settles, so anything reported below stays in document order.
223
- const verdicts = await Promise.all(candidates.map(async img => {
224
- if (img.complete && img.naturalWidth > 0) {
225
- return "loaded"; // Loaded with intrinsic dimensions.
226
- }
227
- if (!img.complete) {
228
- return "loading";
229
- }
230
- // Complete with a zero naturalWidth is ambiguous: a genuine load error
231
- // (404/503) rejects decode(), while a valid but dimensionless image
232
- // (such as an SVG with no intrinsic size) resolves it. Only the former
233
- // should be re-requested; the latter is already settled.
234
- try {
235
- await img.decode();
236
- return "loaded";
237
- } catch {
238
- return "errored";
239
- }
240
- }));
241
- const pending = candidates.filter((img, index) => verdicts[index] !== "loaded");
242
- const errored = candidates.filter((img, index) => verdicts[index] === "errored");
223
+ const verdicts = await Promise.all(
224
+ candidates.map(async (img) => {
225
+ if (img.complete && img.naturalWidth > 0) {
226
+ return "loaded"; // Loaded with intrinsic dimensions.
227
+ }
228
+ if (!img.complete) {
229
+ return "loading";
230
+ }
231
+ // Complete with a zero naturalWidth is ambiguous: a genuine load error
232
+ // (404/503) rejects decode(), while a valid but dimensionless image
233
+ // (such as an SVG with no intrinsic size) resolves it. Only the former
234
+ // should be re-requested; the latter is already settled.
235
+ try {
236
+ await img.decode();
237
+ return "loaded";
238
+ } catch {
239
+ return "errored";
240
+ }
241
+ }),
242
+ );
243
+ const pending = candidates.filter(
244
+ (img, index) => verdicts[index] !== "loaded",
245
+ );
246
+ const errored = candidates.filter(
247
+ (img, index) => verdicts[index] === "errored",
248
+ );
243
249
  if (pending.length === 0) {
244
250
  return [];
245
251
  }
246
252
  if (Date.now() >= deadline) {
247
- return pending.map(img => {
253
+ return pending.map((img) => {
248
254
  const src = img.currentSrc || img.src;
249
255
  try {
250
256
  // Report the URL as the page authored it, without the cache-busting
@@ -262,7 +268,7 @@ export async function decodeVisibleImages(
262
268
  lastReload = Date.now();
263
269
  errored.forEach(reload);
264
270
  }
265
- await new Promise(resolve => setTimeout(resolve, pollMs));
271
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
266
272
  }
267
273
  }
268
274
 
@@ -289,10 +295,13 @@ export async function waitForImagesToDecode(
289
295
  timeoutMs = 15000,
290
296
  recoverErroredImages = false,
291
297
  ): Promise<string[]> {
292
- const undecoded = await page.evaluate(decodeVisibleImages, {timeoutMs, recoverErroredImages});
298
+ const undecoded = await page.evaluate(decodeVisibleImages, {
299
+ timeoutMs,
300
+ recoverErroredImages,
301
+ });
293
302
  if (undecoded.length > 0) {
294
303
  console.warn(
295
- `waitForImagesToDecode: ${undecoded.length} image(s) did not finish loading within ${timeoutMs}ms and may be captured as broken: ${undecoded.join(', ')}`
304
+ `waitForImagesToDecode: ${undecoded.length} image(s) did not finish loading within ${timeoutMs}ms and may be captured as broken: ${undecoded.join(", ")}`,
296
305
  );
297
306
  }
298
307
  return undecoded;
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
- export * from './focus.js'
2
- export * from './accessibility-baseline.js'
3
- export * from './accessibility-baseline-file.js'
4
- export * from './accessible-screenshot.js'
5
- export * from './fonts.js'
6
- export * from './frames.js'
7
- export * from './hover.js'
8
- export * from './images.js'
9
- export * from './interaction-states.js'
10
- export * from './pseudo-state.js'
11
- export * from './videos.js'
12
- export * from './visualdiff.js'
13
- export * from './mock/index.js'
1
+ export * from "./focus.js";
2
+ export * from "./accessibility-baseline.js";
3
+ export * from "./accessibility-baseline-file.js";
4
+ export * from "./accessible-screenshot.js";
5
+ export * from "./fonts.js";
6
+ export * from "./frames.js";
7
+ export * from "./hover.js";
8
+ export * from "./images.js";
9
+ export * from "./interaction-states.js";
10
+ export * from "./pseudo-state.js";
11
+ export * from "./videos.js";
12
+ export * from "./visualdiff.js";
13
+ export * from "./mock/index.js";
@@ -1,48 +1,78 @@
1
- import {describe, expect, it, vi} from 'vitest'
2
- import {applyInteractionStates, validateInteractionStates} from './interaction-states'
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ applyInteractionStates,
4
+ validateInteractionStates,
5
+ } from "./interaction-states";
3
6
 
4
- describe('validateInteractionStates', () => {
5
- it('rejects states that real input cannot hold simultaneously', () => {
6
- const locator = {} as never
7
- expect(() => validateInteractionStates([
8
- {locator, states: ['hover']},
9
- {locator, states: ['hover']},
10
- ])).toThrow('at most one')
11
- expect(() => validateInteractionStates([
12
- {locator, states: ['focus', 'focus']},
13
- ])).toThrow('cannot repeat')
14
- })
15
- })
7
+ describe("validateInteractionStates", () => {
8
+ it("rejects states that real input cannot hold simultaneously", () => {
9
+ const locator = {} as never;
10
+ expect(() =>
11
+ validateInteractionStates([
12
+ { locator, states: ["hover"] },
13
+ { locator, states: ["hover"] },
14
+ ]),
15
+ ).toThrow("at most one");
16
+ expect(() =>
17
+ validateInteractionStates([{ locator, states: ["focus", "focus"] }]),
18
+ ).toThrow("cannot repeat");
19
+ });
20
+ });
16
21
 
17
- describe('applyInteractionStates', () => {
18
- it('applies hover before focus and cleans both states up', async () => {
19
- const hover = vi.fn().mockResolvedValue(undefined)
20
- const focus = vi.fn().mockResolvedValue(undefined)
21
- const blur = vi.fn().mockResolvedValue(undefined)
22
- const clearHover = vi.fn().mockResolvedValue(undefined)
22
+ describe("applyInteractionStates", () => {
23
+ it("applies hover before focus and cleans both states up", async () => {
24
+ const hover = vi.fn().mockResolvedValue(undefined);
25
+ const focus = vi.fn().mockResolvedValue(undefined);
26
+ const blur = vi.fn().mockResolvedValue(undefined);
27
+ const clearHover = vi.fn().mockResolvedValue(undefined);
23
28
 
24
- const cleanup = await applyInteractionStates([
25
- {locator: {hover, focus: vi.fn(), blur: vi.fn()} as never, states: ['hover']},
26
- {locator: {hover: vi.fn(), focus, blur} as never, states: ['focus']},
27
- ], {clearHover})
29
+ const cleanup = await applyInteractionStates(
30
+ [
31
+ {
32
+ locator: { hover, focus: vi.fn(), blur: vi.fn() } as never,
33
+ states: ["hover"],
34
+ },
35
+ {
36
+ locator: { hover: vi.fn(), focus, blur } as never,
37
+ states: ["focus"],
38
+ },
39
+ ],
40
+ { clearHover },
41
+ );
28
42
 
29
- expect(hover.mock.invocationCallOrder[0]).toBeLessThan(focus.mock.invocationCallOrder[0])
30
- await cleanup()
31
- await cleanup()
32
- expect(blur).toHaveBeenCalledOnce()
33
- expect(clearHover).toHaveBeenCalledOnce()
34
- })
43
+ expect(hover.mock.invocationCallOrder[0]).toBeLessThan(
44
+ focus.mock.invocationCallOrder[0],
45
+ );
46
+ await cleanup();
47
+ await cleanup();
48
+ expect(blur).toHaveBeenCalledOnce();
49
+ expect(clearHover).toHaveBeenCalledOnce();
50
+ });
35
51
 
36
- it('cleans already-applied state when a later interaction fails', async () => {
37
- const clearHover = vi.fn().mockResolvedValue(undefined)
38
- const blur = vi.fn().mockResolvedValue(undefined)
39
- const failure = new Error('focus failed')
52
+ it("cleans already-applied state when a later interaction fails", async () => {
53
+ const clearHover = vi.fn().mockResolvedValue(undefined);
54
+ const blur = vi.fn().mockResolvedValue(undefined);
55
+ const failure = new Error("focus failed");
40
56
 
41
- await expect(applyInteractionStates([
42
- {locator: {hover: vi.fn().mockResolvedValue(undefined)} as never, states: ['hover']},
43
- {locator: {focus: vi.fn().mockRejectedValue(failure), blur} as never, states: ['focus']},
44
- ], {clearHover})).rejects.toBe(failure)
45
- expect(blur).toHaveBeenCalledOnce()
46
- expect(clearHover).toHaveBeenCalledOnce()
47
- })
48
- })
57
+ await expect(
58
+ applyInteractionStates(
59
+ [
60
+ {
61
+ locator: { hover: vi.fn().mockResolvedValue(undefined) } as never,
62
+ states: ["hover"],
63
+ },
64
+ {
65
+ locator: {
66
+ focus: vi.fn().mockRejectedValue(failure),
67
+ blur,
68
+ } as never,
69
+ states: ["focus"],
70
+ },
71
+ ],
72
+ { clearHover },
73
+ ),
74
+ ).rejects.toBe(failure);
75
+ expect(blur).toHaveBeenCalledOnce();
76
+ expect(clearHover).toHaveBeenCalledOnce();
77
+ });
78
+ });
@@ -1,35 +1,45 @@
1
- import type {Locator} from '@playwright/test'
1
+ import type { Locator } from "@playwright/test";
2
2
 
3
3
  /** Real browser interaction states supported consistently by Playwright. */
4
- export type InteractionState = 'hover' | 'focus'
4
+ export type InteractionState = "hover" | "focus";
5
5
 
6
6
  /** A locator and the real browser interaction states to apply to it. */
7
7
  export interface ScreenshotInteractionState {
8
- locator: Locator
9
- states: InteractionState[]
8
+ locator: Locator;
9
+ states: InteractionState[];
10
10
  }
11
11
 
12
12
  export interface InteractionStateCleanupOptions {
13
13
  /** Move the pointer away from the hovered locator during cleanup. */
14
- clearHover?: () => Promise<void>
14
+ clearHover?: () => Promise<void>;
15
15
  }
16
16
 
17
17
  /** Validate that the requested states are possible with real browser input. */
18
18
  export function validateInteractionStates(
19
19
  interactionStates: ScreenshotInteractionState[],
20
20
  ): void {
21
- const hoverTargets = interactionStates.filter(({states}) => states.includes('hover'))
22
- const focusTargets = interactionStates.filter(({states}) => states.includes('focus'))
21
+ const hoverTargets = interactionStates.filter(({ states }) =>
22
+ states.includes("hover"),
23
+ );
24
+ const focusTargets = interactionStates.filter(({ states }) =>
25
+ states.includes("focus"),
26
+ );
23
27
 
24
28
  if (hoverTargets.length > 1) {
25
- throw new Error('interactionStates can hover at most one locator at a time.')
29
+ throw new Error(
30
+ "interactionStates can hover at most one locator at a time.",
31
+ );
26
32
  }
27
33
  if (focusTargets.length > 1) {
28
- throw new Error('interactionStates can focus at most one locator at a time.')
34
+ throw new Error(
35
+ "interactionStates can focus at most one locator at a time.",
36
+ );
29
37
  }
30
- for (const {states} of interactionStates) {
38
+ for (const { states } of interactionStates) {
31
39
  if (new Set(states).size !== states.length) {
32
- throw new Error('interactionStates cannot repeat a state for the same locator.')
40
+ throw new Error(
41
+ "interactionStates cannot repeat a state for the same locator.",
42
+ );
33
43
  }
34
44
  }
35
45
  }
@@ -45,50 +55,54 @@ export async function applyInteractionStates(
45
55
  interactionStates: ScreenshotInteractionState[],
46
56
  cleanupOptions: InteractionStateCleanupOptions = {},
47
57
  ): Promise<() => Promise<void>> {
48
- validateInteractionStates(interactionStates)
58
+ validateInteractionStates(interactionStates);
49
59
 
50
- const hoverTarget = interactionStates.find(({states}) => states.includes('hover'))?.locator
51
- const focusTarget = interactionStates.find(({states}) => states.includes('focus'))?.locator
52
- let hoverApplied = false
53
- let focusApplied = false
54
- let cleaned = false
60
+ const hoverTarget = interactionStates.find(({ states }) =>
61
+ states.includes("hover"),
62
+ )?.locator;
63
+ const focusTarget = interactionStates.find(({ states }) =>
64
+ states.includes("focus"),
65
+ )?.locator;
66
+ let hoverApplied = false;
67
+ let focusApplied = false;
68
+ let cleaned = false;
55
69
 
56
70
  const cleanup = async () => {
57
71
  if (cleaned) {
58
- return
72
+ return;
59
73
  }
60
- cleaned = true
74
+ cleaned = true;
61
75
  try {
62
76
  if (focusApplied) {
63
- await focusTarget?.blur()
77
+ await focusTarget?.blur();
64
78
  }
65
79
  } finally {
66
80
  if (hoverApplied) {
67
- await cleanupOptions.clearHover?.()
81
+ await cleanupOptions.clearHover?.();
68
82
  }
69
83
  }
70
- }
84
+ };
71
85
 
72
86
  try {
73
87
  if (hoverTarget) {
74
88
  // Mark the state first: Playwright can move the pointer and then fail
75
89
  // while waiting for actionability, and that partial effect still needs
76
90
  // cleanup.
77
- hoverApplied = true
78
- await hoverTarget.hover()
91
+ hoverApplied = true;
92
+ await hoverTarget.hover();
79
93
  }
80
94
  if (focusTarget) {
81
- focusApplied = true
82
- await focusTarget.focus()
95
+ focusApplied = true;
96
+ await focusTarget.focus();
83
97
  }
84
- return cleanup
98
+ return cleanup;
85
99
  } catch (error) {
86
100
  try {
87
- await cleanup()
101
+ await cleanup();
88
102
  } catch {
89
103
  // Preserve the interaction failure. Cleanup is best effort on this path;
90
104
  // callers cannot act on a cleanup error if applying the state failed.
91
105
  }
92
- throw error
106
+ throw error;
93
107
  }
94
108
  }
package/src/mock/index.ts CHANGED
@@ -1 +1 @@
1
- export { YoutubeMock } from './youtube.js'
1
+ export { YoutubeMock } from "./youtube.js";
@@ -1,38 +1,38 @@
1
- import { describe, it, expect, vi } from 'vitest'
2
- import { YoutubeMock } from './youtube'
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { YoutubeMock } from "./youtube";
3
3
 
4
- describe('YoutubeMock', () => {
5
- it('registers a route handler for youtube.com', async () => {
4
+ describe("YoutubeMock", () => {
5
+ it("registers a route handler for youtube.com", async () => {
6
6
  const mockPage = {
7
7
  route: vi.fn(),
8
- }
8
+ };
9
9
 
10
- const mock = new YoutubeMock()
11
- await mock.mock(mockPage as any)
10
+ const mock = new YoutubeMock();
11
+ await mock.mock(mockPage as any);
12
12
 
13
- expect(mockPage.route).toHaveBeenCalledOnce()
14
- const [pattern] = mockPage.route.mock.calls[0]
15
- expect(pattern).toBeInstanceOf(RegExp)
16
- expect(pattern.test('https://www.youtube.com/embed/abc123')).toBe(true)
17
- expect(pattern.test('https://www.notyoutube.com')).toBe(false)
18
- })
13
+ expect(mockPage.route).toHaveBeenCalledOnce();
14
+ const [pattern] = mockPage.route.mock.calls[0];
15
+ expect(pattern).toBeInstanceOf(RegExp);
16
+ expect(pattern.test("https://www.youtube.com/embed/abc123")).toBe(true);
17
+ expect(pattern.test("https://www.notyoutube.com")).toBe(false);
18
+ });
19
19
 
20
- it('fulfills with HTML mock content', async () => {
21
- const mockFulfill = vi.fn()
20
+ it("fulfills with HTML mock content", async () => {
21
+ const mockFulfill = vi.fn();
22
22
  const mockPage = {
23
23
  route: vi.fn(),
24
- }
24
+ };
25
25
 
26
- const mock = new YoutubeMock()
27
- await mock.mock(mockPage as any)
26
+ const mock = new YoutubeMock();
27
+ await mock.mock(mockPage as any);
28
28
 
29
29
  // Call the route handler
30
- const handler = mockPage.route.mock.calls[0][1]
31
- await handler({ fulfill: mockFulfill })
30
+ const handler = mockPage.route.mock.calls[0][1];
31
+ await handler({ fulfill: mockFulfill });
32
32
 
33
- expect(mockFulfill).toHaveBeenCalledOnce()
34
- const fulfillArg = mockFulfill.mock.calls[0][0]
35
- expect(fulfillArg.contentType).toBe('text/html')
36
- expect(fulfillArg.body).toContain('Youtube Video Mock')
37
- })
38
- })
33
+ expect(mockFulfill).toHaveBeenCalledOnce();
34
+ const fulfillArg = mockFulfill.mock.calls[0][0];
35
+ expect(fulfillArg.contentType).toBe("text/html");
36
+ expect(fulfillArg.body).toContain("Youtube Video Mock");
37
+ });
38
+ });
@@ -1,12 +1,11 @@
1
- import type {Page} from '@playwright/test';
2
- import type {Mockable} from "../visualdiff.js";
1
+ import type { Page } from "@playwright/test";
2
+ import type { Mockable } from "../visualdiff.js";
3
3
 
4
4
  export class YoutubeMock implements Mockable {
5
-
6
5
  public async mock(page: Page): Promise<void> {
7
- await page.route(/www\.youtube\.com/i, async route => {
6
+ await page.route(/www\.youtube\.com/i, async (route) => {
8
7
  await route.fulfill({
9
- contentType: 'text/html',
8
+ contentType: "text/html",
10
9
  body: `
11
10
  <html>
12
11
  <head>
@@ -35,5 +34,4 @@ export class YoutubeMock implements Mockable {
35
34
  });
36
35
  });
37
36
  }
38
-
39
37
  }