@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/lib/nvdaTest.js
CHANGED
|
@@ -4,49 +4,8 @@ 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
|
|
7
|
+
const navigateNVDAtoWebContent_1 = require("./navigateNVDAtoWebContent");
|
|
8
8
|
const nvdaPlaywright = guidepup_1.nvda;
|
|
9
|
-
const MAX_APPLICATION_SWITCH_RETRY_COUNT = 10;
|
|
10
|
-
const SWITCH_APPLICATION = {
|
|
11
|
-
keyCode: [guidepup_1.WindowsKeyCodes.Escape],
|
|
12
|
-
modifiers: [guidepup_1.WindowsModifiers.Alt],
|
|
13
|
-
};
|
|
14
|
-
const MOVE_TO_TOP = {
|
|
15
|
-
keyCode: [guidepup_1.WindowsKeyCodes.Home],
|
|
16
|
-
modifiers: [guidepup_1.WindowsModifiers.Control],
|
|
17
|
-
};
|
|
18
|
-
const cleanString = (str) => str
|
|
19
|
-
.toLowerCase()
|
|
20
|
-
// REF: https://github.com/nvaccess/nvda/blob/master/source/locale/en/symbols.dic
|
|
21
|
-
.replace(/[|¦:;'"`\-‐–—·_()[\]{}\\^~]/g, " ")
|
|
22
|
-
.replace(/\s+/g, " ")
|
|
23
|
-
.trim();
|
|
24
|
-
const hasFocus = ({ applicationName, pageTitle, windowTitle, }) => {
|
|
25
|
-
const cleanedApplicationName = cleanString(applicationName);
|
|
26
|
-
const cleanedPageTitle = cleanString(pageTitle);
|
|
27
|
-
const cleanedWindowTitle = cleanString(windowTitle);
|
|
28
|
-
return ((cleanedPageTitle.length &&
|
|
29
|
-
cleanedWindowTitle.startsWith(cleanedPageTitle)) ||
|
|
30
|
-
cleanedWindowTitle.includes(cleanedApplicationName));
|
|
31
|
-
};
|
|
32
|
-
const focusBrowser = async ({ applicationName, pageTitle, }) => {
|
|
33
|
-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.reportTitle);
|
|
34
|
-
let windowTitle = await nvdaPlaywright.lastSpokenPhrase();
|
|
35
|
-
if (hasFocus({ applicationName, pageTitle, windowTitle })) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
let applicationSwitchRetryCount = 0;
|
|
39
|
-
while (applicationSwitchRetryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
|
|
40
|
-
applicationSwitchRetryCount++;
|
|
41
|
-
await nvdaPlaywright.perform(SWITCH_APPLICATION, { capture: false });
|
|
42
|
-
await (0, delay_1.delay)(500);
|
|
43
|
-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.reportTitle);
|
|
44
|
-
windowTitle = await nvdaPlaywright.lastSpokenPhrase();
|
|
45
|
-
if (hasFocus({ applicationName, pageTitle, windowTitle })) {
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
9
|
/**
|
|
51
10
|
* These tests extend the default Playwright environment that launches the
|
|
52
11
|
* browser with a running instance of the NVDA screen reader for Windows.
|
|
@@ -59,53 +18,14 @@ exports.nvdaTest = test_1.test.extend({
|
|
|
59
18
|
try {
|
|
60
19
|
const applicationName = applicationNameMap_1.applicationNameMap[browserName];
|
|
61
20
|
if (!applicationName) {
|
|
62
|
-
throw new Error(`Browser ${browserName} is not
|
|
21
|
+
throw new Error(`Browser ${browserName} is not recognised.`);
|
|
63
22
|
}
|
|
64
|
-
nvdaPlaywright.navigateToWebContent =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.exitFocusMode, { capture: false });
|
|
71
|
-
await (0, delay_1.delay)(100);
|
|
72
|
-
// Ensure application is brought to front and focused.
|
|
73
|
-
const pageTitle = await page.title();
|
|
74
|
-
await focusBrowser({ applicationName, pageTitle });
|
|
75
|
-
// Ensure the document is ready and focused.
|
|
76
|
-
await page.bringToFront();
|
|
77
|
-
await page.locator("body").waitFor();
|
|
78
|
-
await page.locator("body").focus();
|
|
79
|
-
await page.locator("body").click();
|
|
80
|
-
await page.locator("body").blur();
|
|
81
|
-
// NVDA appears to not work well with Firefox when switching between
|
|
82
|
-
// applications resulting in the entire browser window having NVDA focus
|
|
83
|
-
// with focus mode.
|
|
84
|
-
//
|
|
85
|
-
// One workaround is to tab to the next focusable item. From there we can
|
|
86
|
-
// toggle into (yes although we are already in it...) focus mode and back
|
|
87
|
-
// out. In case this ever transpires to not happen as expect, we then ensure
|
|
88
|
-
// we exit focus mode and move NVDA to the top of the page.
|
|
89
|
-
//
|
|
90
|
-
// REF: https://github.com/nvaccess/nvda/issues/5758
|
|
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 });
|
|
108
|
-
};
|
|
23
|
+
nvdaPlaywright.navigateToWebContent = ({ capture } = {}) => (0, navigateNVDAtoWebContent_1.navigateNVDAtoWebContent)({
|
|
24
|
+
nvda: nvdaPlaywright,
|
|
25
|
+
page,
|
|
26
|
+
applicationName,
|
|
27
|
+
capture,
|
|
28
|
+
});
|
|
109
29
|
await nvdaPlaywright.start(nvdaStartOptions);
|
|
110
30
|
await use(nvdaPlaywright);
|
|
111
31
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type CommandOptions, type Orca, type StartOptions } from "@guidepup/guidepup";
|
|
2
|
+
/**
|
|
3
|
+
* [API Reference](https://www.guidepup.dev/docs/api/class-orca)
|
|
4
|
+
*
|
|
5
|
+
* This object can be used to launch and control Orca.
|
|
6
|
+
*
|
|
7
|
+
* Here's a typical example:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { unstable_orca } from "@guidepup/guidepup";
|
|
11
|
+
*
|
|
12
|
+
* (async () => {
|
|
13
|
+
* // Start Orca.
|
|
14
|
+
* await unstable_orca.start();
|
|
15
|
+
*
|
|
16
|
+
* // Move to the next item.
|
|
17
|
+
* await unstable_orca.next();
|
|
18
|
+
*
|
|
19
|
+
* // ... perform some commands.
|
|
20
|
+
*
|
|
21
|
+
* // Stop Orca.
|
|
22
|
+
* await unstable_orca.stop();
|
|
23
|
+
* })();
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface OrcaPlaywright extends Orca {
|
|
27
|
+
/**
|
|
28
|
+
* Guidepup Playwright specific command that navigates Orca to the beginning
|
|
29
|
+
* of the browser's web content.
|
|
30
|
+
*
|
|
31
|
+
* This command should be used after a page navigation has completed.
|
|
32
|
+
*/
|
|
33
|
+
navigateToWebContent(options?: Pick<CommandOptions, "capture">): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* These tests extend the default Playwright environment that launches the
|
|
37
|
+
* browser with a running instance of the Orca screen reader for macOS.
|
|
38
|
+
*
|
|
39
|
+
* A fresh started Orca instance `orca` is provided to each test.
|
|
40
|
+
*/
|
|
41
|
+
export declare const orcaTest: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
42
|
+
/**
|
|
43
|
+
* [API Reference](https://www.guidepup.dev/docs/api/class-orca)
|
|
44
|
+
*
|
|
45
|
+
* This object can be used to launch and control Orca.
|
|
46
|
+
*
|
|
47
|
+
* Here's a typical example:
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* import { unstable_orca } from "@guidepup/guidepup";
|
|
51
|
+
*
|
|
52
|
+
* (async () => {
|
|
53
|
+
* // Start Orca.
|
|
54
|
+
* await unstable_orca.start();
|
|
55
|
+
*
|
|
56
|
+
* // Move to the next item.
|
|
57
|
+
* await unstable_orca.next();
|
|
58
|
+
*
|
|
59
|
+
* // ... perform some commands.
|
|
60
|
+
*
|
|
61
|
+
* // Stop Orca.
|
|
62
|
+
* await unstable_orca.stop();
|
|
63
|
+
* })();
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
orca: OrcaPlaywright;
|
|
67
|
+
/**
|
|
68
|
+
* [API Reference](https://www.guidepup.dev/docs/api/class-start-options)
|
|
69
|
+
*
|
|
70
|
+
* Options to start Orca with, see also [unstable_orca.start([options])](https://www.guidepup.dev/docs/api/class-orca#unstable-orca-start).
|
|
71
|
+
*/
|
|
72
|
+
orcaStartOptions: StartOptions;
|
|
73
|
+
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
package/lib/orcaTest.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.orcaTest = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const guidepup_1 = require("@guidepup/guidepup");
|
|
6
|
+
const navigateOrcaToWebContent_1 = require("./navigateOrcaToWebContent");
|
|
7
|
+
const orcaPlaywright = guidepup_1.unstable_orca;
|
|
8
|
+
/**
|
|
9
|
+
* These tests extend the default Playwright environment that launches the
|
|
10
|
+
* browser with a running instance of the Orca screen reader for macOS.
|
|
11
|
+
*
|
|
12
|
+
* A fresh started Orca instance `orca` is provided to each test.
|
|
13
|
+
*/
|
|
14
|
+
exports.orcaTest = test_1.test.extend({
|
|
15
|
+
orcaStartOptions: { capture: "initial" },
|
|
16
|
+
orca: async ({ page, orcaStartOptions }, use) => {
|
|
17
|
+
try {
|
|
18
|
+
orcaPlaywright.navigateToWebContent = ({ capture } = {}) => (0, navigateOrcaToWebContent_1.navigateOrcaToWebContent)({
|
|
19
|
+
orca: orcaPlaywright,
|
|
20
|
+
page,
|
|
21
|
+
capture,
|
|
22
|
+
});
|
|
23
|
+
await orcaPlaywright.start(orcaStartOptions);
|
|
24
|
+
await use(orcaPlaywright);
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
try {
|
|
28
|
+
await orcaPlaywright.stop();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// swallow stop failure
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -5,6 +5,8 @@ type Prettify<T> = {
|
|
|
5
5
|
type CaptureCommandOptions = Prettify<Pick<CommandOptions, "capture">>;
|
|
6
6
|
type CaptureStartOptions = Prettify<Pick<StartOptions, "capture" | "settings">>;
|
|
7
7
|
/**
|
|
8
|
+
* [API Reference](https://www.guidepup.dev/docs/api/class-screenreader)
|
|
9
|
+
*
|
|
8
10
|
* This object can be used to launch and control the default screen reader for
|
|
9
11
|
* the environment.
|
|
10
12
|
*
|
|
@@ -46,6 +48,8 @@ export interface ScreenReaderPlaywright extends ScreenReader {
|
|
|
46
48
|
*/
|
|
47
49
|
export declare const screenReaderTest: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions & {
|
|
48
50
|
/**
|
|
51
|
+
* [API Reference](https://www.guidepup.dev/docs/api/class-screenreader)
|
|
52
|
+
*
|
|
49
53
|
* This object can be used to launch and control the default screen reader for
|
|
50
54
|
* the environment.
|
|
51
55
|
*
|
|
@@ -70,7 +74,9 @@ export declare const screenReaderTest: import("@playwright/test").TestType<impor
|
|
|
70
74
|
*/
|
|
71
75
|
screenReader: ScreenReaderPlaywright;
|
|
72
76
|
/**
|
|
73
|
-
*
|
|
77
|
+
* [API Reference](https://www.guidepup.dev/docs/api/class-start-options)
|
|
78
|
+
*
|
|
79
|
+
* Options to start the default screen reader with, see also [screenReader.start([options])](https://www.guidepup.dev/docs/api/class-screenreader#screenreader-start).
|
|
74
80
|
*/
|
|
75
81
|
screenReaderStartOptions: CaptureStartOptions;
|
|
76
82
|
}, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
|
package/lib/screenReaderTest.js
CHANGED
|
@@ -4,90 +4,10 @@ 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
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
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 closeMenus = async ({ screenReaderPlaywright, }) => {
|
|
52
|
-
await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Escape }, { capture: false });
|
|
53
|
-
};
|
|
54
|
-
const cancelCurrentInteraction = async ({ screenReaderPlaywright, }) => {
|
|
55
|
-
await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
|
|
56
|
-
};
|
|
57
|
-
const openItemChooser = async ({ screenReaderPlaywright, }) => {
|
|
58
|
-
let lastSpokenPhrase = "";
|
|
59
|
-
while (!lastSpokenPhrase.toLowerCase().includes("item chooser")) {
|
|
60
|
-
await cancelCurrentInteraction({ screenReaderPlaywright });
|
|
61
|
-
await (0, delay_1.delay)(100);
|
|
62
|
-
await closeMenus({ screenReaderPlaywright });
|
|
63
|
-
await (0, delay_1.delay)(100);
|
|
64
|
-
await screenReaderPlaywright.perform(guidepup_1.voiceOverKeyCodeCommands.openItemChooser, { capture: true });
|
|
65
|
-
lastSpokenPhrase = await screenReaderPlaywright.lastSpokenPhrase();
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
async function selectWebContentItem({ screenReaderPlaywright, }) {
|
|
69
|
-
// eslint-disable-next-line no-constant-condition
|
|
70
|
-
while (true) {
|
|
71
|
-
let matched = false;
|
|
72
|
-
for (const character of "web content") {
|
|
73
|
-
await cancelCurrentInteraction({ screenReaderPlaywright });
|
|
74
|
-
await (0, delay_1.delay)(100);
|
|
75
|
-
await screenReaderPlaywright.type(character, { capture: "initial" });
|
|
76
|
-
const lastSpokenPhrase = await screenReaderPlaywright.lastSpokenPhrase();
|
|
77
|
-
if (lastSpokenPhrase.toLowerCase().includes("web content")) {
|
|
78
|
-
matched = true;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (matched) {
|
|
83
|
-
break;
|
|
84
|
-
}
|
|
85
|
-
// Clear the entire Item Chooser search.
|
|
86
|
-
await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Backspace }, { capture: false });
|
|
87
|
-
await (0, delay_1.delay)(100);
|
|
88
|
-
}
|
|
89
|
-
await screenReaderPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
|
|
90
|
-
}
|
|
7
|
+
const navigateNVDAtoWebContent_1 = require("./navigateNVDAtoWebContent");
|
|
8
|
+
const navigateOrcaToWebContent_1 = require("./navigateOrcaToWebContent");
|
|
9
|
+
const navigateVoiceOverToWebContent_1 = require("./navigateVoiceOverToWebContent");
|
|
10
|
+
const applicationIdMap_1 = require("./applicationIdMap");
|
|
91
11
|
const screenReaderPlaywright = guidepup_1.screenReader;
|
|
92
12
|
/**
|
|
93
13
|
* These tests extend the default Playwright environment that launches the
|
|
@@ -102,124 +22,32 @@ exports.screenReaderTest = test_1.test.extend({
|
|
|
102
22
|
screenReader: async ({ browserName, page, screenReaderStartOptions }, use) => {
|
|
103
23
|
try {
|
|
104
24
|
const applicationName = applicationNameMap_1.applicationNameMap[browserName];
|
|
105
|
-
|
|
106
|
-
|
|
25
|
+
const applicationId = applicationIdMap_1.applicationIdMap[browserName];
|
|
26
|
+
if (!applicationName || !applicationId) {
|
|
27
|
+
throw new Error(`Browser ${browserName} is not recognised.`);
|
|
107
28
|
}
|
|
108
29
|
if (guidepup_1.nvda.default()) {
|
|
109
|
-
screenReaderPlaywright.navigateToWebContent =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
];
|
|
116
|
-
// Make sure NVDA is not in focus mode.
|
|
117
|
-
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode, { capture: false });
|
|
118
|
-
await (0, delay_1.delay)(100);
|
|
119
|
-
// Ensure application is brought to front and focused.
|
|
120
|
-
const pageTitle = await page.title();
|
|
121
|
-
await focusBrowser({ applicationName, pageTitle });
|
|
122
|
-
// Ensure the document is ready and focused.
|
|
123
|
-
await page.bringToFront();
|
|
124
|
-
await page.locator("body").waitFor();
|
|
125
|
-
await page.locator("body").focus();
|
|
126
|
-
await page.locator("body").click();
|
|
127
|
-
await page.locator("body").blur();
|
|
128
|
-
// NVDA appears to not work well with Firefox when switching between
|
|
129
|
-
// applications resulting in the entire browser window having NVDA focus
|
|
130
|
-
// with focus mode.
|
|
131
|
-
//
|
|
132
|
-
// One workaround is to tab to the next focusable item. From there we can
|
|
133
|
-
// toggle into (yes although we are already in it...) focus mode and back
|
|
134
|
-
// out. In case this ever transpires to not happen as expect, we then ensure
|
|
135
|
-
// we exit focus mode and move NVDA to the top of the page.
|
|
136
|
-
//
|
|
137
|
-
// REF: https://github.com/nvaccess/nvda/issues/5758
|
|
138
|
-
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.readNextFocusableItem, { capture: false });
|
|
139
|
-
await (0, delay_1.delay)(100);
|
|
140
|
-
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode, { capture: false });
|
|
141
|
-
await (0, delay_1.delay)(100);
|
|
142
|
-
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.toggleBetweenBrowseAndFocusMode, { capture: false });
|
|
143
|
-
await (0, delay_1.delay)(100);
|
|
144
|
-
await screenReaderPlaywright.perform(guidepup_1.NVDAKeyCodeCommands.exitFocusMode, { capture: false });
|
|
145
|
-
await (0, delay_1.delay)(100);
|
|
146
|
-
await screenReaderPlaywright.clearSpokenPhraseLog();
|
|
147
|
-
await screenReaderPlaywright.clearItemTextLog();
|
|
148
|
-
const spokenPhraseLog = await screenReaderPlaywright.spokenPhraseLog();
|
|
149
|
-
const itemTextLog = await screenReaderPlaywright.itemTextLog();
|
|
150
|
-
spokenPhraseLog.push(...currentSpokenPhraseLog);
|
|
151
|
-
itemTextLog.push(...currentItemTextLog);
|
|
152
|
-
// Navigate to the beginning of the web content, using chosen capture
|
|
153
|
-
// settings, so don't miss announcing the first item on the page.
|
|
154
|
-
await screenReaderPlaywright.perform(MOVE_TO_TOP, { capture });
|
|
155
|
-
};
|
|
30
|
+
screenReaderPlaywright.navigateToWebContent = ({ capture } = {}) => (0, navigateNVDAtoWebContent_1.navigateNVDAtoWebContent)({
|
|
31
|
+
nvda: guidepup_1.nvda,
|
|
32
|
+
page,
|
|
33
|
+
applicationName,
|
|
34
|
+
capture,
|
|
35
|
+
});
|
|
156
36
|
}
|
|
157
37
|
else if (guidepup_1.voiceOver.default()) {
|
|
158
|
-
screenReaderPlaywright.navigateToWebContent =
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
await page.bringToFront();
|
|
172
|
-
await page.locator("body").waitFor();
|
|
173
|
-
try {
|
|
174
|
-
// Add an interactive marker to the page that will force VoiceOver
|
|
175
|
-
// to listen to events emitted by Playwright interactions when
|
|
176
|
-
// navigated to.
|
|
177
|
-
await page.evaluate(() => {
|
|
178
|
-
const marker = document.createElement("input");
|
|
179
|
-
marker.id = "__guidepup_marker__";
|
|
180
|
-
marker.type = "text";
|
|
181
|
-
marker.value = "Guidepup Marker";
|
|
182
|
-
marker.readOnly = true;
|
|
183
|
-
marker.tabIndex = -1;
|
|
184
|
-
marker.autocomplete = "off";
|
|
185
|
-
marker.setAttribute("aria-label", "Guidepup Marker");
|
|
186
|
-
marker.style.cssText = `
|
|
187
|
-
position: absolute;
|
|
188
|
-
width: 1px;
|
|
189
|
-
height: 1px;
|
|
190
|
-
overflow: hidden;
|
|
191
|
-
clip: rect(0 0 0 0);
|
|
192
|
-
white-space: nowrap;
|
|
193
|
-
`;
|
|
194
|
-
document.body.prepend(marker);
|
|
195
|
-
});
|
|
196
|
-
// Open item chooser and select web content.
|
|
197
|
-
await openItemChooser({ screenReaderPlaywright });
|
|
198
|
-
await selectWebContentItem({ screenReaderPlaywright });
|
|
199
|
-
// Navigate into web content.
|
|
200
|
-
await screenReaderPlaywright.interact({ capture: false });
|
|
201
|
-
await (0, delay_1.delay)(100);
|
|
202
|
-
await screenReaderPlaywright.clearSpokenPhraseLog();
|
|
203
|
-
await screenReaderPlaywright.clearItemTextLog();
|
|
204
|
-
const spokenPhraseLog = await screenReaderPlaywright.spokenPhraseLog();
|
|
205
|
-
const itemTextLog = await screenReaderPlaywright.itemTextLog();
|
|
206
|
-
spokenPhraseLog.push(...currentSpokenPhraseLog);
|
|
207
|
-
itemTextLog.push(...currentItemTextLog);
|
|
208
|
-
// Navigate to the first element of the page using the provided
|
|
209
|
-
// capture settings.
|
|
210
|
-
await screenReaderPlaywright.next({ capture });
|
|
211
|
-
}
|
|
212
|
-
finally {
|
|
213
|
-
// Remove the temporary Guidepup marker element to restore original
|
|
214
|
-
// page structure.
|
|
215
|
-
await page.evaluate(() => {
|
|
216
|
-
const marker = document.querySelector("#__guidepup_marker__");
|
|
217
|
-
if (marker) {
|
|
218
|
-
document.body.removeChild(marker);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
};
|
|
38
|
+
screenReaderPlaywright.navigateToWebContent = ({ capture } = {}) => (0, navigateVoiceOverToWebContent_1.navigateVoiceOverToWebContent)({
|
|
39
|
+
voiceOver: guidepup_1.voiceOver,
|
|
40
|
+
page,
|
|
41
|
+
applicationId,
|
|
42
|
+
capture,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
else if (guidepup_1.unstable_orca.default()) {
|
|
46
|
+
screenReaderPlaywright.navigateToWebContent = ({ capture } = {}) => (0, navigateOrcaToWebContent_1.navigateOrcaToWebContent)({
|
|
47
|
+
orca: guidepup_1.unstable_orca,
|
|
48
|
+
page,
|
|
49
|
+
capture,
|
|
50
|
+
});
|
|
223
51
|
}
|
|
224
52
|
else {
|
|
225
53
|
throw new Error("No supported screen reader");
|
package/lib/voiceOverTest.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CommandOptions, type StartOptions, type VoiceOver } from "@guidepup/guidepup";
|
|
2
2
|
/**
|
|
3
3
|
* [API Reference](https://www.guidepup.dev/docs/api/class-voiceover)
|
|
4
4
|
*
|
|
@@ -34,7 +34,7 @@ export interface VoiceOverPlaywright extends VoiceOver {
|
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* These tests extend the default Playwright environment that launches the
|
|
37
|
-
* browser with a running instance of the VoiceOver screen reader for
|
|
37
|
+
* browser with a running instance of the VoiceOver screen reader for macOS.
|
|
38
38
|
*
|
|
39
39
|
* A fresh started VoiceOver instance `voiceOver` is provided to each test.
|
|
40
40
|
*/
|
package/lib/voiceOverTest.js
CHANGED
|
@@ -1,54 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.voiceOverTest = void 0;
|
|
4
|
-
const test_1 = require("@playwright/test");
|
|
5
4
|
const guidepup_1 = require("@guidepup/guidepup");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Escape }, { capture: false });
|
|
10
|
-
};
|
|
11
|
-
const cancelCurrentInteraction = async ({ voiceOverPlaywright, }) => {
|
|
12
|
-
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
|
|
13
|
-
};
|
|
14
|
-
const openItemChooser = async ({ voiceOverPlaywright, }) => {
|
|
15
|
-
let lastSpokenPhrase = "";
|
|
16
|
-
while (!lastSpokenPhrase.toLowerCase().includes("item chooser")) {
|
|
17
|
-
await cancelCurrentInteraction({ voiceOverPlaywright });
|
|
18
|
-
await (0, delay_1.delay)(100);
|
|
19
|
-
await closeMenus({ voiceOverPlaywright });
|
|
20
|
-
await (0, delay_1.delay)(100);
|
|
21
|
-
await voiceOverPlaywright.perform(voiceOverPlaywright.keyboardCommands.openItemChooser, { capture: true });
|
|
22
|
-
lastSpokenPhrase = await voiceOverPlaywright.lastSpokenPhrase();
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
async function selectWebContentItem({ voiceOverPlaywright, }) {
|
|
26
|
-
// eslint-disable-next-line no-constant-condition
|
|
27
|
-
while (true) {
|
|
28
|
-
let matched = false;
|
|
29
|
-
for (const character of "web content") {
|
|
30
|
-
await cancelCurrentInteraction({ voiceOverPlaywright });
|
|
31
|
-
await (0, delay_1.delay)(100);
|
|
32
|
-
await voiceOverPlaywright.type(character, { capture: "initial" });
|
|
33
|
-
const lastSpokenPhrase = await voiceOverPlaywright.lastSpokenPhrase();
|
|
34
|
-
if (lastSpokenPhrase.toLowerCase().includes("web content")) {
|
|
35
|
-
matched = true;
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (matched) {
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
// Clear the entire Item Chooser search.
|
|
43
|
-
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Backspace }, { capture: false });
|
|
44
|
-
await (0, delay_1.delay)(100);
|
|
45
|
-
}
|
|
46
|
-
await voiceOverPlaywright.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
|
|
47
|
-
}
|
|
5
|
+
const applicationIdMap_1 = require("./applicationIdMap");
|
|
6
|
+
const navigateVoiceOverToWebContent_1 = require("./navigateVoiceOverToWebContent");
|
|
7
|
+
const test_1 = require("@playwright/test");
|
|
48
8
|
const voiceOverPlaywright = guidepup_1.voiceOver;
|
|
49
9
|
/**
|
|
50
10
|
* These tests extend the default Playwright environment that launches the
|
|
51
|
-
* browser with a running instance of the VoiceOver screen reader for
|
|
11
|
+
* browser with a running instance of the VoiceOver screen reader for macOS.
|
|
52
12
|
*
|
|
53
13
|
* A fresh started VoiceOver instance `voiceOver` is provided to each test.
|
|
54
14
|
*/
|
|
@@ -56,75 +16,16 @@ exports.voiceOverTest = test_1.test.extend({
|
|
|
56
16
|
voiceOverStartOptions: { capture: "initial" },
|
|
57
17
|
voiceOver: async ({ browserName, page, voiceOverStartOptions }, use) => {
|
|
58
18
|
try {
|
|
59
|
-
const
|
|
60
|
-
if (!
|
|
61
|
-
throw new Error(`Browser ${browserName} is not
|
|
19
|
+
const applicationId = applicationIdMap_1.applicationIdMap[browserName];
|
|
20
|
+
if (!applicationId) {
|
|
21
|
+
throw new Error(`Browser ${browserName} is not recognised.`);
|
|
62
22
|
}
|
|
63
|
-
voiceOverPlaywright.navigateToWebContent =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
];
|
|
70
|
-
// Ensure application is brought to front and focused.
|
|
71
|
-
await (0, guidepup_1.macOSActivate)(applicationName);
|
|
72
|
-
// Cancel automatic behaviours/previous commands.
|
|
73
|
-
await cancelCurrentInteraction({ voiceOverPlaywright });
|
|
74
|
-
await (0, delay_1.delay)(100);
|
|
75
|
-
// Ensure the document is ready and focused.
|
|
76
|
-
await page.bringToFront();
|
|
77
|
-
await page.locator("body").waitFor();
|
|
78
|
-
try {
|
|
79
|
-
// Add an interactive marker to the page that will force VoiceOver
|
|
80
|
-
// to listen to events emitted by Playwright interactions when
|
|
81
|
-
// navigated to.
|
|
82
|
-
await page.evaluate(() => {
|
|
83
|
-
const marker = document.createElement("input");
|
|
84
|
-
marker.id = "__guidepup_marker__";
|
|
85
|
-
marker.type = "text";
|
|
86
|
-
marker.value = "Guidepup Marker";
|
|
87
|
-
marker.readOnly = true;
|
|
88
|
-
marker.tabIndex = -1;
|
|
89
|
-
marker.autocomplete = "off";
|
|
90
|
-
marker.setAttribute("aria-label", "Guidepup Marker");
|
|
91
|
-
marker.style.cssText = `
|
|
92
|
-
position: absolute;
|
|
93
|
-
width: 1px;
|
|
94
|
-
height: 1px;
|
|
95
|
-
overflow: hidden;
|
|
96
|
-
clip: rect(0 0 0 0);
|
|
97
|
-
white-space: nowrap;
|
|
98
|
-
`;
|
|
99
|
-
document.body.prepend(marker);
|
|
100
|
-
});
|
|
101
|
-
// Open item chooser and select web content.
|
|
102
|
-
await openItemChooser({ voiceOverPlaywright });
|
|
103
|
-
await selectWebContentItem({ voiceOverPlaywright });
|
|
104
|
-
// Navigate into web content.
|
|
105
|
-
await voiceOverPlaywright.interact({ capture: false });
|
|
106
|
-
await (0, delay_1.delay)(100);
|
|
107
|
-
await voiceOverPlaywright.clearSpokenPhraseLog();
|
|
108
|
-
await voiceOverPlaywright.clearItemTextLog();
|
|
109
|
-
const spokenPhraseLog = await voiceOverPlaywright.spokenPhraseLog();
|
|
110
|
-
const itemTextLog = await voiceOverPlaywright.itemTextLog();
|
|
111
|
-
spokenPhraseLog.push(...currentSpokenPhraseLog);
|
|
112
|
-
itemTextLog.push(...currentItemTextLog);
|
|
113
|
-
// Navigate to the first element of the page using the provided
|
|
114
|
-
// capture settings.
|
|
115
|
-
await voiceOverPlaywright.next({ capture });
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
// Remove the temporary Guidepup marker element to restore original
|
|
119
|
-
// page structure.
|
|
120
|
-
await page.evaluate(() => {
|
|
121
|
-
const marker = document.querySelector("#__guidepup_marker__");
|
|
122
|
-
if (marker) {
|
|
123
|
-
document.body.removeChild(marker);
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
};
|
|
23
|
+
voiceOverPlaywright.navigateToWebContent = ({ capture } = {}) => (0, navigateVoiceOverToWebContent_1.navigateVoiceOverToWebContent)({
|
|
24
|
+
voiceOver: voiceOverPlaywright,
|
|
25
|
+
page,
|
|
26
|
+
applicationId,
|
|
27
|
+
capture,
|
|
28
|
+
});
|
|
128
29
|
await voiceOverPlaywright.start(voiceOverStartOptions);
|
|
129
30
|
await use(voiceOverPlaywright);
|
|
130
31
|
}
|