testaro 60.2.1 → 60.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/package.json +1 -1
- package/procs/doTestAct.js +2 -1
- package/procs/screenShot.js +32 -0
- package/procs/standardize.js +2 -1
- package/procs/testaro.js +4 -2
- package/procs/visChange.js +7 -25
- package/run.js +189 -88
- package/testaro/adbID.js +2 -2
- package/testaro/altScheme.js +2 -2
- package/testaro/attVal.js +2 -2
- package/testaro/autocomplete.js +2 -2
- package/testaro/captionLoc.js +2 -2
- package/testaro/datalistRef.js +2 -2
- package/testaro/embAc.js +2 -2
- package/testaro/focInd.js +2 -2
- package/testaro/focOp.js +2 -2
- package/testaro/focVis.js +2 -2
- package/testaro/hover.js +2 -2
- package/testaro/labClash.js +2 -2
- package/testaro/lineHeight.js +2 -2
- package/testaro/linkAmb.js +2 -2
- package/testaro/miniText.js +2 -2
- package/testaro/motion.js +67 -59
- package/testaro/motionSolo.js +94 -0
- package/testaro/opFoc.js +2 -2
- package/testaro/phOnly.js +2 -2
- package/testaro/pseudoP.js +2 -2
- package/testaro/radioSet.js +2 -2
- package/testaro/role.js +2 -2
- package/testaro/secHeading.js +2 -2
- package/testaro/shoot.js +57 -0
- package/testaro/targetSmall.js +2 -2
- package/testaro/targetTiny.js +2 -2
- package/testaro/textSem.js +2 -2
- package/testaro/zIndex.js +2 -2
- package/tests/testaro.js +608 -296
package/package.json
CHANGED
package/procs/doTestAct.js
CHANGED
|
@@ -50,9 +50,10 @@ const actIndex = Number.parseInt(process.argv[2]);
|
|
|
50
50
|
|
|
51
51
|
// FUNCTIONS
|
|
52
52
|
|
|
53
|
+
// Performs the tests of the act specified by the caller.
|
|
53
54
|
const doTestAct = async () => {
|
|
54
55
|
const reportPath = `${tmpDir}/report.json`;
|
|
55
|
-
// Get the
|
|
56
|
+
// Get the report from the temporary directory.
|
|
56
57
|
const reportJSON = await fs.readFile(reportPath, 'utf8');
|
|
57
58
|
const report = JSON.parse(reportJSON);
|
|
58
59
|
// Get a reference to the act in the report.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
4
|
+
Licensed under the MIT License. See LICENSE file for details.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
screenShot
|
|
9
|
+
This procedure creates and returns a full-page screenshot, optionally with an exclused element.
|
|
10
|
+
This procedure uses the Playwright page.screenshot method, which is not implemented for the
|
|
11
|
+
firefox browser type.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// FUNCTIONS
|
|
15
|
+
|
|
16
|
+
// Creates and returns a screenshot.
|
|
17
|
+
exports.screenShot = async (page, exclusion = null) => {
|
|
18
|
+
const options = {
|
|
19
|
+
fullPage: true,
|
|
20
|
+
omitBackground: true,
|
|
21
|
+
timeout: 2000
|
|
22
|
+
};
|
|
23
|
+
if (exclusion) {
|
|
24
|
+
options.mask = [exclusion];
|
|
25
|
+
}
|
|
26
|
+
// Make and return a screenshot as a buffer.
|
|
27
|
+
return await page.screenshot(options)
|
|
28
|
+
.catch(error => {
|
|
29
|
+
console.log(`ERROR: Screenshot failed (${error.message})`);
|
|
30
|
+
return '';
|
|
31
|
+
});
|
|
32
|
+
};
|
package/procs/standardize.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
© 2023–2024 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
3
4
|
|
|
4
5
|
MIT License
|
|
5
6
|
|
|
@@ -705,7 +706,7 @@ const convert = (toolName, data, result, standardResult) => {
|
|
|
705
706
|
// Round the totals of the standard result.
|
|
706
707
|
standardResult.totals = standardResult.totals.map(total => Math.round(total));
|
|
707
708
|
};
|
|
708
|
-
// Converts the results.
|
|
709
|
+
// Converts the results of a test act.
|
|
709
710
|
exports.standardize = act => {
|
|
710
711
|
const {which, data, result, standardResult} = act;
|
|
711
712
|
if (which && result && standardResult) {
|
package/procs/testaro.js
CHANGED
|
@@ -68,7 +68,9 @@ const init = exports.init = async (sampleMax, page, locAllSelector, options = {}
|
|
|
68
68
|
};
|
|
69
69
|
|
|
70
70
|
// Populates and returns a result.
|
|
71
|
-
const
|
|
71
|
+
const getRuleResult = exports.getRuleResult = async (
|
|
72
|
+
withItems, all, ruleID, whats, ordinalSeverity, tagName = ''
|
|
73
|
+
) => {
|
|
72
74
|
const {locs, result} = all;
|
|
73
75
|
const {data, totals, standardInstances} = result;
|
|
74
76
|
// For each violation locator:
|
|
@@ -149,7 +151,7 @@ exports.simplify = async (page, withItems, ruleData) => {
|
|
|
149
151
|
complaints.instance,
|
|
150
152
|
complaints.summary
|
|
151
153
|
];
|
|
152
|
-
const result = await
|
|
154
|
+
const result = await getRuleResult(withItems, all, ruleID, whats, ordinalSeverity, summaryTagName);
|
|
153
155
|
// Return the result.
|
|
154
156
|
return result;
|
|
155
157
|
};
|
package/procs/visChange.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
|
-
© 2023–
|
|
2
|
+
© 2023–2025 CVS Health and/or one of its affiliates. All rights reserved.
|
|
3
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
3
4
|
|
|
4
5
|
MIT License
|
|
5
6
|
|
|
@@ -25,37 +26,18 @@
|
|
|
25
26
|
/*
|
|
26
27
|
visChange
|
|
27
28
|
This procedure reports a change in the visible content of a page between two times, optionally
|
|
28
|
-
hovering over a locator-defined element immediately after the first time.
|
|
29
|
-
|
|
30
|
-
WARNING: This test uses the Playwright page.screenshot method, which produces incorrect results
|
|
31
|
-
when the browser type is chromium and is not implemented for the firefox browser type. The only
|
|
32
|
-
browser type usable with this test is webkit.
|
|
29
|
+
hovering over a locator-defined element immediately after the first time. This test uses the
|
|
30
|
+
Playwright page.screenshot method, which is not implemented for the firefox browser type.
|
|
33
31
|
*/
|
|
34
32
|
|
|
35
33
|
// IMPORTS
|
|
36
34
|
|
|
37
35
|
const pixelmatch = require('pixelmatch').default;
|
|
38
36
|
const {PNG} = require('pngjs');
|
|
37
|
+
const {screenShot} = require('./screenShot');
|
|
39
38
|
|
|
40
39
|
// FUNCTIONS
|
|
41
40
|
|
|
42
|
-
// Creates and returns a screenshot.
|
|
43
|
-
const shoot = async (page, exclusion = null) => {
|
|
44
|
-
// Make a screenshot as a buffer.
|
|
45
|
-
const options = {
|
|
46
|
-
fullPage: true,
|
|
47
|
-
omitBackground: true,
|
|
48
|
-
timeout: 2000
|
|
49
|
-
};
|
|
50
|
-
if (exclusion) {
|
|
51
|
-
options.mask = [exclusion];
|
|
52
|
-
}
|
|
53
|
-
return await page.screenshot(options)
|
|
54
|
-
.catch(error => {
|
|
55
|
-
console.log(`ERROR: Screenshot failed (${error.message})`);
|
|
56
|
-
return '';
|
|
57
|
-
});
|
|
58
|
-
};
|
|
59
41
|
exports.visChange = async (page, options = {}) => {
|
|
60
42
|
const {delayBefore, delayBetween, exclusion} = options;
|
|
61
43
|
// Wait, if required.
|
|
@@ -74,7 +56,7 @@ exports.visChange = async (page, options = {}) => {
|
|
|
74
56
|
});
|
|
75
57
|
}
|
|
76
58
|
// Make and get a screenshot, excluding an element if specified.
|
|
77
|
-
const shot0 = await
|
|
59
|
+
const shot0 = await screenShot(page, exclusion);
|
|
78
60
|
// If it succeeded:
|
|
79
61
|
if (shot0.length) {
|
|
80
62
|
// If an exclusion was specified:
|
|
@@ -96,7 +78,7 @@ exports.visChange = async (page, options = {}) => {
|
|
|
96
78
|
// Wait as specified, or 3 seconds.
|
|
97
79
|
await page.waitForTimeout(delayBetween || 3000);
|
|
98
80
|
// Make and get another screenshot.
|
|
99
|
-
const shot1 = await
|
|
81
|
+
const shot1 = await screenShot(page, exclusion);
|
|
100
82
|
// If it succeeded:
|
|
101
83
|
if (shot1.length) {
|
|
102
84
|
// Get the shots as PNG images.
|
package/run.js
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
const fs = require('fs/promises');
|
|
35
35
|
// Module to keep secrets.
|
|
36
36
|
require('dotenv').config({quiet: true});
|
|
37
|
+
// Module to execute shell commands.
|
|
38
|
+
const {execSync} = require('child_process');
|
|
37
39
|
// Module to validate jobs.
|
|
38
40
|
const {isBrowserID, isDeviceID, isURL, isValidJob, tools} = require('./procs/job');
|
|
39
41
|
// Module to evade automation detection.
|
|
@@ -109,12 +111,12 @@ const tmpDir = os.tmpdir();
|
|
|
109
111
|
|
|
110
112
|
// Facts about the current session.
|
|
111
113
|
let actCount = 0;
|
|
112
|
-
let browserCloseIntentional = false;
|
|
113
114
|
// Facts about the current act.
|
|
114
115
|
let actIndex = 0;
|
|
115
116
|
let browser;
|
|
116
117
|
let browserContext;
|
|
117
118
|
let page;
|
|
119
|
+
let report;
|
|
118
120
|
let requestedURL = '';
|
|
119
121
|
|
|
120
122
|
// FUNCTIONS
|
|
@@ -241,25 +243,6 @@ const goTo = async (report, page, url, timeout, waitUntil) => {
|
|
|
241
243
|
};
|
|
242
244
|
}
|
|
243
245
|
};
|
|
244
|
-
// Closes the current browser.
|
|
245
|
-
const browserClose = async () => {
|
|
246
|
-
if (browser) {
|
|
247
|
-
browserCloseIntentional = true;
|
|
248
|
-
for (const context of browser.contexts()) {
|
|
249
|
-
try {
|
|
250
|
-
await context.close();
|
|
251
|
-
}
|
|
252
|
-
catch(error) {
|
|
253
|
-
console.log(
|
|
254
|
-
`ERROR trying to close context: ${error.message.slice(0, 200).replace(/\n.+/s, '')}`
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
await browser.close();
|
|
259
|
-
browserCloseIntentional = false;
|
|
260
|
-
browser = null;
|
|
261
|
-
}
|
|
262
|
-
};
|
|
263
246
|
// Adds an error result to an act.
|
|
264
247
|
const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
|
|
265
248
|
// If the error is to be logged:
|
|
@@ -286,6 +269,25 @@ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
|
|
|
286
269
|
abortActs(report, actIndex);
|
|
287
270
|
}
|
|
288
271
|
};
|
|
272
|
+
// Closes the current browser.
|
|
273
|
+
const browserClose = async () => {
|
|
274
|
+
if (browser) {
|
|
275
|
+
browserCloseIntentional = true;
|
|
276
|
+
for (const context of browser.contexts()) {
|
|
277
|
+
try {
|
|
278
|
+
await context.close();
|
|
279
|
+
}
|
|
280
|
+
catch(error) {
|
|
281
|
+
console.log(
|
|
282
|
+
`ERROR trying to close context: ${error.message.slice(0, 200).replace(/\n.+/s, '')}`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
await browser.close();
|
|
287
|
+
browserCloseIntentional = false;
|
|
288
|
+
browser = null;
|
|
289
|
+
}
|
|
290
|
+
};
|
|
289
291
|
// Launches a browser and navigates to a URL.
|
|
290
292
|
const launch = exports.launch = async (
|
|
291
293
|
report, debug, waits, tempBrowserID, tempURL, retries = 2
|
|
@@ -668,6 +670,11 @@ const abortActs = (report, actIndex) => {
|
|
|
668
670
|
// Report that the job is aborted.
|
|
669
671
|
console.log(`ERROR: Job aborted on act ${actIndex}`);
|
|
670
672
|
};
|
|
673
|
+
// Returns the combination of browser ID and target URL of an act.
|
|
674
|
+
const launchSpecs = (act, report) => [
|
|
675
|
+
act.browserID || report.browserID || '',
|
|
676
|
+
act.target && act.target.url || report.target && report.target.url || ''
|
|
677
|
+
];
|
|
671
678
|
// Performs the acts in a report and adds the results to the report.
|
|
672
679
|
const doActs = async (report, opts = {}) => {
|
|
673
680
|
const {acts} = report;
|
|
@@ -752,13 +759,14 @@ const doActs = async (report, opts = {}) => {
|
|
|
752
759
|
}
|
|
753
760
|
// Otherwise, if the act is a launch:
|
|
754
761
|
else if (type === 'launch') {
|
|
762
|
+
const actLaunchSpecs = launchSpecs(act, report);
|
|
755
763
|
// Launch a browser, navigate to a page, and add the result to the act.
|
|
756
764
|
await launch(
|
|
757
765
|
report,
|
|
758
766
|
debug,
|
|
759
767
|
waits,
|
|
760
|
-
|
|
761
|
-
|
|
768
|
+
actLaunchSpecs[0],
|
|
769
|
+
actLaunchSpecs[1]
|
|
762
770
|
);
|
|
763
771
|
// If this failed:
|
|
764
772
|
if (page.prevented) {
|
|
@@ -819,50 +827,6 @@ const doActs = async (report, opts = {}) => {
|
|
|
819
827
|
toolTimes[act.which] += time;
|
|
820
828
|
// If the act was not prevented:
|
|
821
829
|
if (act.data && ! act.data.prevented) {
|
|
822
|
-
// If standardization is required:
|
|
823
|
-
if (['also', 'only'].includes(standard)) {
|
|
824
|
-
console.log('>>>>>> Standardizing');
|
|
825
|
-
// Initialize the standard result.
|
|
826
|
-
act.standardResult = {
|
|
827
|
-
totals: [0, 0, 0, 0],
|
|
828
|
-
instances: []
|
|
829
|
-
};
|
|
830
|
-
// Populate it.
|
|
831
|
-
standardize(act);
|
|
832
|
-
// Launch a browser and navigate to the page.
|
|
833
|
-
await launch(
|
|
834
|
-
report,
|
|
835
|
-
debug,
|
|
836
|
-
waits,
|
|
837
|
-
act.browserID || report.browserID || '',
|
|
838
|
-
act.target && act.target.url || report.target && report.target.url || ''
|
|
839
|
-
);
|
|
840
|
-
// If this failed:
|
|
841
|
-
if (page.prevented) {
|
|
842
|
-
// Add this to the act.
|
|
843
|
-
act.data ??= {};
|
|
844
|
-
act.data.prevented = true;
|
|
845
|
-
act.data.error = page.error || '';
|
|
846
|
-
}
|
|
847
|
-
// Otherwise, i.e. if it succeeded:
|
|
848
|
-
else {
|
|
849
|
-
// Add a box ID and a path ID to each of its standard instances if missing.
|
|
850
|
-
for (const instance of act.standardResult.instances) {
|
|
851
|
-
const elementID = await identify(instance, page);
|
|
852
|
-
if (! instance.boxID) {
|
|
853
|
-
instance.boxID = elementID ? elementID.boxID : '';
|
|
854
|
-
}
|
|
855
|
-
if (! instance.pathID) {
|
|
856
|
-
instance.pathID = elementID ? elementID.pathID : '';
|
|
857
|
-
}
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
// If the original-format result is not to be included in the report:
|
|
861
|
-
if (standard === 'only') {
|
|
862
|
-
// Remove it.
|
|
863
|
-
delete act.result;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
830
|
// If the act has expectations:
|
|
867
831
|
const expectations = act.expect;
|
|
868
832
|
if (expectations) {
|
|
@@ -892,7 +856,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
892
856
|
// Otherwise, if a current page exists:
|
|
893
857
|
else if (page) {
|
|
894
858
|
// If the act is navigation to a url:
|
|
895
|
-
if (
|
|
859
|
+
if (type === 'url') {
|
|
896
860
|
// Identify the URL.
|
|
897
861
|
const resolved = act.which.replace('__dirname', __dirname);
|
|
898
862
|
requestedURL = resolved;
|
|
@@ -926,7 +890,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
926
890
|
}
|
|
927
891
|
}
|
|
928
892
|
// Otherwise, if the act is a wait for text:
|
|
929
|
-
else if (
|
|
893
|
+
else if (type === 'wait') {
|
|
930
894
|
const {what, which} = act;
|
|
931
895
|
console.log(`>> ${what}`);
|
|
932
896
|
const result = act.result = {};
|
|
@@ -994,7 +958,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
994
958
|
}
|
|
995
959
|
}
|
|
996
960
|
// Otherwise, if the act is a wait for a state:
|
|
997
|
-
else if (
|
|
961
|
+
else if (type === 'state') {
|
|
998
962
|
// Wait for it.
|
|
999
963
|
const stateIndex = ['loaded', 'idle'].indexOf(act.which);
|
|
1000
964
|
await page.waitForLoadState(
|
|
@@ -1016,7 +980,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1016
980
|
}
|
|
1017
981
|
}
|
|
1018
982
|
// Otherwise, if the act is a page switch:
|
|
1019
|
-
else if (
|
|
983
|
+
else if (type === 'page') {
|
|
1020
984
|
// Wait for a page to be created and identify it as current.
|
|
1021
985
|
page = await browserContext.waitForEvent('page');
|
|
1022
986
|
// Wait until it is idle.
|
|
@@ -1033,7 +997,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1033
997
|
// Add the URL to the act.
|
|
1034
998
|
act.actualURL = url;
|
|
1035
999
|
// If the act is a revelation:
|
|
1036
|
-
if (
|
|
1000
|
+
if (type === 'reveal') {
|
|
1037
1001
|
act.result = {
|
|
1038
1002
|
success: true
|
|
1039
1003
|
};
|
|
@@ -1055,8 +1019,8 @@ const doActs = async (report, opts = {}) => {
|
|
|
1055
1019
|
});
|
|
1056
1020
|
}
|
|
1057
1021
|
// Otherwise, if the act is a move:
|
|
1058
|
-
else if (moves[
|
|
1059
|
-
const selector = typeof moves[
|
|
1022
|
+
else if (moves[type]) {
|
|
1023
|
+
const selector = typeof moves[type] === 'string' ? moves[type] : act.what;
|
|
1060
1024
|
// Try up to 5 times to:
|
|
1061
1025
|
act.result = {found: false};
|
|
1062
1026
|
let selection = {};
|
|
@@ -1165,18 +1129,18 @@ const doActs = async (report, opts = {}) => {
|
|
|
1165
1129
|
};
|
|
1166
1130
|
// FUNCTION DEFINITION END
|
|
1167
1131
|
// If the move is a button click, perform it.
|
|
1168
|
-
if (
|
|
1132
|
+
if (type === 'button') {
|
|
1169
1133
|
await selection.click({timeout: 3000});
|
|
1170
1134
|
act.result.success = true;
|
|
1171
1135
|
act.result.move = 'clicked';
|
|
1172
1136
|
}
|
|
1173
1137
|
// Otherwise, if it is checking a radio button or checkbox, perform it.
|
|
1174
|
-
else if (['checkbox', 'radio'].includes(
|
|
1138
|
+
else if (['checkbox', 'radio'].includes(type)) {
|
|
1175
1139
|
await selection.waitForElementState('stable', {timeout: 2000})
|
|
1176
1140
|
.catch(error => {
|
|
1177
|
-
console.log(`ERROR waiting for stable ${
|
|
1141
|
+
console.log(`ERROR waiting for stable ${type} (${error.message})`);
|
|
1178
1142
|
act.result.success = false;
|
|
1179
|
-
act.result.error = `ERROR waiting for stable ${
|
|
1143
|
+
act.result.error = `ERROR waiting for stable ${type}`;
|
|
1180
1144
|
});
|
|
1181
1145
|
if (! act.result.error) {
|
|
1182
1146
|
const isEnabled = await selection.isEnabled();
|
|
@@ -1186,9 +1150,9 @@ const doActs = async (report, opts = {}) => {
|
|
|
1186
1150
|
timeout: 2000
|
|
1187
1151
|
})
|
|
1188
1152
|
.catch(error => {
|
|
1189
|
-
console.log(`ERROR checking ${
|
|
1153
|
+
console.log(`ERROR checking ${type} (${error.message})`);
|
|
1190
1154
|
act.result.success = false;
|
|
1191
|
-
act.result.error = `ERROR checking ${
|
|
1155
|
+
act.result.error = `ERROR checking ${type}`;
|
|
1192
1156
|
});
|
|
1193
1157
|
if (! act.result.error) {
|
|
1194
1158
|
act.result.success = true;
|
|
@@ -1196,20 +1160,20 @@ const doActs = async (report, opts = {}) => {
|
|
|
1196
1160
|
}
|
|
1197
1161
|
}
|
|
1198
1162
|
else {
|
|
1199
|
-
const report = `ERROR: could not check ${
|
|
1163
|
+
const report = `ERROR: could not check ${type} because disabled`;
|
|
1200
1164
|
act.result.success = false;
|
|
1201
1165
|
act.result.error = report;
|
|
1202
1166
|
}
|
|
1203
1167
|
}
|
|
1204
1168
|
}
|
|
1205
1169
|
// Otherwise, if it is focusing the element, perform it.
|
|
1206
|
-
else if (
|
|
1170
|
+
else if (type === 'focus') {
|
|
1207
1171
|
await selection.focus({timeout: 2000});
|
|
1208
1172
|
act.result.success = true;
|
|
1209
1173
|
act.result.move = 'focused';
|
|
1210
1174
|
}
|
|
1211
1175
|
// Otherwise, if it is clicking a link:
|
|
1212
|
-
else if (
|
|
1176
|
+
else if (type === 'link') {
|
|
1213
1177
|
const href = await selection.getAttribute('href');
|
|
1214
1178
|
const target = await selection.getAttribute('target');
|
|
1215
1179
|
act.result.href = href || 'NONE';
|
|
@@ -1248,7 +1212,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1248
1212
|
}
|
|
1249
1213
|
}
|
|
1250
1214
|
// Otherwise, if it is selecting an option in a select list, perform it.
|
|
1251
|
-
else if (
|
|
1215
|
+
else if (type === 'select') {
|
|
1252
1216
|
const options = await selection.$$('option');
|
|
1253
1217
|
let optionText = '';
|
|
1254
1218
|
if (options && Array.isArray(options) && options.length) {
|
|
@@ -1271,7 +1235,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1271
1235
|
act.result.option = optionText;
|
|
1272
1236
|
}
|
|
1273
1237
|
// Otherwise, if it is entering text in an input element:
|
|
1274
|
-
else if (['text', 'search'].includes(
|
|
1238
|
+
else if (['text', 'search'].includes(type)) {
|
|
1275
1239
|
act.result.attributes = {};
|
|
1276
1240
|
const {attributes} = act.result;
|
|
1277
1241
|
const type = await selection.getAttribute('type');
|
|
@@ -1294,7 +1258,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1294
1258
|
act.result.success = true;
|
|
1295
1259
|
act.result.move = 'entered';
|
|
1296
1260
|
// If the input is a search input:
|
|
1297
|
-
if (
|
|
1261
|
+
if (type === 'search') {
|
|
1298
1262
|
// Press the Enter key and wait for a network to be idle.
|
|
1299
1263
|
doAndWait(false);
|
|
1300
1264
|
}
|
|
@@ -1318,7 +1282,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1318
1282
|
}
|
|
1319
1283
|
}
|
|
1320
1284
|
// Otherwise, if the act is a keypress:
|
|
1321
|
-
else if (
|
|
1285
|
+
else if (type === 'press') {
|
|
1322
1286
|
// Identify the number of times to press the key.
|
|
1323
1287
|
let times = 1 + (act.again || 0);
|
|
1324
1288
|
report.jobData.presses += times;
|
|
@@ -1334,7 +1298,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1334
1298
|
};
|
|
1335
1299
|
}
|
|
1336
1300
|
// Otherwise, if it is a repetitive keyboard navigation:
|
|
1337
|
-
else if (
|
|
1301
|
+
else if (type === 'presses') {
|
|
1338
1302
|
const {navKey, what, which, withItems} = act;
|
|
1339
1303
|
const matchTexts = which ? which.map(text => debloat(text)) : [];
|
|
1340
1304
|
// Initialize the loop variables.
|
|
@@ -1501,14 +1465,102 @@ const doActs = async (report, opts = {}) => {
|
|
|
1501
1465
|
}
|
|
1502
1466
|
}
|
|
1503
1467
|
console.log('Acts completed');
|
|
1468
|
+
// If standardization is required:
|
|
1469
|
+
if (['also', 'only'].includes(standard)) {
|
|
1470
|
+
// If granular reporting has been specified:
|
|
1471
|
+
if (report.observe) {
|
|
1472
|
+
// If a progress callback has been provided:
|
|
1473
|
+
if (onProgress) {
|
|
1474
|
+
// Notify the observer of the start of standardization.
|
|
1475
|
+
try {
|
|
1476
|
+
onProgress({
|
|
1477
|
+
type: 'standardization',
|
|
1478
|
+
which: 'start'
|
|
1479
|
+
});
|
|
1480
|
+
console.log(`${'Standardization started'} (observer notified)`);
|
|
1481
|
+
}
|
|
1482
|
+
catch (error) {
|
|
1483
|
+
console.log(`${message} (observer notification failed: ${errorStart(error)})`);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
// Otherwise, i.e. if no progress callback has been provided:
|
|
1487
|
+
else {
|
|
1488
|
+
// Notify the observer of the act and log it.
|
|
1489
|
+
tellServer(report, messageParams, message);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// Notify the observer and log the start of standardization.
|
|
1493
|
+
tellServer(report, '', 'Starting result standardization');
|
|
1494
|
+
}
|
|
1495
|
+
const launchSpecActs = {};
|
|
1496
|
+
// For each act:
|
|
1497
|
+
report.acts.forEach((act, index) => {
|
|
1498
|
+
// If it is a test act:
|
|
1499
|
+
if (act.type === 'test') {
|
|
1500
|
+
// Classify it by its browser ID and target URL.
|
|
1501
|
+
const specs = launchSpecs(act, report);
|
|
1502
|
+
const specString = `${specs[0]}>${specs[1]}`;
|
|
1503
|
+
if (launchSpecActs[specString]) {
|
|
1504
|
+
launchSpecActs[specString].push(index);
|
|
1505
|
+
}
|
|
1506
|
+
else {
|
|
1507
|
+
launchSpecActs[specString] = [index];
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
});
|
|
1511
|
+
// For each browser ID/target URL class:
|
|
1512
|
+
for (const specString of Object.keys(launchSpecActs)) {
|
|
1513
|
+
const specs = specString.split('>');
|
|
1514
|
+
// Launch a browser and navigate to the page.
|
|
1515
|
+
await launch(
|
|
1516
|
+
report,
|
|
1517
|
+
debug,
|
|
1518
|
+
waits,
|
|
1519
|
+
specs[0],
|
|
1520
|
+
specs[1]
|
|
1521
|
+
);
|
|
1522
|
+
// For each test act with the class:
|
|
1523
|
+
for (const specActIndex of launchSpecActs[specString]) {
|
|
1524
|
+
const act = report.acts[specActIndex];
|
|
1525
|
+
// Initialize the standard result.
|
|
1526
|
+
act.standardResult = {
|
|
1527
|
+
totals: [0, 0, 0, 0],
|
|
1528
|
+
instances: []
|
|
1529
|
+
};
|
|
1530
|
+
// Populate it.
|
|
1531
|
+
standardize(act);
|
|
1532
|
+
// If the launch and navigation succeeded:
|
|
1533
|
+
if (! page.prevented) {
|
|
1534
|
+
// Add a box ID and a path ID to each of its standard instances if missing.
|
|
1535
|
+
for (const instance of act.standardResult.instances) {
|
|
1536
|
+
const elementID = await identify(instance, page);
|
|
1537
|
+
if (! instance.boxID) {
|
|
1538
|
+
instance.boxID = elementID ? elementID.boxID : '';
|
|
1539
|
+
}
|
|
1540
|
+
if (! instance.pathID) {
|
|
1541
|
+
instance.pathID = elementID ? elementID.pathID : '';
|
|
1542
|
+
}
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
// If the original-format result is not to be included in the report:
|
|
1546
|
+
if (standard === 'only') {
|
|
1547
|
+
// Remove it.
|
|
1548
|
+
delete act.result;
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
};
|
|
1552
|
+
console.log('>>>> Standardization completed');
|
|
1553
|
+
}
|
|
1554
|
+
// Close the browser.
|
|
1504
1555
|
await browserClose();
|
|
1556
|
+
// Delete the temporary report file.
|
|
1505
1557
|
await fs.rm(reportPath, {force: true});
|
|
1506
1558
|
return report;
|
|
1507
1559
|
};
|
|
1508
1560
|
// Runs a job and returns a report.
|
|
1509
1561
|
exports.doJob = async (job, opts = {}) => {
|
|
1510
1562
|
// Make a report as a copy of the job.
|
|
1511
|
-
|
|
1563
|
+
report = JSON.parse(JSON.stringify(job));
|
|
1512
1564
|
const jobData = report.jobData = {};
|
|
1513
1565
|
// Get whether the job is valid and, if not, why.
|
|
1514
1566
|
const jobInvalidity = isValidJob(job);
|
|
@@ -1568,3 +1620,52 @@ exports.doJob = async (job, opts = {}) => {
|
|
|
1568
1620
|
// Return the report.
|
|
1569
1621
|
return report;
|
|
1570
1622
|
};
|
|
1623
|
+
|
|
1624
|
+
// CLEANUP HANDLERS
|
|
1625
|
+
|
|
1626
|
+
// Track whether cleanup is in progress.
|
|
1627
|
+
let cleanupInProgress = false;
|
|
1628
|
+
let browserCloseIntentional = false;
|
|
1629
|
+
|
|
1630
|
+
// Force-kills any Playwright browser processes synchronously.
|
|
1631
|
+
const forceKillBrowsers = () => {
|
|
1632
|
+
if (cleanupInProgress) {
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
cleanupInProgress = true;
|
|
1636
|
+
try {
|
|
1637
|
+
// Kill any Chromium headless shell processes.
|
|
1638
|
+
execSync('pkill -9 -f "chromium_headless_shell.*headless_shell"', {stdio: 'ignore'});
|
|
1639
|
+
}
|
|
1640
|
+
catch(error) {}
|
|
1641
|
+
};
|
|
1642
|
+
// Force-kills any headless shell processes synchronously on process exit.
|
|
1643
|
+
process.on('exit', () => {
|
|
1644
|
+
forceKillBrowsers();
|
|
1645
|
+
});
|
|
1646
|
+
// Force-kills any headless shell processes synchronously on beforeExit.
|
|
1647
|
+
process.on('beforeExit', async () => {
|
|
1648
|
+
if (!browserCloseIntentional) {
|
|
1649
|
+
await browserClose();
|
|
1650
|
+
}
|
|
1651
|
+
forceKillBrowsers();
|
|
1652
|
+
});
|
|
1653
|
+
// Force-kills any headless shell processes synchronously on uncaught exceptions.
|
|
1654
|
+
process.on('uncaughtException', async error => {
|
|
1655
|
+
console.error('Uncaught exception:', error);
|
|
1656
|
+
await browserClose();
|
|
1657
|
+
forceKillBrowsers();
|
|
1658
|
+
process.exit(1);
|
|
1659
|
+
});
|
|
1660
|
+
// Force-kills any headless shell processes synchronously on SIGINT.
|
|
1661
|
+
process.on('SIGINT', async () => {
|
|
1662
|
+
await browserClose();
|
|
1663
|
+
forceKillBrowsers();
|
|
1664
|
+
process.exit(0);
|
|
1665
|
+
});
|
|
1666
|
+
// Force-kills any headless shell processes synchronously on SIGTERM.
|
|
1667
|
+
process.on('SIGTERM', async () => {
|
|
1668
|
+
await browserClose();
|
|
1669
|
+
forceKillBrowsers();
|
|
1670
|
+
process.exit(0);
|
|
1671
|
+
});
|
package/testaro/adbID.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
Clean-room rule: report elements that reference aria-describedby targets that are missing or ambiguous (duplicate ids).
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
const {init,
|
|
31
|
+
const {init, getRuleResult} = require('../procs/testaro');
|
|
32
32
|
|
|
33
33
|
exports.reporter = async (page, withItems) => {
|
|
34
34
|
// elements that reference aria-describedby
|
|
@@ -63,5 +63,5 @@ exports.reporter = async (page, withItems) => {
|
|
|
63
63
|
'Referenced description of the element is ambiguous or missing',
|
|
64
64
|
'Referenced descriptions of elements are ambiguous or missing'
|
|
65
65
|
];
|
|
66
|
-
return await
|
|
66
|
+
return await getRuleResult(withItems, all, 'adbID', whats, 3);
|
|
67
67
|
};
|
package/testaro/altScheme.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
Identify img elements whose alt attribute is an entire URL or clearly a file name (favicon).
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
const {init,
|
|
31
|
+
const {init, getRuleResult} = require('../procs/testaro');
|
|
32
32
|
|
|
33
33
|
exports.reporter = async (page, withItems) => {
|
|
34
34
|
// Candidate images: any img with an alt attribute (including empty)
|
|
@@ -54,5 +54,5 @@ exports.reporter = async (page, withItems) => {
|
|
|
54
54
|
'Element has an alt attribute with a URL as its entire value',
|
|
55
55
|
'img elements have alt attributes with URLs as their entire values'
|
|
56
56
|
];
|
|
57
|
-
return await
|
|
57
|
+
return await getRuleResult(withItems, all, 'altScheme', whats, 2);
|
|
58
58
|
};
|
package/testaro/attVal.js
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
// ########## IMPORTS
|
|
31
31
|
|
|
32
32
|
// Module to perform common operations.
|
|
33
|
-
const {init,
|
|
33
|
+
const {init, getRuleResult} = require('../procs/testaro');
|
|
34
34
|
|
|
35
35
|
// ########## FUNCTIONS
|
|
36
36
|
|
|
@@ -54,5 +54,5 @@ exports.reporter = async (page, withItems, attributeName, areLicit, values) => {
|
|
|
54
54
|
`Element has attribute ${attributeName} with illicit value “__param__”`,
|
|
55
55
|
`Elements have attribute ${attributeName} with illicit values`
|
|
56
56
|
];
|
|
57
|
-
return await
|
|
57
|
+
return await getRuleResult(withItems, all, 'attVal', whats, 2);
|
|
58
58
|
};
|