@guidepup/playwright 0.16.3 → 0.18.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 +49 -33
- package/lib/delay.d.ts +1 -0
- package/lib/delay.js +6 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -1
- package/lib/nvdaTest.d.ts +1 -3
- package/lib/nvdaTest.js +29 -20
- package/lib/screenReaderTest.d.ts +76 -0
- package/lib/screenReaderTest.js +168 -0
- package/lib/voiceOverTest.d.ts +1 -3
- package/lib/voiceOverTest.js +22 -17
- package/package.json +5 -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,7 +94,7 @@ 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
|
```
|
|
@@ -100,15 +105,18 @@ await nvda.navigateToWebContent();
|
|
|
100
105
|
// ... some commands
|
|
101
106
|
|
|
102
107
|
// Store spoken phrases
|
|
103
|
-
const spokenPhrases = await
|
|
108
|
+
const spokenPhrases = await screenReader.spokenPhraseLog();
|
|
104
109
|
|
|
105
110
|
// Navigate to the web content
|
|
106
|
-
await
|
|
111
|
+
await screenReader.navigateToWebContent();
|
|
107
112
|
|
|
108
113
|
// ... some commands
|
|
109
114
|
|
|
110
115
|
// Collect all spoken phrases
|
|
111
|
-
const allSpokenPhrases = [
|
|
116
|
+
const allSpokenPhrases = [
|
|
117
|
+
...spokenPhrases,
|
|
118
|
+
...(await screenReader.spokenPhraseLog()),
|
|
119
|
+
];
|
|
112
120
|
|
|
113
121
|
// ... do something with spoken phrases
|
|
114
122
|
```
|
|
@@ -119,14 +127,22 @@ or pass a flag to prevent log clearing:
|
|
|
119
127
|
// ... some commands
|
|
120
128
|
|
|
121
129
|
// Navigate to the web content
|
|
122
|
-
await
|
|
130
|
+
await screenReader.navigateToWebContent(false);
|
|
123
131
|
|
|
124
132
|
// ... some commands
|
|
125
133
|
```
|
|
126
134
|
|
|
127
|
-
### Providing
|
|
135
|
+
### Providing screen reader start options
|
|
128
136
|
|
|
129
|
-
The options provided to `nvda.start([options])
|
|
137
|
+
The options provided to `screenReader.start([options])`, `nvda.start([options])`, or `voiceOver.start([options])` can be configured using `test.use(config)` as follows:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// Screen Reader Example
|
|
141
|
+
import { screenReaderTest as test } from "@guidepup/playwright";
|
|
142
|
+
|
|
143
|
+
// Capture all spoken phrases, including usage hints
|
|
144
|
+
test.use({ screenReaderStartOptions: { capture: true } });
|
|
145
|
+
```
|
|
130
146
|
|
|
131
147
|
```ts
|
|
132
148
|
// VoiceOver Example
|
|
@@ -146,7 +162,7 @@ test.use({ nvdaStartOptions: { capture: true } });
|
|
|
146
162
|
|
|
147
163
|
The default for VoiceOver and NVDA is set to `"initial"`. `true` captures all spoken phrases, including usage hints. `false` disables spoken phrase capture.
|
|
148
164
|
|
|
149
|
-
### VoiceOver
|
|
165
|
+
### VoiceOver example
|
|
150
166
|
|
|
151
167
|
`playwright.config.ts`:
|
|
152
168
|
|
|
@@ -206,7 +222,7 @@ test.describe("Playwright VoiceOver", () => {
|
|
|
206
222
|
});
|
|
207
223
|
```
|
|
208
224
|
|
|
209
|
-
### NVDA
|
|
225
|
+
### NVDA example
|
|
210
226
|
|
|
211
227
|
`playwright.config.ts`:
|
|
212
228
|
|
|
@@ -268,7 +284,7 @@ test.describe("Playwright NVDA", () => {
|
|
|
268
284
|
});
|
|
269
285
|
```
|
|
270
286
|
|
|
271
|
-
## Powerful
|
|
287
|
+
## Powerful tooling
|
|
272
288
|
|
|
273
289
|
Check out some of the other Guidepup modules:
|
|
274
290
|
|
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/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { nvdaTest } from "./nvdaTest";
|
|
2
2
|
export type { NVDAPlaywright } from "./nvdaTest";
|
|
3
3
|
export { screenReaderConfig } from "./screenReaderConfig";
|
|
4
|
+
export { screenReaderTest } from "./screenReaderTest";
|
|
5
|
+
export type { ScreenReaderPlaywright } from "./screenReaderTest";
|
|
4
6
|
export { voiceOverTest } from "./voiceOverTest";
|
|
5
7
|
export type { VoiceOverPlaywright } from "./voiceOverTest";
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.voiceOverTest = exports.screenReaderConfig = exports.nvdaTest = void 0;
|
|
3
|
+
exports.voiceOverTest = exports.screenReaderTest = exports.screenReaderConfig = 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
6
|
var screenReaderConfig_1 = require("./screenReaderConfig");
|
|
7
7
|
Object.defineProperty(exports, "screenReaderConfig", { enumerable: true, get: function () { return screenReaderConfig_1.screenReaderConfig; } });
|
|
8
|
+
var screenReaderTest_1 = require("./screenReaderTest");
|
|
9
|
+
Object.defineProperty(exports, "screenReaderTest", { enumerable: true, get: function () { return screenReaderTest_1.screenReaderTest; } });
|
|
8
10
|
var voiceOverTest_1 = require("./voiceOverTest");
|
|
9
11
|
Object.defineProperty(exports, "voiceOverTest", { enumerable: true, get: function () { return voiceOverTest_1.voiceOverTest; } });
|
package/lib/nvdaTest.d.ts
CHANGED
|
@@ -31,10 +31,8 @@ export interface NVDAPlaywright extends NVDA {
|
|
|
31
31
|
* of the browser's web content.
|
|
32
32
|
*
|
|
33
33
|
* This command should be used after page navigation.
|
|
34
|
-
*
|
|
35
|
-
* Note: this command clears all logs by default.
|
|
36
34
|
*/
|
|
37
|
-
navigateToWebContent(
|
|
35
|
+
navigateToWebContent(options?: Pick<CommandOptions, "capture">): Promise<void>;
|
|
38
36
|
}
|
|
39
37
|
/**
|
|
40
38
|
* These tests extend the default Playwright environment that launches the
|
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,17 +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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
nvdaPlaywright.navigateToWebContent = async ({ capture } = {}) => {
|
|
65
|
+
const currentSpokenPhraseLog = [
|
|
66
|
+
...(await nvdaPlaywright.spokenPhraseLog()),
|
|
67
|
+
];
|
|
68
|
+
const currentItemTextLog = [...(await nvdaPlaywright.itemTextLog())];
|
|
65
69
|
// Make sure NVDA is not in focus mode.
|
|
66
|
-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.exitFocusMode);
|
|
67
|
-
|
|
70
|
+
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.exitFocusMode, { capture: false });
|
|
71
|
+
await (0, delay_1.delay)(100);
|
|
68
72
|
// Ensure application is brought to front and focused.
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
pageTitle,
|
|
72
|
-
});
|
|
73
|
+
const pageTitle = await page.title();
|
|
74
|
+
await focusBrowser({ applicationName, pageTitle });
|
|
73
75
|
// Ensure the document is ready and focused.
|
|
74
76
|
await page.bringToFront();
|
|
75
77
|
await page.locator("body").waitFor();
|
|
@@ -86,16 +88,23 @@ exports.nvdaTest = test_1.test.extend({
|
|
|
86
88
|
// we exit focus mode and move NVDA to the top of the page.
|
|
87
89
|
//
|
|
88
90
|
// REF: https://github.com/nvaccess/nvda/issues/5758
|
|
89
|
-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.readNextFocusableItem);
|
|
90
|
-
await
|
|
91
|
-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode);
|
|
92
|
-
await
|
|
93
|
-
await nvdaPlaywright.perform(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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 });
|
|
99
108
|
};
|
|
100
109
|
await nvdaPlaywright.start(nvdaStartOptions);
|
|
101
110
|
await use(nvdaPlaywright);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CommandOptions, type ScreenReader } from "@guidepup/guidepup";
|
|
2
|
+
type Prettify<T> = {
|
|
3
|
+
[K in keyof T]: T[K];
|
|
4
|
+
} & {};
|
|
5
|
+
type CaptureCommandOptions = Prettify<Pick<CommandOptions, "capture">>;
|
|
6
|
+
/**
|
|
7
|
+
* This object can be used to launch and control the default screen reader for
|
|
8
|
+
* the environment.
|
|
9
|
+
*
|
|
10
|
+
* Here's a typical example:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { screenReader } from "@guidepup/guidepup";
|
|
14
|
+
*
|
|
15
|
+
* (async () => {
|
|
16
|
+
* // Start the screen reader.
|
|
17
|
+
* await screenReader.start();
|
|
18
|
+
*
|
|
19
|
+
* // Move to the next item.
|
|
20
|
+
* await screenReader.next();
|
|
21
|
+
*
|
|
22
|
+
* // ... perform some commands.
|
|
23
|
+
*
|
|
24
|
+
* // Stop the screen reader.
|
|
25
|
+
* await screenReader.stop();
|
|
26
|
+
* })();
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export interface ScreenReaderPlaywright extends ScreenReader {
|
|
30
|
+
/**
|
|
31
|
+
* Guidepup Playwright specific command that navigates the screen reader to
|
|
32
|
+
* the beginning of the browser's web content.
|
|
33
|
+
*
|
|
34
|
+
* This command should be used after page navigation.
|
|
35
|
+
*/
|
|
36
|
+
navigateToWebContent(options?: Pick<CommandOptions, "capture">): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* These tests extend the default Playwright environment that launches the
|
|
40
|
+
* browser with a running instance of the default screen reader for the current
|
|
41
|
+
* OS.
|
|
42
|
+
*
|
|
43
|
+
* A fresh started screen reader instance `screenReader` is provided to each
|
|
44
|
+
* test.
|
|
45
|
+
*/
|
|
46
|
+
export declare const screenReaderTest: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
47
|
+
/**
|
|
48
|
+
* This object can be used to launch and control the default screen reader for
|
|
49
|
+
* the environment.
|
|
50
|
+
*
|
|
51
|
+
* Here's a typical example:
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* import { screenReader } from "@guidepup/guidepup";
|
|
55
|
+
*
|
|
56
|
+
* (async () => {
|
|
57
|
+
* // Start the screen reader.
|
|
58
|
+
* await screenReader.start();
|
|
59
|
+
*
|
|
60
|
+
* // Move to the next item.
|
|
61
|
+
* await screenReader.next();
|
|
62
|
+
*
|
|
63
|
+
* // ... perform some commands.
|
|
64
|
+
*
|
|
65
|
+
* // Stop the screen reader.
|
|
66
|
+
* await screenReader.stop();
|
|
67
|
+
* })();
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
screenReader: ScreenReaderPlaywright;
|
|
71
|
+
/**
|
|
72
|
+
* Options to start the default screen reader with.
|
|
73
|
+
*/
|
|
74
|
+
screenReaderStartOptions: CaptureCommandOptions;
|
|
75
|
+
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.screenReaderTest = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const guidepup_1 = require("@guidepup/guidepup");
|
|
6
|
+
const applicationNameMap_1 = require("./applicationNameMap");
|
|
7
|
+
const delay_1 = require("./delay");
|
|
8
|
+
const MAX_APPLICATION_SWITCH_RETRY_COUNT = 10;
|
|
9
|
+
const SWITCH_APPLICATION = {
|
|
10
|
+
keyCode: [guidepup_1.WindowsKeyCodes.Escape],
|
|
11
|
+
modifiers: [guidepup_1.WindowsModifiers.Alt],
|
|
12
|
+
};
|
|
13
|
+
const MOVE_TO_TOP = {
|
|
14
|
+
keyCode: [guidepup_1.WindowsKeyCodes.Home],
|
|
15
|
+
modifiers: [guidepup_1.WindowsModifiers.Control],
|
|
16
|
+
};
|
|
17
|
+
const cleanString = (str) => str
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
// REF: https://github.com/nvaccess/nvda/blob/master/source/locale/en/symbols.dic
|
|
20
|
+
.replace(/[|¦:;'"`\-‐–—·_()[\]{}\\^~]/g, " ")
|
|
21
|
+
.replace(/\s+/g, " ")
|
|
22
|
+
.trim();
|
|
23
|
+
const hasFocus = ({ applicationName, pageTitle, windowTitle, }) => {
|
|
24
|
+
const cleanedApplicationName = cleanString(applicationName);
|
|
25
|
+
const cleanedPageTitle = cleanString(pageTitle);
|
|
26
|
+
const cleanedWindowTitle = cleanString(windowTitle);
|
|
27
|
+
return ((cleanedPageTitle.length &&
|
|
28
|
+
cleanedWindowTitle.startsWith(cleanedPageTitle)) ||
|
|
29
|
+
cleanedWindowTitle.includes(cleanedApplicationName));
|
|
30
|
+
};
|
|
31
|
+
const focusBrowser = async ({ applicationName, pageTitle, }) => {
|
|
32
|
+
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.reportTitle);
|
|
33
|
+
let windowTitle = await screenReaderPlaywright.lastSpokenPhrase();
|
|
34
|
+
if (hasFocus({ applicationName, pageTitle, windowTitle })) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let applicationSwitchRetryCount = 0;
|
|
38
|
+
while (applicationSwitchRetryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
|
|
39
|
+
applicationSwitchRetryCount++;
|
|
40
|
+
await screenReaderPlaywright.perform(SWITCH_APPLICATION, {
|
|
41
|
+
capture: false,
|
|
42
|
+
});
|
|
43
|
+
await (0, delay_1.delay)(500);
|
|
44
|
+
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.reportTitle);
|
|
45
|
+
windowTitle = await screenReaderPlaywright.lastSpokenPhrase();
|
|
46
|
+
if (hasFocus({ applicationName, pageTitle, windowTitle })) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const screenReaderPlaywright = guidepup_1.screenReader;
|
|
52
|
+
/**
|
|
53
|
+
* These tests extend the default Playwright environment that launches the
|
|
54
|
+
* browser with a running instance of the default screen reader for the current
|
|
55
|
+
* OS.
|
|
56
|
+
*
|
|
57
|
+
* A fresh started screen reader instance `screenReader` is provided to each
|
|
58
|
+
* test.
|
|
59
|
+
*/
|
|
60
|
+
exports.screenReaderTest = test_1.test.extend({
|
|
61
|
+
screenReaderStartOptions: { capture: "initial" },
|
|
62
|
+
screenReader: async ({ browserName, page, screenReaderStartOptions }, use) => {
|
|
63
|
+
try {
|
|
64
|
+
const applicationName = applicationNameMap_1.applicationNameMap[browserName];
|
|
65
|
+
if (!applicationName) {
|
|
66
|
+
throw new Error(`Browser ${browserName} is not installed.`);
|
|
67
|
+
}
|
|
68
|
+
if (guidepup_1.nvda.default()) {
|
|
69
|
+
screenReaderPlaywright.navigateToWebContent = async ({ capture, } = {}) => {
|
|
70
|
+
const currentSpokenPhraseLog = [
|
|
71
|
+
...(await screenReaderPlaywright.spokenPhraseLog()),
|
|
72
|
+
];
|
|
73
|
+
const currentItemTextLog = [
|
|
74
|
+
...(await screenReaderPlaywright.itemTextLog()),
|
|
75
|
+
];
|
|
76
|
+
// Make sure NVDA is not in focus mode.
|
|
77
|
+
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode, { capture: false });
|
|
78
|
+
await (0, delay_1.delay)(100);
|
|
79
|
+
// Ensure application is brought to front and focused.
|
|
80
|
+
const pageTitle = await page.title();
|
|
81
|
+
await focusBrowser({ applicationName, pageTitle });
|
|
82
|
+
// Ensure the document is ready and focused.
|
|
83
|
+
await page.bringToFront();
|
|
84
|
+
await page.locator("body").waitFor();
|
|
85
|
+
await page.locator("body").focus();
|
|
86
|
+
await page.locator("body").click();
|
|
87
|
+
await page.locator("body").blur();
|
|
88
|
+
// NVDA appears to not work well with Firefox when switching between
|
|
89
|
+
// applications resulting in the entire browser window having NVDA focus
|
|
90
|
+
// with focus mode.
|
|
91
|
+
//
|
|
92
|
+
// One workaround is to tab to the next focusable item. From there we can
|
|
93
|
+
// toggle into (yes although we are already in it...) focus mode and back
|
|
94
|
+
// out. In case this ever transpires to not happen as expect, we then ensure
|
|
95
|
+
// we exit focus mode and move NVDA to the top of the page.
|
|
96
|
+
//
|
|
97
|
+
// REF: https://github.com/nvaccess/nvda/issues/5758
|
|
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 });
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
else if (guidepup_1.voiceOver.default()) {
|
|
118
|
+
screenReaderPlaywright.navigateToWebContent = async ({ capture, } = {}) => {
|
|
119
|
+
// Ensure application is brought to front and focused.
|
|
120
|
+
await (0, guidepup_1.macOSActivate)(applicationName);
|
|
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.
|
|
125
|
+
await page.bringToFront();
|
|
126
|
+
await page.locator("body").waitFor();
|
|
127
|
+
// Open the web item chooser.
|
|
128
|
+
await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.openItemChooser, { capture: false });
|
|
129
|
+
await (0, delay_1.delay)(500);
|
|
130
|
+
// Filter by "web content" - currently web content items for all browsers
|
|
131
|
+
// are suffixed by "web content".
|
|
132
|
+
for (const character of "web content") {
|
|
133
|
+
await screenReaderPlaywright.type(character, { capture: false });
|
|
134
|
+
await (0, delay_1.delay)(100);
|
|
135
|
+
}
|
|
136
|
+
// Select the web content window spot.
|
|
137
|
+
await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
|
|
138
|
+
await (0, delay_1.delay)(100);
|
|
139
|
+
// Navigate into web content.
|
|
140
|
+
await screenReaderPlaywright.interact({ capture: false });
|
|
141
|
+
await (0, delay_1.delay)(100);
|
|
142
|
+
// Navigate to the beginning of the web content.
|
|
143
|
+
await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.moveToBeginningOfText, { capture: false });
|
|
144
|
+
await (0, delay_1.delay)(100);
|
|
145
|
+
// Cancel auto navigation
|
|
146
|
+
await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
|
|
147
|
+
await (0, delay_1.delay)(100);
|
|
148
|
+
// Navigate to the beginning of the web content, using chosen capture
|
|
149
|
+
// settings, so don't miss announcing the first item on the page.
|
|
150
|
+
await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.moveToBeginningOfText, { capture });
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
throw new Error("No supported screen reader");
|
|
155
|
+
}
|
|
156
|
+
await screenReaderPlaywright.start(screenReaderStartOptions);
|
|
157
|
+
await use(screenReaderPlaywright);
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
try {
|
|
161
|
+
await screenReaderPlaywright.stop();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// swallow stop failure
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
});
|
package/lib/voiceOverTest.d.ts
CHANGED
|
@@ -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
|
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,37 +20,41 @@ 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
32
|
// Open the web item chooser.
|
|
31
|
-
await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.openItemChooser);
|
|
33
|
+
await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.openItemChooser, { capture: false });
|
|
34
|
+
await (0, delay_1.delay)(500);
|
|
32
35
|
// Filter by "web content" - currently web content items for all browsers
|
|
33
36
|
// are suffixed by "web content".
|
|
34
|
-
|
|
37
|
+
for (const character of "web content") {
|
|
38
|
+
await voiceOverPlaywright.type(character, { capture: false });
|
|
39
|
+
await (0, delay_1.delay)(100);
|
|
40
|
+
}
|
|
35
41
|
// Select the web content window spot.
|
|
36
|
-
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter });
|
|
42
|
+
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
|
|
43
|
+
await (0, delay_1.delay)(100);
|
|
37
44
|
// Navigate into web content.
|
|
38
|
-
await voiceOverPlaywright.interact();
|
|
45
|
+
await voiceOverPlaywright.interact({ capture: false });
|
|
46
|
+
await (0, delay_1.delay)(100);
|
|
39
47
|
// Navigate to the beginning of the web content.
|
|
40
|
-
await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.moveToBeginningOfText);
|
|
48
|
+
await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.moveToBeginningOfText, { capture: false });
|
|
49
|
+
await (0, delay_1.delay)(100);
|
|
41
50
|
// Cancel auto navigation
|
|
42
|
-
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control });
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
51
|
+
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
|
|
52
|
+
await (0, delay_1.delay)(100);
|
|
53
|
+
// Navigate to the beginning of the web content, using chosen capture
|
|
54
|
+
// settings, so don't miss announcing the first item on the page.
|
|
55
|
+
await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.moveToBeginningOfText, { capture });
|
|
48
56
|
};
|
|
49
57
|
await voiceOverPlaywright.start(voiceOverStartOptions);
|
|
50
|
-
await (0, guidepup_1.macOSActivate)(applicationName);
|
|
51
|
-
// Cancel auto navigation
|
|
52
|
-
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
|
|
53
58
|
await use(voiceOverPlaywright);
|
|
54
59
|
}
|
|
55
60
|
finally {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guidepup/playwright",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Screen reader automation library for Playwright testing. ",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -34,15 +34,17 @@
|
|
|
34
34
|
"prepublish": "yarn build",
|
|
35
35
|
"pretest": "npx playwright install chromium firefox webkit",
|
|
36
36
|
"test:nvda": "yarn test:nvda:chromium && yarn test:nvda:firefox",
|
|
37
|
+
"test:screenreader": "yarn test:screenreader:chromium",
|
|
37
38
|
"test:voiceover": "yarn test:voiceover:chromium && yarn test:voiceover:firefox && yarn test:voiceover:webkit",
|
|
38
39
|
"test:nvda:chromium": "playwright test --config ./examples/playwright-nvda/chromium.config.ts ./examples/playwright-nvda/tests/chromium/",
|
|
39
40
|
"test:nvda:firefox": "playwright test --config ./examples/playwright-nvda/firefox.config.ts ./examples/playwright-nvda/tests/firefox/",
|
|
41
|
+
"test:screenreader:chromium": "playwright test --config ./examples/playwright-screenreader/chromium.config.ts ./examples/playwright-screenreader/tests/chromium/",
|
|
40
42
|
"test:voiceover:chromium": "playwright test --config ./examples/playwright-voiceover/chromium.config.ts ./examples/playwright-voiceover/tests/chromium/",
|
|
41
43
|
"test:voiceover:firefox": "playwright test --config ./examples/playwright-voiceover/firefox.config.ts ./examples/playwright-voiceover/tests/firefox/",
|
|
42
44
|
"test:voiceover:webkit": "playwright test --config ./examples/playwright-voiceover/webkit.config.ts ./examples/playwright-voiceover/tests/webkit/"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
|
-
"@guidepup/guidepup": "^0.
|
|
47
|
+
"@guidepup/guidepup": "^0.29.1",
|
|
46
48
|
"@guidepup/record": "^0.1.0",
|
|
47
49
|
"@playwright/test": "^1.60.0",
|
|
48
50
|
"@types/node": "^24.10.1",
|
|
@@ -56,7 +58,7 @@
|
|
|
56
58
|
"typescript": "^5.9.3"
|
|
57
59
|
},
|
|
58
60
|
"peerDependencies": {
|
|
59
|
-
"@guidepup/guidepup": ">=0.
|
|
61
|
+
"@guidepup/guidepup": ">=0.27.0",
|
|
60
62
|
"@playwright/test": "^1.57.0"
|
|
61
63
|
},
|
|
62
64
|
"resolutions": {
|