@guidepup/playwright 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -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 -165
- package/lib/voiceOverTest.d.ts +2 -2
- package/lib/voiceOverTest.js +13 -79
- package/lib/withGuidepup.d.ts +2 -0
- package/lib/withGuidepup.js +21 -0
- package/package.json +10 -7
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.navigateVoiceOverToWebContent = void 0;
|
|
4
|
+
const guidepup_1 = require("@guidepup/guidepup");
|
|
5
|
+
const delay_1 = require("./delay");
|
|
6
|
+
const MAX_WEB_CONTENT_SEARCH_ATTEMPTS = 10;
|
|
7
|
+
const cancelCurrentInteraction = async (voiceOver) => {
|
|
8
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Control }, { capture: false });
|
|
9
|
+
};
|
|
10
|
+
const closeMenus = async (voiceOver) => {
|
|
11
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Escape }, { capture: false });
|
|
12
|
+
};
|
|
13
|
+
const openItemChooser = async (voiceOver) => {
|
|
14
|
+
let lastSpokenPhrase = "";
|
|
15
|
+
while (!lastSpokenPhrase.toLowerCase().includes("item chooser")) {
|
|
16
|
+
await cancelCurrentInteraction(voiceOver);
|
|
17
|
+
await (0, delay_1.delay)(100);
|
|
18
|
+
await closeMenus(voiceOver);
|
|
19
|
+
await (0, delay_1.delay)(100);
|
|
20
|
+
await voiceOver.perform(guidepup_1.voiceOverKeyCodeCommands.openItemChooser, {
|
|
21
|
+
capture: true,
|
|
22
|
+
});
|
|
23
|
+
lastSpokenPhrase = await voiceOver.lastSpokenPhrase();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const selectWebContentItem = async (voiceOver) => {
|
|
27
|
+
let matched = false;
|
|
28
|
+
for (let attempt = 0; attempt < MAX_WEB_CONTENT_SEARCH_ATTEMPTS && !matched; attempt++) {
|
|
29
|
+
for (const character of "web content") {
|
|
30
|
+
await cancelCurrentInteraction(voiceOver);
|
|
31
|
+
await (0, delay_1.delay)(100);
|
|
32
|
+
await voiceOver.type(character, { capture: "initial" });
|
|
33
|
+
const lastSpokenPhrase = await voiceOver.lastSpokenPhrase();
|
|
34
|
+
if (lastSpokenPhrase.toLowerCase().includes("web content")) {
|
|
35
|
+
matched = true;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (matched) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
if (attempt + 1 < MAX_WEB_CONTENT_SEARCH_ATTEMPTS) {
|
|
43
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Backspace }, { capture: false });
|
|
44
|
+
await (0, delay_1.delay)(100);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!matched) {
|
|
48
|
+
throw new Error(`Unable to find "web content" in the VoiceOver Item Chooser after ${MAX_WEB_CONTENT_SEARCH_ATTEMPTS} attempts.`);
|
|
49
|
+
}
|
|
50
|
+
await voiceOver.perform({ keyCode: guidepup_1.MacOSKeyCodes.Enter }, { capture: false });
|
|
51
|
+
};
|
|
52
|
+
const navigateVoiceOverToWebContent = async ({ voiceOver, page, applicationId, capture, }) => {
|
|
53
|
+
const currentSpokenPhraseLog = [...(await voiceOver.spokenPhraseLog())];
|
|
54
|
+
const currentItemTextLog = [...(await voiceOver.itemTextLog())];
|
|
55
|
+
await (0, guidepup_1.macOSActivateId)(applicationId);
|
|
56
|
+
await cancelCurrentInteraction(voiceOver);
|
|
57
|
+
await (0, delay_1.delay)(100);
|
|
58
|
+
await page.bringToFront();
|
|
59
|
+
await page.locator("body").waitFor();
|
|
60
|
+
try {
|
|
61
|
+
await page.evaluate(() => {
|
|
62
|
+
const marker = document.createElement("input");
|
|
63
|
+
marker.id = "__guidepup_marker__";
|
|
64
|
+
marker.type = "text";
|
|
65
|
+
marker.value = "Guidepup Marker";
|
|
66
|
+
marker.readOnly = true;
|
|
67
|
+
marker.tabIndex = -1;
|
|
68
|
+
marker.autocomplete = "off";
|
|
69
|
+
marker.setAttribute("aria-label", "Guidepup Marker");
|
|
70
|
+
marker.style.cssText = `
|
|
71
|
+
position: absolute;
|
|
72
|
+
width: 1px;
|
|
73
|
+
height: 1px;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
clip: rect(0 0 0 0);
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
`;
|
|
78
|
+
document.body.prepend(marker);
|
|
79
|
+
});
|
|
80
|
+
await openItemChooser(voiceOver);
|
|
81
|
+
await selectWebContentItem(voiceOver);
|
|
82
|
+
await voiceOver.interact({ capture: false });
|
|
83
|
+
await (0, delay_1.delay)(100);
|
|
84
|
+
await voiceOver.clearSpokenPhraseLog();
|
|
85
|
+
await voiceOver.clearItemTextLog();
|
|
86
|
+
const spokenPhraseLog = await voiceOver.spokenPhraseLog();
|
|
87
|
+
const itemTextLog = await voiceOver.itemTextLog();
|
|
88
|
+
spokenPhraseLog.push(...currentSpokenPhraseLog);
|
|
89
|
+
itemTextLog.push(...currentItemTextLog);
|
|
90
|
+
await voiceOver.next({ capture });
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
await page.evaluate(() => {
|
|
94
|
+
const marker = document.querySelector("#__guidepup_marker__");
|
|
95
|
+
if (marker) {
|
|
96
|
+
document.body.removeChild(marker);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
exports.navigateVoiceOverToWebContent = navigateVoiceOverToWebContent;
|
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,50 +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
|
-
};
|
|
7
|
+
const navigateNVDAtoWebContent_1 = require("./navigateNVDAtoWebContent");
|
|
8
|
+
const navigateOrcaToWebContent_1 = require("./navigateOrcaToWebContent");
|
|
9
|
+
const navigateVoiceOverToWebContent_1 = require("./navigateVoiceOverToWebContent");
|
|
10
|
+
const applicationIdMap_1 = require("./applicationIdMap");
|
|
51
11
|
const screenReaderPlaywright = guidepup_1.screenReader;
|
|
52
12
|
/**
|
|
53
13
|
* These tests extend the default Playwright environment that launches the
|
|
@@ -62,131 +22,32 @@ exports.screenReaderTest = test_1.test.extend({
|
|
|
62
22
|
screenReader: async ({ browserName, page, screenReaderStartOptions }, use) => {
|
|
63
23
|
try {
|
|
64
24
|
const applicationName = applicationNameMap_1.applicationNameMap[browserName];
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
const applicationId = applicationIdMap_1.applicationIdMap[browserName];
|
|
26
|
+
if (!applicationName || !applicationId) {
|
|
27
|
+
throw new Error(`Browser ${browserName} is not recognised.`);
|
|
67
28
|
}
|
|
68
29
|
if (guidepup_1.nvda.default()) {
|
|
69
|
-
screenReaderPlaywright.navigateToWebContent =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
};
|
|
30
|
+
screenReaderPlaywright.navigateToWebContent = ({ capture } = {}) => (0, navigateNVDAtoWebContent_1.navigateNVDAtoWebContent)({
|
|
31
|
+
nvda: guidepup_1.nvda,
|
|
32
|
+
page,
|
|
33
|
+
applicationName,
|
|
34
|
+
capture,
|
|
35
|
+
});
|
|
116
36
|
}
|
|
117
37
|
else if (guidepup_1.voiceOver.default()) {
|
|
118
|
-
screenReaderPlaywright.navigateToWebContent =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
});
|
|
188
|
-
}
|
|
189
|
-
};
|
|
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
|
+
});
|
|
190
51
|
}
|
|
191
52
|
else {
|
|
192
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
|
*/
|