@empiricalrun/playwright-utils 0.20.13 → 0.21.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # @empiricalrun/playwright-utils
2
2
 
3
+ ## 0.21.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0622b6e: feat: add custom expect for visual usability assertions
8
+
9
+ ## 0.20.14
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [04db1a6]
14
+ - Updated dependencies [1b1815d]
15
+ - Updated dependencies [62800d7]
16
+ - @empiricalrun/test-gen@0.45.1
17
+
3
18
  ## 0.20.13
4
19
 
5
20
  ### Patch Changes
@@ -0,0 +1,7 @@
1
+ import type { Page } from "@playwright/test";
2
+ export type VisualMatcherResult = {
3
+ message: () => string;
4
+ pass: boolean;
5
+ };
6
+ export declare function toLookRight(page: Page, pageDescription: string): Promise<VisualMatcherResult>;
7
+ //# sourceMappingURL=expect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expect.d.ts","sourceRoot":"","sources":["../../src/test/expect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAO7C,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,MAAM,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AA0BF,wBAAsB,WAAW,CAC/B,IAAI,EAAE,IAAI,EACV,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,mBAAmB,CAAC,CA6E9B"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toLookRight = void 0;
4
+ const llm_1 = require("@empiricalrun/llm");
5
+ const vision_1 = require("@empiricalrun/llm/vision");
6
+ const mouse_pointer_1 = require("./scripts/mouse-pointer");
7
+ const SYSTEM_PROMPT = `
8
+ You are a software QA tester who specializes in visual testing. You look at a
9
+ UI screenshot and evaluate whether the UI is rendered correctly. While you don't
10
+ have a reference design for it, you can use the page description that is provided
11
+ to you to evaluate the UI.
12
+
13
+ There are signs when a UI is not rendered correctly. UI elements are overlapping
14
+ each other, text is cut off, or the UI components are not aligned. Margins are
15
+ uneven, or the UI components are not spaced correctly.
16
+
17
+ You need to ensure that the UI is rendered correctly. Among other things, you
18
+ should check the following:
19
+ - The page must be coherent. For example, the title of the page should be consistent
20
+ with the content.
21
+ - The page should not have large empty white/gray areas. These indicate UI layout
22
+ issues or page loading bugs.
23
+ - Images and other media on the page should be aligned and sized correctly.
24
+ - The page should have legible text that is easy to read.
25
+ - The page should not have popups or UI elements that are blocking the content.
26
+
27
+ You are to respond to your visual evaluation with a Pass or Fail, and provide
28
+ a reason for your evaluation.
29
+ `;
30
+ async function toLookRight(page, pageDescription) {
31
+ await (0, mouse_pointer_1.removeMousePointerHighlighter)(page);
32
+ const screenshot = await page.screenshot();
33
+ await page.screenshot({ path: "screenshot-from-assertion.png" });
34
+ const base64Image = screenshot.toString("base64");
35
+ await (0, mouse_pointer_1.addHighlighterScriptsToPage)(page);
36
+ const llm = new llm_1.LLM({
37
+ provider: "openai",
38
+ defaultModel: "gpt-4o",
39
+ });
40
+ const response = await llm.createChatCompletion({
41
+ messages: [
42
+ {
43
+ role: "system",
44
+ content: SYSTEM_PROMPT,
45
+ },
46
+ {
47
+ role: "user",
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: "Page description:\n" + pageDescription,
52
+ },
53
+ {
54
+ type: "image_url",
55
+ image_url: {
56
+ url: (0, vision_1.imageFormatForProvider)("anthropic", base64Image),
57
+ },
58
+ },
59
+ ],
60
+ },
61
+ ],
62
+ modelParameters: {
63
+ temperature: 0.1,
64
+ tool_choice: {
65
+ type: "function",
66
+ function: { name: "send_response" },
67
+ },
68
+ },
69
+ tools: [
70
+ {
71
+ type: "function",
72
+ function: {
73
+ name: "send_response",
74
+ description: "Send your response after evaluating the image",
75
+ parameters: {
76
+ type: "object",
77
+ properties: {
78
+ reason: {
79
+ type: "string",
80
+ description: "Reasoning for the evaluation, shared as a step-by-step chain of thought.",
81
+ },
82
+ result: { type: "string", enum: ["Pass", "Fail"] },
83
+ most_critical_reason: {
84
+ type: "string",
85
+ description: `
86
+ Pick the most critical reason for your evaluation. For example, if you chose Fail as the result,
87
+ go through the reasons and pick the one that made you choose Fail`,
88
+ },
89
+ },
90
+ required: ["reason", "result", "most_critical_reason"],
91
+ },
92
+ },
93
+ },
94
+ ],
95
+ });
96
+ const rawResponse = response.tool_calls[0];
97
+ const result = JSON.parse(rawResponse.function.arguments);
98
+ return {
99
+ message: () => result.most_critical_reason,
100
+ pass: result.result === "Pass",
101
+ };
102
+ }
103
+ exports.toLookRight = toLookRight;
@@ -1,6 +1,15 @@
1
1
  import type { BrowserContext, BrowserContextOptions, Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestType } from "@playwright/test";
