@guidepup/playwright 0.17.0 → 0.19.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 CHANGED
@@ -1,8 +1,13 @@
1
- # Guidepup Playwright
1
+ <div align="center">
2
+ <img align="center" alt="" height="120px" width="120px" src="https://github.com/guidepup/guidepup/raw/main/img/logo.png">
3
+ <h1 align="center">Guidepup for Playwright</h1>
4
+ </div>
2
5
 
3
- <a href="https://www.npmjs.com/package/@guidepup/playwright"><img alt="@guidepup/playwright available on NPM" src="https://img.shields.io/npm/v/@guidepup/playwright" /></a>
4
- <a href="https://github.com/guidepup/guidepup-playwright/actions/workflows/test.yml"><img alt="@guidepup/playwright test workflows" src="https://github.com/guidepup/guidepup-playwright/workflows/Test/badge.svg" /></a>
5
- <a href="https://github.com/guidepup/guidepup-playwright/blob/main/LICENSE"><img alt="@guidepup/playwright uses the MIT license" src="https://img.shields.io/github/license/guidepup/guidepup-playwright" /></a>
6
+ <div align="center">
7
+ <a href="https://www.npmjs.com/package/@guidepup/playwright"><img alt="@guidepup/playwright available on NPM" src="https://img.shields.io/npm/v/@guidepup/playwright" /></a>
8
+ <a href="https://github.com/guidepup/guidepup-playwright/actions/workflows/test.yml"><img alt="@guidepup/playwright test workflows" src="https://github.com/guidepup/guidepup-playwright/workflows/Test/badge.svg" /></a>
9
+ <a href="https://github.com/guidepup/guidepup-playwright/blob/main/LICENSE"><img alt="@guidepup/playwright uses the MIT license" src="https://img.shields.io/github/license/guidepup/guidepup-playwright" /></a>
10
+ </div>
6
11
 
7
12
  ## [Documentation](https://guidepup.dev) | [API Reference](https://www.guidepup.dev/docs/api/class-guidepup)
8
13
 
