@guidepup/playwright 0.19.1 → 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 +124 -64
- package/lib/applicationIdMap.d.ts +10 -0
- package/lib/applicationIdMap.js +13 -0
- package/lib/cleanString.d.ts +1 -0
- package/lib/cleanString.js +9 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +5 -1
- package/lib/navigateNVDAtoWebContent.d.ts +8 -0
- package/lib/navigateNVDAtoWebContent.js +82 -0
- package/lib/navigateOrcaToWebContent.d.ts +7 -0
- package/lib/navigateOrcaToWebContent.js +48 -0
- package/lib/navigateVoiceOverToWebContent.d.ts +8 -0
- package/lib/navigateVoiceOverToWebContent.js +101 -0
- package/lib/nvdaTest.js +8 -88
- package/lib/orcaTest.d.ts +73 -0
- package/lib/orcaTest.js +35 -0
- package/lib/screenReaderTest.d.ts +7 -1
- package/lib/screenReaderTest.js +26 -198
- package/lib/voiceOverTest.d.ts +2 -2
- package/lib/voiceOverTest.js +13 -112
- package/lib/withGuidepup.d.ts +2 -0
- package/lib/withGuidepup.js +21 -0
- package/package.json +10 -7
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
|
-
[](https://support.apple.com/en-gb/102662)
|
|
15
|
+
[](https://support.apple.com/en-gb/102662)
|
|
16
|
+
[](https://support.apple.com/en-gb/102662)
|
|
17
|
+
[](https://support.apple.com/en-gb/127455)
|
|
17
18
|
[](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2022)
|
|
18
19
|
[](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2025)
|
|
20
|
+
[](https://releases.ubuntu.com/resolute/)
|
|
21
|
+
[](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-
|
|
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 {
|
|
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
|
-
|
|
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`, `
|
|
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
|
-
//
|
|
116
|
-
import {
|
|
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({
|
|
120
|
+
test.use({ nvdaStartOptions: { capture: true } });
|
|
120
121
|
```
|
|
121
122
|
|
|
122
123
|
```ts
|
|
123
|
-
//
|
|
124
|
-
import {
|
|
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({
|
|
128
|
+
test.use({ orcaStartOptions: { capture: true } });
|
|
128
129
|
```
|
|
129
130
|
|
|
130
|
-
|
|
131
|
+
```ts
|
|
132
|
+
// VoiceOver Example
|
|
133
|
+
import { voiceOverTest as test } from "@guidepup/playwright";
|
|
131
134
|
|
|
132
|
-
|
|
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
|
|
138
|
-
import {
|
|
146
|
+
import { devices } from "@playwright/test";
|
|
147
|
+
import { withGuidepup } from "@guidepup/playwright";
|
|
139
148
|
|
|
140
|
-
const config
|
|
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: "
|
|
148
|
-
|
|
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
|
-
`
|
|
164
|
+
`nvda.spec.ts`:
|
|
159
165
|
|
|
160
166
|
```ts
|
|
161
|
-
import {
|
|
167
|
+
import { nvdaTest as test } from "@guidepup/playwright";
|
|
162
168
|
import { expect } from "@playwright/test";
|
|
163
169
|
|
|
164
|
-
test.describe("Playwright
|
|
165
|
-
test("I can navigate the Guidepup Github page with
|
|
170
|
+
test.describe("Playwright NVDA", () => {
|
|
171
|
+
test("I can navigate the Guidepup Github page with NVDA", async ({
|
|
166
172
|
page,
|
|
167
|
-
|
|
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
|
|
185
|
+
await nvda.navigateToWebContent();
|
|
180
186
|
|
|
181
|
-
// Move across the page menu to the Guidepup heading using
|
|
182
|
-
while ((await
|
|
183
|
-
await
|
|
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
|
|
193
|
+
expect(JSON.stringify(await nvda.spokenPhraseLog())).toMatchSnapshot();
|
|
188
194
|
});
|
|
189
195
|
});
|
|
190
196
|
```
|
|
191
197
|
|
|
192
|
-
###
|
|
198
|
+
### Orca example
|
|
193
199
|
|
|
194
200
|
`playwright.config.ts`:
|
|
195
201
|
|
|
196
202
|
```ts
|
|
197
|
-
import { devices
|
|
198
|
-
import {
|
|
203
|
+
import { devices } from "@playwright/test";
|
|
204
|
+
import { withGuidepup } from "@guidepup/playwright";
|
|
199
205
|
|
|
200
|
-
const config
|
|
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
|
-
|
|
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
|
-
`
|
|
221
|
+
`orca.spec.ts`:
|
|
219
222
|
|
|
220
223
|
```ts
|
|
221
|
-
import {
|
|
224
|
+
import { orcaTest as test } from "@guidepup/playwright";
|
|
222
225
|
import { expect } from "@playwright/test";
|
|
223
226
|
|
|
224
|
-
test.describe("Playwright
|
|
225
|
-
test("I can navigate the Guidepup Github page with
|
|
227
|
+
test.describe("Playwright Orca", () => {
|
|
228
|
+
test("I can navigate the Guidepup Github page with Orca", async ({
|
|
226
229
|
page,
|
|
227
|
-
|
|
230
|
+
orca,
|
|
228
231
|
}) => {
|
|
229
232
|
// Navigate to Guidepup GitHub page
|
|
230
233
|
await page.goto("https://github.com/guidepup/guidepup", {
|
|
@@ -236,17 +239,74 @@ test.describe("Playwright NVDA", () => {
|
|
|
236
239
|
await header.waitFor();
|
|
237
240
|
|
|
238
241
|
// Interact with the page
|
|
239
|
-
await
|
|
242
|
+
await orca.navigateToWebContent();
|
|
240
243
|
|
|
241
|
-
// Move across the page menu to the Guidepup heading using
|
|
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
|
-
|
|
303
|
+
(await voiceOver.lastSpokenPhrase()) !== "heading level 1 Guidepup"
|
|
244
304
|
) {
|
|
245
|
-
await
|
|
305
|
+
await voiceOver.nextHeading();
|
|
246
306
|
}
|
|
247
307
|
|
|
248
308
|
// Assert that the spoken phrases are as expected
|
|
249
|
-
expect(JSON.stringify(await
|
|
309
|
+
expect(JSON.stringify(await voiceOver.spokenPhraseLog())).toMatchSnapshot();
|
|
250
310
|
});
|
|
251
311
|
});
|
|
252
312
|
```
|
|
@@ -267,14 +327,14 @@ expect(capture.spokenPhrase).toContain("Added to cart");
|
|
|
267
327
|
|
|
268
328
|
Check out some of the other Guidepup modules:
|
|
269
329
|
|
|
270
|
-
- [`@guidepup/guidepup`](https://github.com/guidepup/guidepup/) - Reliable automation for your screen reader a11y workflows through JavaScript supporting VoiceOver and NVDA.
|
|
271
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.
|
|
272
332
|
- [`@guidepup/virtual-screen-reader`](https://github.com/guidepup/virtual-screen-reader/) - Reliable unit testing for your screen reader a11y workflows.
|
|
273
333
|
- [`@guidepup/jest`](https://github.com/guidepup/jest/) - Jest matchers for reliable unit testing of your screen reader a11y workflows.
|
|
274
334
|
|
|
275
335
|
## Resources
|
|
276
336
|
|
|
277
|
-
- [Documentation](https://www.guidepup.dev/docs/example)
|
|
337
|
+
- [Documentation](https://www.guidepup.dev/docs/getting-started/real-world-example)
|
|
278
338
|
- [API Reference](https://www.guidepup.dev/docs/api/class-guidepup)
|
|
279
339
|
- [Contributing](.github/CONTRIBUTING.md)
|
|
280
340
|
- [Changelog](https://github.com/guidepup/guidepup-playwright/releases)
|
|
@@ -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>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.navigateVoiceOverToWebContent = void 0;
|
|
4
|
+
const guidepup_1 = require("@guidepup/guidepup");
|
|
5
|
+
const delay_1 = require("./delay");
|
|
6
|
+
const MAX_WEB_CONTENT_SEARCH_ATTEMPTS = 10;
|
|
7
|
+
const cancelCurrentInteraction = async (voiceOver) => {
|
|
8
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
|
|
9
|
+
};
|
|
10
|
+
const closeMenus = async (voiceOver) => {
|
|
11
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Escape }, { capture: false });
|
|
12
|
+
};
|
|
13
|
+
const openItemChooser = async (voiceOver) => {
|
|
14
|
+
let lastSpokenPhrase = "";
|
|
15
|
+
while (!lastSpokenPhrase.toLowerCase().includes("item chooser")) {
|
|
16
|
+
await cancelCurrentInteraction(voiceOver);
|
|
17
|
+
await (0, delay_1.delay)(100);
|
|
18
|
+
await closeMenus(voiceOver);
|
|
19
|
+
await (0, delay_1.delay)(100);
|
|
20
|
+
await voiceOver.perform(guidepup_1.voiceOverKeyCodeCommands.openItemChooser, {
|
|
21
|
+
capture: true,
|
|
22
|
+
});
|
|
23
|
+
lastSpokenPhrase = await voiceOver.lastSpokenPhrase();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const selectWebContentItem = async (voiceOver) => {
|
|
27
|
+
let matched = false;
|
|
28
|
+
for (let attempt = 0; attempt < MAX_WEB_CONTENT_SEARCH_ATTEMPTS && !matched; attempt++) {
|
|
29
|
+
for (const character of "web content") {
|
|
30
|
+
await cancelCurrentInteraction(voiceOver);
|
|
31
|
+
await (0, delay_1.delay)(100);
|
|
32
|
+
await voiceOver.type(character, { capture: "initial" });
|
|
33
|
+
const lastSpokenPhrase = await voiceOver.lastSpokenPhrase();
|
|
34
|
+
if (lastSpokenPhrase.toLowerCase().includes("web content")) {
|
|
35
|
+
matched = true;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (matched) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (attempt + 1 < MAX_WEB_CONTENT_SEARCH_ATTEMPTS) {
|
|
43
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Backspace }, { capture: false });
|
|
44
|
+
await (0, delay_1.delay)(100);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!matched) {
|
|
48
|
+
throw new Error(`Unable to find "web content" in the VoiceOver Item Chooser after ${MAX_WEB_CONTENT_SEARCH_ATTEMPTS} attempts.`);
|
|
49
|
+
}
|
|
50
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
|
|
51
|
+
};
|
|
52
|
+
const navigateVoiceOverToWebContent = async ({ voiceOver, page, applicationId, capture, }) => {
|
|
53
|
+
const currentSpokenPhraseLog = [...(await voiceOver.spokenPhraseLog())];
|
|
54
|
+
const currentItemTextLog = [...(await voiceOver.itemTextLog())];
|
|
55
|
+
await (0, guidepup_1.macOSActivateId)(applicationId);
|
|
56
|
+
await cancelCurrentInteraction(voiceOver);
|
|
57
|
+
await (0, delay_1.delay)(100);
|
|
58
|
+
await page.bringToFront();
|
|
59
|
+
await page.locator("body").waitFor();
|
|
60
|
+
try {
|
|
61
|
+
await page.evaluate(() => {
|
|
62
|
+
const marker = document.createElement("input");
|
|
63
|
+
marker.id = "__guidepup_marker__";
|
|
64
|
+
marker.type = "text";
|
|
65
|
+
marker.value = "Guidepup Marker";
|
|
66
|
+
marker.readOnly = true;
|
|
67
|
+
marker.tabIndex = -1;
|
|
68
|
+
marker.autocomplete = "off";
|
|
69
|
+
marker.setAttribute("aria-label", "Guidepup Marker");
|
|
70
|
+
marker.style.cssText = `
|
|
71
|
+
position: absolute;
|
|
72
|
+
width: 1px;
|
|
73
|
+
height: 1px;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
clip: rect(0 0 0 0);
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
`;
|
|
78
|
+
document.body.prepend(marker);
|
|
79
|
+
});
|
|
80
|
+
await openItemChooser(voiceOver);
|
|
81
|
+
await selectWebContentItem(voiceOver);
|
|
82
|
+
await voiceOver.interact({ capture: false });
|
|
83
|
+
await (0, delay_1.delay)(100);
|
|
84
|
+
await voiceOver.clearSpokenPhraseLog();
|
|
85
|
+
await voiceOver.clearItemTextLog();
|
|
86
|
+
const spokenPhraseLog = await voiceOver.spokenPhraseLog();
|
|
87
|
+
const itemTextLog = await voiceOver.itemTextLog();
|
|
88
|
+
spokenPhraseLog.push(...currentSpokenPhraseLog);
|
|
89
|
+
itemTextLog.push(...currentItemTextLog);
|
|
90
|
+
await voiceOver.next({ capture });
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await page.evaluate(() => {
|
|
94
|
+
const marker = document.querySelector("#__guidepup_marker__");
|
|
95
|
+
if (marker) {
|
|
96
|
+
document.body.removeChild(marker);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
exports.navigateVoiceOverToWebContent = navigateVoiceOverToWebContent;
|