@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/DETAILS.md +1 -1
- package/INTEGRATION.md +36 -490
- package/README.md +24 -12
- package/examples/oobee-cypress-integration-js/cypress/e2e/spec.cy.js +19 -0
- package/examples/oobee-cypress-integration-js/cypress/support/e2e.js +73 -0
- package/examples/oobee-cypress-integration-js/cypress.config.js +62 -0
- package/examples/oobee-cypress-integration-js/package.json +13 -0
- package/examples/oobee-cypress-integration-ts/cypress.config.ts +94 -0
- package/examples/oobee-cypress-integration-ts/cypress.d.ts +22 -0
- package/examples/oobee-cypress-integration-ts/package.json +12 -0
- package/examples/oobee-cypress-integration-ts/src/cypress/e2e/spec.cy.ts +18 -0
- package/examples/oobee-cypress-integration-ts/src/cypress/support/e2e.ts +93 -0
- package/examples/oobee-cypress-integration-ts/tsconfig.json +19 -0
- package/examples/oobee-playwright-integration-js/oobee-playwright-demo.js +64 -0
- package/examples/oobee-playwright-integration-js/package.json +13 -0
- package/examples/oobee-playwright-integration-ts/package.json +15 -0
- package/examples/oobee-playwright-integration-ts/src/oobee-playwright-demo.ts +83 -0
- package/examples/oobee-playwright-integration-ts/tsconfig.json +11 -0
- package/package.json +1 -1
- package/src/crawlers/commonCrawlerFunc.ts +6 -6
- package/src/npmIndex.ts +248 -91
- package/src/utils.ts +1 -1
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
|
-
###
|
|
615
|
+
### Oobee Exits Without An Error
|
|
616
616
|
|
|
617
|
-
**Issue**:
|
|
617
|
+
**Issue**: Oobee does not start a scan as shown below
|
|
618
618
|
|
|
619
619
|
```shell
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
})();
|