testaro 60.4.0 → 60.5.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/AGENTS.md +8 -5
- package/README.md +105 -52
- package/UPGRADES.md +163 -0
- package/package.json +1 -1
- package/procs/doTestAct.js +27 -26
- package/procs/shoot.js +51 -0
- package/procs/tellServer.js +1 -1
- package/procs/visChange.js +2 -4
- package/run.js +41 -39
- package/testaro/motion.js +40 -23
- package/testaro/motionSolo.js +1 -1
- package/testaro/shoot0.js +26 -0
- package/testaro/shoot1.js +26 -0
- package/tests/testaro.js +26 -8
- package/tests/wave.js +3 -2
- package/testaro/shoot.js +0 -57
package/procs/doTestAct.js
CHANGED
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
|
|
33
33
|
// Module to perform file operations.
|
|
34
34
|
const fs = require('fs/promises');
|
|
35
|
-
|
|
35
|
+
//Modules to close and launch browsers.
|
|
36
|
+
const {browserClose, launch} = require(`${__dirname}/../run`);
|
|
36
37
|
// Module to set operating-system constants.
|
|
37
38
|
const os = require('os');
|
|
38
39
|
|
|
@@ -60,7 +61,7 @@ const doTestAct = async () => {
|
|
|
60
61
|
const act = report.acts[actIndex];
|
|
61
62
|
// Get the tool name.
|
|
62
63
|
const {which} = act;
|
|
63
|
-
// Launch a browser, navigate to the URL, and
|
|
64
|
+
// Launch a browser, navigate to the URL, and update the page export of the run module.
|
|
64
65
|
await launch(
|
|
65
66
|
report,
|
|
66
67
|
debug,
|
|
@@ -70,6 +71,8 @@ const doTestAct = async () => {
|
|
|
70
71
|
);
|
|
71
72
|
// If the launch aborted the job:
|
|
72
73
|
if (report.jobData && report.jobData.aborted) {
|
|
74
|
+
// Close any existing browser.
|
|
75
|
+
await browserClose();
|
|
73
76
|
// Save the revised report.
|
|
74
77
|
const reportJSON = JSON.stringify(report);
|
|
75
78
|
await fs.writeFile(reportPath, reportJSON);
|
|
@@ -78,37 +81,33 @@ const doTestAct = async () => {
|
|
|
78
81
|
}
|
|
79
82
|
// Otherwise, i.e. if the launch did not abort the job:
|
|
80
83
|
else {
|
|
81
|
-
// Get the
|
|
84
|
+
// Get the updated page.
|
|
82
85
|
const {page} = require('../run');
|
|
83
86
|
// If it exists:
|
|
84
87
|
if (page) {
|
|
85
88
|
try {
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
// Add the data and result to the act.
|
|
96
|
-
act.data = actReport.data;
|
|
97
|
-
act.result = actReport.result;
|
|
98
|
-
// If the tool reported that the page prevented testing:
|
|
99
|
-
if (act.data && act.data.prevented) {
|
|
100
|
-
// Add prevention data to the job data.
|
|
101
|
-
report.jobData.preventions[which] = act.data.error;
|
|
102
|
-
}
|
|
103
|
-
const reportJSON = JSON.stringify(report);
|
|
104
|
-
// Save the revised report.
|
|
105
|
-
await fs.writeFile(reportPath, reportJSON);
|
|
106
|
-
// Send a completion message.
|
|
107
|
-
process.send('Act completed');
|
|
89
|
+
// Make the act reporter perform the specified tests of the tool.
|
|
90
|
+
const actReport = await require(`../tests/${which}`).reporter(page, report, actIndex, 65);
|
|
91
|
+
// Add the data and result to the act.
|
|
92
|
+
act.data = actReport.data;
|
|
93
|
+
act.result = actReport.result;
|
|
94
|
+
// If the tool reported that the page prevented testing:
|
|
95
|
+
if (act.data && act.data.prevented) {
|
|
96
|
+
// Add prevention data to the job data.
|
|
97
|
+
report.jobData.preventions[which] = act.data.error;
|
|
108
98
|
}
|
|
99
|
+
// Close any existing browser.
|
|
100
|
+
await browserClose();
|
|
101
|
+
const reportJSON = JSON.stringify(report);
|
|
102
|
+
// Save the revised report.
|
|
103
|
+
await fs.writeFile(reportPath, reportJSON);
|
|
104
|
+
// Send a completion message.
|
|
105
|
+
process.send('Act completed');
|
|
109
106
|
}
|
|
110
107
|
// If the tool invocation failed:
|
|
111
108
|
catch(error) {
|
|
109
|
+
// Close any existing browser.
|
|
110
|
+
await browserClose();
|
|
112
111
|
// Save the revised report.
|
|
113
112
|
const reportJSON = JSON.stringify(report);
|
|
114
113
|
await fs.writeFile(reportPath, reportJSON);
|
|
@@ -126,8 +125,10 @@ const doTestAct = async () => {
|
|
|
126
125
|
act.data.error = 'No page';
|
|
127
126
|
// Add prevention data to the job data.
|
|
128
127
|
report.jobData.preventions[which] = act.data.error;
|
|
129
|
-
//
|
|
128
|
+
// Close any existing browser.
|
|
129
|
+
await browserClose();
|
|
130
130
|
const reportJSON = JSON.stringify(report);
|
|
131
|
+
// Save the revised report.
|
|
131
132
|
await fs.writeFile(reportPath, reportJSON);
|
|
132
133
|
// Report this.
|
|
133
134
|
const message = 'ERROR: No page';
|
package/procs/shoot.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
3
|
+
Licensed under the MIT License. See LICENSE file for details.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
shoot
|
|
8
|
+
Makes and saves as a PNG buffer file a full-page screenshot and returns the file path.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// IMPORTS
|
|
12
|
+
|
|
13
|
+
const fs = require('fs/promises');
|
|
14
|
+
const os = require('os');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const {PNG} = require('pngjs');
|
|
17
|
+
const {screenShot} = require('./screenShot');
|
|
18
|
+
|
|
19
|
+
// CONSTANTS
|
|
20
|
+
|
|
21
|
+
const tmpDir = os.tmpdir();
|
|
22
|
+
|
|
23
|
+
// FUNCTIONS
|
|
24
|
+
|
|
25
|
+
exports.shoot = async (page, index) => {
|
|
26
|
+
// Make and get a screenshot as a buffer.
|
|
27
|
+
let shot = await screenShot(page);
|
|
28
|
+
// If it succeeded:
|
|
29
|
+
if (shot.length) {
|
|
30
|
+
// Get the screenshot as an object representation of a PNG image.
|
|
31
|
+
let png = PNG.sync.read(shot);
|
|
32
|
+
shot = null;
|
|
33
|
+
const pngBuffer = PNG.sync.write(png);
|
|
34
|
+
png = null;
|
|
35
|
+
// Force garbage collection if available and if --expose-gc was a node option.
|
|
36
|
+
if (global.gc) {
|
|
37
|
+
global.gc();
|
|
38
|
+
}
|
|
39
|
+
const fileName = `testaro-shoot-${index}.png`;
|
|
40
|
+
const pngPath = path.join(tmpDir, fileName);
|
|
41
|
+
// Save the PNG buffer.
|
|
42
|
+
await fs.writeFile(pngPath, pngBuffer);
|
|
43
|
+
// Return the result.
|
|
44
|
+
return pngPath;
|
|
45
|
+
}
|
|
46
|
+
// Otherwise, i.e. if it failed:
|
|
47
|
+
else {
|
|
48
|
+
// Return this.
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
};
|
package/procs/tellServer.js
CHANGED
|
@@ -35,7 +35,7 @@ const agent = process.env.AGENT;
|
|
|
35
35
|
|
|
36
36
|
// FUNCTIONS
|
|
37
37
|
|
|
38
|
-
// Sends a
|
|
38
|
+
// Sends a notice to an observer.
|
|
39
39
|
exports.tellServer = (report, messageParams, logMessage) => {
|
|
40
40
|
const {serverID} = report.sources;
|
|
41
41
|
const observerURL = typeof serverID === 'number' ? process.env[`NETWATCH_URL_${serverID}_OBSERVE`] : '';
|
package/procs/visChange.js
CHANGED
|
@@ -70,7 +70,7 @@ exports.visChange = async (page, options = {}) => {
|
|
|
70
70
|
}
|
|
71
71
|
catch(error) {
|
|
72
72
|
return {
|
|
73
|
-
|
|
73
|
+
prevented: true,
|
|
74
74
|
error: 'Hovering failed'
|
|
75
75
|
};
|
|
76
76
|
}
|
|
@@ -91,7 +91,7 @@ exports.visChange = async (page, options = {}) => {
|
|
|
91
91
|
const changePercent = 100 * pixelChanges / (width * height);
|
|
92
92
|
// Return this.
|
|
93
93
|
return {
|
|
94
|
-
|
|
94
|
+
prevented: false,
|
|
95
95
|
width,
|
|
96
96
|
height,
|
|
97
97
|
pixelChanges,
|
|
@@ -102,7 +102,6 @@ exports.visChange = async (page, options = {}) => {
|
|
|
102
102
|
else {
|
|
103
103
|
// Return this.
|
|
104
104
|
return {
|
|
105
|
-
success: false,
|
|
106
105
|
prevented: true,
|
|
107
106
|
error: 'Second screenshot failed'
|
|
108
107
|
};
|
|
@@ -112,7 +111,6 @@ exports.visChange = async (page, options = {}) => {
|
|
|
112
111
|
else {
|
|
113
112
|
// Return this.
|
|
114
113
|
return {
|
|
115
|
-
success: false,
|
|
116
114
|
prevented: true,
|
|
117
115
|
error: 'First screenshot failed'
|
|
118
116
|
};
|
package/run.js
CHANGED
|
@@ -114,6 +114,8 @@ let actCount = 0;
|
|
|
114
114
|
// Facts about the current act.
|
|
115
115
|
let actIndex = 0;
|
|
116
116
|
let browser;
|
|
117
|
+
let cleanupInProgress = false;
|
|
118
|
+
let browserCloseIntentional = false;
|
|
117
119
|
let browserContext;
|
|
118
120
|
let page;
|
|
119
121
|
let report;
|
|
@@ -257,7 +259,6 @@ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
|
|
|
257
259
|
act.result.error ??= message;
|
|
258
260
|
if (act.type === 'test') {
|
|
259
261
|
act.data ??= {};
|
|
260
|
-
act.data.success = false;
|
|
261
262
|
act.data.prevented = true;
|
|
262
263
|
act.data.error = message;
|
|
263
264
|
// Add prevention data to the job data.
|
|
@@ -269,20 +270,20 @@ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
|
|
|
269
270
|
abortActs(report, actIndex);
|
|
270
271
|
}
|
|
271
272
|
};
|
|
272
|
-
// Closes
|
|
273
|
-
const browserClose = async () => {
|
|
273
|
+
// Closes any current browser.
|
|
274
|
+
const browserClose = exports.browserClose = async () => {
|
|
275
|
+
// If a browser exists:
|
|
274
276
|
if (browser) {
|
|
275
277
|
browserCloseIntentional = true;
|
|
278
|
+
// Try to close all its contexts and ignore any messages that they are already closed.
|
|
276
279
|
for (const context of browser.contexts()) {
|
|
277
280
|
try {
|
|
278
281
|
await context.close();
|
|
279
282
|
}
|
|
280
283
|
catch(error) {
|
|
281
|
-
console.log(
|
|
282
|
-
`ERROR trying to close context: ${error.message.slice(0, 200).replace(/\n.+/s, '')}`
|
|
283
|
-
);
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
|
+
// Close the browser.
|
|
286
287
|
await browser.close();
|
|
287
288
|
browserCloseIntentional = false;
|
|
288
289
|
browser = null;
|
|
@@ -303,7 +304,7 @@ const launch = exports.launch = async (
|
|
|
303
304
|
report.target.url = url;
|
|
304
305
|
// Create a browser of the specified or default type.
|
|
305
306
|
const browserType = playwrightBrowsers[browserID];
|
|
306
|
-
// Close
|
|
307
|
+
// Close any current browser.
|
|
307
308
|
await browserClose();
|
|
308
309
|
// Define browser options.
|
|
309
310
|
const browserOptions = {
|
|
@@ -414,7 +415,7 @@ const launch = exports.launch = async (
|
|
|
414
415
|
}
|
|
415
416
|
});
|
|
416
417
|
});
|
|
417
|
-
//
|
|
418
|
+
// Reassign the page variable to a new page (tab) of the context (window).
|
|
418
419
|
page = await browserContext.newPage();
|
|
419
420
|
// Wait until it is stable.
|
|
420
421
|
await page.waitForLoadState('domcontentloaded', {timeout: 5000});
|
|
@@ -678,6 +679,7 @@ const launchSpecs = (act, report) => [
|
|
|
678
679
|
// Performs the acts in a report and adds the results to the report.
|
|
679
680
|
const doActs = async (report, opts = {}) => {
|
|
680
681
|
const {acts} = report;
|
|
682
|
+
// Get the granular observation options, if any.
|
|
681
683
|
const {onProgress = null, signal = null} = opts;
|
|
682
684
|
// Get the standardization specification.
|
|
683
685
|
const standard = report.standard || 'only';
|
|
@@ -696,7 +698,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
696
698
|
if (report.observe) {
|
|
697
699
|
const whichParam = which ? `&which=${which}` : '';
|
|
698
700
|
const messageParams = `act=${type}${whichParam}`;
|
|
699
|
-
// If a progress callback has been provided:
|
|
701
|
+
// If a progress callback has been provided by a caller on this host:
|
|
700
702
|
if (onProgress) {
|
|
701
703
|
// Notify the observer of the act.
|
|
702
704
|
try {
|
|
@@ -712,7 +714,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
712
714
|
}
|
|
713
715
|
// Otherwise, i.e. if no progress callback has been provided:
|
|
714
716
|
else {
|
|
715
|
-
// Notify the observer of the act and log it.
|
|
717
|
+
// Notify the remote observer of the act and log it.
|
|
716
718
|
tellServer(report, messageParams, message);
|
|
717
719
|
}
|
|
718
720
|
}
|
|
@@ -769,7 +771,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
769
771
|
actLaunchSpecs[1]
|
|
770
772
|
);
|
|
771
773
|
// If this failed:
|
|
772
|
-
if (page
|
|
774
|
+
if (! page) {
|
|
773
775
|
// Add this to the act.
|
|
774
776
|
act.data ??= {};
|
|
775
777
|
act.data.prevented = true;
|
|
@@ -1511,7 +1513,7 @@ const doActs = async (report, opts = {}) => {
|
|
|
1511
1513
|
// For each browser ID/target URL class:
|
|
1512
1514
|
for (const specString of Object.keys(launchSpecActs)) {
|
|
1513
1515
|
const specs = specString.split('>');
|
|
1514
|
-
//
|
|
1516
|
+
// Replace the browser and navigate to the URL.
|
|
1515
1517
|
await launch(
|
|
1516
1518
|
report,
|
|
1517
1519
|
debug,
|
|
@@ -1519,18 +1521,18 @@ const doActs = async (report, opts = {}) => {
|
|
|
1519
1521
|
specs[0],
|
|
1520
1522
|
specs[1]
|
|
1521
1523
|
);
|
|
1522
|
-
//
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1524
|
+
// If the launch and navigation succeeded:
|
|
1525
|
+
if (page) {
|
|
1526
|
+
// For each test act in the class:
|
|
1527
|
+
for (const specActIndex of launchSpecActs[specString]) {
|
|
1528
|
+
const act = report.acts[specActIndex];
|
|
1529
|
+
// Initialize the standard result.
|
|
1530
|
+
act.standardResult = {
|
|
1531
|
+
totals: [0, 0, 0, 0],
|
|
1532
|
+
instances: []
|
|
1533
|
+
};
|
|
1534
|
+
// Populate it.
|
|
1535
|
+
standardize(act);
|
|
1534
1536
|
// Add a box ID and a path ID to each of its standard instances if missing.
|
|
1535
1537
|
for (const instance of act.standardResult.instances) {
|
|
1536
1538
|
const elementID = await identify(instance, page);
|
|
@@ -1541,18 +1543,22 @@ const doActs = async (report, opts = {}) => {
|
|
|
1541
1543
|
instance.pathID = elementID ? elementID.pathID : '';
|
|
1542
1544
|
}
|
|
1543
1545
|
};
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1546
|
+
// If the original-format result is not to be included in the report:
|
|
1547
|
+
if (standard === 'only') {
|
|
1548
|
+
// Remove it.
|
|
1549
|
+
delete act.result;
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
// Otherwise, i.e. if the launch or navigation failed:
|
|
1554
|
+
else {
|
|
1555
|
+
console.log(`ERROR: Launch or navigation to standardize ${specString} acts failed`);
|
|
1556
|
+
}
|
|
1551
1557
|
};
|
|
1552
|
-
|
|
1558
|
+
// Close the last browser launched for standardization.
|
|
1559
|
+
await browserClose();
|
|
1560
|
+
console.log('Standardization completed');
|
|
1553
1561
|
}
|
|
1554
|
-
// Close the browser.
|
|
1555
|
-
await browserClose();
|
|
1556
1562
|
// Delete the temporary report file.
|
|
1557
1563
|
await fs.rm(reportPath, {force: true});
|
|
1558
1564
|
return report;
|
|
@@ -1598,7 +1604,7 @@ exports.doJob = async (job, opts = {}) => {
|
|
|
1598
1604
|
process.exit();
|
|
1599
1605
|
}
|
|
1600
1606
|
});
|
|
1601
|
-
// Perform the acts and get a report.
|
|
1607
|
+
// Perform the acts with any specified same-host observation options and get a report.
|
|
1602
1608
|
report = await doActs(report, opts);
|
|
1603
1609
|
// Add the end time and duration to the report.
|
|
1604
1610
|
const endTime = new Date();
|
|
@@ -1623,10 +1629,6 @@ exports.doJob = async (job, opts = {}) => {
|
|
|
1623
1629
|
|
|
1624
1630
|
// CLEANUP HANDLERS
|
|
1625
1631
|
|
|
1626
|
-
// Track whether cleanup is in progress.
|
|
1627
|
-
let cleanupInProgress = false;
|
|
1628
|
-
let browserCloseIntentional = false;
|
|
1629
|
-
|
|
1630
1632
|
// Force-kills any Playwright browser processes synchronously.
|
|
1631
1633
|
const forceKillBrowsers = () => {
|
|
1632
1634
|
if (cleanupInProgress) {
|
package/testaro/motion.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/*
|
|
7
7
|
motion
|
|
8
8
|
This test reports motion in a page by comparing the first and last of the screenshots previously
|
|
9
|
-
made by the
|
|
9
|
+
made by the shoot0 and shoot1 tests.
|
|
10
10
|
|
|
11
11
|
For minimal accessibility, standards require motion to be brief, or else stoppable by the user.
|
|
12
12
|
But stopping motion can be difficult or impossible, and, by the time a user manages to stop
|
|
@@ -21,9 +21,18 @@
|
|
|
21
21
|
|
|
22
22
|
// IMPORTS
|
|
23
23
|
|
|
24
|
-
// Module to
|
|
24
|
+
// Module to process files.
|
|
25
|
+
const fs = require('fs/promises');
|
|
26
|
+
// Module to get operating-system properties.
|
|
27
|
+
const os = require('os');
|
|
28
|
+
// Module to compare screenshots.
|
|
25
29
|
const pixelmatch = require('pixelmatch').default;
|
|
26
|
-
|
|
30
|
+
// Module to parse PNGs.
|
|
31
|
+
const {PNG} = require('pngjs');
|
|
32
|
+
|
|
33
|
+
// CONSTANTS
|
|
34
|
+
|
|
35
|
+
const tmpDir = os.tmpdir();
|
|
27
36
|
|
|
28
37
|
// FUNCTIONS
|
|
29
38
|
|
|
@@ -33,29 +42,32 @@ exports.reporter = async page => {
|
|
|
33
42
|
const data = {};
|
|
34
43
|
const totals = [0, 0, 0, 0];
|
|
35
44
|
const standardInstances = [];
|
|
36
|
-
// Get the
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
// Get the screenshot PNG buffers made by the shoot0 and shoot1 tests.
|
|
46
|
+
let shoot0PNGBuffer = await fs.readFile(`${tmpDir}/testaro-shoot-0.png`);
|
|
47
|
+
let shoot1PNGBuffer = await fs.readFile(`${tmpDir}/testaro-shoot-1.png`);
|
|
48
|
+
// Delete the buffers files.
|
|
49
|
+
await fs.unlink(`${tmpDir}/testaro-shoot-0.png`);
|
|
50
|
+
await fs.unlink(`${tmpDir}/testaro-shoot-1.png`);
|
|
51
|
+
// If both buffers exist:
|
|
52
|
+
if (shoot0PNGBuffer && shoot1PNGBuffer) {
|
|
53
|
+
// Parse them into PNG objects.
|
|
54
|
+
let shoot0PNG = PNG.sync.read(shoot0PNGBuffer);
|
|
55
|
+
let shoot1PNG = PNG.sync.read(shoot1PNGBuffer);
|
|
45
56
|
// If their dimensions differ:
|
|
46
|
-
if (width !==
|
|
57
|
+
if (shoot1PNG.width !== shoot0PNG.width || shoot1PNG.height !== shoot0PNG.height) {
|
|
47
58
|
// Report this.
|
|
48
59
|
data.prevented = true;
|
|
49
|
-
data.error = '
|
|
60
|
+
data.error = 'Screenshot dimensions differ';
|
|
50
61
|
}
|
|
51
62
|
// Otherwise, i.e. if their dimensions are identical:
|
|
52
63
|
else {
|
|
64
|
+
const {width, height} = shoot0PNG;
|
|
53
65
|
// Get the count of differing pixels between the shots.
|
|
54
|
-
const pixelChanges = pixelmatch(
|
|
66
|
+
const pixelChanges = pixelmatch(shoot0PNG.data, shoot1PNG.data, null, width, height);
|
|
55
67
|
// Get the ratio of differing to all pixels as a percentage.
|
|
56
68
|
const changePercent = 100 * pixelChanges / (width * height);
|
|
57
69
|
// Free the memory used by screenshots.
|
|
58
|
-
|
|
70
|
+
shoot0PNG = shoot1PNG = shoot0PNGBuffer = shoot1PNGBuffer = null;
|
|
59
71
|
// If any pixels were changed:
|
|
60
72
|
if (pixelChanges) {
|
|
61
73
|
// Get the ordinal severity from the fractional pixel change.
|
|
@@ -65,26 +77,31 @@ exports.reporter = async page => {
|
|
|
65
77
|
// Get a summary standard instance.
|
|
66
78
|
standardInstances.push({
|
|
67
79
|
ruleID: 'motion',
|
|
68
|
-
what:
|
|
80
|
+
what: `Content moves or changes spontaneously (${changePercent}% of pixels changed)`,
|
|
69
81
|
count: 1,
|
|
70
82
|
ordinalSeverity,
|
|
71
83
|
tagName: 'HTML',
|
|
72
84
|
id: '',
|
|
73
85
|
location: {
|
|
74
|
-
doc: '',
|
|
75
|
-
type: '',
|
|
76
|
-
spec:
|
|
86
|
+
doc: 'dom',
|
|
87
|
+
type: 'box',
|
|
88
|
+
spec: {
|
|
89
|
+
x: 0,
|
|
90
|
+
y: 0,
|
|
91
|
+
width,
|
|
92
|
+
height
|
|
93
|
+
}
|
|
77
94
|
},
|
|
78
|
-
excerpt: ''
|
|
95
|
+
excerpt: '<html>…</html>'
|
|
79
96
|
});
|
|
80
97
|
}
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
|
-
// Otherwise, i.e. if
|
|
100
|
+
// Otherwise, i.e. if they do not both exist:
|
|
84
101
|
else {
|
|
85
102
|
// Report this.
|
|
86
103
|
data.prevented = true;
|
|
87
|
-
data.error = '
|
|
104
|
+
data.error = 'At least 1 screenshot missing';
|
|
88
105
|
}
|
|
89
106
|
// Return the result.
|
|
90
107
|
return {
|
package/testaro/motionSolo.js
CHANGED
|
@@ -61,7 +61,7 @@ exports.reporter = async page => {
|
|
|
61
61
|
delayBetween: 3000
|
|
62
62
|
});
|
|
63
63
|
// If the screenshots succeeded:
|
|
64
|
-
if (data.
|
|
64
|
+
if (! data.prevented) {
|
|
65
65
|
// If any pixels were changed:
|
|
66
66
|
if (data.pixelChanges) {
|
|
67
67
|
// Get the ordinal severity from the fractional pixel change.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
3
|
+
Licensed under the MIT License. See LICENSE file for details.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
shoot0
|
|
8
|
+
This test makes and saves the first of two screenshots.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// IMPORTS
|
|
12
|
+
|
|
13
|
+
const {shoot} = require('../procs/shoot');
|
|
14
|
+
|
|
15
|
+
// FUNCTIONS
|
|
16
|
+
|
|
17
|
+
exports.reporter = async page => {
|
|
18
|
+
// Make and save the first screenshot.
|
|
19
|
+
const pngPath = await shoot(page, 0);
|
|
20
|
+
// Return the file path or a failure result.
|
|
21
|
+
return {
|
|
22
|
+
data: {
|
|
23
|
+
prevented: ! pngPath
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/*
|
|
2
|
+
© 2025 Jonathan Robert Pool. All rights reserved.
|
|
3
|
+
Licensed under the MIT License. See LICENSE file for details.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
shoot1
|
|
8
|
+
This test makes and saves the second of two screenshots.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// IMPORTS
|
|
12
|
+
|
|
13
|
+
const {shoot} = require('../procs/shoot');
|
|
14
|
+
|
|
15
|
+
// FUNCTIONS
|
|
16
|
+
|
|
17
|
+
exports.reporter = async page => {
|
|
18
|
+
// Make and save the second screenshot.
|
|
19
|
+
const pngPath = await shoot(page, 1);
|
|
20
|
+
// Return the file path or a failure result.
|
|
21
|
+
return {
|
|
22
|
+
data: {
|
|
23
|
+
prevented: ! pngPath
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
package/tests/testaro.js
CHANGED
|
@@ -42,8 +42,8 @@ const fs = require('fs/promises');
|
|
|
42
42
|
// Metadata of all rules in default execution order.
|
|
43
43
|
const allRules = [
|
|
44
44
|
{
|
|
45
|
-
id: '
|
|
46
|
-
what: 'page screenshot',
|
|
45
|
+
id: 'shoot0',
|
|
46
|
+
what: 'first page screenshot',
|
|
47
47
|
contaminator: false,
|
|
48
48
|
timeOut: 5,
|
|
49
49
|
defaultOn: true
|
|
@@ -350,8 +350,8 @@ const allRules = [
|
|
|
350
350
|
defaultOn: true
|
|
351
351
|
},
|
|
352
352
|
{
|
|
353
|
-
id: '
|
|
354
|
-
what: 'page screenshot',
|
|
353
|
+
id: 'shoot1',
|
|
354
|
+
what: 'second page screenshot',
|
|
355
355
|
contaminator: false,
|
|
356
356
|
timeOut: 5,
|
|
357
357
|
defaultOn: true
|
|
@@ -455,7 +455,7 @@ process.on('unhandledRejection', reason => {
|
|
|
455
455
|
console.error(`ERROR: Unhandled Promise Rejection (${reason})`);
|
|
456
456
|
});
|
|
457
457
|
|
|
458
|
-
//
|
|
458
|
+
// FUNCTIONS
|
|
459
459
|
|
|
460
460
|
// Conducts a JSON-defined test.
|
|
461
461
|
const jsonTest = async (ruleID, ruleArgs) => {
|
|
@@ -503,7 +503,7 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
503
503
|
rulesInvalid: [],
|
|
504
504
|
ruleTestTimes: {}
|
|
505
505
|
};
|
|
506
|
-
const result =
|
|
506
|
+
const result = {};
|
|
507
507
|
const allRuleIDs = allRules.map(rule => rule.id);
|
|
508
508
|
// If the rule specification is invalid:
|
|
509
509
|
if (! (
|
|
@@ -559,6 +559,7 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
559
559
|
// Report crashes and disconnections during this test.
|
|
560
560
|
let crashHandler;
|
|
561
561
|
let disconnectHandler;
|
|
562
|
+
// Get the current browser.
|
|
562
563
|
const {browser} = require('../run');
|
|
563
564
|
if (page && ! page.isClosed()) {
|
|
564
565
|
crashHandler = () => {
|
|
@@ -631,7 +632,10 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
631
632
|
// Prevent a retry of the test.
|
|
632
633
|
testSuccess = true;
|
|
633
634
|
// If testing is to stop after a failure and the page failed the test:
|
|
634
|
-
if (
|
|
635
|
+
if (
|
|
636
|
+
stopOnFail
|
|
637
|
+
&& ruleOrTimeoutReport.totals
|
|
638
|
+
&& ruleOrTimeoutReport.totals.some(total => total)) {
|
|
635
639
|
// Stop testing.
|
|
636
640
|
break;
|
|
637
641
|
}
|
|
@@ -693,9 +697,11 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
693
697
|
// Clear the error listeners.
|
|
694
698
|
if (page && ! page.isClosed() && crashHandler) {
|
|
695
699
|
page.off('crash', crashHandler);
|
|
700
|
+
crashHandler = null;
|
|
696
701
|
}
|
|
697
702
|
if (browser && disconnectHandler) {
|
|
698
703
|
browser.off('disconnected', disconnectHandler);
|
|
704
|
+
disconnectHandler = null;
|
|
699
705
|
}
|
|
700
706
|
}
|
|
701
707
|
// Otherwise, i.e. if the rule is undefined or doubly defined:
|
|
@@ -704,10 +710,22 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
704
710
|
data.rulesInvalid.push(rule);
|
|
705
711
|
console.log(`ERROR: Rule ${rule.id} not validly defined`);
|
|
706
712
|
// Clear the crash listener.
|
|
707
|
-
if (page && ! page.isClosed()) {
|
|
713
|
+
if (page && ! page.isClosed() && crashHandler) {
|
|
708
714
|
page.off('crash', crashHandler);
|
|
715
|
+
crashHandler = null;
|
|
716
|
+
}
|
|
717
|
+
if (browser && disconnectHandler) {
|
|
718
|
+
browser.off('disconnected', disconnectHandler);
|
|
719
|
+
disconnectHandler = null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
// Force a garbage collection.
|
|
723
|
+
try {
|
|
724
|
+
if (global.gc) {
|
|
725
|
+
global.gc();
|
|
709
726
|
}
|
|
710
727
|
}
|
|
728
|
+
catch(error) {}
|
|
711
729
|
};
|
|
712
730
|
// Record the test times in descending order.
|
|
713
731
|
testTimes.sort((a, b) => b[1] - a[1]).forEach(pair => {
|
package/tests/wave.js
CHANGED
|
@@ -75,9 +75,10 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
75
75
|
});
|
|
76
76
|
// When they arrive:
|
|
77
77
|
response.on('end', async () => {
|
|
78
|
+
let actResult = {};
|
|
78
79
|
// Delete unnecessary properties.
|
|
79
80
|
try {
|
|
80
|
-
|
|
81
|
+
actResult = JSON.parse(rawReport);
|
|
81
82
|
const {categories, statistics} = actResult;
|
|
82
83
|
delete categories.feature;
|
|
83
84
|
delete categories.structure;
|
|
@@ -141,7 +142,7 @@ exports.reporter = async (page, report, actIndex) => {
|
|
|
141
142
|
catch(error) {
|
|
142
143
|
data.prevented = true;
|
|
143
144
|
data.error = error.message;
|
|
144
|
-
resolve(
|
|
145
|
+
resolve(actResult);
|
|
145
146
|
};
|
|
146
147
|
});
|
|
147
148
|
}
|