Skip to content

Commit 424e727

Browse files
committed
fix: handle windows browser focus loss better
1 parent 09213ce commit 424e727

File tree

5 files changed

+77
-50
lines changed

5 files changed

+77
-50
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,8 @@
55
},
66
"[yaml]": {
77
"editor.defaultFormatter": "esbenp.prettier-vscode"
8+
},
9+
"[typescript]": {
10+
"editor.defaultFormatter": "esbenp.prettier-vscode"
811
}
912
}

README.md

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,49 @@ export default config;
7373

7474
Check out the configuration this adds [in the `config.ts`` file](./src/config.ts).
7575

76-
### VoiceOver
76+
### Web Content Navigation
77+
78+
In addition to the Guidepup APIs the `voiceOver` and `nvda` instances provided by the Guidepup Playwright setup have an additional utility method `.navigateToWebContent()`.
79+
80+
This method will navigate the screen reader to the first element of the document body in the browser.
81+
82+
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 with NVDA:
83+
84+
```ts
85+
// Navigate to the desired page
86+
await page.goto("https://github.com/guidepup/guidepup", {
87+
waitUntil: "load",
88+
});
89+
90+
// Wait for page to be ready
91+
await page.locator('header[role="banner"]').waitFor();
92+
93+
// Navigate to the web content
94+
await nvda.navigateToWebContent();
95+
96+
// ... some commands
97+
```
98+
99+
**Note:** This command clears all logs meaning `.spokenPhraseLog()` and `.itemTextLog()` are emptied. If logs from prior to the command are required, first store the logs in a variable for later use:
100+
101+
```ts
102+
// ... some commands
103+
104+
// Store spoken phrases
105+
const spokenPhrases = await nvda.spokenPhraseLog();
106+
107+
// Navigate to the web content
108+
await nvda.navigateToWebContent();
109+
110+
// ... some commands
111+
112+
// Collect all spoken phrasees
113+
const allSpokenPhrases = [...spokenPhrases, ...(await nvda.spokenPhraseLog())];
114+
115+
// ... do something with spoken phrases
116+
```
117+
118+
### VoiceOver Example
77119

78120
`playwright.config.ts`:
79121

@@ -131,7 +173,7 @@ test.describe("Playwright VoiceOver", () => {
131173
});
132174
```
133175

134-
### NVDA
176+
### NVDA Example
135177

136178
`playwright.config.ts`:
137179

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@guidepup/playwright",
3-
"version": "0.13.0",
3+
"version": "0.13.1",
44
"description": "Screen reader driver for Playwright tests.",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",

src/nvdaTest.ts

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Page, test } from "@playwright/test";
1+
import { test } from "@playwright/test";
22
import { nvda, WindowsKeyCodes, WindowsModifiers } from "@guidepup/guidepup";
33
import type { NVDA } from "@guidepup/guidepup";
44
import { applicationNameMap } from "./applicationNameMap";
@@ -46,12 +46,15 @@ const SWITCH_APPLICATION = {
4646
modifiers: [WindowsModifiers.Alt],
4747
};
4848

