testaro 12.7.3 → 13.0.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/README.md +8 -0
- package/dirWatch.js +47 -0
- package/package.json +1 -1
- package/run.js +32 -31
- package/tests/dupAtt.js +0 -2
- package/tests/ibm.js +1 -1
- package/watch.js +4 -5
package/README.md
CHANGED
|
@@ -639,6 +639,14 @@ The URL from which Testaro requests jobs is given by the fourth passed argument,
|
|
|
639
639
|
|
|
640
640
|
The URL to which Testaro sends reports is given by the `sources.sendReportTo` property of each job, if the property exists, or, if not, by `process.env.REPORT_URL`.
|
|
641
641
|
|
|
642
|
+
##### Job isolation
|
|
643
|
+
|
|
644
|
+
If you execute a repeating watch and the watch process becomes corrupted, the corruption can damage an indefinite subsequent sequence of job performances. Under some conditions, for example, Playwright has been observed to throw errors with a message starting with “Navigation timeout of 30000 ms exceeded” and ending with the entire HTML content of the web page, even when page navigation has been subjected to shorter time limits. Thereafter, Playwright issues that error message again about every 15 seconds, even if the browser has been closed.
|
|
645
|
+
|
|
646
|
+
To counteract such corruption, you can perform repeating directory watches with job isolation. To do this, use the statement `node dirWatch n`, where `n` is the number of seconds Testaro should wait after finding no job before looking again. The `dirWatch` module spawns a new process for each job, and also shrinks Playwright error messages before they are logged on the console.
|
|
647
|
+
|
|
648
|
+
You can stop a repeating directory watch of this kind by entering `CTRL-c`.
|
|
649
|
+
|
|
642
650
|
### Environment variables
|
|
643
651
|
|
|
644
652
|
In addition to their uses described above, environment variables can be used by acts of type `text`, as documented in the `actSpecs.js` file.
|
package/dirWatch.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
dirWatch.js
|
|
3
|
+
Module for launching a one-time directory watch.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ########## IMPORTS
|
|
7
|
+
|
|
8
|
+
// Module to spawn a child process.
|
|
9
|
+
const {spawn} = require('node:child_process');
|
|
10
|
+
|
|
11
|
+
// ########## CONSTANTS
|
|
12
|
+
|
|
13
|
+
const interval = process.argv[2];
|
|
14
|
+
|
|
15
|
+
// ########## FUNCTIONS
|
|
16
|
+
|
|
17
|
+
// Spawns a one-time directory watch.
|
|
18
|
+
const spawnWatch = (command, args) => spawn(command, args, {stdio: ['inherit', 'inherit', 'pipe']});
|
|
19
|
+
// Repeatedly spawns a one-time directory watch.
|
|
20
|
+
const reWatch = () => {
|
|
21
|
+
const watcher = spawnWatch('node', ['call', 'watch', 'true', 'false', interval]);
|
|
22
|
+
let error = '';
|
|
23
|
+
watcher.stderr.on('data', data => {
|
|
24
|
+
error += data.toString();
|
|
25
|
+
});
|
|
26
|
+
watcher.on('close', async code => {
|
|
27
|
+
if (error) {
|
|
28
|
+
if (error.startsWith('Navigation timeout of 30000 ms exceeded')) {
|
|
29
|
+
console.log('ERROR: Playwright claims 30-second timeout exceeded');
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
console.log(`ERROR: ${error.slice(0, 200)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (code === 0) {
|
|
36
|
+
console.log('Watcher exited successfully');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(`Watcher exited with error code ${code}`);
|
|
40
|
+
}
|
|
41
|
+
reWatch();
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ########## OPERATION
|
|
46
|
+
|
|
47
|
+
reWatch();
|
package/package.json
CHANGED
package/run.js
CHANGED
|
@@ -302,11 +302,15 @@ const nowString = () => (new Date()).toISOString().slice(0, 19);
|
|
|
302
302
|
// Closes the current browser.
|
|
303
303
|
const browserClose = async () => {
|
|
304
304
|
if (browser) {
|
|
305
|
-
const
|
|
305
|
+
const browserType = browser.browserType().name();
|
|
306
|
+
let contexts = browser.contexts();
|
|
306
307
|
for (const context of contexts) {
|
|
307
308
|
await context.close();
|
|
309
|
+
contexts = browser.contexts();
|
|
308
310
|
}
|
|
309
311
|
await browser.close();
|
|
312
|
+
browser = null;
|
|
313
|
+
console.log(`${browserType} browser closed`);
|
|
310
314
|
}
|
|
311
315
|
};
|
|
312
316
|
// Returns the first line of an error message.
|
|
@@ -319,7 +323,12 @@ const launch = async (report, typeName, lowMotion = false) => {
|
|
|
319
323
|
// Close the current browser, if any.
|
|
320
324
|
await browserClose();
|
|
321
325
|
// Launch a browser of that type.
|
|
322
|
-
const browserOptions = {
|
|
326
|
+
const browserOptions = {
|
|
327
|
+
logger: {
|
|
328
|
+
isEnabled: (name, severity) => false,
|
|
329
|
+
log: (name, severity, message, args) => console.log(message.slice(0, 100))
|
|
330
|
+
}
|
|
331
|
+
};
|
|
323
332
|
if (debug) {
|
|
324
333
|
browserOptions.headless = false;
|
|
325
334
|
}
|
|
@@ -375,7 +384,10 @@ const launch = async (report, typeName, lowMotion = false) => {
|
|
|
375
384
|
report.jobData.errorLogSize += msgLength;
|
|
376
385
|
}
|
|
377
386
|
const msgLC = msgText.toLowerCase();
|
|
378
|
-
if (
|
|
387
|
+
if (
|
|
388
|
+
msgText.includes('403') && (msgLC.includes('status')
|
|
389
|
+
|| msgLC.includes('prohibited'))
|
|
390
|
+
) {
|
|
379
391
|
report.jobData.prohibitedCount++;
|
|
380
392
|
}
|
|
381
393
|
});
|
|
@@ -694,41 +706,31 @@ const doActs = async (report, actIndex, page) => {
|
|
|
694
706
|
// Identify the URL.
|
|
695
707
|
const resolved = act.which.replace('__dirname', __dirname);
|
|
696
708
|
requestedURL = resolved;
|
|
697
|
-
// Visit it and wait until the network is idle.
|
|
698
709
|
const {strict} = report;
|
|
699
|
-
|
|
710
|
+
// Visit it and wait until the DOM is loaded.
|
|
711
|
+
response = await goTo(report, page, requestedURL, 15000, 'domcontentloaded', strict);
|
|
700
712
|
// If the visit fails:
|
|
701
713
|
if (response.error) {
|
|
702
|
-
//
|
|
714
|
+
// Launch another browser type.
|
|
715
|
+
const newBrowserName = Object.keys(browserTypeNames)
|
|
716
|
+
.find(name => name !== browserTypeName);
|
|
717
|
+
console.log(`>> Launching ${newBrowserName} instead`);
|
|
718
|
+
await launch(newBrowserName);
|
|
719
|
+
// Identify its only page as current.
|
|
720
|
+
page = browserContext.pages()[0];
|
|
721
|
+
// Visit the URL and wait until the DOM is loaded.
|
|
703
722
|
response = await goTo(report, page, requestedURL, 10000, 'domcontentloaded', strict);
|
|
704
723
|
// If the visit fails:
|
|
705
724
|
if (response.error) {
|
|
706
|
-
//
|
|
707
|
-
|
|
708
|
-
.find(name => name !== browserTypeName);
|
|
709
|
-
console.log(`>> Launching ${newBrowserName} instead`);
|
|
710
|
-
await launch(newBrowserName);
|
|
711
|
-
// Identify its only page as current.
|
|
712
|
-
page = browserContext.pages()[0];
|
|
713
|
-
// Try again until the network is idle.
|
|
714
|
-
response = await goTo(report, page, requestedURL, 10000, 'networkidle', strict);
|
|
725
|
+
// Try again and wait until a load.
|
|
726
|
+
response = await goTo(report, page, requestedURL, 5000, 'load', strict);
|
|
715
727
|
// If the visit fails:
|
|
716
728
|
if (response.error) {
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
response = await goTo(report, page, requestedURL, 5000, 'load', strict);
|
|
723
|
-
// If the visit fails:
|
|
724
|
-
if (response.error) {
|
|
725
|
-
// Navigate to a blank page instead.
|
|
726
|
-
await page.goto('about:blank')
|
|
727
|
-
.catch(error => {
|
|
728
|
-
console.log(`ERROR: Navigation to blank page failed (${error.message})`);
|
|
729
|
-
});
|
|
730
|
-
}
|
|
731
|
-
}
|
|
729
|
+
// Navigate to a blank page instead.
|
|
730
|
+
await page.goto('about:blank')
|
|
731
|
+
.catch(error => {
|
|
732
|
+
console.log(`ERROR: Navigation to blank page failed (${error.message})`);
|
|
733
|
+
});
|
|
732
734
|
}
|
|
733
735
|
}
|
|
734
736
|
}
|
|
@@ -1505,7 +1507,6 @@ const doActs = async (report, actIndex, page) => {
|
|
|
1505
1507
|
else if (! report.jobData.abortTime) {
|
|
1506
1508
|
console.log('Acts completed');
|
|
1507
1509
|
await browserClose();
|
|
1508
|
-
console.log('Browser closed');
|
|
1509
1510
|
}
|
|
1510
1511
|
};
|
|
1511
1512
|
/*
|
package/tests/dupAtt.js
CHANGED
|
@@ -50,14 +50,12 @@ exports.reporter = async (page, withItems) => {
|
|
|
50
50
|
rawPage = rawPage.replace(/<(script [^<>]+)>.*?<\/script>/g, '$1');
|
|
51
51
|
// Remove any comments from it.
|
|
52
52
|
rawPage = rawPage.replace(/<!--.*?-->/g, '');
|
|
53
|
-
console.log(rawPage);
|
|
54
53
|
// Extract the opening tags of its elements.
|
|
55
54
|
let elements = rawPage.match(/<[a-zA-Z][^<>]+>/g);
|
|
56
55
|
// Delete their enclosing angle brackets and the values of any attributes in them.
|
|
57
56
|
elements = elements.map(el => el.replace(/^< *|=[^ ]*| *>$/g, ''));
|
|
58
57
|
// For each element:
|
|
59
58
|
elements.forEach(element => {
|
|
60
|
-
console.log(element);
|
|
61
59
|
// Identify its tag name and attributes.
|
|
62
60
|
const terms = element.split(' ');
|
|
63
61
|
// If it has 2 or more attributes:
|
package/tests/ibm.js
CHANGED
|
@@ -26,7 +26,7 @@ const run = async (content, timeLimit) => {
|
|
|
26
26
|
});
|
|
27
27
|
// Return the result of the test, or null if it timed out.
|
|
28
28
|
try {
|
|
29
|
-
const ibmReport =
|
|
29
|
+
const ibmReport = getCompliance(content, nowLabel);
|
|
30
30
|
const result = await Promise.race([ibmReport, timeout]);
|
|
31
31
|
clearTimeout(timeoutID);
|
|
32
32
|
return result;
|
package/watch.js
CHANGED
|
@@ -214,14 +214,13 @@ const runJob = async (job, isDirWatch) => {
|
|
|
214
214
|
}
|
|
215
215
|
};
|
|
216
216
|
// Checks for a job, performs it, and submits a report, once or repeatedly.
|
|
217
|
-
exports.cycle = async (isDirWatch, isForever, interval, watchee = null) => {
|
|
218
|
-
const intervalMS = 1000 * Number.parseInt(interval);
|
|
217
|
+
exports.cycle = async (isDirWatch, isForever, interval = 300, watchee = null) => {
|
|
219
218
|
let statusOK = true;
|
|
220
219
|
// Prevent a wait before the first iteration.
|
|
221
220
|
let empty = false;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
);
|
|
221
|
+
const intervalMS = 1000 * Number.parseInt(interval);
|
|
222
|
+
const intervalSpec = isForever ? `with intervals of ${interval} seconds when idle ` : '';
|
|
223
|
+
console.log(`Watching started ${intervalSpec}(${nowString()})`);
|
|
225
224
|
while (statusOK) {
|
|
226
225
|
if (empty) {
|
|
227
226
|
await wait(intervalMS);
|