testaro 5.2.1 → 5.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/.eslintrc.json +41 -0
- package/README.md +52 -2
- package/create.js +83 -12
- package/high.js +2 -6
- package/htmlcs/.eslintrc.json +67 -0
- package/htmlcs/HTMLCS.js +3792 -3557
- package/package.json +1 -1
- package/run.js +7 -2
- package/runHost.js +36 -0
- package/tests/hover.js +4 -1
- package/tests/htmlcs.js +4 -4
- package/tests/ibm.js +2 -2
- package/tests/role.js +77 -18
- package/tests/tabNav.js +3 -1
- package/htmlcs/HTMLCS.css +0 -1069
- package/htmlcs/Images/HTMLCS-tools.png +0 -0
- package/htmlcs/Images/bgTexture1.gif +0 -0
- package/htmlcs/Images/summaryLoader-error.gif +0 -0
- package/htmlcs/Images/summaryLoader-notice.gif +0 -0
- package/htmlcs/Images/summaryLoader-warning.gif +0 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": true,
|
|
4
|
+
"commonjs": true,
|
|
5
|
+
"es2021": true,
|
|
6
|
+
"node": true
|
|
7
|
+
},
|
|
8
|
+
"extends": "eslint:recommended",
|
|
9
|
+
"parserOptions": {
|
|
10
|
+
"ecmaVersion": 2021
|
|
11
|
+
},
|
|
12
|
+
"rules": {
|
|
13
|
+
"indent": [
|
|
14
|
+
"error",
|
|
15
|
+
2,
|
|
16
|
+
{
|
|
17
|
+
"MemberExpression": 0,
|
|
18
|
+
"ObjectExpression": "first"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"linebreak-style": [
|
|
22
|
+
"error",
|
|
23
|
+
"unix"
|
|
24
|
+
],
|
|
25
|
+
"quotes": [
|
|
26
|
+
"error",
|
|
27
|
+
"single"
|
|
28
|
+
],
|
|
29
|
+
"semi": [
|
|
30
|
+
"error",
|
|
31
|
+
"always"
|
|
32
|
+
],
|
|
33
|
+
"no-use-before-define": [
|
|
34
|
+
"error"
|
|
35
|
+
],
|
|
36
|
+
"brace-style": [
|
|
37
|
+
"error",
|
|
38
|
+
"stroustrup"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
package/README.md
CHANGED
|
@@ -89,10 +89,13 @@ A script is a JSON file with the properties:
|
|
|
89
89
|
{
|
|
90
90
|
"what": "string: description of the script",
|
|
91
91
|
"strict": "boolean: whether redirections should be treated as failures",
|
|
92
|
+
"timeLimit": "number: limit in seconds on the execution of this script",
|
|
92
93
|
"commands": "array of objects: the commands to be performed"
|
|
93
94
|
}
|
|
94
95
|
```
|
|
95
96
|
|
|
97
|
+
The `timeLimit` property is optional. If it is omitted, a default of 300 seconds (5 minutes) is set.
|
|
98
|
+
|
|
96
99
|
### Example
|
|
97
100
|
|
|
98
101
|
Here is an example of a script:
|
|
@@ -101,6 +104,7 @@ Here is an example of a script:
|
|
|
101
104
|
{
|
|
102
105
|
what: 'Test example.com with alfa',
|
|
103
106
|
strict: true,
|
|
107
|
+
timeLimit: 15,
|
|
104
108
|
commands: [
|
|
105
109
|
{
|
|
106
110
|
type: 'launch',
|
|
@@ -292,6 +296,38 @@ In case you want to perform more than one `tenon` test with the same script, you
|
|
|
292
296
|
|
|
293
297
|
Tenon recommends giving it a public URL rather than giving it the content of a page, if possible. So, it is best to give the `withNewContent` property of the `tenonRequest` command the value `true`, unless the page is not public.
|
|
294
298
|
|
|
299
|
+
###### HTML CodeSniffer
|
|
300
|
+
|
|
301
|
+
The `htmlcs` test makes use of the`htmlcs/HTMLCS.js` file. That file was created, and can be recreated if necessary, as follows:
|
|
302
|
+
|
|
303
|
+
1. Clone the (HTML CodeSniffer package)[https://github.com/squizlabs/HTML_CodeSniffer].
|
|
304
|
+
1. Make that package’s directory the active directory.
|
|
305
|
+
1. Install the HTML CodeSniffer dependencies by executing `npm install`.
|
|
306
|
+
1. Build the HTML CodeSniffer auditor by executing `grunt build`.
|
|
307
|
+
1. Copy the `build/HTMLCS.js` and `build/licence.txt` files into the `htmlcs` directory of Testaro.
|
|
308
|
+
1. Edit the Testaro copy of `htmlcs/HTMLCS.js` to produce the changes shown below.
|
|
309
|
+
|
|
310
|
+
The changes in `htmlcs/HTMLCS.js` are:
|
|
311
|
+
|
|
312
|
+
```diff
|
|
313
|
+
479a480
|
|
314
|
+
> '4_1_2_attribute': 'attribute',
|
|
315
|
+
6482a6484
|
|
316
|
+
> var messageStrings = new Set();
|
|
317
|
+
6496d6497
|
|
318
|
+
< console.log('done');
|
|
319
|
+
6499d6499
|
|
320
|
+
< console.log('done');
|
|
321
|
+
6500a6501
|
|
322
|
+
> return Array.from(messageStrings);
|
|
323
|
+
6531c6532,6534
|
|
324
|
+
< console.log('[HTMLCS] ' + typeName + '|' + msg.code + '|' + nodeName + '|' + elementId + '|' + msg.msg + '|' + html);
|
|
325
|
+
---
|
|
326
|
+
> messageStrings.add(
|
|
327
|
+
> typeName + '|' + msg.code + '|' + nodeName + '|' + elementId + '|' + msg.msg + '|' + html
|
|
328
|
+
> );
|
|
329
|
+
```
|
|
330
|
+
|
|
295
331
|
##### Branching
|
|
296
332
|
|
|
297
333
|
An example of a **branching** command is:
|
|
@@ -454,13 +490,13 @@ Relative paths must be relative to the Testaro project directory. For example, i
|
|
|
454
490
|
|
|
455
491
|
Also ensure that Testaro can read all those directories and write to `REPORTDIR`.
|
|
456
492
|
|
|
457
|
-
Place a script into `SCRIPTDIR` and, optionally, a batch into `BATCHDIR`. Each should be named `
|
|
493
|
+
Place a script into `SCRIPTDIR` and, optionally, a batch into `BATCHDIR`. Each should be named `idvalue.json`, where `idvalue` is replaced with the value of its `id` property. That value must consist of only lower-case ASCII letters and digits.
|
|
458
494
|
|
|
459
495
|
Then execute the statement `node high scriptID` or `node high scriptID batchID`, replacing `scriptID` and `batchID` with the `id` values of the script and the batch, respectively.
|
|
460
496
|
|
|
461
497
|
The `high` module will call the `runJob` function of the `create` module, which in turn will call the `handleRequest` function of the `run` module. The results will be saved in report files in the `REPORTDIR` directory.
|
|
462
498
|
|
|
463
|
-
If there is no batch, the report file will be named with a unique timestamp, suffixed with a `.json` extension. If there is a batch, then the base of each report file’s name will be the same timestamp, suffixed with `-
|
|
499
|
+
If there is no batch, the report file will be named with a unique timestamp, suffixed with a `.json` extension. If there is a batch, then the base of each report file’s name will be the same timestamp, suffixed with `-hostid`, where `hostid` is the value of the `id` property of the `host` object in the batch file. For example, if you execute `node create script01 wikis`, you might get these report files deposited into `REPORTDIR`:
|
|
464
500
|
- `enp46j-wikipedia.json`
|
|
465
501
|
- `enp45j-wiktionary.json`
|
|
466
502
|
- `enp45j-wikidata.json`
|
|
@@ -570,6 +606,16 @@ The rationales motivating the Testaro-defined tests can be found in comments wit
|
|
|
570
606
|
|
|
571
607
|
## Testing challenges
|
|
572
608
|
|
|
609
|
+
### Abnormal termination
|
|
610
|
+
|
|
611
|
+
On rare occasions a test throws an error that terminates the Node process and cannot be handled with a `try`-`catch` structure. It has been observed, for example, that the `ibm` test does this when run on the host at `https://zenythgroup.com/index` or `https://monsido.com`.
|
|
612
|
+
|
|
613
|
+
If a single process performed all of the commands in a batch-based script, the process could perform tens of thousands of commands, and such an error could stop the process at any point.
|
|
614
|
+
|
|
615
|
+
To handle this risk, Testaro processes batch-based jobs by forking a new process for each host. If such an error occurs, it crashes the child process, preventing a report for that host from being written. The parent process waits for the report to appear in the `REPORTDIR` directory until the time limit. When it fails to appear, the parent process continues to the next host.
|
|
616
|
+
|
|
617
|
+
If you are using high-level invocation, your terminal will show the standard output of the parent process and, if there is a batch, the current child process, too. If you interrupt the process with `CTRL-c`, you will send a `SIGINT` signal to the parent process, which will handle it by sending a message to the child process telling it to terminate itself, and then the parent process will terminate by skipping the remaining hosts.
|
|
618
|
+
|
|
573
619
|
### Activation
|
|
574
620
|
|
|
575
621
|
Testing to determine what happens when a control or link is activated is straightforward, except in the context of a comprehensive set of tests of a single page. There, activating a control or link can change the page or navigate away from it, interfering with the remaining planned tests of the page.
|
|
@@ -611,6 +657,10 @@ Testaro omits some functionalities of Autotest, such as:
|
|
|
611
657
|
- file operations for score aggregation, report revision, and HTML reports
|
|
612
658
|
- a web user interface
|
|
613
659
|
|
|
660
|
+
## Code style
|
|
661
|
+
|
|
662
|
+
The JavaScript code in this project generally conforms to the ESLint configuration file `.eslintrc`. However, the `htmlcs/HTMLCS.js` file implements an older version of JavaScript. Its style is regulated by the `htmlcs/.eslintrc.json` file.
|
|
663
|
+
|
|
614
664
|
## Origin
|
|
615
665
|
|
|
616
666
|
Work on the custom tests in this package began in 2017, and work on the multi-package federation that Testaro implements began in early 2018. These two aspects were combined into the [Autotest](https://github.com/jrpool/autotest) package in early 2021 and into the more single-purpose packages, Testaro and Testilo, in January 2022.
|
package/create.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
require('dotenv').config();
|
|
10
10
|
// Module to read and write files.
|
|
11
11
|
const fs = require('fs/promises');
|
|
12
|
+
const {fork} = require('child_process');
|
|
12
13
|
const {handleRequest} = require('./run');
|
|
13
14
|
// Module to convert a script and a batch to a batch-based array of scripts.
|
|
14
15
|
const {batchify} = require('./batchify');
|
|
@@ -21,10 +22,10 @@ const reportDir = process.env.REPORTDIR;
|
|
|
21
22
|
// ########## FUNCTIONS
|
|
22
23
|
|
|
23
24
|
// Runs one script and writes a report file.
|
|
24
|
-
const runHost = async (id, script
|
|
25
|
+
const runHost = async (id, script) => {
|
|
25
26
|
const report = {
|
|
26
27
|
id,
|
|
27
|
-
host,
|
|
28
|
+
host: {},
|
|
28
29
|
log: [],
|
|
29
30
|
script,
|
|
30
31
|
acts: []
|
|
@@ -35,10 +36,19 @@ const runHost = async (id, script, host = {}) => {
|
|
|
35
36
|
};
|
|
36
37
|
// Runs a file-based job and writes a report file for the script or each host.
|
|
37
38
|
exports.runJob = async (scriptID, batchID) => {
|
|
39
|
+
let healthy = true;
|
|
40
|
+
let childAlive = true;
|
|
41
|
+
process.on('SIGINT', () => {
|
|
42
|
+
console.log('ERROR: Terminal interrupted runJob');
|
|
43
|
+
healthy = false;
|
|
44
|
+
});
|
|
38
45
|
if (scriptID) {
|
|
39
46
|
try {
|
|
40
47
|
const scriptJSON = await fs.readFile(`${scriptDir}/${scriptID}.json`, 'utf8');
|
|
41
48
|
const script = JSON.parse(scriptJSON);
|
|
49
|
+
// Get the time limit of the script or, if none, set it to 5 minutes.
|
|
50
|
+
let {timeLimit} = script;
|
|
51
|
+
timeLimit = timeLimit || 300;
|
|
42
52
|
// Identify the start time and a timestamp.
|
|
43
53
|
const timeStamp = Math.floor((Date.now() - Date.UTC(2022, 1)) / 2000).toString(36);
|
|
44
54
|
// If there is a batch:
|
|
@@ -48,28 +58,89 @@ exports.runJob = async (scriptID, batchID) => {
|
|
|
48
58
|
const batchJSON = await fs.readFile(`${batchDir}/${batchID}.json`, 'utf8');
|
|
49
59
|
batch = JSON.parse(batchJSON);
|
|
50
60
|
const specs = batchify(script, batch, timeStamp);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
const batchSize = specs.length;
|
|
62
|
+
const sizedRep = `${batchSize} report${batchSize > 1 ? 's' : ''}`;
|
|
63
|
+
const timeoutHosts = [];
|
|
64
|
+
const crashHosts = [];
|
|
65
|
+
// FUNCTION DEFINITION START
|
|
66
|
+
// Recursively runs host scripts.
|
|
67
|
+
const runHosts = specs => {
|
|
68
|
+
// If any scripts remain to be run and the process has not been interrupted:
|
|
69
|
+
if (specs.length && healthy) {
|
|
70
|
+
childAlive = true;
|
|
71
|
+
// Run the first one and save the report with a host-suffixed ID.
|
|
72
|
+
const spec = specs.shift();
|
|
73
|
+
const {id, host, script} = spec;
|
|
74
|
+
const subprocess = fork(
|
|
75
|
+
'runHost', [id, JSON.stringify(script), JSON.stringify(host)],
|
|
76
|
+
{
|
|
77
|
+
detached: true,
|
|
78
|
+
stdio: [0, 1, 'ipc']
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
subprocess.on('exit', () => {
|
|
82
|
+
childAlive = false;
|
|
83
|
+
});
|
|
84
|
+
const startTime = Date.now();
|
|
85
|
+
// At 5-second intervals:
|
|
86
|
+
const reCheck = setInterval(async () => {
|
|
87
|
+
// If the user has not interrupted the process:
|
|
88
|
+
if (healthy) {
|
|
89
|
+
// If there is no need to keep checking:
|
|
90
|
+
const reportNames = await fs.readdir(reportDir);
|
|
91
|
+
const timedOut = Date.now() - startTime > 1000 * timeLimit;
|
|
92
|
+
if (timedOut || reportNames.includes(`${id}.json`) || ! childAlive) {
|
|
93
|
+
// Stop checking.
|
|
94
|
+
clearInterval(reCheck);
|
|
95
|
+
// If the cause is a timeout:
|
|
96
|
+
if (timedOut) {
|
|
97
|
+
// Add the host to the array of timed-out hosts.
|
|
98
|
+
timeoutHosts.push(id);
|
|
99
|
+
}
|
|
100
|
+
// Otherwise, if the cause is a child crash:
|
|
101
|
+
else if (! childAlive) {
|
|
102
|
+
// Add the host to the array of crashed hosts.
|
|
103
|
+
crashHosts.push(id);
|
|
104
|
+
}
|
|
105
|
+
// Run the script of the next host.
|
|
106
|
+
runHosts(specs);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Otherwise, i.e. if the user has interrupted the process:
|
|
110
|
+
else {
|
|
111
|
+
// Tell the script run to quit.
|
|
112
|
+
subprocess.send('interrupt');
|
|
113
|
+
// Stop checking.
|
|
114
|
+
clearInterval(reCheck);
|
|
115
|
+
}
|
|
116
|
+
}, 5000);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(`${sizedRep} ${timeStamp}-....json in ${process.env.REPORTDIR}`);
|
|
120
|
+
if (timeoutHosts.length) {
|
|
121
|
+
console.log(`Reports not created:\n${JSON.stringify(timeoutHosts), null, 2}`);
|
|
122
|
+
}
|
|
123
|
+
if (crashHosts.length) {
|
|
124
|
+
console.log(`Hosts crashed:\n${JSON.stringify(crashHosts), null, 2}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
// FUNCTION DEFINITION END
|
|
129
|
+
// Recursively run each host script and save the reports.
|
|
130
|
+
runHosts(specs);
|
|
58
131
|
}
|
|
59
132
|
// Otherwise, i.e. if there is no batch:
|
|
60
133
|
else {
|
|
61
134
|
// Run the script and save the result with a timestamp ID.
|
|
62
135
|
await runHost(timeStamp, script);
|
|
136
|
+
console.log(`Report ${timeStamp}.json in ${process.env.REPORTDIR}`);
|
|
63
137
|
}
|
|
64
|
-
return timeStamp;
|
|
65
138
|
}
|
|
66
139
|
catch(error) {
|
|
67
140
|
console.log(`ERROR: ${error.message}\n${error.stack}`);
|
|
68
|
-
return null;
|
|
69
141
|
}
|
|
70
142
|
}
|
|
71
143
|
else {
|
|
72
144
|
console.log('ERROR: no script specified');
|
|
73
|
-
return null;
|
|
74
145
|
}
|
|
75
146
|
};
|
package/high.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
2
|
high.js
|
|
3
3
|
Invokes Testaro with the high-level method.
|
|
4
|
-
Usage example: node high
|
|
4
|
+
Usage example: node high tp12 weborgs
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const {runJob} = require('./create');
|
|
8
8
|
const scriptID = process.argv[2];
|
|
9
9
|
const batchID = process.argv[3];
|
|
10
|
-
|
|
11
|
-
const timeStamp = await runJob(scriptID, batchID);
|
|
12
|
-
console.log(`Reports in ${process.env.REPORTDIR}; ID base ${timeStamp}`);
|
|
13
|
-
};
|
|
14
|
-
run(scriptID, batchID);
|
|
10
|
+
runJob(scriptID, batchID);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"root": true,
|
|
3
|
+
"env": {
|
|
4
|
+
"browser": true,
|
|
5
|
+
"commonjs": true,
|
|
6
|
+
"es2021": true,
|
|
7
|
+
"node": true
|
|
8
|
+
},
|
|
9
|
+
"extends": "eslint:recommended",
|
|
10
|
+
"parserOptions": {
|
|
11
|
+
"ecmaVersion": 5
|
|
12
|
+
},
|
|
13
|
+
"rules": {
|
|
14
|
+
"indent": [
|
|
15
|
+
"error",
|
|
16
|
+
2,
|
|
17
|
+
{
|
|
18
|
+
"MemberExpression": 0,
|
|
19
|
+
"ObjectExpression": "first"
|
|
20
|
+
}
|
|
21
|
+
],
|
|
22
|
+
"linebreak-style": [
|
|
23
|
+
"error",
|
|
24
|
+
"unix"
|
|
25
|
+
],
|
|
26
|
+
"quotes": [
|
|
27
|
+
"error",
|
|
28
|
+
"single"
|
|
29
|
+
],
|
|
30
|
+
"semi": [
|
|
31
|
+
"error",
|
|
32
|
+
"always"
|
|
33
|
+
],
|
|
34
|
+
"brace-style": [
|
|
35
|
+
"error",
|
|
36
|
+
"stroustrup"
|
|
37
|
+
],
|
|
38
|
+
"no-undef": "off",
|
|
39
|
+
"no-redeclare": "off",
|
|
40
|
+
"no-unused-vars": "off",
|
|
41
|
+
"no-empty": "off",
|
|
42
|
+
"no-prototype-builtins": "off",
|
|
43
|
+
"no-cond-assign": "off",
|
|
44
|
+
"no-fallthrough": "off",
|
|
45
|
+
"no-constant-condition": "off",
|
|
46
|
+
"block-spacing": [
|
|
47
|
+
"error",
|
|
48
|
+
"never"
|
|
49
|
+
],
|
|
50
|
+
"array-bracket-spacing": [
|
|
51
|
+
"error",
|
|
52
|
+
"never"
|
|
53
|
+
],
|
|
54
|
+
"space-before-function-paren": [
|
|
55
|
+
"error",
|
|
56
|
+
"always"
|
|
57
|
+
],
|
|
58
|
+
"space-in-parens": [
|
|
59
|
+
"error",
|
|
60
|
+
"never"
|
|
61
|
+
],
|
|
62
|
+
"object-curly-spacing": [
|
|
63
|
+
"error",
|
|
64
|
+
"never"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|