@guidepup/playwright 0.19.0 → 0.20.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
@@ -11,15 +11,18 @@
11
11
 
12
12
  ## [Documentation](https://guidepup.dev) | [API Reference](https://www.guidepup.dev/docs/api/class-guidepup)
13
13
 
14
- [![MacOS Sonoma Support](https://img.shields.io/badge/macos-Somona-blue.svg?logo=apple)](https://apps.apple.com/us/app/macos-sonoma/id6450717509)
15
- [![MacOS Sequoia Support](https://img.shields.io/badge/macos-Sequoia-blue.svg?logo=apple)](https://apps.apple.com/us/app/macos-sequoia/id6596773750)
16
- [![MacOS Tahoe Support](https://img.shields.io/badge/macos-Tahoe-blue.svg?logo=apple)](https://www.apple.com/uk/os/macos/)
14
+ [![macOS Sonoma Support](https://img.shields.io/badge/macos-Sonoma-blue.svg?logo=apple)](https://support.apple.com/en-gb/102662)
15
+ [![macOS Sequoia Support](https://img.shields.io/badge/macos-Sequoia-blue.svg?logo=apple)](https://support.apple.com/en-gb/102662)
16
+ [![macOS Tahoe Support](https://img.shields.io/badge/macos-Tahoe-blue.svg?logo=apple)](https://support.apple.com/en-gb/102662)
17
+ [![macOS Golden Gate Support](https://img.shields.io/badge/macos-Golden%20Gate-blue.svg?logo=apple)](https://support.apple.com/en-gb/127455)
17
18
  [![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)
18
19
  [![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)
20
+ [![Ubuntu Resolute Support](https://img.shields.io/badge/ubuntu-26.04%2B-blue.svg?logo=ubuntu)](https://releases.ubuntu.com/resolute/)
21
+ [![Debian Trixie Support](https://img.shields.io/badge/debian-13%2B-blue.svg?logo=debian)](https://www.debian.org/releases/trixie/)
19
22
 
20
23
  Guidepup is a screen reader automation library for testing.
21
24
 
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>.
25
+ 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-nvda"><b>NVDA on Windows</b></a>, <a href="https://www.guidepup.dev/docs/api/class-orca"><b>Orca on Linux</b></a>, and <a href="https://www.guidepup.dev/docs/api/class-voiceover"><b>VoiceOver on macOS</b></a>.
23
26
 
24
27
  ## Capabilities
25
28
 
@@ -62,23 +65,21 @@ You can also check out these [examples](https://github.com/guidepup/guidepup-pla
62
65
  In your `playwright.config.ts` add the following for the best results with Guidepup for Screen Reader automation:
63
66
 
64
67
  ```ts
65
- import { devices, PlaywrightTestConfig } from "@playwright/test";
66
- import { screenReaderConfig } from "@guidepup/playwright";
67
-
68
- const config: PlaywrightTestConfig = {
69
- ...screenReaderConfig,
68
+ import { withGuidepup } from "@guidepup/playwright";
70
69
 
70
+ export default withGuidepup({
71
71
  // ... your custom config
72
- };
73
-
74
- export default config;
72
+ });
75
73
  ```
76
74
 
77
- Check out the configuration this adds [in the `config.ts` file](./src/config.ts).
75
+ `withGuidepup` configures Playwright to run tests serially and forces headed
76
+ browsers, including when browser options are specified in individual projects.
77
+ The existing `screenReaderConfig` export remains available if you prefer to
78
+ spread the configuration manually.
78
79
 
79
80
  ### Web content navigation
80
81
 
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()`.
82
+ In addition to the Guidepup APIs the `screenReader`, `nvda`, `orca`, and `voiceOver` instances provided by the Guidepup Playwright setup have an additional utility method `.navigateToWebContent()`.
82
83
 
83
84
  This method will navigate the screen reader to the first element of the document body in the browser.
84
85
 
@@ -101,7 +102,7 @@ await screenReader.navigateToWebContent();
101
102
 
102
103
  ### Providing screen reader start options
103
104
 
104
- The options provided to `screenReader.start([options])`, `nvda.start([options])`, or `voiceOver.start([options])` can be configured using `test.use(config)` as follows:
105
+ The options provided to `screenReader.start([options])`, `nvda.start([options])`, `orca.start([options])`, or `voiceOver.start([options])` can be configured using `test.use(config)` as follows:
105
106
 
106
107
  ```ts
107
108
  // Screen Reader Example
@@ -112,119 +113,121 @@ test.use({ screenReaderStartOptions: { capture: true } });
112
113
  ```
113
114
 
114
115
  ```ts
115
- // VoiceOver Example
116
- import { voiceOverTest as test } from "@guidepup/playwright";
116
+ // NVDA Example
117
+ import { nvdaTest as test } from "@guidepup/playwright";
117
118
 
118
119
  // Capture all spoken phrases, including usage hints
119
- test.use({ voiceOverStartOptions: { capture: true } });
120
+ test.use({ nvdaStartOptions: { capture: true } });
120
121
  ```
121
122
 
122
123
  ```ts
123
- // NVDA Example
124
- import { nvdaTest as test } from "@guidepup/playwright";
124
+ // Orca Example
125
+ import { orcaTest as test } from "@guidepup/playwright";
125
126
 
126
127
  // Capture all spoken phrases, including usage hints
127
- test.use({ nvdaStartOptions: { capture: true } });
128
+ test.use({ orcaStartOptions: { capture: true } });
128
129
  ```
129
130
 
130
- The default for VoiceOver and NVDA is set to `"initial"`. `true` captures all spoken phrases, including usage hints. `false` disables spoken phrase capture.
131
+ ```ts
132
+ // VoiceOver Example
133
+ import { voiceOverTest as test } from "@guidepup/playwright";
131
134
 
132
- ### VoiceOver example
135
+ // Capture all spoken phrases, including usage hints
136
+ test.use({ voiceOverStartOptions: { capture: true } });
137
+ ```
138
+
139
+ The default for NVDA, Orca, and VoiceOver is set to `"initial"`. `true` captures all spoken phrases, including usage hints. `false` disables spoken phrase capture.
140
+
141
+ ### NVDA example
133
142
 
134
143
  `playwright.config.ts`:
135
144
 
136
145
  ```ts
137
- import { devices, PlaywrightTestConfig } from "@playwright/test";
138
- import { screenReaderConfig } from "@guidepup/playwright";
146
+ import { devices } from "@playwright/test";
147
+ import { withGuidepup } from "@guidepup/playwright";
139
148
 
140
- const config: PlaywrightTestConfig = {
141
- ...screenReaderConfig,
149
+ const config = withGuidepup({
142
150
  reportSlowTests: null,
143
151
  timeout: 5 * 60 * 1000,
144
152
  retries: 2,
145
153
  projects: [
146
154
  {
147
- name: "webkit",
148
- // Take care to ensure all usage is headed - screen readers cannot
149
- // operate against headless browsers.
150
- use: { ...devices["Desktop Safari"], headless: false },
155
+ name: "chrome",
156
+ use: { ...devices["Desktop Chrome"] },
151
157
  },
152
158
  ],
153
- };
159
+ });
154
160
 
155
161
  export default config;
156
162
  ```
157
163
 
158
- `voiceOver.spec.ts`:
164
+ `nvda.spec.ts`:
159
165
 
160
166
  ```ts
161
- import { voiceOverTest as test } from "@guidepup/playwright";
167
+ import { nvdaTest as test } from "@guidepup/playwright";
162
168
  import { expect } from "@playwright/test";
163
169
 
164
- test.describe("Playwright VoiceOver", () => {
165
- test("I can navigate the Guidepup Github page with VoiceOver", async ({
170
+ test.describe("Playwright NVDA", () => {
171
+ test("I can navigate the Guidepup Github page with NVDA", async ({
166
172
  page,
167
- voiceOver,
173
+ nvda,
168
174
  }) => {
169
175
  // Navigate to Guidepup GitHub page
170
176
  await page.goto("https://github.com/guidepup/guidepup", {
171
177
  waitUntil: "load",
172
178
  });
173
179
 
174
- // Wait for page to be ready
180
+ // Wait for page to be ready and setup
175
181
  const header = page.locator('header[role="banner"]');
176
182
  await header.waitFor();
177
183
 
178
184
  // Interact with the page
179
- await voiceOver.navigateToWebContent();
185
+ await nvda.navigateToWebContent();
180
186
 
181
- // Move across the page menu to the Guidepup heading using VoiceOver
182
- while ((await voiceOver.itemText()) !== "Guidepup heading level 1") {
183
- await voiceOver.nextHeading();
187
+ // Move across the page menu to the Guidepup heading using NVDA
188
+ while ((await nvda.lastSpokenPhrase()) !== "Guidepup, heading, level 1") {
189
+ await nvda.nextHeading();
184
190
  }
185
191
 
186
192
  // Assert that the spoken phrases are as expected
187
- expect(JSON.stringify(await voiceOver.spokenPhraseLog())).toMatchSnapshot();
193
+ expect(JSON.stringify(await nvda.spokenPhraseLog())).toMatchSnapshot();
188
194
  });
189
195
  });
190
196
  ```
191
197
 
192
- ### NVDA example
198
+ ### Orca example
193
199
 
194
200
  `playwright.config.ts`:
195
201
 
196
202
  ```ts
197
- import { devices, PlaywrightTestConfig } from "@playwright/test";
198
- import { screenReaderConfig } from "@guidepup/playwright";
203
+ import { devices } from "@playwright/test";
204
+ import { withGuidepup } from "@guidepup/playwright";
199
205
 
200
- const config: PlaywrightTestConfig = {
201
- ...screenReaderConfig,
206
+ const config = withGuidepup({
202
207
  reportSlowTests: null,
203
208
  timeout: 5 * 60 * 1000,
204
209
  retries: 2,
205
210
  projects: [
206
211
  {
207
212
  name: "firefox",
208
- // Take care to ensure all usage is headed - screen readers cannot
209
- // operate against headless browsers.
210
- use: { ...devices["Desktop Firefox"], headless: false },
213
+ use: { ...devices["Desktop Firefox"] },
211
214
  },
212
215
  ],
213
- };
216
+ });
214
217
 
215
218
  export default config;
216
219
  ```
217
220
 
218
- `nvda.spec.ts`:
221
+ `orca.spec.ts`:
219
222
 
220
223
  ```ts
221
- import { nvdaTest as test } from "@guidepup/playwright";
224
+ import { orcaTest as test } from "@guidepup/playwright";
222
225
  import { expect } from "@playwright/test";
223
226
 
224
- test.describe("Playwright NVDA", () => {
225
- test("I can navigate the Guidepup Github page with NVDA", async ({
227
+ test.describe("Playwright Orca", () => {
228
+ test("I can navigate the Guidepup Github page with Orca", async ({
226
229
  page,
227
- nvda,
230
+ orca,
228
231
  }) => {
229
232
  // Navigate to Guidepup GitHub page
230
233
  await page.goto("https://github.com/guidepup/guidepup", {
@@ -236,33 +239,102 @@ test.describe("Playwright NVDA", () => {
236
239
  await header.waitFor();
237
240
 
238
241
  // Interact with the page
239
- await nvda.navigateToWebContent();
242
+ await orca.navigateToWebContent();
240
243
 
241
- // Move across the page menu to the Guidepup heading using NVDA
244
+ // Move across the page menu to the Guidepup heading using Orca
245
+ while ((await orca.lastSpokenPhrase()) !== "Guidepup. heading 1") {
246
+ await orca.nextHeading();
247
+ }
248
+
249
+ // Assert that the spoken phrases are as expected
250
+ expect(JSON.stringify(await orca.spokenPhraseLog())).toMatchSnapshot();
251
+ });
252
+ });
253
+ ```
254
+
255
+ ### VoiceOver example
256
+
257
+ `playwright.config.ts`:
258
+
259
+ ```ts
260
+ import { devices } from "@playwright/test";
261
+ import { withGuidepup } from "@guidepup/playwright";
262
+
263
+ const config = withGuidepup({
264
+ reportSlowTests: null,
265
+ timeout: 5 * 60 * 1000,
266
+ retries: 2,
267
+ projects: [
268
+ {
269
+ name: "webkit",
270
+ use: { ...devices["Desktop Safari"] },
271
+ },
272
+ ],
273
+ });
274
+
275
+ export default config;
276
+ ```
277
+
278
+ `voiceOver.spec.ts`:
279
+
280
+ ```ts
281
+ import { voiceOverTest as test } from "@guidepup/playwright";
282
+ import { expect } from "@playwright/test";
283
+
284
+ test.describe("Playwright VoiceOver", () => {
285
+ test("I can navigate the Guidepup Github page with VoiceOver", async ({
286
+ page,
287
+ voiceOver,
288
+ }) => {
289
+ // Navigate to Guidepup GitHub page
290
+ await page.goto("https://github.com/guidepup/guidepup", {
291
+ waitUntil: "load",
292
+ });
293
+
294
+ // Wait for page to be ready
295
+ const header = page.locator('header[role="banner"]');
296
+ await header.waitFor();
297
+
298
+ // Interact with the page
299
+ await voiceOver.navigateToWebContent();
300
+
301
+ // Move across the page menu to the Guidepup heading using VoiceOver
242
302
  while (
243
- !(await nvda.lastSpokenPhrase()).includes("Guidepup, heading, level 1")
303
+ (await voiceOver.lastSpokenPhrase()) !== "heading level 1 Guidepup"
244
304
  ) {
245
- await nvda.nextHeading();
305
+ await voiceOver.nextHeading();
246
306
  }
247
307
 
248
308
  // Assert that the spoken phrases are as expected
249
- expect(JSON.stringify(await nvda.spokenPhraseLog())).toMatchSnapshot();
309
+ expect(JSON.stringify(await voiceOver.spokenPhraseLog())).toMatchSnapshot();
250
310
  });
251
311
  });
252
312
  ```
253
313
 
314
+ ### Capture Playwright Interactions
315
+
316
+ Use the `capture()` API to wrap Playwright interactions and capture the resulting screen reader output without needing to perform the interaction through Guidepup.
317
+
318
+ ```ts
319
+ const capture = await screenReader.capture(() =>
320
+ page.getByRole("button", { name: "Add to cart" }).click(),
321
+ );
322
+
323
+ expect(capture.spokenPhrase).toContain("Added to cart");
324
+ ```
325
+
254
326
  ## Powerful tooling
255
327
 
256
328
  Check out some of the other Guidepup modules:
257
329
 
258
- - [`@guidepup/guidepup`](https://github.com/guidepup/guidepup/) - Reliable automation for your screen reader a11y workflows through JavaScript supporting VoiceOver and NVDA.
259
330
  - [`@guidepup/setup`](https://github.com/guidepup/setup/) - Set up your local or CI environment for screen reader test automation.
331
+ - [`@guidepup/guidepup`](https://github.com/guidepup/guidepup/) - Reliable automation for your screen reader a11y workflows through JavaScript supporting VoiceOver, NVDA, and Orca.
260
332
  - [`@guidepup/virtual-screen-reader`](https://github.com/guidepup/virtual-screen-reader/) - Reliable unit testing for your screen reader a11y workflows.
261
333
  - [`@guidepup/jest`](https://github.com/guidepup/jest/) - Jest matchers for reliable unit testing of your screen reader a11y workflows.
262
334
 
263
335
  ## Resources
264
336
 
265
- - [Documentation](https://www.guidepup.dev/docs/example)
337
+ - [Documentation](https://www.guidepup.dev/docs/getting-started/real-world-example)
266
338
  - [API Reference](https://www.guidepup.dev/docs/api/class-guidepup)
267
339
  - [Contributing](.github/CONTRIBUTING.md)
268
340
  - [Changelog](https://github.com/guidepup/guidepup-playwright/releases)
@@ -0,0 +1,10 @@
1
+ export declare const applicationIdMap: {
2
+ chromium: string;
3
+ chrome: string;
4
+ "chrome-beta": string;
5
+ msedge: string;
6
+ "msedge-beta": string;
7
+ "msedge-dev": string;
8
+ firefox: string;
9
+ webkit: string;
10
+ };
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.applicationIdMap = void 0;
4
+ exports.applicationIdMap = {
5
+ chromium: "com.google.chrome.for.testing",
6
+ chrome: "com.google.Chrome",
7
+ "chrome-beta": "com.google.Chrome.beta",
8
+ msedge: "com.microsoft.edgemac",
9
+ "msedge-beta": "com.microsoft.edgemac.Beta",
10
+ "msedge-dev": "com.microsoft.edgemac.Dev",
11
+ firefox: "org.mozilla.nightlyunofficial",
12
+ webkit: "org.webkit.Playwright",
13
+ };
@@ -0,0 +1 @@
1
+ export declare const cleanString: (str: string) => string;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanString = void 0;
4
+ const cleanString = (str) => str
5
+ .toLowerCase()
6
+ .replace(/[|¦:;'"`\-‐–—·_()[\]{}\\^~]/g, " ")
7
+ .replace(/\s+/g, " ")
8
+ .trim();
9
+ exports.cleanString = cleanString;
package/lib/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export { nvdaTest } from "./nvdaTest";
2
2
  export type { NVDAPlaywright } from "./nvdaTest";
3
+ export { orcaTest } from "./orcaTest";
4
+ export type { OrcaPlaywright } from "./orcaTest";
3
5
  export { screenReaderConfig } from "./screenReaderConfig";
4
6
  export { screenReaderTest } from "./screenReaderTest";
5
7
  export type { ScreenReaderPlaywright } from "./screenReaderTest";
6
8
  export { voiceOverTest } from "./voiceOverTest";
7
9
  export type { VoiceOverPlaywright } from "./voiceOverTest";
10
+ export { withGuidepup } from "./withGuidepup";
package/lib/index.js CHANGED
@@ -1,11 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.voiceOverTest = exports.screenReaderTest = exports.screenReaderConfig = exports.nvdaTest = void 0;
3
+ exports.withGuidepup = exports.voiceOverTest = exports.screenReaderTest = exports.screenReaderConfig = exports.orcaTest = exports.nvdaTest = void 0;
4
4
  var nvdaTest_1 = require("./nvdaTest");
5
5
  Object.defineProperty(exports, "nvdaTest", { enumerable: true, get: function () { return nvdaTest_1.nvdaTest; } });
6
+ var orcaTest_1 = require("./orcaTest");
7
+ Object.defineProperty(exports, "orcaTest", { enumerable: true, get: function () { return orcaTest_1.orcaTest; } });
6
8
  var screenReaderConfig_1 = require("./screenReaderConfig");
7
9
  Object.defineProperty(exports, "screenReaderConfig", { enumerable: true, get: function () { return screenReaderConfig_1.screenReaderConfig; } });
8
10
  var screenReaderTest_1 = require("./screenReaderTest");
9
11
  Object.defineProperty(exports, "screenReaderTest", { enumerable: true, get: function () { return screenReaderTest_1.screenReaderTest; } });
10
12
  var voiceOverTest_1 = require("./voiceOverTest");
11
13
  Object.defineProperty(exports, "voiceOverTest", { enumerable: true, get: function () { return voiceOverTest_1.voiceOverTest; } });
14
+ var withGuidepup_1 = require("./withGuidepup");
15
+ Object.defineProperty(exports, "withGuidepup", { enumerable: true, get: function () { return withGuidepup_1.withGuidepup; } });
@@ -0,0 +1,8 @@
1
+ import { type CommandOptions, type NVDA } from "@guidepup/guidepup";
2
+ import type { Page } from "@playwright/test";
3
+ export declare const navigateNVDAtoWebContent: ({ nvda, page, applicationName, capture, }: {
4
+ nvda: NVDA;
5
+ page: Page;
6
+ applicationName: string;
7
+ capture?: CommandOptions["capture"];
8
+ }) => Promise<void>;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.navigateNVDAtoWebContent = void 0;
4
+ const guidepup_1 = require("@guidepup/guidepup");
5
+ const cleanString_1 = require("./cleanString");
6
+ const delay_1 = require("./delay");
7
+ const MAX_APPLICATION_SWITCH_RETRY_COUNT = 10;
8
+ const SWITCH_APPLICATION = {
9
+ keyCode: [guidepup_1.WindowsKeyCodes.Escape],
10
+ modifiers: [guidepup_1.WindowsModifiers.Alt],
11
+ };
12
+ const MOVE_TO_TOP = {
13
+ keyCode: [guidepup_1.WindowsKeyCodes.Home],
14
+ modifiers: [guidepup_1.WindowsModifiers.Control],
15
+ };
16
+ const hasBrowserFocus = ({ applicationName, pageTitle, windowTitle, }) => {
17
+ const cleanedApplicationName = (0, cleanString_1.cleanString)(applicationName);
18
+ const cleanedPageTitle = (0, cleanString_1.cleanString)(pageTitle);
19
+ const cleanedWindowTitle = (0, cleanString_1.cleanString)(windowTitle);
20
+ return ((cleanedPageTitle.length > 0 &&
21
+ cleanedWindowTitle.startsWith(cleanedPageTitle)) ||
22
+ cleanedWindowTitle.includes(cleanedApplicationName));
23
+ };
24
+ const focusBrowser = async ({ nvda, applicationName, pageTitle, }) => {
25
+ await nvda.perform(guidepup_1.NVDAKeyCodeCommands.reportTitle);
26
+ let windowTitle = await nvda.lastSpokenPhrase();
27
+ if (hasBrowserFocus({ applicationName, pageTitle, windowTitle })) {
28
+ return;
29
+ }
30
+ let applicationSwitchRetryCount = 0;
31
+ while (applicationSwitchRetryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
32
+ applicationSwitchRetryCount++;
33
+ await nvda.perform(SWITCH_APPLICATION, { capture: false });
34
+ await (0, delay_1.delay)(500);
35
+ await nvda.perform(guidepup_1.NVDAKeyCodeCommands.reportTitle);
36
+ windowTitle = await nvda.lastSpokenPhrase();
37
+ if (hasBrowserFocus({ applicationName, pageTitle, windowTitle })) {
38
+ break;
39
+ }
40
+ }
41
+ if (applicationSwitchRetryCount === MAX_APPLICATION_SWITCH_RETRY_COUNT) {
42
+ throw new Error(`Unable to focus the application window with name "${applicationName}" or title "${pageTitle}" after ${MAX_APPLICATION_SWITCH_RETRY_COUNT} attempts.`);
43
+ }
44
+ };
45
+ const navigateNVDAtoWebContent = async ({ nvda, page, applicationName, capture, }) => {
46
+ const currentSpokenPhraseLog = [...(await nvda.spokenPhraseLog())];
47
+ const currentItemTextLog = [...(await nvda.itemTextLog())];
48
+ await nvda.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode, { capture: false });
49
+ await (0, delay_1.delay)(100);
50
+ const pageTitle = await page.title();
51
+ await focusBrowser({ nvda, applicationName, pageTitle });
52
+ await page.bringToFront();
53
+ await page.locator("body").waitFor();
54
+ await page.locator("body").focus();
55
+ await page.locator("body").click();
56
+ await page.locator("body").blur();
57
+ // Firefox can leave NVDA focused on the whole browser after an application
58
+ // switch. Toggling browse/focus mode after moving to a focusable item restores
59
+ // browse mode. REF: https://github.com/nvaccess/nvda/issues/5758
60
+ await nvda.perform(guidepup_1.NVDAKeyCodeCommands.readNextFocusableItem, {
61
+ capture: false,
62
+ });
63
+ await (0, delay_1.delay)(100);
64
+ await nvda.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode, {
65
+ capture: false,
66
+ });
67
+ await (0, delay_1.delay)(100);
68
+ await nvda.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode, {
69
+ capture: false,
70
+ });
71
+ await (0, delay_1.delay)(100);
72
+ await nvda.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode, { capture: false });
73
+ await (0, delay_1.delay)(100);
74
+ await nvda.clearSpokenPhraseLog();
75
+ await nvda.clearItemTextLog();
76
+ const spokenPhraseLog = await nvda.spokenPhraseLog();
77
+ const itemTextLog = await nvda.itemTextLog();
78
+ spokenPhraseLog.push(...currentSpokenPhraseLog);
79
+ itemTextLog.push(...currentItemTextLog);
80
+ await nvda.perform(MOVE_TO_TOP, { capture });
81
+ };
82
+ exports.navigateNVDAtoWebContent = navigateNVDAtoWebContent;
@@ -0,0 +1,7 @@
1
+ import { type CommandOptions, type Orca } from "@guidepup/guidepup";
2
+ import type { Page } from "@playwright/test";
3
+ export declare const navigateOrcaToWebContent: ({ orca, page, capture, }: {
4
+ orca: Orca;
5
+ page: Page;
6
+ capture?: CommandOptions["capture"];
7
+ }) => Promise<void>;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.navigateOrcaToWebContent = void 0;
4
+ const node_child_process_1 = require("node:child_process");
5
+ const guidepup_1 = require("@guidepup/guidepup");
6
+ const cleanString_1 = require("./cleanString");
7
+ const delay_1 = require("./delay");
8
+ const MAX_APPLICATION_SWITCH_RETRY_COUNT = 10;
9
+ const focusOrcaBrowser = async ({ orca, pageTitle, }) => {
10
+ const cleanedPageTitle = (0, cleanString_1.cleanString)(pageTitle);
11
+ if (!cleanedPageTitle) {
12
+ throw new Error("Cannot focus an application window with an empty page title.");
13
+ }
14
+ const currentSpokenPhraseLog = [...(await orca.spokenPhraseLog())];
15
+ try {
16
+ for (let retryCount = 0; retryCount <= MAX_APPLICATION_SWITCH_RETRY_COUNT; retryCount++) {
17
+ await orca.clearSpokenPhraseLog();
18
+ await orca.perform(guidepup_1.orcaKeyCodeCommands.PresentTitle, {
19
+ capture: "initial",
20
+ });
21
+ const windowTitle = (0, cleanString_1.cleanString)(await orca.lastSpokenPhrase());
22
+ if (windowTitle.includes(cleanedPageTitle)) {
23
+ return;
24
+ }
25
+ if (retryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
26
+ (0, node_child_process_1.execFileSync)("xdotool", ["key", "--clearmodifiers", "alt+Tab"]);
27
+ await (0, delay_1.delay)(100);
28
+ }
29
+ }
30
+ throw new Error(`Unable to focus the application window with title "${pageTitle}" after ${MAX_APPLICATION_SWITCH_RETRY_COUNT} attempts.`);
31
+ }
32
+ finally {
33
+ await orca.clearSpokenPhraseLog();
34
+ const spokenPhraseLog = await orca.spokenPhraseLog();
35
+ spokenPhraseLog.push(...currentSpokenPhraseLog);
36
+ }
37
+ };
38
+ const navigateOrcaToWebContent = async ({ orca, page, capture, }) => {
39
+ const pageTitle = await page.title();
40
+ await focusOrcaBrowser({ orca, pageTitle });
41
+ await page.bringToFront();
42
+ await page.locator("body").waitFor();
43
+ await page.locator("body").focus();
44
+ await page.locator("body").click();
45
+ await page.locator("body").blur();
46
+ await orca.perform(guidepup_1.orcaKeyCodeCommands.StartOfFile, { capture });
47
+ };
48
+ exports.navigateOrcaToWebContent = navigateOrcaToWebContent;
@@ -0,0 +1,8 @@
1
+ import { type CommandOptions, type VoiceOver } from "@guidepup/guidepup";
2
+ import type { Page } from "@playwright/test";
3
+ export declare const navigateVoiceOverToWebContent: ({ voiceOver, page, applicationId, capture, }: {
4
+ voiceOver: VoiceOver;
5
+ page: Page;
6
+ applicationId: string;
7
+ capture?: CommandOptions["capture"];
8
+ }) => Promise<void>;