Skip to content

Commit 7c3ac63

Browse files
authored
feat!: OS agnostic screen reader instance (#40)
1 parent 05efea5 commit 7c3ac63

18 files changed

Lines changed: 471 additions & 27 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Test Screen Reader
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test-screenreader:
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [macos-14, macos-15, macos-26, windows-2022, windows-2025]
19+
browser: [chromium]
20+
steps:
21+
- uses: actions/checkout@v6
22+
- uses: actions/setup-node@v6
23+
with:
24+
node-version-file: .nvmrc
25+
- name: Guidepup Setup
26+
uses: guidepup/setup-action@0.20.0
27+
with:
28+
record: true
29+
- run: yarn install --frozen-lockfile
30+
- run: yarn pretest
31+
- run: yarn test:screenreader:${{ matrix.browser }}
32+
- uses: actions/upload-artifact@v4
33+
if: always()
34+
continue-on-error: true
35+
with:
36+
name: artifacts-${{ matrix.os }}-${{ matrix.browser }}
37+
path: |
38+
**/test-results/**/*
39+
**/recordings/**/*
Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
export const logIncludesExpectedPhrases = (
22
log: string[],
3-
expectedPhrases: string[]
3+
expectedPhrases: string[],
44
) => {
55
const failures: string[] = [];
66

77
for (const expectedPhrase of expectedPhrases) {
8-
const foundExpectedPhrase = !!log.find((logItem) =>
9-
logItem.includes(expectedPhrase)
8+
const foundExpectedPhrase = !!log.find(
9+
(logItem) =>
10+
logItem.includes(expectedPhrase) ||
11+
logItem.replaceAll(/\s/g, "").includes(expectedPhrase),
1012
);
1113

1214
if (!foundExpectedPhrase) {
@@ -16,7 +18,7 @@ export const logIncludesExpectedPhrases = (
1618

1719
if (failures.length) {
1820
throw new Error(
19-
`Did not find the following expected text:\n- ${failures.join("\n- ")}`
21+
`Did not find the following expected text:\n- ${failures.join("\n- ")}`,
2022
);
2123
}
2224
};

examples/playwright-nvda/tests/chromium/chromium.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { nvdaTest as test } from "../../../../src";
88
test.use({ nvdaStartOptions: { capture: "initial" } });
99

1010
test.describe("Chromium Playwright NVDA", () => {
11-
test("I can navigate the Guidepup Github page", async ({
11+
test("I can navigate the Guidepup documentation site", async ({
1212
browser,
1313
browserName,
1414
page,

examples/playwright-nvda/tests/firefox/firefox.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { nvdaTest as test } from "../../../../src";
88
test.use({ nvdaStartOptions: { capture: "initial" } });
99

1010
test.describe("Firefox Playwright VoiceOver", () => {
11-
test("I can navigate the Guidepup Github page", async ({
11+
test("I can navigate the Guidepup documentation site", async ({
1212
browser,
1313
browserName,
1414
page,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Screen Reader Example
2+
3+
An example demonstrating using Guidepup for testing the default screen reader automation with [Playwright](https://playwright.dev/).
4+
5+
Run this example's test with the following from the root directory:
6+
7+
```console
8+
npm run test
9+
```
10+
11+
> Note: please ensure you have [setup you environment](https://www.guidepup.dev/docs/guides/automated-environment-setup) for NVDA or VoiceOver automation before running this example.
12+
13+
## Test flow
14+
15+
1. The test launches the browser using Playwright
16+
2. Navigates to the GitHub website
17+
3. Moves through the website using the default screen reader for the environment, controlled by Guidepup
18+
4. Traverses headings until the Guidepup heading in the README.md is found
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { devices, PlaywrightTestConfig } from "@playwright/test";
2+
import { screenReaderConfig } from "../../src";
3+
4+
const config: PlaywrightTestConfig = {
5+
...screenReaderConfig,
6+
reportSlowTests: null,
7+
timeout: 5 * 60 * 1000,
8+
retries: 1,
9+
projects: [
10+
{
11+
name: "chromium",
12+
use: { ...devices["Desktop Chrome"], headless: false },
13+
},
14+
],
15+
reporter: process.env.CI ? [["github"], ["html", { open: "never" }]] : "list",
16+
};
17+
18+
export default config;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { platform, release } from "os";
2+
import { headerNavigation } from "../headerNavigation";
3+
import { logIncludesExpectedPhrases } from "../../../logIncludesExpectedPhrases";
4+
import spokenPhraseSnapshot from "./chromium.spokenPhrase.snapshot.json";
5+
import { screenReaderTest as test } from "../../../../src";
6+
7+
test.describe("Chromium Playwright Screen Reader", () => {
8+
test("I can navigate the Guidepup documentation site", async ({
9+
browser,
10+
browserName,
11+
page,
12+
screenReader,
13+
}) => {
14+
const osName = platform();
15+
const osVersion = release();
16+
const browserVersion = browser.version();
17+
const { retry } = test.info();
18+
19+
console.table({
20+
osName,
21+
osVersion,
22+
browserName,
23+
browserVersion,
24+
retry,
25+
});
26+
27+
await headerNavigation({ page, screenReader });
28+
29+
// Assert that we've ended up where we expected and what we were told on
30+
// the way there is as expected.
31+
32+
const itemTextLog = await screenReader.itemTextLog();
33+
const spokenPhraseLog = await screenReader.spokenPhraseLog();
34+
35+
console.log(JSON.stringify(itemTextLog, undefined, 2));
36+
console.log(JSON.stringify(spokenPhraseLog, undefined, 2));
37+
38+
logIncludesExpectedPhrases(spokenPhraseLog, spokenPhraseSnapshot);
39+
});
40+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
["Guidepup", "Docs", "API", "GitHub"]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { log } from "../../log";
2+
import { Page } from "@playwright/test";
3+
import type { ScreenReaderPlaywright } from "../../../src";
4+
5+
const MAX_NAVIGATION_LOOP = 10;
6+
7+
export async function delay(ms: number) {
8+
return new Promise((resolve) => setTimeout(resolve, ms));
9+
}
10+
11+
export async function headerNavigation({
12+
page,
13+
screenReader,
14+
}: {
15+
page: Page;
16+
screenReader: ScreenReaderPlaywright;
17+
}) {
18+
// Navigate to Guidepup Website 🎉
19+
log("Navigating to URL: https://www.guidepup.dev.");
20+
await page.goto("https://www.guidepup.dev", {
21+
waitUntil: "load",
22+
});
23+
24+
// Wait for page to be ready and interact 🙌
25+
const header = page.locator("h1");
26+
await header.waitFor();
27+
await delay(500);
28+
29+
// Make sure interacting with the web content
30+
await screenReader.navigateToWebContent();
31+
await delay(500);
32+
33+
let navigationCount = 0;
34+
35+
// Move across the content
36+
while (
37+
!(await screenReader.itemText()).replaceAll(/\s/g, "").includes("GitHub") &&
38+
navigationCount <= MAX_NAVIGATION_LOOP
39+
) {
40+
navigationCount++;
41+
42+
log(`Performing command: next`);
43+
await screenReader.next();
44+
log(`Screen reader output: "${await screenReader.lastSpokenPhrase()}".`);
45+
}
46+
47+
log(`Performing command: act`);
48+
await screenReader.act();
49+
log(`Screen reader output: "${await screenReader.lastSpokenPhrase()}".`);
50+
}

examples/playwright-voiceover/tests/chromium/chromium.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { voiceOverTest as test } from "../../../../src";
99
test.use({ voiceOverStartOptions: { capture: "initial" } });
1010

1111
test.describe("Chromium Playwright VoiceOver", () => {
12-
test("I can navigate the Guidepup Github page", async ({
12+
test("I can navigate the Guidepup documentation site", async ({
1313
browser,
1414
browserName,
1515
page,

0 commit comments

Comments
 (0)