@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 +40 -57
- package/lib/delay.d.ts +1 -0
- package/lib/delay.js +6 -0
- package/lib/nvdaTest.d.ts +5 -6
- package/lib/nvdaTest.js +29 -18
- package/lib/screenReaderTest.d.ts +4 -5
- package/lib/screenReaderTest.js +99 -31
- package/lib/voiceOverTest.d.ts +4 -6
- package/lib/voiceOverTest.js +66 -20
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
|
|
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
|
-
<
|
|
4
|
-
<a href="https://
|
|
5
|
-
<a href="https://github.com/guidepup/guidepup-playwright/
|
|
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
|
[](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2022)
|
|
13
18
|
[](https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2025)
|
|
14
19
|
|
|
15
|
-
|
|
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
|
|
29
|
+
## Getting started
|
|
23
30
|
|
|
24
|
-
Set up your
|
|
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
|
-
|
|
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 [
|
|
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
|
|
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
|
|
79
|
+
### Web content navigation
|
|
75
80
|
|
|
76
|
-
In addition to the Guidepup APIs the `voiceOver
|
|
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
|
|
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
|
|
97
|
+
await screenReader.navigateToWebContent();
|
|
93
98
|
|
|
94
99
|
// ... some commands
|
|
95
100
|
```
|
|
96
101
|
|
|
97
|
-
|
|
102
|
+
### Providing screen reader start options
|
|
98
103
|
|
|
99
|
-
|
|
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
|
-
//
|
|
107
|
+
// Screen Reader Example
|
|
108
|
+
import { screenReaderTest as test } from "@guidepup/playwright";
|
|
120
109
|
|
|
121
|
-
//
|
|
122
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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
|
|
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
package/lib/nvdaTest.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type
|
|
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(
|
|
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-
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
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
|
|
68
|
-
|
|
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
|
|
89
|
-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode);
|
|
90
|
-
await
|
|
91
|
-
await nvdaPlaywright.perform(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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(
|
|
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:
|
|
75
|
+
screenReaderStartOptions: CaptureStartOptions;
|
|
77
76
|
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
78
77
|
export {};
|
package/lib/screenReaderTest.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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
|
|
89
|
-
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode);
|
|
90
|
-
await
|
|
91
|
-
await screenReaderPlaywright.perform(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 (
|
|
118
|
+
screenReaderPlaywright.navigateToWebContent = async ({ capture, } = {}) => {
|
|
119
|
+
// Ensure application is brought to front and focused.
|
|
101
120
|
await (0, guidepup_1.macOSActivate)(applicationName);
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
}
|
package/lib/voiceOverTest.d.ts
CHANGED
|
@@ -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(
|
|
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-
|
|
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:
|
|
72
|
+
voiceOverStartOptions: StartOptions;
|
|
75
73
|
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
package/lib/voiceOverTest.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
61
|
+
"@guidepup/guidepup": ">=0.33.0",
|
|
62
62
|
"@playwright/test": "^1.57.0"
|
|
63
63
|
},
|
|
64
64
|
"resolutions": {
|