2
+ import { expect } from "@playwright/test";
2
3
  import { injectLocatorHighlightScripts } from "./scripts";
3
4
  import { HighlighterOpts } from "./types";
5
+ declare global {
6
+ namespace PlaywrightTest {
7
+ interface Matchers<R> {
8
+ toLookRight(description: string): Promise<R>;
9
+ }
10
+ }
11
+ }
12
+ declare const extendExpect: (expectInstance: typeof expect) => import("@playwright/test").Expect<{}>;
4
13
  type TestOptions = {
5
14
  page: Page;
6
15
  customContextPageProvider: (options?: BrowserContextOptions) => Promise<{
@@ -9,6 +18,6 @@ type TestOptions = {
9
18
  }>;
10
19
  saveVideos: void;
11
20
  };
12
- export declare const baseTestFixture: (testFn: TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>, options?: HighlighterOpts) => TestType<PlaywrightTestArgs & PlaywrightTestOptions & TestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
13
- export { injectLocatorHighlightScripts };
21
+ declare const baseTestFixture: (testFn: TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>, options?: HighlighterOpts) => TestType<PlaywrightTestArgs & PlaywrightTestOptions & TestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
22
+ export { baseTestFixture, extendExpect, injectLocatorHighlightScripts };
14
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,IAAI,EACJ,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAK1B,OAAO,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,yBAAyB,EAAE,CACzB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACtD,UAAU,EAAE,IAAI,CAAC;CAClB,CAAC;AAIF,eAAO,MAAM,eAAe,WAClB,SACN,kBAAkB,GAAG,qBAAqB,EAC1C,oBAAoB,GAAG,uBAAuB,CAC/C,YACQ,eAAe,uHA0DzB,CAAC;AAEF,OAAO,EAAE,6BAA6B,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,IAAI,EACJ,kBAAkB,EAClB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,QAAQ,EACT,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1C,OAAO,EAAE,6BAA6B,EAAE,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,cAAc,CAAC;QACvB,UAAU,QAAQ,CAAC,CAAC;YAClB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;SAC9C;KACF;CACF;AAED,QAAA,MAAM,YAAY,mBAA6B,aAAa,0CAG3D,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,IAAI,CAAC;IACX,yBAAyB,EAAE,CACzB,OAAO,CAAC,EAAE,qBAAqB,KAC5B,OAAO,CAAC;QAAE,OAAO,EAAE,cAAc,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,CAAC,CAAC;IACtD,UAAU,EAAE,IAAI,CAAC;CAClB,CAAC;AAIF,QAAA,MAAM,eAAe,WACX,SACN,kBAAkB,GAAG,qBAAqB,EAC1C,oBAAoB,GAAG,uBAAuB,CAC/C,YACQ,eAAe,uHA0DzB,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,6BAA6B,EAAE,CAAC"}
@@ -3,12 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.injectLocatorHighlightScripts = exports.baseTestFixture = void 0;
6
+ exports.injectLocatorHighlightScripts = exports.extendExpect = exports.baseTestFixture = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const rimraf_1 = require("rimraf");
10
+ const expect_1 = require("./expect");
10
11
  const scripts_1 = require("./scripts");
11
12
  Object.defineProperty(exports, "injectLocatorHighlightScripts", { enumerable: true, get: function () { return scripts_1.injectLocatorHighlightScripts; } });
13
+ const extendExpect = function (expectInstance) {
14
+ expectInstance.extend({ toLookRight: expect_1.toLookRight });
15
+ return expectInstance;
16
+ };
17
+ exports.extendExpect = extendExpect;
12
18
  const videoStore = "videos-store";
13
19
  const baseTestFixture = function (testFn, options = {
14
20
  mousePointerHighlighter: true,
@@ -1,3 +1,5 @@
1
1
  import type { Page } from "@playwright/test";
2
+ export declare function addHighlighterScriptsToPage(page: Page): Promise<void>;
3
+ export declare function removeMousePointerHighlighter(page: Page): Promise<void>;
2
4
  export declare function addMousePointerHighlighter(page: Page): void;
3
5
  //# sourceMappingURL=mouse-pointer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mouse-pointer.d.ts","sourceRoot":"","sources":["../../../src/test/scripts/mouse-pointer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAI7C,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,IAAI,QA0MpD"}
1
+ {"version":3,"file":"mouse-pointer.d.ts","sourceRoot":"","sources":["../../../src/test/scripts/mouse-pointer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAmD7C,wBAAsB,2BAA2B,CAAC,IAAI,EAAE,IAAI,iBAqD3D;AAED,wBAAsB,6BAA6B,CAAC,IAAI,EAAE,IAAI,iBA6B7D;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,IAAI,QAUpD"}
@@ -1,166 +1,125 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.addMousePointerHighlighter = void 0;
3
+ exports.addMousePointerHighlighter = exports.removeMousePointerHighlighter = exports.addHighlighterScriptsToPage = void 0;
4
4
  const logger_1 = require("../../logger");
5
- function addMousePointerHighlighter(page) {
6
- page.on("load", async () => {
7
- try {
8
- await page?.evaluate(() => {
9
- const box = document.createElement("div");
10
- box.classList.add("empirical-mouse-pointer");
11
- const styleElement = document.createElement("style");
12
- styleElement.textContent = `
13
- .empirical-mouse-pointer {
14
- pointer-events: none;
15
- position: absolute;
16
- top: 0;
17
- z-index: 10000;
18
- left: 0;
19
- width: 20px;
20
- height: 20px;
21
- background: rgba(0,0,0,.4);
22
- border: 1px dashed #FF000D;
23
- border-radius: 10px;
24
- margin: -10px 0 0 -10px;
25
- padding: 0;
26
- transition: background .2s, border-radius .2s, border-color .2s;
27
- }
28
- .empirical-mouse-pointer.button-1 {
29
- transition: none;
30
- background: rgba(0,0,0,0.9);
31
- }
32
- .empirical-mouse-pointer.button-2 {
33
- transition: none;
34
- border-color: rgba(0,0,255,0.9);
35
- }
36
- .empirical-mouse-pointer.button-3 {
37
- transition: none;
38
- border-radius: 4px;
39
- }
40
- .empirical-mouse-pointer.button-4 {
41
- transition: none;
42
- border-color: rgba(255,0,0,0.9);
43
- }
44
- .empirical-mouse-pointer.button-5 {
45
- transition: none;
46
- border-color: rgba(0,255,0,0.9);
47
- }
48
- .empirical-element-grab-highlight {
49
- outline: 2px dashed cyan !important;
50
- }
51
- *:focus {
52
- outline: 2px dashed #FF000D !important;
53
- }
54
- *:focus-visible {
55
- outline: 2px dashed #FF000D !important;
56
- }
57
- `;
58
- document.head.appendChild(styleElement);
59
- document.body.appendChild(box);
60
- document.addEventListener("mousemove", (event) => {
61
- box.style.left = `${event.pageX}px`;
62
- box.style.top = `${event.pageY}px`;
63
- updateButtons(event.buttons);
64
- }, true);
65
- document.addEventListener("mousedown", (event) => {
66
- updateButtons(event.buttons);
67
- box.classList.add(`button-${event.button + 1}`);
68
- }, true);
69
- document.addEventListener("mouseup", (event) => {
70
- updateButtons(event.buttons);
71
- box.classList.remove(`button-${event.button + 1}`);
72
- }, true);
73
- // @ts-ignore
74
- function updateButtons(buttons) {
75
- for (let i = 0; i < 5; i++) {
76
- box.classList.toggle(`button-${i + 1}`, (buttons / Math.pow(2, i)) % 2 >= 1);
77
- }
5
+ const STYLES_FOR_MOUSE_POINTER = `
6
+ .empirical-mouse-pointer {
7
+ pointer-events: none;
8
+ position: absolute;
9
+ top: 0;
10
+ z-index: 10000;
11
+ left: 0;
12
+ width: 20px;
13
+ height: 20px;
14
+ background: rgba(0,0,0,.4);
15
+ border: 1px dashed #FF000D;
16
+ border-radius: 10px;
17
+ margin: -10px 0 0 -10px;
18
+ padding: 0;
19
+ transition: background .2s, border-radius .2s, border-color .2s;
20
+ }
21
+ .empirical-mouse-pointer.button-1 {
22
+ transition: none;
23
+ background: rgba(0,0,0,0.9);
24
+ }
25
+ .empirical-mouse-pointer.button-2 {
26
+ transition: none;
27
+ border-color: rgba(0,0,255,0.9);
28
+ }
29
+ .empirical-mouse-pointer.button-3 {
30
+ transition: none;
31
+ border-radius: 4px;
32
+ }
33
+ .empirical-mouse-pointer.button-4 {
34
+ transition: none;
35
+ border-color: rgba(255,0,0,0.9);
36
+ }
37
+ .empirical-mouse-pointer.button-5 {
38
+ transition: none;
39
+ border-color: rgba(0,255,0,0.9);
40
+ }
41
+ .empirical-element-grab-highlight {
42
+ outline: 2px dashed cyan !important;
43
+ }
44
+ *:focus {
45
+ outline: 2px dashed #FF000D !important;
46
+ }
47
+ *:focus-visible {
48
+ outline: 2px dashed #FF000D !important;
49
+ }
50
+ `;
51
+ async function addHighlighterScriptsToPage(page) {
52
+ try {
53
+ await page.evaluate((stylesString) => {
54
+ const mouseBox = document.createElement("div");
55
+ mouseBox.classList.add("empirical-mouse-pointer");
56
+ const styleElement = document.createElement("style");
57
+ styleElement.textContent = stylesString;
58
+ document.head.appendChild(styleElement);
59
+ document.body.appendChild(mouseBox);
60
+ document.addEventListener("mousemove", (event) => {
61
+ mouseBox.style.left = `${event.pageX}px`;
62
+ mouseBox.style.top = `${event.pageY}px`;
63
+ updateButtons(event.buttons);
64
+ }, true);
65
+ document.addEventListener("mousedown", (event) => {
66
+ updateButtons(event.buttons);
67
+ mouseBox.classList.add(`button-${event.button + 1}`);
68
+ }, true);
69
+ document.addEventListener("mouseup", (event) => {
70
+ updateButtons(event.buttons);
71
+ mouseBox.classList.remove(`button-${event.button + 1}`);
72
+ }, true);
73
+ // @ts-ignore
74
+ function updateButtons(buttons) {
75
+ for (let i = 0; i < 5; i++) {
76
+ mouseBox.classList.toggle(`button-${i + 1}`, (buttons / Math.pow(2, i)) % 2 >= 1);
77
+ }
78
+ }
79
+ }, STYLES_FOR_MOUSE_POINTER);
80
+ }
81
+ catch (e) {
82
+ logger_1.logger.debug(`Error adding mouse pointer highlighter`, e);
83
+ }
84
+ }
85
+ exports.addHighlighterScriptsToPage = addHighlighterScriptsToPage;
86
+ async function removeMousePointerHighlighter(page) {
87
+ try {
88
+ await page.evaluate(() => {
89
+ // check if any element has the class empirical-element-grab-highlight
90
+ // if so, remove the class
91
+ const hasHighlight = document.querySelectorAll(".empirical-element-grab-highlight");
92
+ if (hasHighlight) {
93
+ hasHighlight.forEach((highlight) => {
94
+ highlight.classList.remove("empirical-element-grab-highlight");
95
+ });
96
+ }
97
+ // find div.empirical-mouse-pointer and remove it
98
+ const mousePointer = document.querySelector(".empirical-mouse-pointer");
99
+ if (mousePointer) {
100
+ mousePointer.remove();
101
+ }
102
+ // find style element with text content of STYLE_FOR_MOUSE_POINTER
103
+ const styleElements = document.querySelectorAll("style");
104
+ styleElements.forEach((styleElement) => {
105
+ if (styleElement.textContent?.includes("empirical-mouse-pointer")) {
106
+ styleElement.remove();
78
107
  }
79
108
  });
80
- }
81
- catch (e) {
82
- logger_1.logger.debug(`Error adding mouse pointer highlighter`, e);
83
- }
109
+ });
110
+ }
111
+ catch (e) {
112
+ logger_1.logger.debug(`Error removing mouse pointer highlighter`, e);
113
+ }
114
+ }
115
+ exports.removeMousePointerHighlighter = removeMousePointerHighlighter;
116
+ function addMousePointerHighlighter(page) {
117
+ page.on("load", async () => {
118
+ await addHighlighterScriptsToPage(page);
84
119
  });
85
120
  page.context().on("page", async (newPage) => {
86
121
  newPage?.on("load", async () => {
87
- try {
88
- await newPage?.evaluate(() => {
89
- const box = document.createElement("div");
90
- box.classList.add("empirical-mouse-pointer");
91
- const styleElement = document.createElement("style");
92
- styleElement.textContent = `
93
- .empirical-mouse-pointer {
94
- pointer-events: none;
95
- position: absolute;
96
- top: 0;
97
- z-index: 10000;
98
- left: 0;
99
- width: 20px;
100
- height: 20px;
101
- background: rgba(0,0,0,.4);
102
- border: 1px dashed #FF000D;
103
- border-radius: 10px;
104
- margin: -10px 0 0 -10px;
105
- padding: 0;
106
- transition: background .2s, border-radius .2s, border-color .2s;
107
- }
108
- .empirical-mouse-pointer.button-1 {
109
- transition: none;
110
- background: rgba(0,0,0,0.9);
111
- }
112
- .empirical-mouse-pointer.button-2 {
113
- transition: none;
114
- border-color: rgba(0,0,255,0.9);
115
- }
116
- .empirical-mouse-pointer.button-3 {
117
- transition: none;
118
- border-radius: 4px;
119
- }
120
- .empirical-mouse-pointer.button-4 {
121
- transition: none;
122
- border-color: rgba(255,0,0,0.9);
123
- }
124
- .empirical-mouse-pointer.button-5 {
125
- transition: none;
126
- border-color: rgba(0,255,0,0.9);
127
- }
128
- .empirical-element-grab-highlight {
129
- outline: 2px dashed cyan !important;
130
- }
131
- *:focus {
132
- outline: 2px dashed #FF000D !important;
133
- }
134
- *:focus-visible {
135
- outline: 2px dashed #FF000D !important;
136
- }
137
- `;
138
- document.head.appendChild(styleElement);
139
- document.body.appendChild(box);
140
- document.addEventListener("mousemove", (event) => {
141
- box.style.left = `${event.pageX}px`;
142
- box.style.top = `${event.pageY}px`;
143
- updateButtons(event.buttons);
144
- }, true);
145
- document.addEventListener("mousedown", (event) => {
146
- updateButtons(event.buttons);
147
- box.classList.add(`button-${event.button + 1}`);
148
- }, true);
149
- document.addEventListener("mouseup", (event) => {
150
- updateButtons(event.buttons);
151
- box.classList.remove(`button-${event.button + 1}`);
152
- }, true);
153
- // @ts-ignore
154
- function updateButtons(buttons) {
155
- for (let i = 0; i < 5; i++) {
156
- box.classList.toggle(`button-${i + 1}`, (buttons / Math.pow(2, i)) % 2 >= 1);
157
- }
158
- }
159
- });
160
- }
161
- catch (e) {
162
- logger_1.logger.debug(`Error adding mouse pointer highlighter`, e);
163
- }
122
+ await addHighlighterScriptsToPage(newPage);
164
123
  });
165
124
  });
166
125
  }
package/docs/fixtures.md CHANGED
@@ -4,20 +4,45 @@ The playwright-utils package provides fixtures that wrap around Playwright's bui
4
4
  `page`, `context` fixtures to provide a mouse highlighter (which makes it easier to
5
5
  see actions taken in a video).
6
6
 
7
- To use this, you probably just want to use the `baseTestFixture` import and use it in your
8
- fixtures file.
7
+ To use this, you can use the `baseTestFixture` and `extendExpect` imports
8
+ in your fixtures file.
9
9
 
10
10
  ```ts
11
- import { test as base, Page } from "@playwright/test";
12
- import { baseTestFixture } from "@empiricalrun/playwright-utils/test";
11
+ import { test as base, expect as baseExpect } from "@playwright/test";
12
+ import { baseTestFixture, extendExpect } from "@empiricalrun/playwright-utils/test";
13
13
 
14
14
  export const test = baseTestFixture(base);
15
- export const expect = test.expect;
15
+ export const expect = extendExpect(baseExpect);
16
16
  ```
17
17
 
18
+ ## Highlighters and agentic behaviors
19
+
20
+ `baseTestFixture` patches the playwright page object to add the following methods to add
21
+ mouse highlights and locator highlights.
22
+
23
+ These methods also enable agentic behaviors like dismissing overlays.
24
+
25
+ ## Custom Matchers
26
+
27
+ The `extendExpect` method adds custom matchers that can be used with Playwright's expect:
28
+
29
+ ### Visual Testing
30
+
31
+ The `toLookRight` matcher uses LLM to analyze screenshots for visual inconsistencies:
32
+
33
+ ```ts
34
+ test('page looks visually correct', async ({ page }) => {
35
+ await page.goto('https://your-site.com');
36
+ const description = "A login page with email and password fields, and a submit button"
37
+ await expect(page).toLookRight(description);
38
+ });
39
+ ```
40
+
41
+ ## Advanced Use Cases
42
+
18
43
  The packages also expose methods to enable advanced use-cases.
19
44
 
20
- ## Get a new browser context
45
+ ### Get a new browser context
21
46
 
22
47
  This package provides a fixture `customContextPageProvider` which is a good way to create
23
48
  a fresh, new browser context, and a page inside it.
@@ -36,7 +61,7 @@ test("Example test", async ({ page: builtInPage, customContextPageProvider }) =>
36
61
  });
37
62
  ```
38
63
 
39
- ## Inject mouse highlighter manually
64
+ ### Inject mouse highlighter manually
40
65
 
41
66
  In some cases, you want to inject mouse highlights to a page. This is useful if the test
42
67
  requires `launchPersistentContext`, which means the browser context is created manually.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.20.13",
3
+ "version": "0.21.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -42,9 +42,9 @@
42
42
  "playwright-core": "1.47.1",
43
43
  "puppeteer-extra-plugin-recaptcha": "^3.6.8",
44
44
  "rimraf": "^6.0.1",
45
- "@empiricalrun/llm": "^0.9.35",
46
45
  "@empiricalrun/r2-uploader": "^0.3.8",
47
- "@empiricalrun/test-gen": "^0.45.0"
46
+ "@empiricalrun/llm": "^0.9.35",
47
+ "@empiricalrun/test-gen": "^0.45.1"
48
48
  },
49
49
  "scripts": {
50
50
  "dev": "tsc --build --watch",