@govtechsg/oobee 0.10.68 → 0.10.69

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 CHANGED
@@ -612,26 +612,38 @@ the module (for instance, using `npm rebuild` or `npm install`).
612
612
 
613
613
  **Solution**: As recommended in the error message, run `npm rebuild` or `npm install`
614
614
 
615
- ### dyld Error
615
+ ### Oobee Exits Without An Error
616
616
 
617
- **Issue**: Not able to run Oobee due to the following error shown below
617
+ **Issue**: Oobee does not start a scan as shown below
618
618
 
619
619
  ```shell
620
- dyld: lazy symbol binding failed: Symbol not found: __ZN2v87Isolate37AdjustAmountOfExternalAllocatedMemoryEx
621
- Referenced from: <user_path>/oobee/node_modules/libxmljs/build/Release/xmljs.node
622
- Expected in: flat namespace
620
+ > @govtechsg/oobee@0.10.68 cli
621
+ > node --max-old-space-size=10000 dist/cli.js -c -u https://example.com
622
+ ```
623
+
624
+ **Solutions**:
623
625
 
624
- dyld: Symbol not found: __ZN2v87Isolate37AdjustAmountOfExternalAllocatedMemoryEx
625
- Referenced from: <user_path>/PURPLE_A11y/oobee/node_modules/libxmljs/build/Release/xmljs.node
626
- Expected in: flat namespace
626
+ 1. Delete existing `node_modules` folder and re-install the npm packages with `npm install`.
627
627
 
628
- zsh: abort node index.js
628
+ 2. Set Oobee Verbose mode so more errors are shown during a scan
629
+ #### On MacOS:
630
+ ```
631
+ export OOBEE_VERBOSE=1
632
+ ```
633
+ #### On Windows
634
+ ```
635
+ $env:OOBEE_VERBOSE=1
629
636
  ```
630
637
 
