testaro 4.1.2 → 4.2.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 +86 -26
- package/batchify.js +24 -0
- package/{job.js → create.js} +10 -28
- package/high.js +11 -0
- package/package.json +1 -1
- package/procs/{test/allText.js → allText.js} +0 -0
- package/procs/{test/allVis.js → allVis.js} +0 -0
- package/procs/{test/linksByType.js → linksByType.js} +0 -0
- package/procs/{test/textOf.txt → textOf.txt} +0 -0
- package/run.js +10 -9
- package/samples/scripts/simple.json +2 -1
- package/tests/focInd.js +1 -1
- package/tests/linkUl.js +1 -1
- package/tests/menuNav.js +1 -1
- package/tests/radioSet.js +1 -1
- package/tests/styleDiff.js +1 -1
- package/tests/tabNav.js +1 -1
- package/tests/wave.js +1 -1
- package/validation/exJobs/README.md +3 -0
- package/validation/executors/high1.js +32 -0
- package/validation/executors/high2.js +37 -0
- package/validation/executors/low.js +31 -0
- package/validation/executors/watchDir.js +17 -0
- package/validation/executors/watchNet.js +106 -0
- package/validation/jobs/README.md +3 -0
- package/validation/protoJobs/README.md +3 -0
- package/validation/protoJobs/val1.json +25 -0
- package/validation/protoJobs/val2.json +41 -0
- package/watch.js +281 -0
- package/procs/score/asp09.js +0 -555
- package/procs/score/asp09NoWAVE.js +0 -508
- package/validation/executors/app.js +0 -66
- package/validation/executors/test.js +0 -48
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// watchDir.js
|
|
2
|
+
// Validator for network watching.
|
|
3
|
+
|
|
4
|
+
const fs = require('fs/promises');
|
|
5
|
+
process.env.WATCH_TYPE = 'net';
|
|
6
|
+
process.env.INTERVAL = 5;
|
|
7
|
+
process.env.WATCH_FOREVER = false;
|
|
8
|
+
process.env.PROTOCOL = 'http';
|
|
9
|
+
process.env.JOB_URL = 'localhost:3007/job';
|
|
10
|
+
process.env.REPORT_URL = 'localhost:3007/report';
|
|
11
|
+
process.env.AUTH_CODE = 'testarauth';
|
|
12
|
+
process.env.PROTOCOL = 'http';
|
|
13
|
+
const http = require('http');
|
|
14
|
+
// Start a timer.
|
|
15
|
+
const startTime = Date.now();
|
|
16
|
+
// Initialize the state.
|
|
17
|
+
let jobGiven = false;
|
|
18
|
+
// Start checking for jobs every 5 seconds in 5 seconds.
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
require('../../watch');
|
|
21
|
+
}, 5000);
|
|
22
|
+
let server;
|
|
23
|
+
// Handles Testaro requests to the server.
|
|
24
|
+
const requestHandler = (request, response) => {
|
|
25
|
+
const {method} = request;
|
|
26
|
+
const bodyParts = [];
|
|
27
|
+
request.on('error', err => {
|
|
28
|
+
console.error(err);
|
|
29
|
+
})
|
|
30
|
+
.on('data', chunk => {
|
|
31
|
+
bodyParts.push(chunk);
|
|
32
|
+
})
|
|
33
|
+
.on('end', async () => {
|
|
34
|
+
// Remove any trailing slash from the URL.
|
|
35
|
+
const requestURL = request.url.replace(/\/$/, '');
|
|
36
|
+
// If the request method is GET:
|
|
37
|
+
if (method === 'GET') {
|
|
38
|
+
// If a job is validly requested:
|
|
39
|
+
console.log('Server got a job request from Testaro');
|
|
40
|
+
if (requestURL === '/job?authCode=testarauth') {
|
|
41
|
+
// If at least 7 seconds has elapsed since timing started:
|
|
42
|
+
if (Date.now() > startTime + 7000) {
|
|
43
|
+
// Respond with a job.
|
|
44
|
+
const jobJSON = await fs.readFile(`${__dirname}/../protoJobs/val1.json`);
|
|
45
|
+
await response.end(jobJSON);
|
|
46
|
+
console.log('Server sent job val1 to Testaro');
|
|
47
|
+
jobGiven = true;
|
|
48
|
+
}
|
|
49
|
+
// Otherwise, i.e. if timing started less than 7 seconds ago:
|
|
50
|
+
else {
|
|
51
|
+
// Send an empty-object response.
|
|
52
|
+
await response.end('{}');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const error = {
|
|
57
|
+
error: 'ERROR: Job request invalid'
|
|
58
|
+
};
|
|
59
|
+
const errorJSON = JSON.stringify(error);
|
|
60
|
+
await response.end(errorJSON);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Otherwise, if the request method is POST:
|
|
64
|
+
else if (method === 'POST') {
|
|
65
|
+
console.log('Server got report from Testaro');
|
|
66
|
+
const ack = {};
|
|
67
|
+
// If a report is validly submitted:
|
|
68
|
+
if (requestURL === '/report?authCode=testarauth') {
|
|
69
|
+
// If a job was earlier given to Testaro:
|
|
70
|
+
if (jobGiven) {
|
|
71
|
+
// Respond, reporting success or failure.
|
|
72
|
+
try {
|
|
73
|
+
const bodyJSON = bodyParts.join('');
|
|
74
|
+
const body = JSON.parse(bodyJSON);
|
|
75
|
+
if (body.jobID && body.script && body.acts) {
|
|
76
|
+
ack.result = 'Success: Valid report submitted';
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
ack.result = 'Failure: Report invalid';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch(error) {
|
|
83
|
+
ack.result = `ERROR: ${error.message}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
ack.result = 'ERROR: Report submission invalid';
|
|
89
|
+
}
|
|
90
|
+
const ackJSON = JSON.stringify(ack);
|
|
91
|
+
response.end(ackJSON);
|
|
92
|
+
console.log(`Server responded: ${ack.result}`);
|
|
93
|
+
// This ends the validation, so stop the server.
|
|
94
|
+
server.close();
|
|
95
|
+
console.log('Server closed');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
// Create a server.
|
|
100
|
+
server = http.createServer({}, requestHandler);
|
|
101
|
+
// Start a server listening for Testaro requests.
|
|
102
|
+
server.listen(3007, () => {
|
|
103
|
+
console.log('Job and report server listening on port 3007');
|
|
104
|
+
});
|
|
105
|
+
// Start checking for jobs every 5 seconds.
|
|
106
|
+
require('../../watch');
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jobID": "val1",
|
|
3
|
+
"script": {
|
|
4
|
+
"id": "simple",
|
|
5
|
+
"what": "Test example.com with bulk",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"commands": [
|
|
8
|
+
{
|
|
9
|
+
"type": "launch",
|
|
10
|
+
"which": "chromium",
|
|
11
|
+
"what": "Chromium browser"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "url",
|
|
15
|
+
"which": "https://example.com/",
|
|
16
|
+
"what": "page with a few accessibility defects"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "test",
|
|
20
|
+
"which": "bulk",
|
|
21
|
+
"what": "bulk"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"jobID": "val2",
|
|
3
|
+
"script": {
|
|
4
|
+
"id": "simple",
|
|
5
|
+
"what": "Test example.com with bulk",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"commands": [
|
|
8
|
+
{
|
|
9
|
+
"type": "launch",
|
|
10
|
+
"which": "chromium",
|
|
11
|
+
"what": "Chromium browser"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"type": "url",
|
|
15
|
+
"which": "https://example.com/",
|
|
16
|
+
"what": "page with a few accessibility defects"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"type": "test",
|
|
20
|
+
"which": "bulk",
|
|
21
|
+
"what": "bulk"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"batch": {
|
|
26
|
+
"id": "weborgs",
|
|
27
|
+
"what": "Web organizations",
|
|
28
|
+
"hosts": [
|
|
29
|
+
{
|
|
30
|
+
"id": "mozilla",
|
|
31
|
+
"which": "https://www.mozilla.org/en-US/",
|
|
32
|
+
"what": "Mozilla"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "w3c",
|
|
36
|
+
"which": "https://www.w3.org/",
|
|
37
|
+
"what": "W3C"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
package/watch.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/*
|
|
2
|
+
watch.js
|
|
3
|
+
Watches for jobs and runs them.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ########## IMPORTS
|
|
7
|
+
|
|
8
|
+
// Module to keep secrets local.
|
|
9
|
+
// require('dotenv').config({override: true});
|
|
10
|
+
require('dotenv').config();
|
|
11
|
+
// Module to read and write files.
|
|
12
|
+
const fs = require('fs/promises');
|
|
13
|
+
// Module to perform tests.
|
|
14
|
+
const {handleRequest} = require('./run');
|
|
15
|
+
// Module to convert a script and a batch to a batch-based array of scripts.
|
|
16
|
+
const {batchify} = require('./batchify');
|
|
17
|
+
|
|
18
|
+
// ########## CONSTANTS
|
|
19
|
+
|
|
20
|
+
const watchType = process.env.WATCH_TYPE;
|
|
21
|
+
let client;
|
|
22
|
+
if (watchType === 'net') {
|
|
23
|
+
client = require(process.env.PROTOCOL || 'https');
|
|
24
|
+
}
|
|
25
|
+
const jobURL = process.env.JOB_URL;
|
|
26
|
+
const authCode = process.env.AUTH_CODE;
|
|
27
|
+
const jobDir = process.env.JOBDIR;
|
|
28
|
+
const exJobDir = process.env.EXJOBDIR;
|
|
29
|
+
const reportURL = process.env.REPORT_URL;
|
|
30
|
+
const reportDir = process.env.REPORTDIR;
|
|
31
|
+
const interval = process.env.INTERVAL;
|
|
32
|
+
// Values of process.env properties are coerced to strings.
|
|
33
|
+
const watchForever = process.env.WATCH_FOREVER == 'true';
|
|
34
|
+
|
|
35
|
+
// ########## FUNCTIONS
|
|
36
|
+
|
|
37
|
+
// Checks for a directory job.
|
|
38
|
+
const checkDirJob = async () => {
|
|
39
|
+
const jobDirFileNames = await fs.readdir(jobDir);
|
|
40
|
+
const jobFileNames = jobDirFileNames.filter(fileName => fileName.endsWith('.json'));
|
|
41
|
+
if (jobFileNames.length) {
|
|
42
|
+
const firstJobID = jobFileNames[0].slice(0, -5);
|
|
43
|
+
const jobJSON = await fs.readFile(`${jobDir}/${jobFileNames[0]}`, 'utf8');
|
|
44
|
+
try {
|
|
45
|
+
const job = JSON.parse(jobJSON, null, 2);
|
|
46
|
+
job.jobID = firstJobID;
|
|
47
|
+
return job;
|
|
48
|
+
}
|
|
49
|
+
catch(error) {
|
|
50
|
+
return {
|
|
51
|
+
error: 'ERROR: Job was not JSON',
|
|
52
|
+
message: error.message
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
// Checks for a network job.
|
|
61
|
+
const checkNetJob = async () => {
|
|
62
|
+
const job = await new Promise(resolve => {
|
|
63
|
+
const wholeURL = `${process.env.PROTOCOL}://${jobURL}?authCode=${authCode}`;
|
|
64
|
+
const request = client.request(wholeURL, response => {
|
|
65
|
+
const chunks = [];
|
|
66
|
+
response.on('data', chunk => {
|
|
67
|
+
chunks.push(chunk);
|
|
68
|
+
});
|
|
69
|
+
response.on('end', () => {
|
|
70
|
+
try {
|
|
71
|
+
const jobJSON = chunks.join('');
|
|
72
|
+
const job = JSON.parse(jobJSON);
|
|
73
|
+
// If a qualifying job was received:
|
|
74
|
+
if (job.jobID) {
|
|
75
|
+
// Return it.
|
|
76
|
+
resolve(job);
|
|
77
|
+
}
|
|
78
|
+
// Otherwise, i.e. if there was no qualifying job:
|
|
79
|
+
else {
|
|
80
|
+
// Return this.
|
|
81
|
+
resolve({});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch(error) {
|
|
85
|
+
resolve({
|
|
86
|
+
error: 'ERROR: Response was not JSON',
|
|
87
|
+
message: error.message,
|
|
88
|
+
status: response.statusCode
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
request.end();
|
|
94
|
+
});
|
|
95
|
+
return job;
|
|
96
|
+
};
|
|
97
|
+
// Writes a directory report.
|
|
98
|
+
const writeDirReport = async report => {
|
|
99
|
+
const {id, jobID} = report;
|
|
100
|
+
if (id && jobID) {
|
|
101
|
+
const reportJSON = JSON.stringify(report, null, 2);
|
|
102
|
+
try {
|
|
103
|
+
await fs.writeFile(`${reportDir}/${id}.json`, reportJSON);
|
|
104
|
+
console.log(`Report ${id}.json saved`);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch(error) {
|
|
108
|
+
console.log(`ERROR: Failed to write report ${id} for job ${jobID}`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
// Submits a network report.
|
|
114
|
+
const writeNetReport = async report => {
|
|
115
|
+
const ack = await new Promise(resolve => {
|
|
116
|
+
const wholeURL = `${process.env.PROTOCOL}://${reportURL}?authCode=${authCode}`;
|
|
117
|
+
const request = client.request(wholeURL, {method: 'POST'}, response => {
|
|
118
|
+
const chunks = [];
|
|
119
|
+
response.on('data', chunk => {
|
|
120
|
+
chunks.push(chunk);
|
|
121
|
+
});
|
|
122
|
+
response.on('end', () => {
|
|
123
|
+
try {
|
|
124
|
+
resolve(JSON.parse(chunks.join('')));
|
|
125
|
+
}
|
|
126
|
+
catch(error) {
|
|
127
|
+
resolve({
|
|
128
|
+
error: 'ERROR: Response was not JSON',
|
|
129
|
+
message: error.message,
|
|
130
|
+
status: response.statusCode
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
request.write(JSON.stringify(report, null, 2));
|
|
136
|
+
request.end();
|
|
137
|
+
console.log(`Report with ID ${report.id} submitted`);
|
|
138
|
+
});
|
|
139
|
+
return ack;
|
|
140
|
+
};
|
|
141
|
+
// Archives a job.
|
|
142
|
+
const exifyJob = async (job) => {
|
|
143
|
+
const jobJSON = JSON.stringify(job, null, 2);
|
|
144
|
+
await fs.writeFile(`${exJobDir}/${job.timeStamp}.json`, jobJSON);
|
|
145
|
+
await fs.rm(`${jobDir}/${job.jobID}.json`);
|
|
146
|
+
};
|
|
147
|
+
// Waits.
|
|
148
|
+
const wait = ms => {
|
|
149
|
+
return new Promise(resolve => {
|
|
150
|
+
setTimeout(() => {
|
|
151
|
+
resolve('');
|
|
152
|
+
}, ms);
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
// Runs one script and writes or sends a report.
|
|
156
|
+
const runHost = async (jobID, timeStamp, id, script) => {
|
|
157
|
+
const report = {
|
|
158
|
+
jobID,
|
|
159
|
+
timeStamp,
|
|
160
|
+
id,
|
|
161
|
+
log: [],
|
|
162
|
+
script,
|
|
163
|
+
acts: []
|
|
164
|
+
};
|
|
165
|
+
await handleRequest(report);
|
|
166
|
+
if (watchType === 'dir') {
|
|
167
|
+
return await writeDirReport(report);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const ack = await writeNetReport(report);
|
|
171
|
+
if (ack.error) {
|
|
172
|
+
console.log(JSON.stringify(ack, null, 2));
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
// Runs a job and returns a report file for the script or each host.
|
|
181
|
+
const runJob = async job => {
|
|
182
|
+
const {jobID, script, batch} = job;
|
|
183
|
+
if (jobID) {
|
|
184
|
+
if (script) {
|
|
185
|
+
try {
|
|
186
|
+
// Identify the start time and a time stamp.
|
|
187
|
+
const timeStamp = Math.floor((Date.now() - Date.UTC(2022, 1)) / 2000).toString(36);
|
|
188
|
+
job.timeStamp = timeStamp;
|
|
189
|
+
// If there is a batch:
|
|
190
|
+
if (batch) {
|
|
191
|
+
// Convert the script to a set of host scripts.
|
|
192
|
+
const specs = batchify(script, batch, timeStamp);
|
|
193
|
+
// For each host script:
|
|
194
|
+
let success = true;
|
|
195
|
+
while (specs.length && success) {
|
|
196
|
+
// Run it and write or submit a report with a host-suffixed time-stamp ID.
|
|
197
|
+
const spec = specs.shift();
|
|
198
|
+
const {id} = spec;
|
|
199
|
+
const hostScript = spec.script;
|
|
200
|
+
success = await runHost(jobID, timeStamp, id, hostScript);
|
|
201
|
+
}
|
|
202
|
+
return success;
|
|
203
|
+
}
|
|
204
|
+
// Otherwise, i.e. if there is no batch:
|
|
205
|
+
else {
|
|
206
|
+
// Run the script and submit a report with a timestamp ID.
|
|
207
|
+
return await runHost(jobID, timeStamp, timeStamp, script);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch(error) {
|
|
211
|
+
console.log(`ERROR: ${error.message}\n${error.stack}`);
|
|
212
|
+
return {
|
|
213
|
+
error: `ERROR: ${error.message}\n${error.stack}`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.log('ERROR: no script specified');
|
|
219
|
+
return {
|
|
220
|
+
error: 'ERROR: no script specified'
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
console.log('ERROR: no job ID property in job');
|
|
226
|
+
return {
|
|
227
|
+
error: 'ERROR: no job ID property in job'
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
// Repeatedly checks for jobs, runs them, and submits reports.
|
|
232
|
+
const cycle = async forever => {
|
|
233
|
+
const intervalMS = Number.parseInt(interval);
|
|
234
|
+
let statusOK = true;
|
|
235
|
+
let empty = false;
|
|
236
|
+
console.log(`Watching started with intervals of ${interval} seconds when idle`);
|
|
237
|
+
while (statusOK) {
|
|
238
|
+
if (empty) {
|
|
239
|
+
await wait(1000 * intervalMS);
|
|
240
|
+
}
|
|
241
|
+
// Check for a job.
|
|
242
|
+
let job;
|
|
243
|
+
if (watchType === 'dir') {
|
|
244
|
+
job = await checkDirJob();
|
|
245
|
+
}
|
|
246
|
+
else if (watchType === 'net') {
|
|
247
|
+
job = await checkNetJob();
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
job = {};
|
|
251
|
+
console.log('ERROR: invalid WATCH_TYPE environment variable');
|
|
252
|
+
statusOK = false;
|
|
253
|
+
}
|
|
254
|
+
// If there was one:
|
|
255
|
+
if (job.jobID) {
|
|
256
|
+
// Run it.
|
|
257
|
+
console.log(`Running job ${job.jobID}`);
|
|
258
|
+
statusOK = await runJob(job);
|
|
259
|
+
console.log(`Job ${job.jobID} finished with time stamp ${job.timeStamp}`);
|
|
260
|
+
if (statusOK) {
|
|
261
|
+
// If the job was a file:
|
|
262
|
+
if (watchType === 'dir') {
|
|
263
|
+
// Archive it.
|
|
264
|
+
await exifyJob(job);
|
|
265
|
+
console.log(`Job archived as ${job.timeStamp}.json`);
|
|
266
|
+
}
|
|
267
|
+
// If watching was specified for only 1 job, stop.
|
|
268
|
+
statusOK = forever;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
empty = true;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
console.log('Watching ended');
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// ########## OPERATION
|
|
279
|
+
|
|
280
|
+
// Start watching, as specified, either forever or until 1 job is run.
|
|
281
|
+
cycle(watchForever);
|