@@ -12,26 +17,21 @@
12
17
  [![Windows Server 2022 Support](https://img.shields.io/badge/windows_server-2022-blue.svg?logo=windows)](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2022)
13
18
  [![Windows Server 2025 Support](https://img.shields.io/badge/windows_server-2025-blue.svg?logo=windows)](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2025)
14
19
 
15
- This package provides [Guidepup](https://github.com/guidepup/guidepup) integration with [Playwright](https://playwright.dev/) for writing screen reader tests that automate <a href="https://www.guidepup.dev/docs/api/class-voiceover"><b>VoiceOver on MacOS</b></a> and <a href="https://www.guidepup.dev/docs/api/class-nvda"><b>NVDA on Windows</b></a>.
20
+ Guidepup is a screen reader automation library for testing.
21
+
22
+ This package provides [Guidepup](https://github.com/guidepup/guidepup) integration with [Playwright](https://playwright.dev/) to enable testing with <a href="https://www.guidepup.dev/docs/api/class-voiceover"><b>VoiceOver on MacOS</b></a> and <a href="https://www.guidepup.dev/docs/api/class-nvda"><b>NVDA on Windows</b></a>.
16
23
 
17
24
  ## Capabilities
18
25
 
19
26
  - **Full Control** - If a screen reader has a keyboard command, then Guidepup supports it.
20
27
  - **Mirrors Real User Experience** - Assert on what users really do and hear when using screen readers.
21
28
 
22
- ## Getting Started
29
+ ## Getting started
23
30
 
24
- Set up your environment for screen reader automation with [`@guidepup/setup`](https://github.com/guidepup/setup):
31
+ Set up your machine for screen reader automation:
25
32
 
26
33
  ```sh
27
- npx @guidepup/setup
28
- ```
29
-
30
- If you are using GitHub Actions, check out the dedicated [`guidepup/setup-action`](https://github.com/marketplace/actions/guidepup-setup):
31
-
32
- ```yaml
33
- - name: Setup Environment
34
- uses: guidepup/setup-action
34
+ npx @guidepup/setup setup
35
35
  ```
36
36
 
37
37
  Install `@guidepup/playwright` to your project:
@@ -40,7 +40,14 @@ Install `@guidepup/playwright` to your project:
40
40
  npm install --save-dev @guidepup/playwright @guidepup/guidepup @playwright/test
41
41
  ```
42
42
 
43
- Note: you require `@guidepup/guidepup` and `@playwright/test` as they are peer dependencies to this project.
43
+ > [!NOTE]
44
+ > `@guidepup/guidepup` and `@playwright/test` are required as peer dependencies of this project.
45
+
46
+ Install the Guidepup screen reader assets:
47
+
48
+ ```sh
49
+ npx @guidepup/setup install
50
+ ```
44
51
 
45
52
  And get cracking with your first screen reader tests in Playwright!
46
53
 
@@ -48,11 +55,9 @@ And get cracking with your first screen reader tests in Playwright!
48
55
 
49
56
  Head over to the [Guidepup Website](https://www.guidepup.dev/) for guides, real world examples, environment setup, and complete API documentation with examples.
50
57
 
51
- You can also check out these [awesome examples](https://github.com/guidepup/guidepup/tree/main/examples) to learn how you could use Guidepup with Playwright in your projects.
52
-
53
- Alternatively check out [this project](https://github.com/guidepup/aria-at-tests) which runs several thousand tests to assert screen reader compatibility against [W3C ARIA-AT](https://github.com/w3c/aria-at) test suite.
58
+ You can also check out these [examples](https://github.com/guidepup/guidepup-playwright/tree/main/examples) to learn how you could use Guidepup with Playwright in your projects.
54
59
 
55
- ### Playwright Config
60
+ ### Playwright config
56
61
 
57
62
  In your `playwright.config.ts` add the following for the best results with Guidepup for Screen Reader automation:
58
63
 
@@ -71,13 +76,13 @@ export default config;
71
76
 
72
77
  Check out the configuration this adds [in the `config.ts` file](./src/config.ts).
73
78
 
74
- ### Web Content Navigation
79
+ ### Web content navigation
75
80
 
76
- In addition to the Guidepup APIs the `voiceOver` and `nvda` instances provided by the Guidepup Playwright setup have an additional utility method `.navigateToWebContent()`.
81
+ In addition to the Guidepup APIs the `screenReader`, `voiceOver`, and `nvda` instances provided by the Guidepup Playwright setup have an additional utility method `.navigateToWebContent()`.
77
82
 
78
83
  This method will navigate the screen reader to the first element of the document body in the browser.
79
84
 
80
- Use this method after you navigate to a page and have made any necessary checks that the page has loaded as expected. For example, this is how you might use the method with NVDA:
85
+ Use this method after you navigate to a page and have made any necessary checks that the page has loaded as expected. For example, this is how you might use the method:
81
86
 
82
87
  ```ts
83
88
  // Navigate to the desired page
@@ -89,45 +94,23 @@ await page.goto("https://github.com/guidepup/guidepup", {
89
94
  await page.locator('header[role="banner"]').waitFor();
90
95
 
91
96
  // Navigate to the web content
92
- await nvda.navigateToWebContent();
97
+ await screenReader.navigateToWebContent();
93
98
 
94
99
  // ... some commands
95
100
  ```
96
101
 
97
- **Note:** This command clears all logs by default meaning `.spokenPhraseLog()` and `.itemTextLog()` are emptied. If logs from prior to the command are required, either store the logs in a variable for later use:
102
+ ### Providing screen reader start options
98
103
 
99
- ```ts
100
- // ... some commands
101
-
102
- // Store spoken phrases
103
- const spokenPhrases = await nvda.spokenPhraseLog();
104
-
105
- // Navigate to the web content
106
- await nvda.navigateToWebContent();
107
-
108
- // ... some commands
109
-
110
- // Collect all spoken phrases
111
- const allSpokenPhrases = [...spokenPhrases, ...(await nvda.spokenPhraseLog())];
112
-
113
- // ... do something with spoken phrases
114
- ```
115
-
116
- or pass a flag to prevent log clearing:
104
+ The options provided to `screenReader.start([options])`, `nvda.start([options])`, or `voiceOver.start([options])` can be configured using `test.use(config)` as follows:
117
105
 
118
106
  ```ts
119
- // ... some commands
107
+ // Screen Reader Example
108
+ import { screenReaderTest as test } from "@guidepup/playwright";
120
109
 
121
- // Navigate to the web content
122
- await nvda.navigateToWebContent(false);
123
-
124
- // ... some commands
110
+ // Capture all spoken phrases, including usage hints
111
+ test.use({ screenReaderStartOptions: { capture: true } });
125
112
  ```
126
113
 
127
- ### Providing Screen Reader Start Options
128
-
129
- The options provided to `nvda.start([options])` or `voiceOver.start([options])` can be configured using `test.use(config)` as follows:
130
-
131
114
  ```ts
132
115
  // VoiceOver Example
133
116
  import { voiceOverTest as test } from "@guidepup/playwright";
@@ -146,7 +129,7 @@ test.use({ nvdaStartOptions: { capture: true } });
146
129
 
147
130
  The default for VoiceOver and NVDA is set to `"initial"`. `true` captures all spoken phrases, including usage hints. `false` disables spoken phrase capture.
148
131
 
149
- ### VoiceOver Example
132
+ ### VoiceOver example
150
133
 
151
134
  `playwright.config.ts`:
152
135
 
@@ -197,7 +180,7 @@ test.describe("Playwright VoiceOver", () => {
197
180
 
198
181
  // Move across the page menu to the Guidepup heading using VoiceOver
199
182
  while ((await voiceOver.itemText()) !== "Guidepup heading level 1") {
200
- await voiceOver.perform(voiceOver.keyboardCommands.findNextHeading);
183
+ await voiceOver.nextHeading();
201
184
  }
202
185
 
203
186
  // Assert that the spoken phrases are as expected
@@ -206,7 +189,7 @@ test.describe("Playwright VoiceOver", () => {
206
189
  });
207
190
  ```
208
191
 
209
- ### NVDA Example
192
+ ### NVDA example
210
193
 
211
194
  `playwright.config.ts`:
212
195
 
@@ -259,7 +242,7 @@ test.describe("Playwright NVDA", () => {
259
242
  while (
260
243
  !(await nvda.lastSpokenPhrase()).includes("Guidepup, heading, level 1")
261
244
  ) {
262
- await nvda.perform(nvda.keyboardCommands.moveToNextHeading);
245
+ await nvda.nextHeading();
263
246
  }
264
247
 
265
248
  // Assert that the spoken phrases are as expected
@@ -268,7 +251,7 @@ test.describe("Playwright NVDA", () => {
268
251
  });
269
252
  ```
270
253
 
271
- ## Powerful Tooling
254
+ ## Powerful tooling
272
255
 
273
256
  Check out some of the other Guidepup modules:
274
257
 
package/lib/delay.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function delay(ms: number): Promise<unknown>;
package/lib/delay.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.delay = delay;
4
+ async function delay(ms) {
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
package/lib/nvdaTest.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import type { CommandOptions, NVDA } from "@guidepup/guidepup";
1
+ import { type CommandOptions, type NVDA, type StartOptions } from "@guidepup/guidepup";
2
2
  type Prettify<T> = {
3
3
  [K in keyof T]: T[K];
4
4
  } & {};
5
5
  type CaptureCommandOptions = Prettify<Pick<CommandOptions, "capture">>;
6
+ type CaptureStartOptions = Prettify<Pick<StartOptions, "capture" | "settings">>;
6
7
  /**
7
8
  * [API Reference](https://www.guidepup.dev/docs/api/class-nvda)
8
9
  *
@@ -31,10 +32,8 @@ export interface NVDAPlaywright extends NVDA {
31
32
  * of the browser's web content.
32
33
  *
33
34
  * This command should be used after page navigation.
34
- *
35
- * Note: this command clears all logs by default.
36
35
  */
37
- navigateToWebContent(clearLogs?: boolean): Promise<void>;
36
+ navigateToWebContent(options?: CaptureCommandOptions): Promise<void>;
38
37
  }
39
38
  /**
40
39
  * These tests extend the default Playwright environment that launches the
@@ -67,10 +66,10 @@ export declare const nvdaTest: import("@playwright/test").TestType<import("@play
67
66
  */
68
67
  nvda: NVDAPlaywright;
69
68
  /**
70
- * [API Reference](https://www.guidepup.dev/docs/api/class-command-options)
69
+ * [API Reference](https://www.guidepup.dev/docs/api/class-start-options)
71
70
  *
72
71
  * Options to start NVDA with, see also [nvda.start([options])](https://www.guidepup.dev/docs/api/class-nvda#nvda-start).
73
72
  */
74
- nvdaStartOptions: CaptureCommandOptions;
73
+ nvdaStartOptions: CaptureStartOptions;
75
74
  }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
76
75
  export {};
package/lib/nvdaTest.js CHANGED
@@ -4,6 +4,7 @@ exports.nvdaTest = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const guidepup_1 = require("@guidepup/guidepup");
6
6
  const applicationNameMap_1 = require("./applicationNameMap");
7
+ const delay_1 = require("./delay");
7
8
  const nvdaPlaywright = guidepup_1.nvda;
8
9
  const MAX_APPLICATION_SWITCH_RETRY_COUNT = 10;
9
10
  const SWITCH_APPLICATION = {
@@ -37,7 +38,8 @@ const focusBrowser = async ({ applicationName, pageTitle, }) => {
37
38
  let applicationSwitchRetryCount = 0;
38
39
  while (applicationSwitchRetryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
39
40
  applicationSwitchRetryCount++;
40
- await nvdaPlaywright.perform(SWITCH_APPLICATION);
41
+ await nvdaPlaywright.perform(SWITCH_APPLICATION, { capture: false });
42
+ await (0, delay_1.delay)(500);
41
43
  await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.reportTitle);
42
44
  windowTitle = await nvdaPlaywright.lastSpokenPhrase();
43
45
  if (hasFocus({ applicationName, pageTitle, windowTitle })) {
@@ -59,15 +61,17 @@ exports.nvdaTest = test_1.test.extend({
59
61
  if (!applicationName) {
60
62
  throw new Error(`Browser ${browserName} is not installed.`);
61
63
  }
62
- nvdaPlaywright.navigateToWebContent = async (clearLogs = true) => {
64
+ nvdaPlaywright.navigateToWebContent = async ({ capture } = {}) => {
65
+ const currentSpokenPhraseLog = [
66
+ ...(await nvdaPlaywright.spokenPhraseLog()),
67
+ ];
68
+ const currentItemTextLog = [...(await nvdaPlaywright.itemTextLog())];
63
69
  // Make sure NVDA is not in focus mode.
64
- await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.exitFocusMode);
65
- const pageTitle = await page.title();
70
+ await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.exitFocusMode, { capture: false });
71
+ await (0, delay_1.delay)(100);
66
72
  // Ensure application is brought to front and focused.
67
- await focusBrowser({
68
- applicationName,
69
- pageTitle,
70
- });
73
+ const pageTitle = await page.title();
74
+ await focusBrowser({ applicationName, pageTitle });
71
75
  // Ensure the document is ready and focused.
72
76
  await page.bringToFront();
73
77
  await page.locator("body").waitFor();
@@ -84,16 +88,23 @@ exports.nvdaTest = test_1.test.extend({
84
88
  // we exit focus mode and move NVDA to the top of the page.
85
89
  //
86
90
  // REF: https://github.com/nvaccess/nvda/issues/5758
87
- await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.readNextFocusableItem);
88
- await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode);
89
- await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode);
90
- await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.exitFocusMode);
91
- await nvdaPlaywright.perform(MOVE_TO_TOP);
92
- if (clearLogs) {
93
- // Clear out logs.
94
- await nvdaPlaywright.clearItemTextLog();
95
- await nvdaPlaywright.clearSpokenPhraseLog();
96
- }
91
+ await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.readNextFocusableItem, { capture: false });
92
+ await (0, delay_1.delay)(100);
93
+ await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode, { capture: false });
94
+ await (0, delay_1.delay)(100);
95
+ await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode, { capture: false });
96
+ await (0, delay_1.delay)(100);
97
+ await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.exitFocusMode, { capture: false });
98
+ await (0, delay_1.delay)(100);
99
+ await nvdaPlaywright.clearSpokenPhraseLog();
100
+ await nvdaPlaywright.clearItemTextLog();
101
+ const spokenPhraseLog = await nvdaPlaywright.spokenPhraseLog();
102
+ const itemTextLog = await nvdaPlaywright.itemTextLog();
103
+ spokenPhraseLog.push(...currentSpokenPhraseLog);
104
+ itemTextLog.push(...currentItemTextLog);
105
+ // Navigate to the beginning of the web content, using chosen capture
106
+ // settings, so don't miss announcing the first item on the page.
107
+ await nvdaPlaywright.perform(MOVE_TO_TOP, { capture });
97
108
  };
98
109
  await nvdaPlaywright.start(nvdaStartOptions);
99
110
  await use(nvdaPlaywright);
@@ -1,8 +1,9 @@
1
- import { CommandOptions, type ScreenReader } from "@guidepup/guidepup";
1
+ import { type CommandOptions, type ScreenReader, type StartOptions } from "@guidepup/guidepup";
2
2
  type Prettify<T> = {
3
3
  [K in keyof T]: T[K];
4
4
  } & {};
5
5
  type CaptureCommandOptions = Prettify<Pick<CommandOptions, "capture">>;
6
+ type CaptureStartOptions = Prettify<Pick<StartOptions, "capture" | "settings">>;
6
7
  /**
7
8
  * This object can be used to launch and control the default screen reader for
8
9
  * the environment.
@@ -32,10 +33,8 @@ export interface ScreenReaderPlaywright extends ScreenReader {
32
33
  * the beginning of the browser's web content.
33
34
  *
34
35
  * This command should be used after page navigation.
35
- *
36
- * Note: this command clears all logs by default.
37
36
  */
38
- navigateToWebContent(clearLogs?: boolean): Promise<void>;
37
+ navigateToWebContent(options?: CaptureCommandOptions): Promise<void>;
39
38
  }
40
39
  /**
41
40
  * These tests extend the default Playwright environment that launches the
@@ -73,6 +72,6 @@ export declare const screenReaderTest: import("@playwright/test").TestType<impor
73
72
  /**
74
73
  * Options to start the default screen reader with.
75
74
  */
76
- screenReaderStartOptions: CaptureCommandOptions;
75
+ screenReaderStartOptions: CaptureStartOptions;
77
76
  }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
78
77
  export {};
@@ -4,6 +4,7 @@ exports.screenReaderTest = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const guidepup_1 = require("@guidepup/guidepup");
6
6
  const applicationNameMap_1 = require("./applicationNameMap");
7
+ const delay_1 = require("./delay");
7
8
  const MAX_APPLICATION_SWITCH_RETRY_COUNT = 10;
8
9
  const SWITCH_APPLICATION = {
9
10
  keyCode: [guidepup_1.WindowsKeyCodes.Escape],
@@ -36,7 +37,10 @@ const focusBrowser = async ({ applicationName, pageTitle, }) => {
36
37
  let applicationSwitchRetryCount = 0;
37
38
  while (applicationSwitchRetryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
38
39
  applicationSwitchRetryCount++;
39
- await screenReaderPlaywright.perform(SWITCH_APPLICATION);
40
+ await screenReaderPlaywright.perform(SWITCH_APPLICATION, {
41
+ capture: false,
42
+ });
43
+ await (0, delay_1.delay)(500);
40
44
  await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.reportTitle);
41
45
  windowTitle = await screenReaderPlaywright.lastSpokenPhrase();
42
46
  if (hasFocus({ applicationName, pageTitle, windowTitle })) {
@@ -62,11 +66,18 @@ exports.screenReaderTest = test_1.test.extend({
62
66
  throw new Error(`Browser ${browserName} is not installed.`);
63
67
  }
64
68
  if (guidepup_1.nvda.default()) {
65
- screenReaderPlaywright.navigateToWebContent = async (clearLogs = true) => {
69
+ screenReaderPlaywright.navigateToWebContent = async ({ capture, } = {}) => {
70
+ const currentSpokenPhraseLog = [
71
+ ...(await screenReaderPlaywright.spokenPhraseLog()),
72
+ ];
73
+ const currentItemTextLog = [
74
+ ...(await screenReaderPlaywright.itemTextLog()),
75
+ ];
66
76
  // Make sure NVDA is not in focus mode.
67
- await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode);
68
- const pageTitle = await page.title();
77
+ await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode, { capture: false });
78
+ await (0, delay_1.delay)(100);
69
79
  // Ensure application is brought to front and focused.
80
+ const pageTitle = await page.title();
70
81
  await focusBrowser({ applicationName, pageTitle });
71
82
  // Ensure the document is ready and focused.
72
83
  await page.bringToFront();
@@ -84,39 +95,96 @@ exports.screenReaderTest = test_1.test.extend({
84
95
  // we exit focus mode and move NVDA to the top of the page.
85
96
  //
86
97
  // REF: https://github.com/nvaccess/nvda/issues/5758
87
- await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.readNextFocusableItem);
88
- await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode);
89
- await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode);
90
- await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode);
91
- await screenReaderPlaywright.perform(MOVE_TO_TOP);
92
- if (clearLogs) {
93
- // Clear out logs.
94
- await screenReaderPlaywright.clearItemTextLog();
95
- await screenReaderPlaywright.clearSpokenPhraseLog();
96
- }
98
+ await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.readNextFocusableItem, { capture: false });
99
+ await (0, delay_1.delay)(100);
100
+ await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode, { capture: false });
101
+ await (0, delay_1.delay)(100);
102
+ await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode, { capture: false });
103
+ await (0, delay_1.delay)(100);
104
+ await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode, { capture: false });
105
+ await (0, delay_1.delay)(100);
106
+ await screenReaderPlaywright.clearSpokenPhraseLog();
107
+ await screenReaderPlaywright.clearItemTextLog();
108
+ const spokenPhraseLog = await screenReaderPlaywright.spokenPhraseLog();
109
+ const itemTextLog = await screenReaderPlaywright.itemTextLog();
110
+ spokenPhraseLog.push(...currentSpokenPhraseLog);
111
+ itemTextLog.push(...currentItemTextLog);
112
+ // Navigate to the beginning of the web content, using chosen capture
113
+ // settings, so don't miss announcing the first item on the page.
114
+ await screenReaderPlaywright.perform(MOVE_TO_TOP, { capture });
97
115
  };
98
116
  }
99
117
  else if (guidepup_1.voiceOver.default()) {
100
- screenReaderPlaywright.navigateToWebContent = async (clearLogs = true) => {
118
+ screenReaderPlaywright.navigateToWebContent = async ({ capture, } = {}) => {
119
+ // Ensure application is brought to front and focused.
101
120
  await (0, guidepup_1.macOSActivate)(applicationName);
102
- await screenReaderPlaywright.perform({
103
- keyCode: guidepup_1.MacOSKeyCodes.Control,
104
- });
121
+ // Cancel auto navigation.
122
+ await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
123
+ await (0, delay_1.delay)(100);
124
+ // Ensure the document is ready and focused.
105
125
  await page.bringToFront();
106
126
  await page.locator("body").waitFor();
107
- await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.openItemChooser);
108
- await screenReaderPlaywright.type("web content");
109
- await screenReaderPlaywright.perform({
110
- keyCode: guidepup_1.MacOSKeyCodes.Enter,
111
- });
112
- await screenReaderPlaywright.interact();
113
- await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.moveToBeginningOfText);
114
- await screenReaderPlaywright.perform({
115
- keyCode: guidepup_1.MacOSKeyCodes.Control,
116
- });
117
- if (clearLogs) {
118
- await screenReaderPlaywright.clearItemTextLog();
119
- await screenReaderPlaywright.clearSpokenPhraseLog();
127
+ try {
128
+ // Add an interactive marker to the page that will force VoiceOver
129
+ // to listen to events emitted by Playwright interactions when
130
+ // navigated to.
131
+ await page.evaluate(() => {
132
+ const marker = document.createElement("input");
133
+ marker.id = "__guidepup_marker__";
134
+ marker.type = "text";
135
+ marker.value = "Guidepup Marker";
136
+ marker.readOnly = true;
137
+ marker.tabIndex = -1;
138
+ marker.autocomplete = "off";
139
+ marker.setAttribute("aria-label", "Guidepup Marker");
140
+ marker.style.cssText = `
141
+ position: absolute;
142
+ width: 1px;
143
+ height: 1px;
144
+ overflow: hidden;
145
+ clip: rect(0 0 0 0);
146
+ white-space: nowrap;
147
+ `;
148
+ document.body.prepend(marker);
149
+ });
150
+ // Open the web item chooser.
151
+ await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.openItemChooser, { capture: false });
152
+ await (0, delay_1.delay)(500);
153
+ // Filter by "web content" - currently web content items for all browsers
154
+ // are suffixed by "web content".
155
+ for (const character of "web content") {
156
+ await screenReaderPlaywright.type(character, { capture: false });
157
+ await (0, delay_1.delay)(100);
158
+ }
159
+ // Select the web content window spot.
160
+ await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
161
+ await (0, delay_1.delay)(100);
162
+ // Navigate into web content.
163
+ await screenReaderPlaywright.interact({ capture: false });
164
+ await (0, delay_1.delay)(100);
165
+ // Navigate to the beginning of the web content.
166
+ await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.moveToBeginningOfText, { capture: false });
167
+ await (0, delay_1.delay)(100);
168
+ // Cancel auto navigation
169
+ await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
170
+ await (0, delay_1.delay)(100);
171
+ // Navigate to the Guidepup marker element at beginning of the web
172
+ // content.
173
+ await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.moveToBeginningOfText, { capture: false });
174
+ await (0, delay_1.delay)(100);
175
+ // Navigate to the first element of the page using the provided
176
+ // capture settings.
177
+ await screenReaderPlaywright.next({ capture });
178
+ }
179
+ finally {
180
+ // Remove the temporary Guidepup marker element to restore original
181
+ // page structure.
182
+ await page.evaluate(() => {
183
+ const marker = document.querySelector("#__guidepup_marker__");
184
+ if (marker) {
185
+ document.body.removeChild(marker);
186
+ }
187
+ });
120
188
  }
121
189
  };
122
190
  }
@@ -1,4 +1,4 @@
1
- import type { CommandOptions, VoiceOver } from "@guidepup/guidepup";
1
+ import type { StartOptions, CommandOptions, VoiceOver } from "@guidepup/guidepup";
2
2
  /**
3
3
  * [API Reference](https://www.guidepup.dev/docs/api/class-voiceover)
4
4
  *
@@ -29,10 +29,8 @@ export interface VoiceOverPlaywright extends VoiceOver {
29
29
  * of the browser's web content.
30
30
  *
31
31
  * This command should be used after a page navigation has completed.
32
- *
33
- * Note: this command clears all logs by default.
34
32
  */
35
- navigateToWebContent(clearLogs?: boolean): Promise<void>;
33
+ navigateToWebContent(options?: Pick<CommandOptions, "capture">): Promise<void>;
36
34
  }
