testaro 78.0.8 → 78.4.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/.gitattributes +94 -0
- package/.github/workflows/ci.yml +58 -0
- package/.github/workflows/publish.yml +49 -0
- package/.github/workflows/typescript.yml +35 -0
- package/AGENTS.md +2 -2
- package/CLAUDE.md +24 -18
- package/CONTAINERS.md +9 -6
- package/CONTRIBUTING.md +29 -3
- package/Dockerfile +1 -1
- package/README.md +73 -19
- package/UPGRADES.md +4 -0
- package/actSpecs-doc.md +13 -1
- package/actSpecs.js +29 -5
- package/call.js +6 -5
- package/docker-compose.yml +8 -2
- package/docs/checkpoint-scanning.md +199 -0
- package/docs/standard-result-outcome.md +165 -0
- package/env.example +44 -10
- package/eslint.config.mjs +101 -0
- package/netWatch.js +372 -222
- package/package.json +46 -31
- package/pour/README.md +47 -0
- package/pour/pour.min.js +8 -0
- package/procs/actDo.js +808 -0
- package/procs/catalog.d.ts +17 -0
- package/procs/catalog.js +312 -221
- package/procs/catalog.ts +398 -0
- package/procs/checkpoint.js +110 -0
- package/procs/config.d.ts +35 -0
- package/procs/config.js +62 -0
- package/procs/dateTime.js +2 -1
- package/procs/doActs.js +442 -849
- package/procs/doTestAct.js +27 -121
- package/procs/flow.js +221 -0
- package/procs/generateRuleRegistry.js +94 -0
- package/procs/getSource.d.ts +26 -0
- package/procs/job.js +115 -1
- package/procs/launch.d.ts +40 -0
- package/procs/launch.js +239 -108
- package/procs/nu.d.ts +48 -0
- package/procs/scope.js +196 -0
- package/procs/shoot.d.ts +33 -0
- package/procs/standard.d.ts +18 -0
- package/procs/standard.js +83 -0
- package/procs/standard.ts +126 -0
- package/procs/testAct.js +145 -0
- package/procs/testaro.d.ts +20 -0
- package/procs/testaro.js +236 -189
- package/procs/testaro.ts +321 -0
- package/procs/userPath.js +126 -0
- package/procs/xPath.d.ts +4 -0
- package/procs/xPath.js +81 -69
- package/procs/xPath.ts +106 -0
- package/procs/xPathScript.d.ts +6 -0
- package/procs/xPathScript.js +70 -0
- package/run.js +3 -2
- package/surea11y/README.md +44 -0
- package/surea11y/surea11y.browser.js +14 -0
- package/testaro/adbID.d.ts +3 -0
- package/testaro/adbID.js +38 -41
- package/testaro/adbID.ts +66 -0
- package/testaro/allCapStyle.d.ts +3 -0
- package/testaro/allCapStyle.js +31 -34
- package/testaro/allCapStyle.ts +55 -0
- package/testaro/allCaps.d.ts +16 -0
- package/testaro/allCaps.js +179 -151
- package/testaro/allCaps.ts +228 -0
- package/testaro/allHidden.d.ts +13 -0
- package/testaro/allHidden.js +29 -30
- package/testaro/allHidden.ts +50 -0
- package/testaro/allSlanted.d.ts +3 -0
- package/testaro/allSlanted.js +30 -33
- package/testaro/allSlanted.ts +54 -0
- package/testaro/altScheme.d.ts +3 -0
- package/testaro/altScheme.js +27 -30
- package/testaro/altScheme.ts +50 -0
- package/testaro/attVal.d.ts +3 -0
- package/testaro/attVal.js +20 -21
- package/testaro/attVal.ts +52 -0
- package/testaro/autocomplete.d.ts +3 -0
- package/testaro/autocomplete.js +59 -75
- package/testaro/autocomplete.ts +101 -0
- package/testaro/bulk.d.ts +13 -0
- package/testaro/bulk.js +32 -33
- package/testaro/bulk.ts +55 -0
- package/testaro/buttonMenu.d.ts +9 -0
- package/testaro/buttonMenu.js +317 -319
- package/testaro/buttonMenu.ts +391 -0
- package/testaro/captionLoc.d.ts +3 -0
- package/testaro/captionLoc.js +17 -20
- package/testaro/captionLoc.ts +40 -0
- package/testaro/datalistRef.d.ts +3 -0
- package/testaro/datalistRef.js +33 -36
- package/testaro/datalistRef.ts +55 -0
- package/testaro/distortion.d.ts +3 -0
- package/testaro/distortion.js +57 -26
- package/testaro/distortion.ts +81 -0
- package/testaro/docType.d.ts +15 -0
- package/testaro/docType.js +25 -25
- package/testaro/docType.ts +45 -0
- package/testaro/dupAtt.d.ts +16 -0
- package/testaro/dupAtt.js +113 -104
- package/testaro/dupAtt.ts +144 -0
- package/testaro/elements.d.ts +6 -0
- package/testaro/elements.js +153 -153
- package/testaro/elements.ts +215 -0
- package/testaro/embAc.d.ts +3 -0
- package/testaro/embAc.js +19 -20
- package/testaro/embAc.ts +40 -0
- package/testaro/focAll.d.ts +13 -0
- package/testaro/focAll.js +185 -191
- package/testaro/focAll.ts +217 -0
- package/testaro/focAndOp.d.ts +3 -0
- package/testaro/focAndOp.js +100 -104
- package/testaro/focAndOp.ts +128 -0
- package/testaro/focInd.d.ts +3 -0
- package/testaro/focInd.js +64 -66
- package/testaro/focInd.ts +96 -0
- package/testaro/focVis.d.ts +3 -0
- package/testaro/focVis.js +28 -30
- package/testaro/focVis.ts +52 -0
- package/testaro/headEl.d.ts +10 -0
- package/testaro/headEl.js +61 -62
- package/testaro/headEl.ts +82 -0
- package/testaro/headingAmb.d.ts +3 -0
- package/testaro/headingAmb.js +54 -63
- package/testaro/headingAmb.ts +72 -0
- package/testaro/hovInd.d.ts +12 -0
- package/testaro/hovInd.js +163 -130
- package/testaro/hovInd.ts +199 -0
- package/testaro/hover.d.ts +3 -0
- package/testaro/hover.js +155 -126
- package/testaro/hover.ts +154 -0
- package/testaro/hr.d.ts +3 -0
- package/testaro/hr.js +14 -17
- package/testaro/hr.ts +36 -0
- package/testaro/imageLink.d.ts +3 -0
- package/testaro/imageLink.js +17 -20
- package/testaro/imageLink.ts +42 -0
- package/testaro/labClash.d.ts +3 -0
- package/testaro/labClash.js +31 -33
- package/testaro/labClash.ts +54 -0
- package/testaro/legendLoc.d.ts +3 -0
- package/testaro/legendLoc.js +17 -20
- package/testaro/legendLoc.ts +42 -0
- package/testaro/lineHeight.d.ts +3 -0
- package/testaro/lineHeight.js +43 -48
- package/testaro/lineHeight.ts +70 -0
- package/testaro/linkAmb.d.ts +7 -0
- package/testaro/linkAmb.js +79 -80
- package/testaro/linkAmb.ts +105 -0
- package/testaro/linkExt.d.ts +3 -0
- package/testaro/linkExt.js +13 -16
- package/testaro/linkExt.ts +35 -0
- package/testaro/linkOldAtt.d.ts +3 -0
- package/testaro/linkOldAtt.js +25 -28
- package/testaro/linkOldAtt.ts +48 -0
- package/testaro/linkTo.d.ts +3 -0
- package/testaro/linkTo.js +22 -22
- package/testaro/linkTo.ts +43 -0
- package/testaro/linkUl.d.ts +3 -0
- package/testaro/linkUl.js +30 -34
- package/testaro/linkUl.ts +54 -0
- package/testaro/miniText.d.ts +3 -0
- package/testaro/miniText.js +41 -44
- package/testaro/miniText.ts +68 -0
- package/testaro/motion.d.ts +10 -0
- package/testaro/motion.js +92 -96
- package/testaro/motion.ts +125 -0
- package/testaro/nonTable.d.ts +3 -0
- package/testaro/nonTable.js +39 -45
- package/testaro/nonTable.ts +66 -0
- package/testaro/optRoleSel.d.ts +3 -0
- package/testaro/optRoleSel.js +16 -19
- package/testaro/optRoleSel.ts +41 -0
- package/testaro/phOnly.d.ts +3 -0
- package/testaro/phOnly.js +18 -21
- package/testaro/phOnly.ts +43 -0
- package/testaro/pseudoP.d.ts +3 -0
- package/testaro/pseudoP.js +36 -38
- package/testaro/pseudoP.ts +59 -0
- package/testaro/radioSet.d.ts +3 -0
- package/testaro/radioSet.js +57 -59
- package/testaro/radioSet.ts +79 -0
- package/testaro/registry.d.ts +63 -0
- package/testaro/registry.js +67 -0
- package/testaro/registry.ts +141 -0
- package/testaro/role.d.ts +3 -0
- package/testaro/role.js +27 -29
- package/testaro/role.ts +53 -0
- package/testaro/secHeading.d.ts +3 -0
- package/testaro/secHeading.js +30 -33
- package/testaro/secHeading.ts +53 -0
- package/testaro/styleDiff.d.ts +25 -0
- package/testaro/styleDiff.js +248 -252
- package/testaro/styleDiff.ts +303 -0
- package/testaro/tabNav.d.ts +33 -0
- package/testaro/tabNav.js +272 -342
- package/testaro/tabNav.ts +454 -0
- package/testaro/targetsNear.d.ts +9 -0
- package/testaro/targetsNear.js +130 -132
- package/testaro/targetsNear.ts +160 -0
- package/testaro/textNodes.d.ts +6 -0
- package/testaro/textNodes.js +139 -135
- package/testaro/textNodes.ts +185 -0
- package/testaro/textSem.d.ts +3 -0
- package/testaro/textSem.js +25 -28
- package/testaro/textSem.ts +47 -0
- package/testaro/title.d.ts +9 -0
- package/testaro/title.js +16 -13
- package/testaro/title.ts +31 -0
- package/testaro/titledEl.d.ts +3 -0
- package/testaro/titledEl.js +15 -18
- package/testaro/titledEl.ts +38 -0
- package/testaro/zIndex.d.ts +3 -0
- package/testaro/zIndex.js +19 -22
- package/testaro/zIndex.ts +42 -0
- package/tests/alfa.d.ts +45 -0
- package/tests/alfa.js +138 -141
- package/tests/alfa.ts +214 -0
- package/tests/aslint.d.ts +33 -0
- package/tests/aslint.js +272 -249
- package/tests/aslint.ts +301 -0
- package/tests/axe.d.ts +27 -0
- package/tests/axe.js +199 -200
- package/tests/axe.ts +277 -0
- package/tests/ed11y.d.ts +28 -0
- package/tests/ed11y.js +141 -99
- package/tests/ed11y.ts +178 -0
- package/tests/htmlcs.d.ts +21 -0
- package/tests/htmlcs.js +174 -140
- package/tests/htmlcs.ts +181 -0
- package/tests/ibm.d.ts +52 -0
- package/tests/ibm.js +164 -166
- package/tests/ibm.ts +251 -0
- package/tests/nuVal.d.ts +13 -0
- package/tests/nuVal.js +107 -112
- package/tests/nuVal.ts +145 -0
- package/tests/nuVnu.d.ts +15 -0
- package/tests/nuVnu.js +141 -111
- package/tests/nuVnu.ts +144 -0
- package/tests/pour.d.ts +31 -0
- package/tests/pour.js +242 -0
- package/tests/pour.ts +273 -0
- package/tests/qualWeb.d.ts +39 -0
- package/tests/qualWeb.js +302 -272
- package/tests/qualWeb.ts +415 -0
- package/tests/surea11y.d.ts +33 -0
- package/tests/surea11y.js +288 -0
- package/tests/surea11y.ts +334 -0
- package/tests/testaro.d.ts +25 -0
- package/tests/testaro.js +745 -652
- package/tests/testaro.ts +862 -0
- package/tests/wave.d.ts +46 -0
- package/tests/wave.js +166 -177
- package/tests/wave.ts +252 -0
- package/tsconfig.json +17 -0
- package/types.d.ts +243 -0
- package/types.js +10 -0
- package/types.ts +376 -0
- package/validation/act/README.md +46 -0
- package/validation/act/capture.js +424 -0
- package/validation/act/chromium-issue-draft.md +66 -0
- package/validation/act/fp-triage-2026-08-22.md +86 -0
- package/validation/act/isolation-notes.md +159 -0
- package/validation/act/playwright-issue-draft.md +73 -0
- package/validation/act/propose-mappings.js +0 -0
- package/validation/act/repro-cdp-raw.js +105 -0
- package/validation/act/repro-metarefresh.js +65 -0
- package/validation/act/score.js +184 -0
- package/validation/act/stage3a-stress-report.md +69 -0
- package/validation/act/stage3b-mapping-proposals.md +94 -0
- package/validation/act/stage3b-triage-draft.md +138 -0
- package/validation/act/surea11y-track-a-2026-09-01.md +50 -0
- package/validation/executors/netWatch.js +180 -90
- package/validation/executors/test.js +17 -2
- package/validation/executors/tests.js +118 -10
- package/validation/jobs/reports/raw/260901T1000-surea11y-validation.json +964 -0
- package/validation/jobs/todo/240101T1200-simple-example.json +14 -6
- package/validation/jobs/todo/240101T1300-shoot-example.json +2 -1
- package/validation/jobs/todo/260821T1900-pour-validation.json +45 -0
- package/validation/jobs/todo/260901T1000-surea11y-validation.json +45 -0
- package/validation/knownFailures.json +1 -0
- package/validation/tests/jobProperties/adbID.json +27 -2
- package/validation/tests/jobProperties/{focOp.json → allCapStyle.json} +56 -53
- package/validation/tests/jobProperties/allCaps.json +25 -0
- package/validation/tests/jobProperties/allHidden.json +133 -13
- package/validation/tests/jobProperties/allSlanted.json +3 -3
- package/validation/tests/jobProperties/altScheme.json +23 -8
- package/validation/tests/jobProperties/attVal.json +57 -57
- package/validation/tests/jobProperties/autocomplete.json +11 -1
- package/validation/tests/jobProperties/bulk.json +6 -1
- package/validation/tests/jobProperties/buttonMenu.json +63 -42
- package/validation/tests/jobProperties/captionLoc.json +1 -6
- package/validation/tests/jobProperties/checkpoint-browser.json +401 -0
- package/validation/tests/jobProperties/checkpoint-page.json +397 -0
- package/validation/tests/jobProperties/checkpoint.json +395 -0
- package/validation/tests/jobProperties/datalistRef.json +20 -5
- package/validation/tests/jobProperties/distortion.json +28 -3
- package/validation/tests/jobProperties/docType.json +2 -2
- package/validation/tests/jobProperties/dupAtt.json +48 -33
- package/validation/tests/jobProperties/elements.json +28 -28
- package/validation/tests/jobProperties/embAc.json +36 -31
- package/validation/tests/jobProperties/focAndOp.json +284 -0
- package/validation/tests/jobProperties/focInd.json +34 -34
- package/validation/tests/jobProperties/focVis.json +1 -1
- package/validation/tests/jobProperties/hover.json +39 -37
- package/validation/tests/jobProperties/hr.json +13 -3
- package/validation/tests/jobProperties/imageLink.json +0 -5
- package/validation/tests/jobProperties/labClash.json +68 -33
- package/validation/tests/jobProperties/legendLoc.json +1 -6
- package/validation/tests/jobProperties/lineHeight.json +2 -2
- package/validation/tests/jobProperties/linkAmb.json +13 -18
- package/validation/tests/jobProperties/linkExt.json +1 -1
- package/validation/tests/jobProperties/linkOldAtt.json +12 -2
- package/validation/tests/jobProperties/linkTo.json +1 -1
- package/validation/tests/jobProperties/linkUl.json +65 -65
- package/validation/tests/jobProperties/miniText.json +7 -2
- package/validation/tests/jobProperties/motion.json +4 -50
- package/validation/tests/jobProperties/nonTable.json +48 -3
- package/validation/tests/jobProperties/optRoleSel.json +12 -2
- package/validation/tests/jobProperties/phOnly.json +6 -16
- package/validation/tests/jobProperties/pseudoP.json +17 -2
- package/validation/tests/jobProperties/radioSet.json +30 -30
- package/validation/tests/jobProperties/role.json +25 -5
- package/validation/tests/jobProperties/secHeading.json +26 -21
- package/validation/tests/jobProperties/styleDiff.json +35 -5
- package/validation/tests/jobProperties/tabNav.json +3 -1
- package/validation/tests/jobProperties/targetsNear.json +237 -0
- package/validation/tests/jobProperties/textNodes.json +29 -29
- package/validation/tests/jobProperties/textSem.json +26 -1
- package/validation/tests/jobProperties/title.json +12 -2
- package/validation/tests/jobProperties/titledEl.json +39 -9
- package/validation/tests/jobProperties/userPath.json +317 -0
- package/validation/tests/jobProperties/zIndex.json +40 -35
- package/validation/tests/targets/allCapStyle/index.html +26 -0
- package/validation/tests/targets/checkpoint/index.html +38 -0
- package/validation/tests/targets/datalistRef/index.html +1 -1
- package/validation/tests/targets/focAndOp/bad.html +29 -0
- package/validation/tests/targets/{focOp → focAndOp}/good.html +3 -1
- package/validation/tests/targets/focInd/bad.html +2 -1
- package/validation/tests/targets/headEl/index.html +10 -1
- package/validation/tests/targets/{targetSmall → targetsNear}/index.html +15 -1
- package/validation/tests/targets/userPath/index.html +42 -0
- package/validation/validateTest.js +61 -9
- package/.claude/settings.local.json +0 -11
- package/.eslintrc.json +0 -41
- package/htmlcs/.eslintrc.json +0 -67
- package/validation/tests/jobProperties/linkTitle.json +0 -127
- package/validation/tests/jobProperties/opFoc.json +0 -164
- package/validation/tests/jobProperties/targetSmall.json +0 -152
- package/validation/tests/jobProperties/targetTiny.json +0 -142
- package/validation/tests/targets/focOp/bad.html +0 -25
- package/validation/tests/targets/linkTitle/index.html +0 -24
- package/validation/tests/targets/opFoc/bad.html +0 -26
- package/validation/tests/targets/opFoc/good.html +0 -23
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { Catalog, Report } from '../types';
|
|
3
|
+
export interface CatalogSnapshot {
|
|
4
|
+
entries: Catalog;
|
|
5
|
+
pathIDs: Record<string, string>;
|
|
6
|
+
firstIndex: number;
|
|
7
|
+
nextIndex: number;
|
|
8
|
+
elementCount: number;
|
|
9
|
+
}
|
|
10
|
+
export declare const getAriaSnapshot: (page: Page) => Promise<string>;
|
|
11
|
+
export declare const catalogPage: (page: Page, report: Report, { checkpoint, restoreDetails }: {
|
|
12
|
+
checkpoint: number;
|
|
13
|
+
restoreDetails: boolean;
|
|
14
|
+
}) => Promise<CatalogSnapshot>;
|
|
15
|
+
export declare const getCatalog: (report: Report) => Promise<Catalog>;
|
|
16
|
+
export declare const pruneCheckpoint: (report: Report, checkpoint: number) => number;
|
|
17
|
+
export declare const pruneCatalog: (report: Report) => void;
|
package/procs/catalog.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/*
|
|
2
3
|
© 2026 Jeff Witt.
|
|
3
4
|
© 2025–2026 Jonathan Robert Pool.
|
|
@@ -6,95 +7,50 @@
|
|
|
6
7
|
|
|
7
8
|
SPDX-License-Identifier: MIT
|
|
8
9
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
catalog
|
|
12
|
-
Creates and returns a catalog of a target.
|
|
13
|
-
|
|
14
|
-
A catalog is an object with one property per element in the target. Each property has the element index as its key and has an object as its value, with 7 properties:
|
|
15
|
-
- tagName
|
|
16
|
-
- id
|
|
17
|
-
- startTag
|
|
18
|
-
- text
|
|
19
|
-
- textLinkable
|
|
20
|
-
- boxID
|
|
21
|
-
- pathID
|
|
22
|
-
- headingIndex
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
// IMPORTS
|
|
26
|
-
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.pruneCatalog = exports.pruneCheckpoint = exports.getCatalog = exports.catalogPage = exports.getAriaSnapshot = void 0;
|
|
27
12
|
// Module to close and launch browsers.
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
13
|
+
const launch_1 = require("./launch");
|
|
14
|
+
const shoot_1 = require("./shoot");
|
|
15
|
+
// Function to define window.getXPath on a live page.
|
|
16
|
+
const xPathScript_1 = require("./xPathScript");
|
|
17
|
+
// CONSTANTS
|
|
18
|
+
// The ARIA snapshot of a page, for checkpoints; empty if the browser cannot make one.
|
|
19
|
+
const getAriaSnapshot = async (page) => {
|
|
20
|
+
try {
|
|
21
|
+
return await page.locator('body').ariaSnapshot();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.log(`ERROR: ARIA snapshot failed (${error.message})`);
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.getAriaSnapshot = getAriaSnapshot;
|
|
31
29
|
// FUNCTIONS
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const imageScale = Number.isFinite(report.imageScale) && report.imageScale > 1
|
|
45
|
-
? report.imageScale
|
|
46
|
-
: 1;
|
|
47
|
-
// If the report specifies a global browser ID and a global target URL:
|
|
48
|
-
if (browserID && targetURL) {
|
|
49
|
-
// Launch a browser and visit the target, or abort the job on failure.
|
|
50
|
-
const page = await launch({
|
|
51
|
-
report,
|
|
52
|
-
actIndex: null,
|
|
53
|
-
tempBrowserID: browserID,
|
|
54
|
-
tempURL: targetURL,
|
|
55
|
-
contextOverrides: imageScale > 1 ? {deviceScaleFactor: imageScale} : {}
|
|
56
|
-
});
|
|
57
|
-
// If the launch and navigation succeeded:
|
|
58
|
-
if (page) {
|
|
59
|
-
// Expand closed details elements before the page image and the box measurements, so
|
|
60
|
-
// both see the same fully disclosed state. Chromium lays out the content of a closed
|
|
61
|
-
// details element (content-visibility: hidden) without painting it and without
|
|
62
|
-
// shifting the content after it, so getBoundingClientRect returns coordinates that
|
|
63
|
-
// overlap unrelated visible elements, making box IDs disagree with the page image.
|
|
64
|
-
await page.evaluate(() => {
|
|
30
|
+
// Catalogs the elements of a live page and returns the snapshot. Expands closed details
|
|
31
|
+
// elements first, so box measurements agree with a page image taken in the same state; with
|
|
32
|
+
// restoreDetails, closes them again afterward so a live page keeps its state.
|
|
33
|
+
const catalogPage = async (page, report, { checkpoint, restoreDetails }) => {
|
|
34
|
+
// Ensure the page can compute XPaths (a page launched for acts lacks the script).
|
|
35
|
+
await (0, xPathScript_1.defineGetXPath)(page);
|
|
36
|
+
// Expand closed details elements before the page image and the box measurements, so
|
|
37
|
+
// both see the same fully disclosed state. Chromium lays out the content of a closed
|
|
38
|
+
// details element (content-visibility: hidden) without painting it and without
|
|
39
|
+
// shifting the content after it, so getBoundingClientRect returns coordinates that
|
|
40
|
+
// overlap unrelated visible elements, making box IDs disagree with the page image.
|
|
41
|
+
await page.evaluate(() => {
|
|
65
42
|
document.querySelectorAll('details:not([open])').forEach(details => {
|
|
66
|
-
|
|
43
|
+
details.setAttribute('open', '');
|
|
44
|
+
details.setAttribute('data-testaro-opened', '');
|
|
67
45
|
});
|
|
68
|
-
|
|
46
|
+
}).catch(error => {
|
|
69
47
|
console.log(`ERROR: Expanding details elements failed (${error.message})`);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
// images[0], is unaffected by the imageScale option.
|
|
77
|
-
console.log('Creating page image');
|
|
78
|
-
await shoot(page, report, {
|
|
79
|
-
exclusionSelector: '',
|
|
80
|
-
colorType: report.imageColor,
|
|
81
|
-
action: 'report'
|
|
82
|
-
});
|
|
83
|
-
// If a supersampled page image is also required:
|
|
84
|
-
if (imageScale > 1) {
|
|
85
|
-
// Create one at device-pixel scale and add it to the report as images[1].
|
|
86
|
-
console.log(`Creating page image at ${imageScale}x device scale`);
|
|
87
|
-
await shoot(page, report, {
|
|
88
|
-
exclusionSelector: '',
|
|
89
|
-
colorType: report.imageColor,
|
|
90
|
-
action: 'report',
|
|
91
|
-
scale: 'device'
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Get a catalog of the elements in the page and a map of path IDs to catalog indexes.
|
|
96
|
-
console.log('Creating catalog');
|
|
97
|
-
const {cat: catalog, pathIDs} = await page.evaluate(() => {
|
|
48
|
+
});
|
|
49
|
+
const startIndex = report.catalogNextIndex ?? 0;
|
|
50
|
+
// Get a catalog of the elements in the page and a map of path IDs to catalog indexes.
|
|
51
|
+
console.log(`Creating catalog for checkpoint ${checkpoint}`);
|
|
52
|
+
const { cat: entries, pathIDs, elementCount } = await page.evaluate(({ startIndex, checkpoint }) => {
|
|
53
|
+
// HTMLElement covers the innerText reads below; SVG-only members are guarded.
|
|
98
54
|
const elements = Array.from(document.querySelectorAll('*'));
|
|
99
55
|
// Initialize a catalog.
|
|
100
56
|
const cat = {};
|
|
@@ -114,151 +70,286 @@ exports.getCatalog = async report => {
|
|
|
114
70
|
// real elements. `index` is kept as a string so the catalog keys
|
|
115
71
|
// (cat[index], texts[...].push(index)) are unchanged.
|
|
116
72
|
for (let i = 0; i < elements.length; i++) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
pathIDs[pathID] = index;
|
|
73
|
+
const index = String(startIndex + i);
|
|
74
|
+
const element = elements[i];
|
|
75
|
+
// Get its ID and tag name.
|
|
76
|
+
const { id, tagName } = element;
|
|
77
|
+
// Get its start tag.
|
|
78
|
+
const startTag = element.outerHTML?.replace(/^.*?</s, '<').replace(/>.*$/s, '>') ?? '';
|
|
79
|
+
// Get whether it is eligible for text-fragment acquisition.
|
|
80
|
+
const isTextable = element.closest('body')
|
|
81
|
+
&& !element.closest('svg')
|
|
82
|
+
&& !['SCRIPT', 'STYLE', 'svg'].includes(element.tagName);
|
|
83
|
+
const innerText = isTextable
|
|
84
|
+
? element.innerText.trim() || (element.parentElement?.innerText?.trim() ?? '')
|
|
85
|
+
: '';
|
|
86
|
+
let text = '';
|
|
87
|
+
// If it is eligible and has an inner text:
|
|
88
|
+
if (innerText) {
|
|
89
|
+
const segments = innerText?.split('\n') ?? [];
|
|
90
|
+
const tidySegments = segments.map(segment => segment.trim().replace(/\s+/g, ' '));
|
|
91
|
+
const neededSegments = tidySegments.filter(segment => segment.length);
|
|
92
|
+
neededSegments.splice(1, neededSegments.length - 2);
|
|
93
|
+
// Get its text fragments.
|
|
94
|
+
text = neededSegments.join('\n');
|
|
95
|
+
// Add its index to the directory of text fragments.
|
|
96
|
+
texts[text] ??= [];
|
|
97
|
+
texts[text].push(index);
|
|
98
|
+
}
|
|
99
|
+
// Get its bounding box, but only if the element is painted. Chromium reports
|
|
100
|
+
// plausible nonzero boxes for laid-out but unpainted content (visibility: hidden
|
|
101
|
+
// and content-visibility: hidden subtrees), and such a box disagrees with the
|
|
102
|
+
// page image, overlapping unrelated visible elements.
|
|
103
|
+
const isVisible = typeof element.checkVisibility === 'function'
|
|
104
|
+
? element.checkVisibility({ checkVisibilityCSS: true, visibilityProperty: true })
|
|
105
|
+
: true;
|
|
106
|
+
const domRect = isVisible ? element.getBoundingClientRect() : null;
|
|
107
|
+
// Get its box ID.
|
|
108
|
+
const boxID = domRect
|
|
109
|
+
? ['x', 'y', 'width', 'height'].map(key => Math.round(domRect[key])).join(':')
|
|
110
|
+
: '';
|
|
111
|
+
// Get its path ID.
|
|
112
|
+
const pathID = window.getXPath(element) ?? '/html';
|
|
113
|
+
// If it is a heading that nullifies an existing current heading index:
|
|
114
|
+
if (headingIndex
|
|
115
|
+
&& ['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(tagName)
|
|
116
|
+
&& cat[headingIndex].tagName >= tagName) {
|
|
117
|
+
// Nullify the current heading index.
|
|
118
|
+
headingIndex = '';
|
|
119
|
+
}
|
|
120
|
+
// Add an entry for it to the catalog.
|
|
121
|
+
cat[index] = {
|
|
122
|
+
tagName,
|
|
123
|
+
id: id || '',
|
|
124
|
+
startTag,
|
|
125
|
+
text,
|
|
126
|
+
textLinkable: false,
|
|
127
|
+
boxID,
|
|
128
|
+
pathID,
|
|
129
|
+
headingIndex,
|
|
130
|
+
checkpoint
|
|
131
|
+
};
|
|
132
|
+
// If the element is a heading:
|
|
133
|
+
if (['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(tagName)) {
|
|
134
|
+
// Assign its index to the current heading index.
|
|
135
|
+
headingIndex = index;
|
|
136
|
+
}
|
|
137
|
+
// Add the path ID to the map of path IDs.
|
|
138
|
+
pathIDs[pathID] = index;
|
|
184
139
|
}
|
|
185
140
|
// For each text in the catalog:
|
|
186
141
|
Object.keys(texts).forEach(text => {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
142
|
+
const textElementIndexes = texts[text].sort((a, b) => Number(a) - Number(b));
|
|
143
|
+
// If every element that has it is in the same subtree, so the text is page-unique:
|
|
144
|
+
if (textElementIndexes.slice(0, -1).every((elementIndex, index) => cat[textElementIndexes[index + 1]]
|
|
145
|
+
.pathID
|
|
146
|
+
.startsWith(cat[elementIndex].pathID))) {
|
|
147
|
+
// For each element that has it:
|
|
148
|
+
textElementIndexes.forEach(index => {
|
|
149
|
+
// If it is not in the head, a script, a style, or a noscript element:
|
|
150
|
+
if (!['/head[1]', '/script[', '/style[', '/noscript[']
|
|
151
|
+
.some(excluder => cat[index].pathID.includes(excluder))) {
|
|
152
|
+
// Mark it as linkable in the element data in the catalog.
|
|
153
|
+
cat[index].textLinkable = true;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
return { cat, pathIDs, elementCount: elements.length };
|
|
159
|
+
}, { startIndex, checkpoint });
|
|
160
|
+
// If the page is to keep its state, close the details elements that were opened.
|
|
161
|
+
if (restoreDetails) {
|
|
162
|
+
await page.evaluate(() => {
|
|
163
|
+
document.querySelectorAll('details[data-testaro-opened]').forEach(details => {
|
|
164
|
+
details.removeAttribute('open');
|
|
165
|
+
details.removeAttribute('data-testaro-opened');
|
|
206
166
|
});
|
|
207
|
-
|
|
167
|
+
}).catch(() => { });
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
entries,
|
|
171
|
+
pathIDs,
|
|
172
|
+
firstIndex: startIndex,
|
|
173
|
+
nextIndex: startIndex + elementCount,
|
|
174
|
+
elementCount
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
exports.catalogPage = catalogPage;
|
|
178
|
+
// Creates and returns a catalog.
|
|
179
|
+
const getCatalog = async (report) => {
|
|
180
|
+
const { browserID } = report;
|
|
181
|
+
const targetURL = report.target?.url;
|
|
182
|
+
// Image scale factor (report.imageScale). When greater than 1, the catalog context
|
|
183
|
+
// runs at that deviceScaleFactor and a supplemental page image is captured at device
|
|
184
|
+
// scale, i.e. with imageScale times the pixels of the CSS layout. Box IDs are
|
|
185
|
+
// CSS-pixel regardless, because getBoundingClientRect reports CSS pixels by
|
|
186
|
+
// definition; consumers map them onto the supplemental image by multiplying by
|
|
187
|
+
// imageScale. Omitted, 1, or invalid values keep the behavior identical to before
|
|
188
|
+
// this option existed.
|
|
189
|
+
const imageScale = Number.isFinite(report.imageScale) && report.imageScale > 1
|
|
190
|
+
? report.imageScale
|
|
191
|
+
: 1;
|
|
192
|
+
// If the report specifies a global browser ID and a global target URL:
|
|
193
|
+
if (browserID && targetURL) {
|
|
194
|
+
// Launch a browser and visit the target, or abort the job on failure.
|
|
195
|
+
const page = await (0, launch_1.launch)({
|
|
196
|
+
report,
|
|
197
|
+
actIndex: null,
|
|
198
|
+
tempBrowserID: browserID,
|
|
199
|
+
tempURL: targetURL,
|
|
200
|
+
contextOverrides: imageScale > 1 ? { deviceScaleFactor: imageScale } : {}
|
|
208
201
|
});
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
202
|
+
// If the launch and navigation succeeded:
|
|
203
|
+
if (page) {
|
|
204
|
+
const startTime = Date.now();
|
|
205
|
+
// If a page image is required:
|
|
206
|
+
if ([0, 2, 4, 6].includes(report.imageColor)) {
|
|
207
|
+
// Expand closed details elements first, so the image and the box measurements below
|
|
208
|
+
// see the same fully disclosed state (catalogPage expands them again, idempotently).
|
|
209
|
+
await page.evaluate(() => {
|
|
210
|
+
document.querySelectorAll('details:not([open])').forEach(details => {
|
|
211
|
+
details.setAttribute('open', '');
|
|
212
|
+
});
|
|
213
|
+
}).catch(error => {
|
|
214
|
+
console.log(`ERROR: Expanding details elements failed (${error.message})`);
|
|
215
|
+
});
|
|
216
|
+
// Create one at CSS-pixel scale and add it to the report as images[0]. This
|
|
217
|
+
// scale is invariant to imageScale and to the context's deviceScaleFactor, so
|
|
218
|
+
// the testaro motion rule, which compares its own CSS-scale screenshot with
|
|
219
|
+
// images[0], is unaffected by the imageScale option.
|
|
220
|
+
console.log('Creating page image');
|
|
221
|
+
await (0, shoot_1.shoot)(page, report, {
|
|
222
|
+
exclusionSelector: '',
|
|
223
|
+
colorType: report.imageColor,
|
|
224
|
+
action: 'report'
|
|
225
|
+
});
|
|
226
|
+
// If a supersampled page image is also required:
|
|
227
|
+
if (imageScale > 1) {
|
|
228
|
+
// Create one at device-pixel scale and add it to the report as images[1].
|
|
229
|
+
console.log(`Creating page image at ${imageScale}x device scale`);
|
|
230
|
+
await (0, shoot_1.shoot)(page, report, {
|
|
231
|
+
exclusionSelector: '',
|
|
232
|
+
colorType: report.imageColor,
|
|
233
|
+
action: 'report',
|
|
234
|
+
scale: 'device'
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Snapshot the page as checkpoint 0.
|
|
239
|
+
const snapshot = await (0, exports.catalogPage)(page, report, { checkpoint: 0, restoreDetails: false });
|
|
240
|
+
report.catalog = snapshot.entries;
|
|
241
|
+
report.pathIDs = { 0: snapshot.pathIDs };
|
|
242
|
+
report.catalogNextIndex = snapshot.nextIndex;
|
|
243
|
+
// Record the checkpoint.
|
|
244
|
+
const imageIndexes = (report.images ?? []).map((image, index) => index);
|
|
245
|
+
const checkpoint = {
|
|
246
|
+
index: 0,
|
|
247
|
+
name: 'start',
|
|
248
|
+
implicit: false,
|
|
249
|
+
actIndex: null,
|
|
250
|
+
launchActIndex: null,
|
|
251
|
+
launchURL: page.url(),
|
|
252
|
+
replay: [],
|
|
253
|
+
interaction: { modality: 'efficient' },
|
|
254
|
+
kind: 'navigation',
|
|
255
|
+
url: page.url(),
|
|
256
|
+
title: await page.title().catch(() => ''),
|
|
257
|
+
imageIndexes,
|
|
258
|
+
catalogRange: [snapshot.firstIndex, snapshot.nextIndex - 1],
|
|
259
|
+
elementCount: snapshot.elementCount,
|
|
260
|
+
ariaSnapshot: await (0, exports.getAriaSnapshot)(page),
|
|
261
|
+
elapsedMs: Date.now() - startTime,
|
|
262
|
+
testActs: []
|
|
263
|
+
};
|
|
264
|
+
report.checkpoints = [checkpoint];
|
|
265
|
+
const catalog = report.catalog;
|
|
266
|
+
// Close the browser and its context.
|
|
267
|
+
await (0, launch_1.browserClose)(page);
|
|
268
|
+
// Return the catalog.
|
|
269
|
+
return catalog;
|
|
270
|
+
}
|
|
271
|
+
// Otherwise, i.e. if the launch or navigation failed, report and return this.
|
|
272
|
+
console.log('ERROR: Launch or navigation failure prevented cataloguing and aborted job');
|
|
273
|
+
return {};
|
|
217
274
|
}
|
|
218
|
-
// Otherwise, i.e. if the
|
|
219
|
-
console.log('ERROR:
|
|
275
|
+
// Otherwise, i.e. if the report specification is incomplete, report and return this.
|
|
276
|
+
console.log('ERROR: Job omits browser ID or target URL, preventing catalog creation');
|
|
220
277
|
return {};
|
|
221
|
-
}
|
|
222
|
-
// Otherwise, i.e. if the report specification is incomplete, report and return this.
|
|
223
|
-
console.log('ERROR: Job omits browser ID or target URL, preventing catalog creation');
|
|
224
|
-
return {};
|
|
225
278
|
};
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
279
|
+
exports.getCatalog = getCatalog;
|
|
280
|
+
// Returns the catalog indexes cited by the standard instances of the test acts (all of them,
|
|
281
|
+
// or those of one checkpoint), plus the heading indexes of the cited entries.
|
|
282
|
+
const getCitedIndexes = (report, checkpoint = null) => {
|
|
283
|
+
const { acts, catalog } = report;
|
|
284
|
+
const citedElementIndexes = new Set();
|
|
285
|
+
// For each act in the report:
|
|
286
|
+
acts.forEach(act => {
|
|
287
|
+
// If it is a test (of the checkpoint, if one is specified) with a standard result:
|
|
288
|
+
if (act.type === 'test' && act.result?.standardResult
|
|
289
|
+
&& (checkpoint === null || act.checkpoint === checkpoint)) {
|
|
290
|
+
// The guard above makes the fallback unreachable and instances present; the
|
|
291
|
+
// expression is kept verbatim from the JavaScript original.
|
|
292
|
+
const { instances } = (act.result?.standardResult ?? []);
|
|
293
|
+
// For each instance of the standard result:
|
|
294
|
+
instances.forEach(instance => {
|
|
295
|
+
const catalogIndex = instance?.catalogIndex;
|
|
296
|
+
// If the instance has a catalog index (an index of '0' counts, so test for presence):
|
|
297
|
+
if (catalogIndex !== undefined && catalogIndex !== '') {
|
|
298
|
+
// Ensure the index is classified as cited, as a string so it matches the
|
|
299
|
+
// catalog keys tested below (Object.keys() yields strings).
|
|
300
|
+
citedElementIndexes.add(String(catalogIndex));
|
|
301
|
+
const { headingIndex } = catalog[catalogIndex] ?? {};
|
|
302
|
+
// If the catalog item has a heading index:
|
|
303
|
+
if (headingIndex) {
|
|
304
|
+
// Ensure it, too, is classified as cited.
|
|
305
|
+
citedElementIndexes.add(String(headingIndex));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
250
309
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
310
|
+
});
|
|
311
|
+
return citedElementIndexes;
|
|
312
|
+
};
|
|
313
|
+
/*
|
|
314
|
+
Prunes the entries of one checkpoint that no test act of that checkpoint cites, and drops
|
|
315
|
+
its job-time XPath map. Called when the next checkpoint is created: test acts belong to the
|
|
316
|
+
latest checkpoint, so no later act can cite the earlier one, and pruning it then bounds the
|
|
317
|
+
temporary report that every test act reads. The structure diff with the next checkpoint is
|
|
318
|
+
computed before this runs (procs/checkpoint.js).
|
|
319
|
+
*/
|
|
320
|
+
const pruneCheckpoint = (report, checkpoint) => {
|
|
321
|
+
const { catalog } = report;
|
|
322
|
+
const cited = getCitedIndexes(report, checkpoint);
|
|
323
|
+
let prunedCount = 0;
|
|
324
|
+
Object.keys(catalog).forEach(elementIndex => {
|
|
325
|
+
if (catalog[elementIndex].checkpoint === checkpoint && !cited.has(elementIndex)) {
|
|
326
|
+
delete catalog[elementIndex];
|
|
327
|
+
prunedCount++;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
if (report.pathIDs) {
|
|
331
|
+
delete report.pathIDs[checkpoint];
|
|
262
332
|
}
|
|
263
|
-
|
|
333
|
+
return prunedCount;
|
|
334
|
+
};
|
|
335
|
+
exports.pruneCheckpoint = pruneCheckpoint;
|
|
336
|
+
// Prunes a catalog.
|
|
337
|
+
const pruneCatalog = (report) => {
|
|
338
|
+
console.log('Pruning catalog');
|
|
339
|
+
// The catalog always exists by pruning time (getCatalog ran at job start).
|
|
340
|
+
const { catalog } = report;
|
|
341
|
+
const citedElementIndexes = getCitedIndexes(report);
|
|
342
|
+
// Delete the temporary job-time properties of the report.
|
|
343
|
+
delete report.pathIDs;
|
|
344
|
+
delete report.catalogNextIndex;
|
|
345
|
+
delete report.activeCheckpoint;
|
|
346
|
+
// For each element in the catalog:
|
|
347
|
+
Object.keys(catalog).forEach(elementIndex => {
|
|
348
|
+
// If it is not cited by any instance or by any cited element:
|
|
349
|
+
if (!citedElementIndexes.has(elementIndex)) {
|
|
350
|
+
// Delete it in the catalog.
|
|
351
|
+
delete catalog[elementIndex];
|
|
352
|
+
}
|
|
353
|
+
});
|
|
264
354
|
};
|
|
355
|
+
exports.pruneCatalog = pruneCatalog;
|