631
- **Solutions**:
638
+ 3. Re-run a scan and see if the issue is resolved.
639
+
640
+ 4. Get the error log information. Error log is available at the log file that is written to `<uuid>.txt`
641
+ ```
642
+ {"timestamp":"2025-09-26 11:13:57","level":"info","message":"Logger writing to: /Users/.../Oobee/...txt"}
643
+ ```
644
+
645
+ 5. Open an [https://github.com/GovTechSG/oobee/issues](issue). Describe the steps to the problem and paste a copy of the error log.
632
646
 
633
- 1. Delete existing `node_modules` folder and re-install the NPM packages with `npm install`.
634
- 2. Refer to this [GitHub issue](https://github.com/fsevents/fsevents/issues/313) for more alternative solutions
635
647
 
636
648
  ### Element Screenshot Limitation
637
649
 
@@ -0,0 +1,19 @@
1
+ describe("template spec", () => {
2
+ it("should run oobee A11y", () => {
3
+ cy.visit(
4
+ "https://govtechsg.github.io/purple-banner-embeds/purple-integrated-scan-example.htm"
5
+ );
6
+ cy.injectOobeeA11yScripts();
7
+ cy.runOobeeA11yScan();
8
+
9
+ cy.get("button[onclick=\"toggleSecondSection()\"]").click();
10
+ // Run a scan on <input> and <button> elements
11
+ cy.runOobeeA11yScan({
12
+ elementsToScan: ["input", "button"],
13
+ elementsToClick: ["button[onclick=\"toggleSecondSection()\"]"],
14
+ metadata: "Clicked button"
15
+ });
16
+
17
+ cy.terminateOobeeA11y();
18
+ });
19
+ });
@@ -0,0 +1,73 @@
1
+ Cypress.Commands.add("injectOobeeA11yScripts", () => {
2
+ cy.task("getAxeScript").then((s) => {
3
+ cy.window().then((win) => {
4
+ try {
5
+ win.eval(s);
6
+ }
7
+ catch (error) {
8
+ // If eval fails due to cross-origin issues, try alternative injection
9
+ if (error.message.includes('SecurityError') || error.message.includes('cross-origin')) {
10
+ cy.log('Cross-origin error detected, attempting alternative script injection');
11
+ // Create a script tag as fallback
12
+ const script = win.document.createElement('script');
13
+ script.textContent = s;
14
+ win.document.head.appendChild(script);
15
+ }
16
+ else {
17
+ throw error;
18
+ }
19
+ }
20
+ });
21
+ });
22
+ cy.task("getOobeeA11yScripts").then((s) => {
23
+ cy.window().then((win) => {
24
+ try {
25
+ win.eval(s);
26
+ }
27
+ catch (error) {
28
+ // If eval fails due to cross-origin issues, try alternative injection
29
+ if (error.message.includes('SecurityError') || error.message.includes('cross-origin')) {
30
+ cy.log('Cross-origin error detected, attempting alternative script injection');
31
+ // Create a script tag as fallback
32
+ const script = win.document.createElement('script');
33
+ script.textContent = s;
34
+ win.document.head.appendChild(script);
35
+ }
36
+ else {
37
+ throw error;
38
+ }
39
+ }
40
+ });
41
+ });
42
+ });
43
+
44
+ Cypress.Commands.add("runOobeeA11yScan", (items = {}) => {
45
+ cy.window().then(async (win) => {
46
+ const { elementsToScan, elementsToClick, metadata } = items;
47
+
48
+ // extract text from the page for readability grading
49
+ const sentences = win.extractText();
50
+ // run readability grading separately as it cannot be done within the browser context
51
+ cy.task("gradeReadability", sentences).then(
52
+ async (gradingReadabilityFlag) => {
53
+ // passing the grading flag to runA11yScan to inject violation as needed
54
+ const res = await win.runA11yScan(
55
+ elementsToScan,
56
+ gradingReadabilityFlag,
57
+ );
58
+ cy.task("pushOobeeA11yScanResults", {
59
+ res,
60
+ metadata,
61
+ elementsToClick,
62
+ }).then((count) => {
63
+ return count;
64
+ });
65
+ },
66
+ );
67
+ cy.task("finishOobeeA11yTestCase"); // test the accumulated number of issue occurrences against specified thresholds. If exceed, terminate oobeeA11y instance.
68
+ });
69
+ });
70
+
71
+ Cypress.Commands.add("terminateOobeeA11y", () => {
72
+ cy.task("terminateOobeeA11y");
73
+ });
@@ -0,0 +1,62 @@
1
+ import { defineConfig } from "cypress";
2
+ import oobeeA11yInit from "@govtechsg/oobee";
3
+
4
+ // viewport used in tests to optimise screenshots
5
+ const viewportSettings = { width: 1920, height: 1040 };
6
+ // specifies the number of occurrences before error is thrown for test failure
7
+ const thresholds = { mustFix: 20, goodToFix: 25 };
8
+ // additional information to include in the "Scan About" section of the report
9
+ const scanAboutMetadata = { browser: 'Chrome (Desktop)' };
10
+ // name of the generated zip of the results at the end of scan
11
+ const resultsZipName = "oobee-scan-results.zip";
12
+
13
+ const oobeeA11y = await oobeeA11yInit({
14
+ entryUrl: "https://govtechsg.github.io/purple-banner-embeds/purple-integrated-scan-example.htm", // initial url to start scan
15
+ testLabel: "Demo Cypress Scan", // label for test
16
+ name: "Your Name",
17
+ email: "email@domain.com",
18
+ includeScreenshots: true, // include screenshots of affected elements in the report
19
+ viewportSettings,
20
+ thresholds,
21
+ scanAboutMetadata,
22
+ zip: resultsZipName,
23
+ deviceChosen: "E2E Test Device",
24
+ strategy: undefined,
25
+ ruleset: ["enable-wcag-aaa"], // add "disable-oobee" to disable Oobee custom checks
26
+ specifiedMaxConcurrency: undefined,
27
+ followRobots: undefined,
28
+ });
29
+
30
+ export default defineConfig({
31
+ taskTimeout: 120000, // need to extend as screenshot function requires some time
32
+ viewportHeight: viewportSettings.height,
33
+ viewportWidth: viewportSettings.width,
34
+ e2e: {
35
+ setupNodeEvents(on, _config) {
36
+ on("task", {
37
+ getAxeScript() {
38
+ return oobeeA11y.getAxeScript();
39
+ },
40
+ getOobeeA11yScripts() {
41
+ return oobeeA11y.getOobeeFunctions();
42
+ },
43
+ gradeReadability(sentences) {
44
+ return oobeeA11y.gradeReadability(sentences);
45
+ },
46
+ async pushOobeeA11yScanResults({ res, metadata, elementsToClick }) {
47
+ return await oobeeA11y.pushScanResults(res, metadata, elementsToClick);
48
+ },
49
+ returnResultsDir() {
50
+ return `results/${oobeeA11y.randomToken}_${oobeeA11y.scanDetails.urlsCrawled.scanned.length}pages/report.html`;
51
+ },
52
+ finishOobeeA11yTestCase() {
53
+ oobeeA11y.testThresholds();
54
+ return null;
55
+ },
56
+ async terminateOobeeA11y() {
57
+ return await oobeeA11y.terminate();
58
+ },
59
+ });
60
+ },
61
+ },
62
+ });
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "oobee-cypress-integration-js",
3
+ "version": "1.0.0",
4
+ "description": "Oobee Cypress integration example (JavaScript)",
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "cypress run"
8
+ },
9
+ "devDependencies": {
10
+ "cypress": "^13.0.0",
11
+ "@govtechsg/oobee": "^0.10.69"
12
+ }
13
+ }
@@ -0,0 +1,94 @@
1
+ import { defineConfig } from "cypress";
2
+ import oobeeA11yInit from "@govtechsg/oobee";
3
+
4
+ interface ViewportSettings {
5
+ width: number;
6
+ height: number;
7
+ }
8
+
9
+ interface Thresholds {
10
+ mustFix: number;
11
+ goodToFix: number;
12
+ }
13
+
14
+ interface ScanAboutMetadata {
15
+ browser: string;
16
+ }
17
+
18
+ // viewport used in tests to optimise screenshots
19
+ const viewportSettings: ViewportSettings = { width: 1920, height: 1040 };
20
+ // specifies the number of occurrences before error is thrown for test failure
21
+ const thresholds: Thresholds = { mustFix: 20, goodToFix: 60 };
22
+ // additional information to include in the "Scan About" section of the report
23
+ const scanAboutMetadata: ScanAboutMetadata = { browser: 'Chrome (Desktop)' };
24
+ // name of the generated zip of the results at the end of scan
25
+ const resultsZipName: string = "oobee-scan-results.zip";
26
+
27
+ // Initialize oobee instance variable - will be set lazily
28
+ let oobeeA11y: any = null;
29
+
30
+ const initOobeeIfNeeded = async () => {
31
+ if (!oobeeA11y) {
32
+ oobeeA11y = await oobeeA11yInit({
33
+ entryUrl: "https://govtechsg.github.io/purple-banner-embeds/purple-integrated-scan-example.htm", // initial url to start scan
34
+ testLabel: "Demo Cypress Scan", // label for test
35
+ name: "Your Name",
36
+ email: "email@domain.com",
37
+ includeScreenshots: true, // include screenshots of affected elements in the report
38
+ viewportSettings,
39
+ thresholds: { mustFix: undefined, goodToFix: undefined },
40
+ scanAboutMetadata: scanAboutMetadata as any,
41
+ zip: resultsZipName,
42
+ deviceChosen: "E2E Test Device",
43
+ strategy: undefined,
44
+ ruleset: ["enable-wcag-aaa"], // add "disable-oobee" to disable Oobee custom checks
45
+ specifiedMaxConcurrency: undefined,
46
+ followRobots: undefined,
47
+ });
48
+ }
49
+ return oobeeA11y;
50
+ };
51
+
52
+ export default defineConfig({
53
+ taskTimeout: 120000, // need to extend as screenshot function requires some time
54
+ viewportHeight: viewportSettings.height,
55
+ viewportWidth: viewportSettings.width,
56
+ chromeWebSecurity: false, // Disable web security to handle cross-origin frames
57
+ e2e: {
58
+ setupNodeEvents(on, _config) {
59
+ on("task", {
60
+ async getAxeScript(): Promise<string> {
61
+ const instance = await initOobeeIfNeeded();
62
+ return instance.getAxeScript();
63
+ },
64
+ async getOobeeA11yScripts(): Promise<string> {
65
+ const instance = await initOobeeIfNeeded();
66
+ return instance.getOobeeFunctions();
67
+ },
68
+ async gradeReadability(sentences: string[]): Promise<string> {
69
+ const instance = await initOobeeIfNeeded();
70
+ return instance.gradeReadability(sentences);
71
+ },
72
+ async pushOobeeA11yScanResults({res, metadata, elementsToClick}: { res: any, metadata: any, elementsToClick: any[] }): Promise<{ mustFix: number, goodToFix: number }> {
73
+ const instance = await initOobeeIfNeeded();
74
+ return await instance.pushScanResults(res, metadata, elementsToClick);
75
+ },
76
+ async returnResultsDir(): Promise<string> {
77
+ const instance = await initOobeeIfNeeded();
78
+ return `results/${instance.randomToken}_${instance.scanDetails.urlsCrawled.scanned.length}pages/reports/report.html`;
79
+ },
80
+ async finishOobeeA11yTestCase(): Promise<null> {
81
+ const instance = await initOobeeIfNeeded();
82
+ instance.testThresholds();
83
+ return null;
84
+ },
85
+ async terminateOobeeA11y(): Promise<string> {
86
+ const instance = await initOobeeIfNeeded();
87
+ return await instance.terminate();
88
+ },
89
+ });
90
+ },
91
+ supportFile: 'dist/cypress/support/e2e.js',
92
+ specPattern: 'dist/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
93
+ },
94
+ });
@@ -0,0 +1,22 @@
1
+ /// <reference types="cypress" />
2
+
3
+ export interface OobeeScanOptions {
4
+ elementsToScan?: string[];
5
+ elementsToClick?: string[];
6
+ metadata?: string;
7
+ }
8
+
9
+ declare global {
10
+ namespace Cypress {
11
+ interface Chainable<Subject = any> {
12
+ injectOobeeA11yScripts(): Chainable<void>;
13
+ runOobeeA11yScan(options?: OobeeScanOptions): Chainable<void>;
14
+ terminateOobeeA11y(): Chainable<any>;
15
+ }
16
+ }
17
+
18
+ interface Window {
19
+ runA11yScan: (elementsToScan?: string[], gradingReadabilityFlag?: string) => Promise<any>;
20
+ extractText: () => string[];
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "oobee-cypress-integration-ts",
3
+ "version": "1.0.0",
4
+ "description": "Oobee Cypress integration example (TypeScript)",
5
+ "type": "module",
6
+ "devDependencies": {
7
+ "@govtechsg/oobee": "^0.10.69",
8
+ "@types/jest": "^30.0.0",
9
+ "cypress": "^15.3.0",
10
+ "typescript": "^5.9.3"
11
+ }
12
+ }
@@ -0,0 +1,18 @@
1
+ /// <reference types="cypress" />
2
+
3
+ describe("template spec", () => {
4
+ it("should run oobee A11y", () => {
5
+ cy.visit("https://govtechsg.github.io/purple-banner-embeds/purple-integrated-scan-example.htm");
6
+ cy.injectOobeeA11yScripts();
7
+ cy.runOobeeA11yScan();
8
+ cy.get("button[onclick=\"toggleSecondSection()\"]").click();
9
+ // Run a scan on <input> and <button> elements
10
+ cy.runOobeeA11yScan({
11
+ elementsToScan: ["input", "button"],
12
+ elementsToClick: ["button[onclick=\"toggleSecondSection()\"]"],
13
+ metadata: "Clicked button"
14
+ });
15
+
16
+ cy.terminateOobeeA11y();
17
+ });
18
+ });
@@ -0,0 +1,93 @@
1
+ /// <reference types="cypress" />
2
+
3
+ import { OobeeScanOptions } from "../../../cypress.d";
4
+
5
+ Cypress.Commands.add("injectOobeeA11yScripts", () => {
6
+ cy.task("getAxeScript").then((s: string) => {
7
+ cy.window().then((win) => {
8
+ try {
9
+ win.eval(s);
10
+ } catch (error) {
11
+ // If eval fails due to cross-origin issues, try alternative injection
12
+ if (error.message.includes('SecurityError') || error.message.includes('cross-origin')) {
13
+ cy.log('Cross-origin error detected, attempting alternative script injection');
14
+ // Create a script tag as fallback
15
+ const script = win.document.createElement('script');
16
+ script.textContent = s;
17
+ win.document.head.appendChild(script);
18
+ } else {
19
+ throw error;
20
+ }
21
+ }
22
+ });
23
+ });
24
+ cy.task("getOobeeA11yScripts").then((s: string) => {
25
+ cy.window().then((win) => {
26
+ try {
27
+ win.eval(s);
28
+ } catch (error) {
29
+ // If eval fails due to cross-origin issues, try alternative injection
30
+ if (error.message.includes('SecurityError') || error.message.includes('cross-origin')) {
31
+ cy.log('Cross-origin error detected, attempting alternative script injection');
32
+ // Create a script tag as fallback
33
+ const script = win.document.createElement('script');
34
+ script.textContent = s;
35
+ win.document.head.appendChild(script);
36
+ } else {
37
+ throw error;
38
+ }
39
+ }
40
+ });
41
+ });
42
+ });
43
+
44
+ Cypress.Commands.add("runOobeeA11yScan", (items: OobeeScanOptions = {}) => {
45
+ cy.window().then(async (win) => {
46
+ const { elementsToScan, elementsToClick, metadata } = items;
47
+
48
+ // extract text from the page for readability grading
49
+ const sentences = win.extractText();
50
+ // run readability grading separately as it cannot be done within the browser context
51
+ cy.task("gradeReadability", sentences).then(
52
+ async (gradingReadabilityFlag: string) => {
53
+ // passing the grading flag to runA11yScan to inject violation as needed
54
+ const res = await win.runA11yScan(
55
+ elementsToScan,
56
+ gradingReadabilityFlag,
57
+ );
58
+ cy.task("pushOobeeA11yScanResults", {
59
+ res,
60
+ metadata,
61
+ elementsToClick,
62
+ }).then((count) => {
63
+ return count;
64
+ });
65
+ },
66
+ );
67
+ cy.task("finishOobeeA11yTestCase"); // test the accumulated number of issue occurrences against specified thresholds. If exceed, terminate oobeeA11y instance.
68
+ });
69
+ });
70
+
71
+ Cypress.Commands.add("terminateOobeeA11y", () => {
72
+ cy.task("terminateOobeeA11y");
73
+ });
74
+
75
+ // Suppress ResizeObserver errors and cross-origin security errors
76
+ Cypress.on('uncaught:exception', (err, runnable) => {
77
+ if (err.message.includes('ResizeObserver loop completed with undelivered notifications')) {
78
+ return false; // prevents Cypress from failing the test
79
+ }
80
+ if (err.message.includes('SecurityError') && err.message.includes('cross-origin frame')) {
81
+ return false; // prevents Cypress from failing due to cross-origin frame access
82
+ }
83
+ if (err.message.includes("Failed to read a named property 'eval' from 'Window'")) {
84
+ return false; // prevents Cypress from failing due to eval access on cross-origin frames
85
+ }
86
+ if (err.message.includes('Minified React error')) {
87
+ return false; // prevents Cypress from failing due to React errors
88
+ }
89
+ if (err.message.includes('https://reactjs.org/docs/error-decoder.html')) {
90
+ return false; // prevents Cypress from failing due to React errors
91
+ }
92
+ return true;
93
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist",
4
+ "allowJs": true,
5
+ "target": "es2021",
6
+ "lib": ["es2021", "dom"],
7
+ "module": "commonjs",
8
+ "rootDir": "./src",
9
+ "skipLibCheck": true,
10
+ "esModuleInterop": true,
11
+ "allowSyntheticDefaultImports": true,
12
+ "strict": false,
13
+ "types": ["cypress"],
14
+ "moduleResolution": "node",
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": ["./src/**/*", "cypress.d.ts"],
18
+ "exclude": ["node_modules"]
19
+ }
@@ -0,0 +1,64 @@
1
+ import { chromium } from "playwright";
2
+ import oobeeA11yInit from "@govtechsg/oobee";
3
+ import { extractText } from "@govtechsg/oobee/dist/crawlers/custom/extractText.js";
4
+
5
+ // viewport used in tests to optimise screenshots
6
+ const viewportSettings = { width: 1920, height: 1040 };
7
+ // specifies the number of occurrences before error is thrown for test failure
8
+ const thresholds = { mustFix: 20, goodToFix: 25 };
9
+ // additional information to include in the "Scan About" section of the report
10
+ const scanAboutMetadata = { browser: 'Chrome (Desktop)' };
11
+ // name of the generated zip of the results at the end of scan
12
+ const resultsZipName = "oobee-scan-results.zip";
13
+
14
+ const oobeeA11y = await oobeeA11yInit({
15
+ entryUrl: "https://govtechsg.github.io", // initial url to start scan
16
+ testLabel: "Demo Playwright Scan", // label for test
17
+ name: "Your Name",
18
+ email: "email@domain.com",
19
+ includeScreenshots: true, // include screenshots of affected elements in the report
20
+ viewportSettings,
21
+ thresholds,
22
+ scanAboutMetadata,
23
+ zip: resultsZipName,
24
+ deviceChosen: "E2E Test Device",
25
+ strategy: undefined,
26
+ ruleset: ["enable-wcag-aaa"],
27
+ specifiedMaxConcurrency: undefined,
28
+ followRobots: undefined,
29
+ });
30
+
31
+ (async () => {
32
+ const browser = await chromium.launch({
33
+ headless: false,
34
+ });
35
+ const context = await browser.newContext();
36
+ const page = await context.newPage();
37
+
38
+ const runOobeeA11yScan = async (elementsToScan, gradingReadabilityFlag) => {
39
+ const scanRes = await page.evaluate(
40
+ async ({ elementsToScan, gradingReadabilityFlag }) => await runA11yScan(elementsToScan, gradingReadabilityFlag),
41
+ { elementsToScan, gradingReadabilityFlag },
42
+ );
43
+ await oobeeA11y.pushScanResults(scanRes);
44
+ oobeeA11y.testThresholds(); // test the accumulated number of issue occurrences against specified thresholds. If exceed, terminate oobeeA11y instance.
45
+ };
46
+
47
+ await page.goto('https://govtechsg.github.io/purple-banner-embeds/purple-integrated-scan-example.htm');
48
+ await page.evaluate(oobeeA11y.getAxeScript());
49
+ await page.evaluate(oobeeA11y.getOobeeFunctions());
50
+
51
+ const sentences = await page.evaluate(() => extractText());
52
+ const gradingReadabilityFlag = await oobeeA11y.gradeReadability(sentences);
53
+
54
+ await runOobeeA11yScan([], gradingReadabilityFlag);
55
+
56
+ await page.getByRole('button', { name: 'Click Me' }).click();
57
+ // Run a scan on <input> and <button> elements
58
+ await runOobeeA11yScan(['input', 'button'])
59
+
60
+ // ---------------------
61
+ await context.close();
62
+ await browser.close();
63
+ await oobeeA11y.terminate();
64
+ })();
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "oobee-playwright-integration-js",
3
+ "version": "1.0.0",
4
+ "description": "Oobee Playwright integration example (JavaScript)",
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node oobee-playwright-demo.js"
8
+ },
9
+ "devDependencies": {
10
+ "playwright": "^1.55.0",
11
+ "@govtechsg/oobee": "^0.10.69"
12
+ }
13
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "oobee-playwright-integration-ts",
3
+ "version": "1.0.0",
4
+ "description": "Oobee Playwright integration example (TypeScript)",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "test": "tsc && node dist/oobee-playwright-demo.js"
9
+ },
10
+ "devDependencies": {
11
+ "playwright": "^1.55.0",
12
+ "@govtechsg/oobee": "^0.10.69",
13
+ "typescript": "^5.9.3"
14
+ }
15
+ }
@@ -0,0 +1,83 @@
1
+ import { Browser, BrowserContext, Page, chromium } from "playwright";
2
+ import oobeeA11yInit from "@govtechsg/oobee";
3
+ import { extractText } from "@govtechsg/oobee/dist/crawlers/custom/extractText.js";
4
+
5
+ declare const runA11yScan: (
6
+ elementsToScan?: string[],
7
+ gradingReadabilityFlag?: string,
8
+ ) => Promise<any>;
9
+
10
+ interface ViewportSettings {
11
+ width: number;
12
+ height: number;
13
+ }
14
+
15
+ interface Thresholds {
16
+ mustFix: number;
17
+ goodToFix: number;
18
+ }
19
+
20
+ interface ScanAboutMetadata {
21
+ browser: string;
22
+ }
23
+
24
+ // viewport used in tests to optimise screenshots
25
+ const viewportSettings: ViewportSettings = { width: 1920, height: 1040 };
26
+ // specifies the number of occurrences before error is thrown for test failure
27
+ const thresholds: Thresholds = { mustFix: 20, goodToFix: 25 };
28
+ // additional information to include in the "Scan About" section of the report
29
+ const scanAboutMetadata: ScanAboutMetadata = { browser: 'Chrome (Desktop)' };
30
+ // name of the generated zip of the results at the end of scan
31
+ const resultsZipName: string = "oobee-scan-results.zip";
32
+
33
+ const oobeeA11y = await oobeeA11yInit({
34
+ entryUrl: "https://govtechsg.github.io", // initial url to start scan
35
+ testLabel: "Demo Playwright Scan", // label for test
36
+ name: "Your Name",
37
+ email: "email@domain.com",
38
+ includeScreenshots: true, // include screenshots of affected elements in the report
39
+ viewportSettings,
40
+ thresholds,
41
+ scanAboutMetadata,
42
+ zip: resultsZipName,
43
+ deviceChosen: "E2E Test Device",
44
+ strategy: undefined,
45
+ ruleset: ["enable-wcag-aaa"],
46
+ specifiedMaxConcurrency: undefined,
47
+ followRobots: undefined,
48
+ });
49
+
50
+ (async () => {
51
+ const browser: Browser = await chromium.launch({
52
+ headless: false,
53
+ });
54
+ const context: BrowserContext = await browser.newContext();
55
+ const page: Page = await context.newPage();
56
+
57
+ const runOobeeA11yScan = async (elementsToScan?: string[], gradingReadabilityFlag?: string) => {
58
+ const scanRes = await page.evaluate(
59
+ async ({ elementsToScan, gradingReadabilityFlag }) => await runA11yScan(elementsToScan, gradingReadabilityFlag),
60
+ { elementsToScan, gradingReadabilityFlag },
61
+ );
62
+ await oobeeA11y.pushScanResults(scanRes);
63
+ oobeeA11y.testThresholds(); // test the accumulated number of issue occurrences against specified thresholds. If exceed, terminate oobeeA11y instance.
64
+ };
65
+
66
+ await page.goto('https://govtechsg.github.io/purple-banner-embeds/purple-integrated-scan-example.htm');
67
+ await page.evaluate(oobeeA11y.getAxeScript());
68
+ await page.evaluate(oobeeA11y.getOobeeFunctions());
69
+
70
+ const sentences = await page.evaluate(() => extractText());
71
+ const gradingReadabilityFlag = await oobeeA11y.gradeReadability(sentences);
72
+
73
+ await runOobeeA11yScan([], gradingReadabilityFlag);
74
+
75
+ await page.getByRole('button', { name: 'Click Me' }).click();
76
+ // Run a scan on <input> and <button> elements
77
+ await runOobeeA11yScan(['input', 'button'])
78
+
79
+ // ---------------------
80
+ await context.close();
81
+ await browser.close();
82
+ await oobeeA11y.terminate();
83
+ })();