49+
const MOVE_TO_TOP = {
50+
keyCode: [WindowsKeyCodes.Home],
51+
modifiers: [WindowsModifiers.Control],
52+
};
53+
4954
const focusBrowser = async ({
5055
applicationName,
51-
page,
5256
}: {
5357
applicationName: string;
54-
page: Page;
5558
}) => {
5659
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.reportTitle);
5760
let windowTitle = await nvdaPlaywright.lastSpokenPhrase();
@@ -60,11 +63,6 @@ const focusBrowser = async ({
6063
return;
6164
}
6265

63-
// Firefox has a bug with NVDA where NVDA gets stuck in focus mode if
64-
// Firefox is the currently focused application.
65-
// REF: https://github.com/nvaccess/nvda/issues/5758
66-
// We swap to a different application, restart NVDA, and then switch back.
67-
6866
let applicationSwitchRetryCount = 0;
6967

7068
while (applicationSwitchRetryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
@@ -74,32 +72,7 @@ const focusBrowser = async ({
7472
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.reportTitle);
7573
windowTitle = await nvdaPlaywright.lastSpokenPhrase();
7674

77-
if (!windowTitle.includes(applicationName)) {
78-
break;
79-
}
80-
}
81-
82-
await nvdaPlaywright.stop();
83-
await nvdaPlaywright.start();
84-
await page.bringToFront();
85-
86-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.reportTitle);
87-
windowTitle = await nvdaPlaywright.lastSpokenPhrase();
88-
89-
if (windowTitle.includes(applicationName)) {
90-
return;
91-
}
92-
93-
applicationSwitchRetryCount = 0;
94-
95-
while (applicationSwitchRetryCount < MAX_APPLICATION_SWITCH_RETRY_COUNT) {
96-
applicationSwitchRetryCount++;
97-
98-
await nvdaPlaywright.perform(SWITCH_APPLICATION);
99-
await nvdaPlaywright.perform(nvdaPlaywright.keyboardCommands.reportTitle);
100-
windowTitle = await nvdaPlaywright.lastSpokenPhrase();
101-
102-
if (!windowTitle.includes(applicationName)) {
75+
if (windowTitle.includes(applicationName)) {
10376
break;
10477
}
10578
}
@@ -152,21 +125,33 @@ export const nvdaTest = test.extend<{
152125
await nvdaPlaywright.perform(
153126
nvdaPlaywright.keyboardCommands.exitFocusMode
154127
);
155-
await nvdaPlaywright.lastSpokenPhrase();
156128

157129
// Ensure application is brought to front and focused.
158-
await focusBrowser({ applicationName, page });
159-
160-
// Make sure NVDA is not in focus mode.
130+
await focusBrowser({ applicationName });
131+
132+
// NVDA appears to not work well with Firefox when switching between
133+
// applications resulting in the entire browser window having NVDA focus
134+
// with focus mode.
135+
//
136+
// One workaround is to tab to the next focusable item. From there we can
137+
// toggle into (yes although we are already in it...) focus mode and back
138+
// out. In case this ever transpires to not happen as expect, we then ensure
139+
// we exit focus mode and move NVDA to the top of the page.
140+
//
141+
// REF: https://github.com/nvaccess/nvda/issues/5758
142+
await nvdaPlaywright.perform(
143+
nvdaPlaywright.keyboardCommands.readNextFocusableItem
144+
);
145+
await nvdaPlaywright.perform(
146+
nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode
147+
);
148+
await nvdaPlaywright.perform(
149+
nvdaPlaywright.keyboardCommands.toggleBetweenBrowseAndFocusMode
150+
);
161151
await nvdaPlaywright.perform(
162152
nvdaPlaywright.keyboardCommands.exitFocusMode
163153
);
164-
await nvdaPlaywright.lastSpokenPhrase();
165-
166-
// Ensure the document is ready and focused.
167-
await page.bringToFront();
168-
await page.locator("body").waitFor();
169-
await page.locator("body").focus();
154+
await nvdaPlaywright.perform(MOVE_TO_TOP);
170155

171156
// Clear out logs.
172157
await nvdaPlaywright.clearItemTextLog();

src/voiceOverTest.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,9 @@ export const voiceOverTest = test.extend<{
9494

9595
// Navigate to the beginning of the web content.
9696
await voiceOverPlaywright.interact();
97-
await voiceOverPlaywright.lastSpokenPhrase();
98-
9997
await voiceOverPlaywright.perform(
10098
voiceOverPlaywright.keyboardCommands.jumpToLeftEdge
10199
);
102-
await voiceOverPlaywright.lastSpokenPhrase();
103100

104101
// Clear out logs.
105102
await voiceOverPlaywright.clearItemTextLog();

0 commit comments

Comments
 (0)