37
35
  /**
38
36
  * These tests extend the default Playwright environment that launches the
@@ -67,9 +65,9 @@ export declare const voiceOverTest: import("@playwright/test").TestType<import("
67
65
  */
68
66
  voiceOver: VoiceOverPlaywright;
69
67
  /**
70
- * [API Reference](https://www.guidepup.dev/docs/api/class-command-options)
68
+ * [API Reference](https://www.guidepup.dev/docs/api/class-start-options)
71
69
  *
72
70
  * Options to start VoiceOver with, see also [voiceOver.start([options])](https://www.guidepup.dev/docs/api/class-voiceover#voiceover-start).
73
71
  */
74
- voiceOverStartOptions: CommandOptions;
72
+ voiceOverStartOptions: StartOptions;
75
73
  }, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
@@ -4,6 +4,7 @@ exports.voiceOverTest = void 0;
4
4
  const test_1 = require("@playwright/test");
5
5
  const guidepup_1 = require("@guidepup/guidepup");
6
6
  const applicationNameMap_1 = require("./applicationNameMap");
7
+ const delay_1 = require("./delay");
7
8
  const voiceOverPlaywright = guidepup_1.voiceOver;
8
9
  /**
9
10
  * These tests extend the default Playwright environment that launches the
@@ -19,31 +20,76 @@ exports.voiceOverTest = test_1.test.extend({
19
20
  if (!applicationName) {
20
21
  throw new Error(`Browser ${browserName} is not installed.`);
21
22
  }
22
- voiceOverPlaywright.navigateToWebContent = async (clearLogs = true) => {
23
+ voiceOverPlaywright.navigateToWebContent = async ({ capture } = {}) => {
23
24
  // Ensure application is brought to front and focused.
24
25
  await (0, guidepup_1.macOSActivate)(applicationName);
25
- // Cancel auto navigation
26
- await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control });
26
+ // Cancel auto navigation.
27
+ await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
28
+ await (0, delay_1.delay)(100);
27
29
  // Ensure the document is ready and focused.
28
30
  await page.bringToFront();
29
31
  await page.locator("body").waitFor();
30
- // Open the web item chooser.
31
- await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.openItemChooser);
32
- // Filter by "web content" - currently web content items for all browsers
33
- // are suffixed by "web content".
34
- await voiceOverPlaywright.type("web content");
35
- // Select the web content window spot.
36
- await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter });
37
- // Navigate into web content.
38
- await voiceOverPlaywright.interact();
39
- // Navigate to the beginning of the web content.
40
- await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.moveToBeginningOfText);
41
- // Cancel auto navigation
42
- await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control });
43
- if (clearLogs) {
44
- // Clear out logs.
45
- await voiceOverPlaywright.clearItemTextLog();
46
- await voiceOverPlaywright.clearSpokenPhraseLog();
32
+ try {
33
+ // Add an interactive marker to the page that will force VoiceOver
34
+ // to listen to events emitted by Playwright interactions when
35
+ // navigated to.
36
+ await page.evaluate(() => {
37
+ const marker = document.createElement("input");
38
+ marker.id = "__guidepup_marker__";
39
+ marker.type = "text";
40
+ marker.value = "Guidepup Marker";
41
+ marker.readOnly = true;
42
+ marker.tabIndex = -1;
43
+ marker.autocomplete = "off";
44
+ marker.setAttribute("aria-label", "Guidepup Marker");
45
+ marker.style.cssText = `
46
+ position: absolute;
47
+ width: 1px;
48
+ height: 1px;
49
+ overflow: hidden;
50
+ clip: rect(0 0 0 0);
51
+ white-space: nowrap;
52
+ `;
53
+ document.body.prepend(marker);
54
+ });
55
+ // Open the web item chooser.
56
+ await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.openItemChooser, { capture: false });
57
+ await (0, delay_1.delay)(500);
58
+ // Filter by "web content" - currently web content items for all browsers
59
+ // are suffixed by "web content".
60
+ for (const character of "web content") {
61
+ await voiceOverPlaywright.type(character, { capture: false });
62
+ await (0, delay_1.delay)(100);
63
+ }
64
+ // Select the web content window spot.
65
+ await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
66
+ await (0, delay_1.delay)(100);
67
+ // Navigate into web content.
68
+ await voiceOverPlaywright.interact({ capture: false });
69
+ await (0, delay_1.delay)(100);
70
+ // Navigate to the beginning of the web content.
71
+ await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.moveToBeginningOfText, { capture: false });
72
+ await (0, delay_1.delay)(100);
73
+ // Cancel auto navigation
74
+ await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
75
+ await (0, delay_1.delay)(100);
76
+ // Navigate to the Guidepup marker element at beginning of the web
77
+ // content.
78
+ await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.moveToBeginningOfText, { capture: false });
79
+ await (0, delay_1.delay)(100);
80
+ // Navigate to the first element of the page using the provided
81
+ // capture settings.
82
+ await voiceOverPlaywright.next({ capture });
83
+ }
84
+ finally {
85
+ // Remove the temporary Guidepup marker element to restore original
86
+ // page structure.
87
+ await page.evaluate(() => {
88
+ const marker = document.querySelector("#__guidepup_marker__");
89
+ if (marker) {
90
+ document.body.removeChild(marker);
91
+ }
92
+ });
47
93
  }
48
94
  };
49
95
  await voiceOverPlaywright.start(voiceOverStartOptions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@guidepup/playwright",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Screen reader automation library for Playwright testing. ",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
@@ -44,7 +44,7 @@
44
44
  "test:voiceover:webkit": "playwright test --config ./examples/playwright-voiceover/webkit.config.ts ./examples/playwright-voiceover/tests/webkit/"
45
45
  },
46
46
  "devDependencies": {
47
- "@guidepup/guidepup": "^0.27.0",
47
+ "@guidepup/guidepup": "^0.33.0",
48
48
  "@guidepup/record": "^0.1.0",
49
49
  "@playwright/test": "^1.60.0",
50
50
  "@types/node": "^24.10.1",
@@ -58,7 +58,7 @@
58
58
  "typescript": "^5.9.3"
59
59
  },
60
60
  "peerDependencies": {
61
- "@guidepup/guidepup": ">=0.27.0",
61
+ "@guidepup/guidepup": ">=0.33.0",
62
62
  "@playwright/test": "^1.57.0"
63
63
  },
64
64
  "resolutions": {