testilo 23.0.0 → 24.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 +54 -74
- package/batch.js +24 -21
- package/call.js +22 -21
- package/merge.js +42 -88
- package/package.json +1 -1
- package/procs/test.js +20 -0
- package/procs/util.js +63 -0
- package/script.js +1 -4
package/README.md
CHANGED
|
@@ -65,8 +65,7 @@ Testilo can, however, make job preparation more efficient in these scenarios:
|
|
|
65
65
|
|
|
66
66
|
The simplest version of a list of targets is a _target list_. It is an array of arrays defining 1 or more targets. It can be stored as a tab-delimited text file.
|
|
67
67
|
|
|
68
|
-
A target is defined by
|
|
69
|
-
- An ID
|
|
68
|
+
A target is defined by 2 items:
|
|
70
69
|
- A description
|
|
71
70
|
- A URL
|
|
72
71
|
|
|
@@ -74,16 +73,16 @@ For example, a target list might be:
|
|
|
74
73
|
|
|
75
74
|
```javaScript
|
|
76
75
|
[
|
|
77
|
-
['
|
|
78
|
-
['
|
|
76
|
+
['World Wide Web Consortium', 'https://www.w3.org/'],
|
|
77
|
+
['Mozilla Foundation', 'https://foundation.mozilla.org/en/']
|
|
79
78
|
]
|
|
80
79
|
```
|
|
81
80
|
|
|
82
81
|
If this target list were stored as a file, its content would be this (with “→” representing the Tab character):
|
|
83
82
|
|
|
84
83
|
```text
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
World Wide Web Consortium→https://www.w3.org/
|
|
85
|
+
Mozilla Foundation→https://foundation.mozilla.org/en/
|
|
87
86
|
```
|
|
88
87
|
|
|
89
88
|
### Batches
|
|
@@ -97,27 +96,21 @@ Targets can be specified in a more complex way, too. That allows you to create j
|
|
|
97
96
|
targets: [
|
|
98
97
|
{
|
|
99
98
|
id: 'acme',
|
|
100
|
-
which: 'https://acmeclothes.com/',
|
|
101
99
|
what: 'Acme Clothes',
|
|
100
|
+
which: 'https://acmeclothes.com/',
|
|
102
101
|
acts: {
|
|
103
102
|
public: [
|
|
104
103
|
{
|
|
105
|
-
type: 'launch'
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
type: 'url',
|
|
109
|
-
which: 'https://acmeclothes.com/',
|
|
110
|
-
what: 'Acme Clothes home page'
|
|
104
|
+
type: 'launch',
|
|
105
|
+
what: 'Acme Clothes home page',
|
|
106
|
+
url: 'https://acmeclothes.com/'
|
|
111
107
|
}
|
|
112
108
|
],
|
|
113
109
|
private: [
|
|
114
110
|
{
|
|
115
|
-
type: 'launch'
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
type: 'url',
|
|
119
|
-
which: 'https://acmeclothes.com/login.html',
|
|
120
|
-
what: 'Acme Clothes login page'
|
|
111
|
+
type: 'launch',
|
|
112
|
+
what: 'Acme Clothes login page',
|
|
113
|
+
url: 'https://acmeclothes.com/login.html'
|
|
121
114
|
},
|
|
122
115
|
{
|
|
123
116
|
type: 'text',
|
|
@@ -164,12 +157,6 @@ Here is a script:
|
|
|
164
157
|
strict: true,
|
|
165
158
|
isolate: true,
|
|
166
159
|
timeLimit: 60,
|
|
167
|
-
standard: 'also',
|
|
168
|
-
observe: false,
|
|
169
|
-
timeStamp: '240115T1200',
|
|
170
|
-
requester: 'you@yourdomain.com',
|
|
171
|
-
urlPrefix: '/file/reports',
|
|
172
|
-
urlSuffix: '.json',
|
|
173
160
|
acts: [
|
|
174
161
|
{
|
|
175
162
|
type: 'placeholder',
|
|
@@ -199,17 +186,12 @@ A script has several properties that specify facts about the jobs to be created.
|
|
|
199
186
|
- `what`: a description of the script.
|
|
200
187
|
- `strict`: `true` if Testaro is to abort jobs when a target redirects a request to a URL differing substantially from the one specified. If `false` Testaro is to allow redirection. All differences are considered substantial unless the URLs differ only in the presence and absence of a trailing slash.
|
|
201
188
|
- `isolate`: If `true`, Testilo, before creating a job, will isolate test acts, as needed, from effects of previous test acts, by inserting a copy of the latest placeholder after each target-modifying test act other than the final act. If `false`, placeholders will not be duplicated.
|
|
202
|
-
- `
|
|
203
|
-
- `observe`: If `true`, jobs will tell Testaro to allow granular observation of job progress. If `false`, jobs will tell Testaro not to permit granular observation, but only to send the report to the server when the report is completed. It is generally user-friendly to allow granular observation, and for user applications to implement it, if they make users wait while jobs are assigned and performed, since that process typically takes about 3 minutes.
|
|
204
|
-
- `timeStamp`: This string specifies a UTC date and time when jobs created with the script are to be permitted to be assigned to agents. Thus, jobs can be created from a script for later performance. The value of `timeStamp` is a compact representation in the format `yymmddThhMM`. If the value is an empty string, Testilo will make the date and time equal to the time when jobs are created.
|
|
205
|
-
- `requester`: the email address that any notices of job completion can be sent to, or an empty string if there is a `REQUESTER` environment variable and it is to be used.
|
|
206
|
-
- `urlPrefix`: the start of a URL that the Testaro reports will be retrievable at.
|
|
207
|
-
- `urlSuffix`: the end of that URL. The job ID will be inserted between the `urlPrefix` and the `urlSuffix`.
|
|
189
|
+
- `timeLimit`: This specifies the maximum duration, in seconds, of a job. Testaro will abort jobs that are not completed within that time.
|
|
208
190
|
- `acts`: an array of acts.
|
|
209
191
|
|
|
210
192
|
The first act in this example script is a placeholder, whose `which` property is `'private'`. If the above batch were merged with this script, in each job the placeholder would be replaced with the `private` acts of a target. For example, the first act of the first job would launch a Chromium browser, navigate to the Acme login page, complete and submit the login form, wait for the account page to load, run the Axe tests, and then run the QualWeb tests. If the batch contained additional targets, additional jobs would be created, with the login actions for each target specified in the `private` array of the `acts` object of that target.
|
|
211
193
|
|
|
212
|
-
As shown in this example, when a browser is launched by placeholder substitution, the script can determine the browser type (`chromium`, `firefox`, or `webkit`) by assigning a value to a `launch` property of the placeholder.
|
|
194
|
+
As shown in this example, when a browser is launched by placeholder substitution, the script can determine the browser type (`chromium`, `firefox`, or `webkit`) by assigning a value to a `launch` property of the placeholder. This allows a script to ensure that the tests it requires are performed with appropriate browser types. Some browser types are incompatible with some tests.
|
|
213
195
|
|
|
214
196
|
### Target list to batch
|
|
215
197
|
|
|
@@ -225,13 +207,15 @@ A module can invoke `batch` in this way:
|
|
|
225
207
|
|
|
226
208
|
```javaScript
|
|
227
209
|
const {batch} = require('testilo/batch');
|
|
228
|
-
const batchObj = batch(
|
|
210
|
+
const batchObj = batch(id, what, targets);
|
|
229
211
|
```
|
|
230
212
|
|
|
231
|
-
This invocation references `
|
|
213
|
+
This invocation references `id`, `what`, and `targets` variables that the module must have already defined. The `id` variable is a unique identifier for the target list. The `what` variable describes the target list. The `targets` variable is an array of arrays, with each array containing the 2 items (description and URL) defining one target.
|
|
232
214
|
|
|
233
215
|
The `batch()` function of the `batch` module generates a batch and returns it as an object. The invoking module can further dispose of the batch as needed.
|
|
234
216
|
|
|
217
|
+
The ID assigned to each target by the `batch()` function is a sequential (base-62 alphanumeric) string, rather than a mnemonic like the one (`'acme'`) in the above example.
|
|
218
|
+
|
|
235
219
|
##### By a user
|
|
236
220
|
|
|
237
221
|
A user can invoke `batch` in this way:
|
|
@@ -299,6 +283,8 @@ To create a script without any issue restrictions, a user can execute the statem
|
|
|
299
283
|
|
|
300
284
|
#### Options
|
|
301
285
|
|
|
286
|
+
The `script` module will use the value of the `SEND_REPORT_TO` environment variable as the value of the `sendReportTo` property of the script, if that variable exists, and otherwise will leave that property with an empty-string value.
|
|
287
|
+
|
|
302
288
|
When the `script` module creates a script for you, it does not ask you for all of the options that the script may require. Instead, it chooses default options. After you invoke `script`, you can edit the script that it creates to revise options.
|
|
303
289
|
|
|
304
290
|
### Merge
|
|
@@ -317,16 +303,14 @@ Suppose you ask for a merger of the above batch and script. Then the first job p
|
|
|
317
303
|
timeLimit: 60,
|
|
318
304
|
standard: 'also',
|
|
319
305
|
observe: false,
|
|
306
|
+
sendReportTo: 'https://ourdomain.com/testman/api/report'
|
|
320
307
|
timeStamp: '240115T1200',
|
|
321
308
|
acts: [
|
|
322
309
|
{
|
|
323
310
|
type: 'launch',
|
|
324
|
-
which: 'chromium'
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
type: 'url',
|
|
328
|
-
which: 'https://acmeclothes.com/login.html',
|
|
329
|
-
what: 'Acme Clothes login page'
|
|
311
|
+
which: 'chromium',
|
|
312
|
+
what: 'Acme Clothes login page',
|
|
313
|
+
url: 'https://acmeclothes.com/login.html'
|
|
330
314
|
},
|
|
331
315
|
{
|
|
332
316
|
type: 'text',
|
|
@@ -357,12 +341,9 @@ Suppose you ask for a merger of the above batch and script. Then the first job p
|
|
|
357
341
|
},
|
|
358
342
|
{
|
|
359
343
|
type: 'launch',
|
|
360
|
-
which: 'chromium'
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
type: 'url',
|
|
364
|
-
which: 'https://acmeclothes.com/login.html',
|
|
365
|
-
what: 'Acme Clothes login page'
|
|
344
|
+
which: 'chromium',
|
|
345
|
+
what: 'Acme Clothes login page',
|
|
346
|
+
url: 'https://acmeclothes.com/login.html'
|
|
366
347
|
},
|
|
367
348
|
{
|
|
368
349
|
type: 'text',
|
|
@@ -399,19 +380,18 @@ Suppose you ask for a merger of the above batch and script. Then the first job p
|
|
|
399
380
|
id: 'acme',
|
|
400
381
|
what: 'Acme Clothes'
|
|
401
382
|
},
|
|
402
|
-
requester: 'you@yourdomain.tld'
|
|
403
|
-
url: '/file/reports/240115T1200-4Rw-acme.json'
|
|
383
|
+
requester: 'you@yourdomain.tld'
|
|
404
384
|
},
|
|
405
|
-
creationTime: '
|
|
385
|
+
creationTime: '241120T1550'
|
|
406
386
|
}
|
|
407
387
|
```
|
|
408
388
|
|
|
409
389
|
Testilo has substituted the `private` acts from the `acme` target of the batch for the placeholder when creating the job. Testilo also has:
|
|
410
390
|
- inserted a copy of those same acts after the `axe` test act, because `axe` is a target-modifying tool.
|
|
411
391
|
- let the script determine the browser type of the `launch` act.
|
|
412
|
-
- added the creation time to the job.
|
|
413
392
|
- given the job an ID that combines the time stamp with a differentiator and the batch ID.
|
|
414
393
|
- inserted a `sources` property into the job, recording facts about the script, the batch, the target, the requester, and the report URL.
|
|
394
|
+
- added a time stamp describing the creation time to the job.
|
|
415
395
|
|
|
416
396
|
This is a valid Testaro job.
|
|
417
397
|
|
|
@@ -425,25 +405,18 @@ A module can invoke `merge` in this way:
|
|
|
425
405
|
|
|
426
406
|
```javaScript
|
|
427
407
|
const {merge} = require('testilo/merge');
|
|
428
|
-
const jobs = merge(
|
|
429
|
-
script, batch, requester, isolate, standard, isGranular, timeStamp, 'file/reports/', '.json'
|
|
430
|
-
);
|
|
408
|
+
const jobs = merge(script, batch, standard, observe, requester, timeStamp);
|
|
431
409
|
```
|
|
432
410
|
|
|
433
|
-
The `merge` module uses these
|
|
411
|
+
The `merge` module uses these 4 arguments to create jobs from a script and a batch.
|
|
434
412
|
|
|
435
413
|
The arguments are:
|
|
436
414
|
- `script`: a script.
|
|
437
415
|
- `batch`: a batch.
|
|
438
|
-
- `
|
|
439
|
-
- `
|
|
440
|
-
- `
|
|
441
|
-
- `
|
|
442
|
-
- `timeStamp`: a time stamp in the format `240115T1200`.
|
|
443
|
-
- `urlPnrefix`: the start of an absolute or relative report URL.
|
|
444
|
-
- `urlSuffix`: the end of a report URL.
|
|
445
|
-
|
|
446
|
-
In this case, a job with ID `240115T1200-4Rw-acme` will produce a report that can be retrieved at the relative URL `/file/reports/240115T1200-4Rw-acme.json`.
|
|
416
|
+
- `standard`: how to handle standardization. If `also`, jobs will tell Testaro to include in its reports both the original results of the tests of tools and the Testaro-standardized results. If `only`, reports are to include only the standardized test results. If `no`, reports are to include only the original results, without standardization.
|
|
417
|
+
- `observe`: whether to allow granular observation. If `true`, jobs will tell Testaro to permit granular observation of job progress. If `false`, jobs will tell Testaro not to permit granular observation, but only to send the report to the server when the report is completed. It is generally user-friendly to allow granular observation, and for user applications to implement it, if they make users wait while jobs are assigned and performed, since that process typically takes about 3 minutes.
|
|
418
|
+
- `requester`: an email address.
|
|
419
|
+
- `timeStamp`: the earliest UTC date and time when the jobs may be assigned (format `240415T1230`), or an empty string if now.
|
|
447
420
|
|
|
448
421
|
The `merge()` function of the `merge` module generates jobs and returns them in an array. The invoking module can further dispose of the jobs as needed.
|
|
449
422
|
|
|
@@ -453,21 +426,20 @@ A user can invoke `merge` in this way:
|
|
|
453
426
|
|
|
454
427
|
- Create a script and save it as a JSON file in the `scripts` subdirectory of the `SPECDIR` directory.
|
|
455
428
|
- Create a batch and save it as a JSON file in the `batches` subdirectory of the `SPECDIR` directory.
|
|
456
|
-
- In the Testilo project directory, execute the statement
|
|
429
|
+
- In the Testilo project directory, execute the statement:
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
node call merge scriptID batchID standard observe requester timeStamp todoDir
|
|
433
|
+
```
|
|
457
434
|
|
|
458
435
|
In this statement, replace:
|
|
459
|
-
- `
|
|
460
|
-
- `
|
|
461
|
-
- `
|
|
462
|
-
- `
|
|
463
|
-
- `standard` with `'also'`, `'only'`, or `'no'` to specify the treatment of standard-format results.
|
|
464
|
-
- `observe` with `true` if granular observation is to be permitted, or `false` if not.
|
|
465
|
-
- `todoDir` with `true` if the job is to be saved in the `todo` subdirectory, or `false` if it is to be saved in the `pending` subdirectory, of the `JOBDIR` directory.
|
|
466
|
-
- `urlPrefix` with the pre-ID part of the report URL.
|
|
467
|
-
- `urlSuffix` with the post-ID part of the report URL.
|
|
436
|
+
- `scriptID` with the ID (which is also the base of the file name) of the script.
|
|
437
|
+
- `batchID` with the ID (which is also the base of the file name) of the batch.
|
|
438
|
+
- `standard`, `observe`, `requester`, and `timeStamp` as described above.
|
|
439
|
+
- `todoDir`: `true` if the jobs are to be saved in the `todo` subdirectory, or `false` if they are to be saved in the `pending` subdirectory, of the `JOBDIR` directory.
|
|
468
440
|
|
|
469
441
|
The `call` module will retrieve the named script and batch from their respective directories.
|
|
470
|
-
The `merge` module will create an array of jobs
|
|
442
|
+
The `merge` module will create an array of jobs.
|
|
471
443
|
The `call` module will save the jobs as JSON files in the `todo` or `pending` subdirectory of the `JOBDIR` directory.
|
|
472
444
|
|
|
473
445
|
#### Validation
|
|
@@ -733,3 +705,11 @@ The third argument to `call` (`23pl` in this example) is optional. If it is omit
|
|
|
733
705
|
### Validation
|
|
734
706
|
|
|
735
707
|
To test the `compare` module, in the project directory you can execute the statement `node validation/compare/validate`. If `compare` is valid, all logging statements will begin with “Success” and none will begin with “ERROR”.
|
|
708
|
+
|
|
709
|
+
## Origin
|
|
710
|
+
|
|
711
|
+
Work on the functionalities of Testaro and Testilo began in 2017. It was named [Autotest](https://github.com/jrpool/autotest) in early 2021 and then partitioned into the more single-purpose packages Testaro and Testilo in January 2022.
|
|
712
|
+
|
|
713
|
+
## Etymology
|
|
714
|
+
|
|
715
|
+
“Testilo” means “testing tool” in Esperanto.
|
package/batch.js
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
Converts a target list to a batch.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {alphaNumOf} = require('./procs/util');
|
|
9
|
+
|
|
10
|
+
// FUNCTIONS
|
|
7
11
|
|
|
8
12
|
// Converts a target list to a batch and returns the batch.
|
|
9
13
|
exports.batch = (id, what, targetList) => {
|
|
@@ -17,9 +21,9 @@ exports.batch = (id, what, targetList) => {
|
|
|
17
21
|
&& targetList.length
|
|
18
22
|
&& targetList.every(
|
|
19
23
|
target => Array.isArray(target)
|
|
24
|
+
&& target.length === 2
|
|
20
25
|
&& target.every(item => typeof item === 'string')
|
|
21
26
|
)
|
|
22
|
-
&& targetList.some(target => target.length === 3)
|
|
23
27
|
) {
|
|
24
28
|
// Initialize the batch.
|
|
25
29
|
const batch = {
|
|
@@ -27,25 +31,23 @@ exports.batch = (id, what, targetList) => {
|
|
|
27
31
|
what,
|
|
28
32
|
targets: []
|
|
29
33
|
};
|
|
30
|
-
// For each
|
|
31
|
-
targetList.forEach(target => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
});
|
|
48
|
-
}
|
|
34
|
+
// For each target:
|
|
35
|
+
targetList.forEach((target, index) => {
|
|
36
|
+
// Add it to the batch.
|
|
37
|
+
batch.targets.push({
|
|
38
|
+
id: alphaNumOf(index),
|
|
39
|
+
what: target[0],
|
|
40
|
+
which: target[1],
|
|
41
|
+
acts: {
|
|
42
|
+
main: [
|
|
43
|
+
{
|
|
44
|
+
type: 'launch',
|
|
45
|
+
what: target[0],
|
|
46
|
+
url: target[1]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
});
|
|
49
51
|
});
|
|
50
52
|
// Return the batch.
|
|
51
53
|
return batch;
|
|
@@ -53,6 +55,7 @@ exports.batch = (id, what, targetList) => {
|
|
|
53
55
|
// Otherwise, i.e. if the arguments are invalid:
|
|
54
56
|
else {
|
|
55
57
|
// Return this.
|
|
58
|
+
console.log('ERROR: Batch information missing or invalid');
|
|
56
59
|
return null;
|
|
57
60
|
}
|
|
58
61
|
};
|
package/call.js
CHANGED
|
@@ -43,16 +43,21 @@ const fnArgs = process.argv.slice(3);
|
|
|
43
43
|
// ########## FUNCTIONS
|
|
44
44
|
|
|
45
45
|
// Converts a target list to a batch.
|
|
46
|
-
const callBatch = async (
|
|
46
|
+
const callBatch = async (id, what) => {
|
|
47
47
|
// Get the target list.
|
|
48
|
-
const listString = await fs.readFile(`${specDir}/targetLists/${
|
|
49
|
-
const list = listString
|
|
48
|
+
const listString = await fs.readFile(`${specDir}/targetLists/${id}.tsv`, 'utf8');
|
|
49
|
+
const list = listString
|
|
50
|
+
.split('\n')
|
|
51
|
+
.filter(target => target.length)
|
|
52
|
+
.map(target => target.split('\t'));
|
|
50
53
|
// Convert it to a batch.
|
|
51
|
-
const batchObj = batch(
|
|
54
|
+
const batchObj = batch(id, what, list);
|
|
52
55
|
// Save the batch.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
if (batchObj) {
|
|
57
|
+
const batchJSON = JSON.stringify(batchObj, null, 2);
|
|
58
|
+
await fs.writeFile(`${specDir}/batches/${id}.json`, `${batchJSON}\n`);
|
|
59
|
+
console.log(`Target list ${id} converted to a batch and saved in ${specDir}/batches`);
|
|
60
|
+
}
|
|
56
61
|
};
|
|
57
62
|
// Fulfills a script-creation request.
|
|
58
63
|
const callScript = async (scriptID, classificationID = null, ... issueIDs) => {
|
|
@@ -71,11 +76,11 @@ const callScript = async (scriptID, classificationID = null, ... issueIDs) => {
|
|
|
71
76
|
const callMerge = async (
|
|
72
77
|
scriptID,
|
|
73
78
|
batchID,
|
|
74
|
-
requester,
|
|
75
|
-
withIsolation,
|
|
76
79
|
standard,
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
observe,
|
|
81
|
+
requester,
|
|
82
|
+
timeStamp,
|
|
83
|
+
todoDir
|
|
79
84
|
) => {
|
|
80
85
|
// Get the script and the batch.
|
|
81
86
|
const scriptJSON = await fs.readFile(`${specDir}/scripts/${scriptID}.json`, 'utf8');
|
|
@@ -83,19 +88,15 @@ const callMerge = async (
|
|
|
83
88
|
const batchJSON = await fs.readFile(`${specDir}/batches/${batchID}.json`, 'utf8');
|
|
84
89
|
const batch = JSON.parse(batchJSON);
|
|
85
90
|
// Merge them into an array of jobs.
|
|
86
|
-
const jobs = merge(
|
|
87
|
-
script, batch, requester, withIsolation, standard, isGranular, null, urlPrefix, urlSuffix
|
|
88
|
-
);
|
|
91
|
+
const jobs = merge(script, batch, standard, observe === 'true', requester, timeStamp);
|
|
89
92
|
// Save the jobs.
|
|
90
|
-
const
|
|
93
|
+
const subdir = `${jobDir}/${todoDir === 'true' ? 'todo' : 'pending'}`;
|
|
91
94
|
for (const job of jobs) {
|
|
92
95
|
const jobJSON = JSON.stringify(job, null, 2);
|
|
93
|
-
await fs.writeFile(`${
|
|
96
|
+
await fs.writeFile(`${subdir}/${job.id}.json`, `${jobJSON}\n`);
|
|
94
97
|
}
|
|
95
|
-
const
|
|
96
|
-
console.log(
|
|
97
|
-
`Script ${scriptID} and batch ${batchID} merged as ${timeStamp}-… in ${jobDir}/${destination}`
|
|
98
|
-
);
|
|
98
|
+
const truncatedID = `${jobs[0].timeStamp}-${jobs[0].mergeID}-…`;
|
|
99
|
+
console.log(`Script ${scriptID} and batch ${batchID} merged as ${truncatedID} in ${subdir}`);
|
|
99
100
|
};
|
|
100
101
|
// Gets selected reports.
|
|
101
102
|
const getReports = async (type, selector = '') => {
|
|
@@ -222,7 +223,7 @@ else if (fn === 'script' && fnArgs.length) {
|
|
|
222
223
|
console.log('Execution completed');
|
|
223
224
|
});
|
|
224
225
|
}
|
|
225
|
-
else if (fn === 'merge' && fnArgs.length ===
|
|
226
|
+
else if (fn === 'merge' && fnArgs.length === 7) {
|
|
226
227
|
callMerge(... fnArgs)
|
|
227
228
|
.then(() => {
|
|
228
229
|
console.log('Execution completed');
|
package/merge.js
CHANGED
|
@@ -1,83 +1,34 @@
|
|
|
1
1
|
/*
|
|
2
2
|
merge.js
|
|
3
3
|
Merges a script and a batch and returns jobs.
|
|
4
|
-
Arguments:
|
|
5
|
-
0. script
|
|
6
|
-
1. batch
|
|
7
|
-
2. requester
|
|
8
|
-
3. whether to provide test isolation
|
|
9
|
-
4. value of the standard property
|
|
10
|
-
5. whether reporting is to be granular
|
|
11
|
-
6. date and time as a compact timestamp for job execution, if not now
|
|
12
4
|
*/
|
|
13
5
|
|
|
14
6
|
// ########## IMPORTS
|
|
15
7
|
|
|
16
8
|
// Module to keep secrets.
|
|
17
9
|
require('dotenv').config();
|
|
10
|
+
// Module to perform common actions.
|
|
11
|
+
const {alphaNumOf, dateOf, getRandomString, getNowStamp} = require('./procs/util');
|
|
18
12
|
|
|
19
13
|
// ########## CONSTANTS
|
|
20
14
|
|
|
21
|
-
// Standard requester.
|
|
22
|
-
const stdRequester = process.env.REQUESTER || 'nobody@nodomain.tld';
|
|
23
|
-
// Length of the random part of a job ID, as a string.
|
|
24
|
-
const randomIDLength = process.env.RANDOM_ID_LENGTH || '3';
|
|
25
15
|
// Tools that alter the page.
|
|
26
16
|
const contaminantNames = new Set([
|
|
27
17
|
'alfa',
|
|
28
18
|
'aslint',
|
|
29
19
|
'axe',
|
|
20
|
+
'ed11y',
|
|
30
21
|
'htmlcs',
|
|
31
22
|
'testaro'
|
|
32
23
|
]);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const uppers = Array(26).fill('').map((letter, index) => String.fromCodePoint(65 + index));
|
|
36
|
-
const lowers = Array(26).fill('').map((letter, index) => String.fromCodePoint(97 + index));
|
|
37
|
-
return digits.concat(uppers, lowers);
|
|
38
|
-
})();
|
|
39
|
-
|
|
24
|
+
// Length of the random merger ID.
|
|
25
|
+
const mergeIDLength = 2;
|
|
40
26
|
|
|
41
27
|
// ########## FUNCTIONS
|
|
42
28
|
|
|
43
|
-
// Inserts a character periodically in a string.
|
|
44
|
-
const punctuate = (string, insertion, chunkSize) => {
|
|
45
|
-
const segments = [];
|
|
46
|
-
let startIndex = 0;
|
|
47
|
-
while (startIndex < string.length) {
|
|
48
|
-
segments.push(string.slice(startIndex, startIndex + chunkSize));
|
|
49
|
-
startIndex += chunkSize;
|
|
50
|
-
}
|
|
51
|
-
return segments.join(insertion);
|
|
52
|
-
};
|
|
53
|
-
// Converts a compact timestamp to a date.
|
|
54
|
-
const dateOf = timeStamp => {
|
|
55
|
-
if (/^\d{6}T\d{4}$/.test(timeStamp)) {
|
|
56
|
-
const dateString = punctuate(timeStamp.slice(0, 6), '-', 2);
|
|
57
|
-
const timeString = punctuate(timeStamp.slice(7, 11), ':', 2);
|
|
58
|
-
return new Date(`20${dateString}T${timeString}Z`);
|
|
59
|
-
} else {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
// Converts a date and time to a compact timestamp.
|
|
64
|
-
const stampTime = date => date.toISOString().replace(/[-:]/g, '').slice(2, 13);
|
|
65
|
-
// Generates a random string.
|
|
66
|
-
const getRandomID = length => {
|
|
67
|
-
const chars = [];
|
|
68
|
-
for (let i = 0; i < length; i++) {
|
|
69
|
-
chars.push(randomIDChars[Math.floor(62 * Math.random())]);
|
|
70
|
-
}
|
|
71
|
-
return chars.join('');
|
|
72
|
-
};
|
|
73
29
|
// Merges a script and a batch and returns jobs.
|
|
74
|
-
exports.merge = (
|
|
75
|
-
|
|
76
|
-
) => {
|
|
77
|
-
if (isolate === 'false') {
|
|
78
|
-
isolate = false;
|
|
79
|
-
}
|
|
80
|
-
// If a timestamp was specified:
|
|
30
|
+
exports.merge = (script, batch, standard, observe, requester, timeStamp) => {
|
|
31
|
+
// If a time stamp was specified:
|
|
81
32
|
if (timeStamp) {
|
|
82
33
|
// If it is invalid:
|
|
83
34
|
if (! dateOf(timeStamp)) {
|
|
@@ -86,77 +37,79 @@ exports.merge = (
|
|
|
86
37
|
return [];
|
|
87
38
|
}
|
|
88
39
|
}
|
|
89
|
-
// Otherwise, i.e. if no
|
|
40
|
+
// Otherwise, i.e. if no time stamp was specified:
|
|
90
41
|
else {
|
|
91
42
|
// Create one for the job.
|
|
92
|
-
timeStamp =
|
|
43
|
+
timeStamp = getNowStamp();
|
|
93
44
|
}
|
|
94
|
-
//
|
|
95
|
-
requester ||= stdRequester;
|
|
96
|
-
// Create a creation-time description.
|
|
97
|
-
const creationTime = (new Date()).toISOString().slice(0, 16);
|
|
98
|
-
// Initialize a target-independent job.
|
|
45
|
+
// Initialize a job as a copy of the script.
|
|
99
46
|
const protoJob = JSON.parse(JSON.stringify(script));
|
|
100
|
-
//
|
|
101
|
-
protoJob.id = `${timeStamp}-${getRandomID(Number.parseInt(randomIDLength, 10))}-`;
|
|
102
|
-
// Add a sources property to the job.
|
|
47
|
+
// Add an initialized sources property to it.
|
|
103
48
|
protoJob.sources = {
|
|
104
49
|
script: script.id,
|
|
105
50
|
batch: batch.id,
|
|
106
51
|
target: {
|
|
107
52
|
id: '',
|
|
108
|
-
|
|
109
|
-
|
|
53
|
+
what: '',
|
|
54
|
+
which: ''
|
|
110
55
|
},
|
|
111
|
-
requester
|
|
112
|
-
sendReportTo: process.env.REPORT_URL || '',
|
|
113
|
-
url: ''
|
|
56
|
+
requester
|
|
114
57
|
};
|
|
115
58
|
// Add properties to the job.
|
|
116
|
-
protoJob.
|
|
59
|
+
protoJob.standard = standard;
|
|
60
|
+
protoJob.observe = observe;
|
|
117
61
|
protoJob.timeStamp = timeStamp;
|
|
118
|
-
protoJob.
|
|
119
|
-
protoJob.observe = observe || false;
|
|
62
|
+
protoJob.creationTimeStamp = getNowStamp();
|
|
120
63
|
// If isolation was requested:
|
|
121
|
-
if (isolate) {
|
|
122
|
-
//
|
|
64
|
+
if (script.isolate) {
|
|
65
|
+
// For each act:
|
|
123
66
|
let {acts} = protoJob;
|
|
124
67
|
let lastPlaceholder = {};
|
|
125
68
|
for (const actIndexString in acts) {
|
|
69
|
+
// If it is a placeholder:
|
|
126
70
|
const actIndex = Number.parseInt(actIndexString);
|
|
127
71
|
const act = acts[actIndex];
|
|
128
|
-
const nextAct = acts[actIndex + 1];
|
|
129
72
|
if (act.type === 'placeholder') {
|
|
73
|
+
// Identify it as the current one.
|
|
130
74
|
lastPlaceholder = act;
|
|
131
75
|
}
|
|
76
|
+
// Otherwise, if it is a non-final target-modifying test act:
|
|
132
77
|
else if (
|
|
133
78
|
act.type === 'test'
|
|
134
79
|
&& contaminantNames.has(act.which)
|
|
135
80
|
&& actIndex < acts.length - 1
|
|
136
|
-
&& (nextAct.type === 'test')
|
|
137
81
|
) {
|
|
82
|
+
// Change it to an array of itself and the current placeholder.
|
|
138
83
|
acts[actIndex] = JSON.parse(JSON.stringify([act, lastPlaceholder]));
|
|
139
84
|
}
|
|
140
85
|
};
|
|
86
|
+
// Flatten the acts.
|
|
141
87
|
protoJob.acts = acts.flat();
|
|
142
88
|
}
|
|
89
|
+
// Delete the no-longer-necessary job property.
|
|
90
|
+
delete protoJob.isolate;
|
|
143
91
|
// Initialize an array of jobs.
|
|
144
92
|
const jobs = [];
|
|
93
|
+
// Get an ID for the merger.
|
|
94
|
+
const mergeID = getRandomString(mergeIDLength);
|
|
145
95
|
// For each target in the batch:
|
|
146
96
|
const {targets} = batch;
|
|
147
|
-
|
|
97
|
+
targets.forEach((target, index) => {
|
|
148
98
|
// If the target has the required identifiers:
|
|
149
|
-
const {id,
|
|
150
|
-
if (id &&
|
|
99
|
+
const {id, what, which} = target;
|
|
100
|
+
if (id && what && which) {
|
|
151
101
|
// Initialize a job.
|
|
152
102
|
const job = JSON.parse(JSON.stringify(protoJob));
|
|
153
|
-
//
|
|
154
|
-
|
|
103
|
+
// Make the job ID unique.
|
|
104
|
+
const targetID = alphaNumOf(index);
|
|
105
|
+
job.id = `${timeStamp}-${mergeID}-${targetID}`;
|
|
106
|
+
// Add properties to the job.
|
|
107
|
+
job.mergeID = mergeID;
|
|
108
|
+
job.sendReportTo = process.env.SEND_REPORT_TO || '';
|
|
155
109
|
// Add data to the sources property of the job.
|
|
156
|
-
job.sources.target.id =
|
|
157
|
-
job.sources.target.which = target.which;
|
|
110
|
+
job.sources.target.id = targetID;
|
|
158
111
|
job.sources.target.what = target.what;
|
|
159
|
-
job.sources.
|
|
112
|
+
job.sources.target.which = target.which;
|
|
160
113
|
// Replace each placeholder object in the job with the named replacer array of the target.
|
|
161
114
|
let {acts} = job;
|
|
162
115
|
for (const actIndex in acts) {
|
|
@@ -184,13 +137,14 @@ exports.merge = (
|
|
|
184
137
|
}
|
|
185
138
|
}
|
|
186
139
|
}
|
|
140
|
+
// Flatten the acts.
|
|
187
141
|
job.acts = acts.flat();
|
|
188
142
|
// Append the job to the array of jobs.
|
|
189
143
|
jobs.push(job);
|
|
190
144
|
}
|
|
191
145
|
else {
|
|
192
|
-
console.log('ERROR: Target in batch missing id,
|
|
146
|
+
console.log('ERROR: Target in batch missing id, what, or which property');
|
|
193
147
|
}
|
|
194
|
-
};
|
|
148
|
+
});
|
|
195
149
|
return jobs;
|
|
196
150
|
};
|
package/package.json
CHANGED
package/procs/test.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Array of 62 alphanumeric characters.
|
|
2
|
+
const alphaNumChars = (() => {
|
|
3
|
+
const digits = Array(10).fill('').map((digit, index) => index.toString());
|
|
4
|
+
const uppers = Array(26).fill('').map((letter, index) => String.fromCodePoint(65 + index));
|
|
5
|
+
const lowers = Array(26).fill('').map((letter, index) => String.fromCodePoint(97 + index));
|
|
6
|
+
return digits.concat(uppers, lowers);
|
|
7
|
+
})();
|
|
8
|
+
|
|
9
|
+
// Returns an alphanumeric representation of an integer.
|
|
10
|
+
const alphaNumOf = num => {
|
|
11
|
+
let resultDigits = [];
|
|
12
|
+
while (num) {
|
|
13
|
+
const remainder = num % 62;
|
|
14
|
+
resultDigits.unshift(alphaNumChars[remainder]);
|
|
15
|
+
num = Math.floor(num / 62);
|
|
16
|
+
}
|
|
17
|
+
return resultDigits.join('');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
console.log(alphaNumOf(process.argv[2]));
|
package/procs/util.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
util.js
|
|
3
|
+
Utility functions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// CONSTANTS
|
|
7
|
+
|
|
8
|
+
// Array of 62 alphanumeric characters.
|
|
9
|
+
const alphaNumChars = (() => {
|
|
10
|
+
const digits = Array(10).fill('').map((digit, index) => index.toString());
|
|
11
|
+
const uppers = Array(26).fill('').map((letter, index) => String.fromCodePoint(65 + index));
|
|
12
|
+
const lowers = Array(26).fill('').map((letter, index) => String.fromCodePoint(97 + index));
|
|
13
|
+
return digits.concat(uppers, lowers);
|
|
14
|
+
})();
|
|
15
|
+
|
|
16
|
+
// FUNCTIONS
|
|
17
|
+
|
|
18
|
+
// Returns a string representing a date and time.
|
|
19
|
+
const getTimeString = date => date.toISOString().slice(0, 19);
|
|
20
|
+
// Returns a string representing the date and time.
|
|
21
|
+
exports.getNowString = () => getTimeString(new Date());
|
|
22
|
+
// Returns a time stamp representing a date and time.
|
|
23
|
+
const getTimeStamp = date => getTimeString(date).replace(/[-:]/g, '').slice(2, 13);
|
|
24
|
+
// Returns a time stamp representing the date and time.
|
|
25
|
+
exports.getNowStamp = () => getTimeStamp(new Date());
|
|
26
|
+
// Inserts a character periodically in a string.
|
|
27
|
+
const punctuate = (string, insertion, chunkSize) => {
|
|
28
|
+
const segments = [];
|
|
29
|
+
let startIndex = 0;
|
|
30
|
+
while (startIndex < string.length) {
|
|
31
|
+
segments.push(string.slice(startIndex, startIndex + chunkSize));
|
|
32
|
+
startIndex += chunkSize;
|
|
33
|
+
}
|
|
34
|
+
return segments.join(insertion);
|
|
35
|
+
};
|
|
36
|
+
// Converts a compact timestamp to a date.
|
|
37
|
+
exports.dateOf = timeStamp => {
|
|
38
|
+
if (/^\d{6}T\d{4}$/.test(timeStamp)) {
|
|
39
|
+
const dateString = punctuate(timeStamp.slice(0, 6), '-', 2);
|
|
40
|
+
const timeString = punctuate(timeStamp.slice(7, 11), ':', 2);
|
|
41
|
+
return new Date(`20${dateString}T${timeString}Z`);
|
|
42
|
+
} else {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
// Returns a base-62 alphanumeric representation of an integer.
|
|
47
|
+
exports.alphaNumOf = num => {
|
|
48
|
+
let resultDigits = [];
|
|
49
|
+
while (num || ! resultDigits.length) {
|
|
50
|
+
const remainder = num % 62;
|
|
51
|
+
resultDigits.unshift(alphaNumChars[remainder]);
|
|
52
|
+
num = Math.floor(num / 62);
|
|
53
|
+
}
|
|
54
|
+
return resultDigits.join('');
|
|
55
|
+
};
|
|
56
|
+
// Returns a random string.
|
|
57
|
+
exports.getRandomString = length => {
|
|
58
|
+
const chars = [];
|
|
59
|
+
for (let i = 0; i < length; i++) {
|
|
60
|
+
chars.push(alphaNumChars[Math.floor(62 * Math.random())]);
|
|
61
|
+
}
|
|
62
|
+
return chars.join('');
|
|
63
|
+
};
|
package/script.js
CHANGED
|
@@ -71,9 +71,6 @@ exports.script = (id, issues = null, ... issueIDs) => {
|
|
|
71
71
|
strict: true,
|
|
72
72
|
isolate: true,
|
|
73
73
|
timeLimit: 30 + (10 * issueIDs.length || 30 * toolIDs.length),
|
|
74
|
-
standard: 'only',
|
|
75
|
-
observe: true,
|
|
76
|
-
timeStamp: '',
|
|
77
74
|
acts: [
|
|
78
75
|
{
|
|
79
76
|
"type": "placeholder",
|
|
@@ -99,7 +96,7 @@ exports.script = (id, issues = null, ... issueIDs) => {
|
|
|
99
96
|
toolAct.rules.unshift('y');
|
|
100
97
|
}
|
|
101
98
|
}
|
|
102
|
-
// Add any needed option
|
|
99
|
+
// Add any needed option defaults to the act.
|
|
103
100
|
if (toolID === 'axe') {
|
|
104
101
|
toolAct.detailLevel = 2;
|
|
105
102
|
}
|