@jrpool/kilotest 25.0.1 → 26.0.1
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/index.js +13 -4
- package/package.json +1 -1
- package/reportIssues/api.js +3 -14
- package/reportIssues/util.js +5 -21
- package/reportsExpungeForm/index.js +7 -7
- package/reportsPruneForm/index.js +7 -7
- package/reportsRewindForm/index.js +7 -7
- package/researchAgent.js +56 -20
- package/targets/api.js +101 -0
- package/targets/index.js +25 -25
- package/util.js +87 -27
package/index.js
CHANGED
|
@@ -582,9 +582,18 @@ const requestHandler = async (request, response) => {
|
|
|
582
582
|
}
|
|
583
583
|
// Otherwise, if the agent is the authorized research agent and it is authenticated:
|
|
584
584
|
else if (agentID === researchAgent && postData.agentPW === researchAgentPW) {
|
|
585
|
-
// If the service is provision of facts about
|
|
586
|
-
if (service === '
|
|
587
|
-
// Get the
|
|
585
|
+
// If the service is provision of facts about the available reports:
|
|
586
|
+
if (service === 'targets') {
|
|
587
|
+
// Get the agent ID from the path.
|
|
588
|
+
const args = [agentID];
|
|
589
|
+
// Get the response (potentially error) data.
|
|
590
|
+
const responseData = await require(path.join(__dirname, 'targets', 'api')).response(args);
|
|
591
|
+
// Send them.
|
|
592
|
+
response.end(JSON.stringify(responseData));
|
|
593
|
+
}
|
|
594
|
+
// Otherwise, if the service is provision of facts about issues in a report:
|
|
595
|
+
else if (service === 'reportIssues') {
|
|
596
|
+
// Get the agent ID and report identifiers from the path.
|
|
588
597
|
const [timeStamp, jobID] = specs;
|
|
589
598
|
const args = [agentID, timeStamp, jobID];
|
|
590
599
|
const reportSpecsBad = await isHidden(timeStamp, jobID);
|
|
@@ -609,7 +618,7 @@ const requestHandler = async (request, response) => {
|
|
|
609
618
|
// Otherwise, i.e. if the service is invalid:
|
|
610
619
|
else {
|
|
611
620
|
// Report this.
|
|
612
|
-
await serveError({message: 'ERROR: Invalid service request'}, response, false);
|
|
621
|
+
await serveError({message: 'ERROR: Invalid service request from research agent'}, response, false);
|
|
613
622
|
}
|
|
614
623
|
}
|
|
615
624
|
// Otherwise, i.e. if the agent is not authorized or not authenticated:
|
package/package.json
CHANGED
package/reportIssues/api.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {getData
|
|
8
|
+
const {getData} = require('./util');
|
|
9
9
|
const {
|
|
10
10
|
getDateTime,
|
|
11
11
|
getNowStamp,
|
|
12
12
|
getRandomString,
|
|
13
|
+
getToolsFacts,
|
|
13
14
|
isHidden,
|
|
14
15
|
researchAgents,
|
|
15
16
|
tools
|
|
@@ -17,18 +18,6 @@ const {
|
|
|
17
18
|
|
|
18
19
|
// FUNCTIONS
|
|
19
20
|
|
|
20
|
-
// Gets facts about tools.
|
|
21
|
-
const getToolFacts = toolIDs => {
|
|
22
|
-
const crypticData = getToolData(toolIDs);
|
|
23
|
-
return crypticData.map(tool => {
|
|
24
|
-
const {toolID, toolName, toolMaker} = tool;
|
|
25
|
-
return {
|
|
26
|
-
identifier: toolID,
|
|
27
|
-
name: toolName,
|
|
28
|
-
sponsor: toolMaker
|
|
29
|
-
};
|
|
30
|
-
});
|
|
31
|
-
};
|
|
32
21
|
// Gets facts about an issue.
|
|
33
22
|
const getIssueFacts = (thisHost, agentID, timeStamp, jobID, issue) => {
|
|
34
23
|
const {issueID, reporterCount, reporters, summary, violatorCount, wcag, why} = issue;
|
|
@@ -103,7 +92,7 @@ exports.response = async args => {
|
|
|
103
92
|
description: what,
|
|
104
93
|
URL: url
|
|
105
94
|
},
|
|
106
|
-
'tools that tried to test the page':
|
|
95
|
+
'tools that tried to test the page': getToolsFacts(Object.keys(tools)),
|
|
107
96
|
'tools that were unable to test the page': preventedTools,
|
|
108
97
|
'tools that reported issues': {
|
|
109
98
|
number: reporterCount,
|
package/reportIssues/util.js
CHANGED
|
@@ -5,29 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
getPageData, getReport, getToolsData, getToolList, isValidReport, objectSort
|
|
10
|
+
} = require('../util');
|
|
9
11
|
const issuesClassification = require('testilo/procs/score/tic').issues;
|
|
10
12
|
|
|
11
13
|
// FUNCTIONS
|
|
12
14
|
|
|
13
|
-
// Converts tool IDs to tool data sorted by tool name.
|
|
14
|
-
const getToolData = exports.getToolData = toolIDs => objectSort(
|
|
15
|
-
Array.from(toolIDs).map(toolID => {
|
|
16
|
-
const toolData = tools[toolID];
|
|
17
|
-
return {
|
|
18
|
-
toolID,
|
|
19
|
-
toolName: toolData[0],
|
|
20
|
-
toolMaker: toolData[1]
|
|
21
|
-
}
|
|
22
|
-
}),
|
|
23
|
-
'toolName',
|
|
24
|
-
'alpha'
|
|
25
|
-
);
|
|
26
|
-
// Returns a +-delimited list of sorted tool names.
|
|
27
|
-
const getToolList = toolIDs => Array.from(toolIDs)
|
|
28
|
-
.map(toolID => tools[toolID][0])
|
|
29
|
-
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'}))
|
|
30
|
-
.join(' + ');
|
|
31
15
|
// Returns data on the issues reported by a report.
|
|
32
16
|
const getIssuesData = async (timeStamp, jobID) => {
|
|
33
17
|
// Get the report.
|
|
@@ -96,7 +80,7 @@ const getIssuesData = async (timeStamp, jobID) => {
|
|
|
96
80
|
}
|
|
97
81
|
});
|
|
98
82
|
// Finish populating the final data.
|
|
99
|
-
final.reporters =
|
|
83
|
+
final.reporters = getToolsData(temp.reporters);
|
|
100
84
|
final.reporterList = getToolList(temp.reporters);
|
|
101
85
|
final.reporterCount = final.reporters.length;
|
|
102
86
|
final.violatorCount = temp.violators.size;
|
|
@@ -109,7 +93,7 @@ const getIssuesData = async (timeStamp, jobID) => {
|
|
|
109
93
|
why,
|
|
110
94
|
weight
|
|
111
95
|
};
|
|
112
|
-
finalIssue.reporters =
|
|
96
|
+
finalIssue.reporters = getToolsData(issue.reporters);
|
|
113
97
|
finalIssue.reporterList = getToolList(issue.reporters);
|
|
114
98
|
finalIssue.reporterCount = finalIssue.reporters.length;
|
|
115
99
|
finalIssue.violatorCount = issue.violators.size;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const {getReportData, logsPath, reportsPath} = require('../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -43,13 +43,13 @@ exports.answer = async (_, search) => {
|
|
|
43
43
|
for (const reportName of reportNames) {
|
|
44
44
|
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
45
45
|
// Get a summary of it.
|
|
46
|
-
const reportSummary = await
|
|
47
|
-
const {
|
|
46
|
+
const reportSummary = await getReportData(timeStamp, jobID);
|
|
47
|
+
const {issueCount, preventedToolCount, url} = reportSummary;
|
|
48
48
|
reportSpecs.push({
|
|
49
49
|
timeStamp,
|
|
50
50
|
jobID,
|
|
51
|
-
issueCount
|
|
52
|
-
|
|
51
|
+
issueCount,
|
|
52
|
+
preventedToolCount,
|
|
53
53
|
url
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -65,9 +65,9 @@ exports.answer = async (_, search) => {
|
|
|
65
65
|
let anyDeletable = false;
|
|
66
66
|
// For each summary:
|
|
67
67
|
reportSpecs.forEach((spec, index) => {
|
|
68
|
-
const {timeStamp, jobID, issueCount,
|
|
68
|
+
const {timeStamp, jobID, issueCount, preventedToolCount, url} = spec;
|
|
69
69
|
const jobName = `${timeStamp}-${jobID}`;
|
|
70
|
-
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${
|
|
70
|
+
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${preventedToolCount}, issues ${issueCount}`;
|
|
71
71
|
// If its report is the sole report on a target:
|
|
72
72
|
if (reportSpecs[index - 1]?.url !== url && reportSpecs[index + 1]?.url !== url) {
|
|
73
73
|
// Add a line with a deletion checkbox.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const {getReportData, logsPath, reportsPath} = require('../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -43,13 +43,13 @@ exports.answer = async (_, search) => {
|
|
|
43
43
|
for (const reportName of reportNames) {
|
|
44
44
|
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
45
45
|
// Get a summary of it.
|
|
46
|
-
const reportSummary = await
|
|
47
|
-
const {
|
|
46
|
+
const reportSummary = await getReportData(timeStamp, jobID);
|
|
47
|
+
const {issueCount, preventedToolCount, url} = reportSummary;
|
|
48
48
|
reportSpecs.push({
|
|
49
49
|
timeStamp,
|
|
50
50
|
jobID,
|
|
51
|
-
issueCount
|
|
52
|
-
|
|
51
|
+
issueCount,
|
|
52
|
+
preventedToolCount,
|
|
53
53
|
url
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -65,9 +65,9 @@ exports.answer = async (_, search) => {
|
|
|
65
65
|
let anyDeletable = false;
|
|
66
66
|
// For each summary:
|
|
67
67
|
reportSpecs.forEach((spec, index) => {
|
|
68
|
-
const {timeStamp, jobID, issueCount,
|
|
68
|
+
const {timeStamp, jobID, issueCount, preventedToolCount, url} = spec;
|
|
69
69
|
const jobName = `${timeStamp}-${jobID}`;
|
|
70
|
-
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${
|
|
70
|
+
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${preventedToolCount}, issues ${issueCount}`;
|
|
71
71
|
// If its report is a non-latest report:
|
|
72
72
|
if (reportSpecs[index + 1]?.url === url) {
|
|
73
73
|
// Add a line with a deletion checkbox.
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
|
-
const {
|
|
8
|
+
const {getReportData, logsPath, reportsPath} = require('../util');
|
|
9
9
|
const fs = require('fs/promises');
|
|
10
10
|
const path = require('path');
|
|
11
11
|
|
|
@@ -43,13 +43,13 @@ exports.answer = async (_, search) => {
|
|
|
43
43
|
for (const reportName of reportNames) {
|
|
44
44
|
const [timeStamp, jobID] = reportName.slice(0, -5).split('-');
|
|
45
45
|
// Get a summary of it.
|
|
46
|
-
const reportSummary = await
|
|
47
|
-
const {
|
|
46
|
+
const reportSummary = await getReportData(timeStamp, jobID);
|
|
47
|
+
const {issueCount, preventedToolCount, url} = reportSummary;
|
|
48
48
|
reportSpecs.push({
|
|
49
49
|
timeStamp,
|
|
50
50
|
jobID,
|
|
51
|
-
issueCount
|
|
52
|
-
|
|
51
|
+
issueCount,
|
|
52
|
+
preventedToolCount,
|
|
53
53
|
url
|
|
54
54
|
});
|
|
55
55
|
}
|
|
@@ -65,9 +65,9 @@ exports.answer = async (_, search) => {
|
|
|
65
65
|
let anyDeletable = false;
|
|
66
66
|
// For each summary:
|
|
67
67
|
reportSpecs.forEach((spec, index) => {
|
|
68
|
-
const {timeStamp, jobID, issueCount,
|
|
68
|
+
const {timeStamp, jobID, issueCount, preventedToolCount, url} = spec;
|
|
69
69
|
const jobName = `${timeStamp}-${jobID}`;
|
|
70
|
-
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${
|
|
70
|
+
const specString = `<code>${url}</code> (<code>${jobName}</code>): preventions ${preventedToolCount}, issues ${issueCount}`;
|
|
71
71
|
// If its report is the latest report on a target with at least 2 reports:
|
|
72
72
|
if (reportSpecs[index - 1]?.url === url && reportSpecs[index + 1]?.url !== url) {
|
|
73
73
|
// Add a line with a deletion checkbox.
|
package/researchAgent.js
CHANGED
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
/*
|
|
11
11
|
researchAgent.js
|
|
12
|
-
|
|
12
|
+
Simulates a research agent.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// IMPORTS
|
|
16
16
|
|
|
17
|
-
const {getLogs} = require('./util');
|
|
18
17
|
require('dotenv').config();
|
|
19
18
|
const httpClient = require('http');
|
|
20
19
|
const httpsClient = require('https');
|
|
@@ -30,22 +29,13 @@ const hostParts = kilotestHost.split(/:\/*/);
|
|
|
30
29
|
const scheme = hostParts[0];
|
|
31
30
|
const host = hostParts[1];
|
|
32
31
|
const port = hostParts[2] || (scheme === 'https' ? 443 : 80);
|
|
33
|
-
const services = ['reportIssues'];
|
|
34
|
-
// Randomly chosen service.
|
|
35
|
-
const service = services[Math.floor(services.length * Math.random())];
|
|
36
32
|
|
|
37
33
|
// FUNCTIONS
|
|
38
34
|
|
|
39
|
-
// Submits
|
|
35
|
+
// Submits two request to a random Kilotest host.
|
|
40
36
|
const requestService = async () => {
|
|
41
|
-
const logs = await getLogs();
|
|
42
|
-
// Randomly chosen log.
|
|
43
|
-
const log = logs[Math.floor(logs.length * Math.random())];
|
|
44
|
-
const specs = log.jobName.split('-').join('/');
|
|
45
|
-
const path = `/api/${agent}/${service}/${specs}`;
|
|
46
37
|
const client = scheme === 'https' ? httpsClient : httpClient;
|
|
47
|
-
|
|
48
|
-
const requestOptions = {
|
|
38
|
+
const getRequestOptions = path => ({
|
|
49
39
|
method: 'POST',
|
|
50
40
|
host,
|
|
51
41
|
port,
|
|
@@ -53,10 +43,11 @@ const requestService = async () => {
|
|
|
53
43
|
headers: {
|
|
54
44
|
'content-type': 'application/json; charset=utf-8'
|
|
55
45
|
}
|
|
56
|
-
};
|
|
46
|
+
});
|
|
47
|
+
const path = `/api/${agent}/targets`;
|
|
57
48
|
console.log(`About to submit ${scheme} request as JSON on port ${port} to ${host}${path}`);
|
|
58
|
-
// Submit a request.
|
|
59
|
-
client.request(
|
|
49
|
+
// Submit a targets request.
|
|
50
|
+
client.request(getRequestOptions(path), response => {
|
|
60
51
|
// Initialize a collection of data from the response.
|
|
61
52
|
const chunks = [];
|
|
62
53
|
response
|
|
@@ -73,17 +64,62 @@ const requestService = async () => {
|
|
|
73
64
|
// When the response is completed:
|
|
74
65
|
.on('end', async () => {
|
|
75
66
|
const content = chunks.join('');
|
|
76
|
-
// Output it.
|
|
77
67
|
try {
|
|
78
|
-
|
|
68
|
+
// Output it.
|
|
69
|
+
const contentObj = JSON.parse(content);
|
|
70
|
+
console.log('======================');
|
|
71
|
+
console.log(JSON.stringify(contentObj, null, 2));
|
|
72
|
+
// Get the IDs of the available reports.
|
|
73
|
+
const reportIDs = contentObj['available reports'].map(report => report.identifier);
|
|
74
|
+
// Choose one at random.
|
|
75
|
+
const [timeStamp, jobID] = reportIDs[Math.floor(Math.random() * reportIDs.length)]
|
|
76
|
+
.split('-');
|
|
77
|
+
console.log('======================');
|
|
78
|
+
const path = `/api/${agent}/reportIssues/${timeStamp}/${jobID}`;
|
|
79
|
+
console.log(`About to submit ${scheme} request as JSON on port ${port} to ${host}${path}`);
|
|
80
|
+
const requestOptions = getRequestOptions(path);
|
|
81
|
+
// Submit an issues request for it.
|
|
82
|
+
client.request(requestOptions, response => {
|
|
83
|
+
// Initialize a collection of data from the response.
|
|
84
|
+
const chunks = [];
|
|
85
|
+
response
|
|
86
|
+
// If the response throws an error:
|
|
87
|
+
.on('error', async error => {
|
|
88
|
+
// Report it.
|
|
89
|
+
console.log(error.message);
|
|
90
|
+
})
|
|
91
|
+
// If the response delivers data:
|
|
92
|
+
.on('data', chunk => {
|
|
93
|
+
// Add them to the collection.
|
|
94
|
+
chunks.push(chunk);
|
|
95
|
+
})
|
|
96
|
+
// When the response is completed:
|
|
97
|
+
.on('end', async () => {
|
|
98
|
+
const content = chunks.join('');
|
|
99
|
+
try {
|
|
100
|
+
// Output it.
|
|
101
|
+
const contentObj = JSON.parse(content);
|
|
102
|
+
console.log(JSON.stringify(contentObj, null, 2));
|
|
103
|
+
console.log('======================');
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.log(error.message);
|
|
107
|
+
console.log(`Issues response content: ${content || 'No content'}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
})
|
|
111
|
+
// Finish sending the issues request.
|
|
112
|
+
.end(JSON.stringify({
|
|
113
|
+
agentPW
|
|
114
|
+
}));
|
|
79
115
|
}
|
|
80
116
|
catch (error) {
|
|
81
117
|
console.log(error.message);
|
|
82
|
-
console.log(`
|
|
118
|
+
console.log(`Targets response content: ${content || 'No content'}`);
|
|
83
119
|
}
|
|
84
120
|
});
|
|
85
121
|
})
|
|
86
|
-
// Finish sending the
|
|
122
|
+
// Finish sending the targets request.
|
|
87
123
|
.end(JSON.stringify({
|
|
88
124
|
agentPW
|
|
89
125
|
}));
|
package/targets/api.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/*
|
|
2
|
+
api.js
|
|
3
|
+
Responds to the targets request.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getLogs,
|
|
10
|
+
getNowStamp,
|
|
11
|
+
getRandomString,
|
|
12
|
+
getReportData,
|
|
13
|
+
researchAgents
|
|
14
|
+
} = require('../util');
|
|
15
|
+
|
|
16
|
+
// CONSTANTS
|
|
17
|
+
|
|
18
|
+
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
19
|
+
|
|
20
|
+
// FUNCTIONS
|
|
21
|
+
|
|
22
|
+
// Returns a response to a targets request.
|
|
23
|
+
exports.response = async agentID => {
|
|
24
|
+
const availableReports = [];
|
|
25
|
+
// Get the non-hidden logs.
|
|
26
|
+
const targetLogs = await getLogs();
|
|
27
|
+
// For each log:
|
|
28
|
+
for (const targetLog of targetLogs) {
|
|
29
|
+
const {jobName} = targetLog;
|
|
30
|
+
const [timeStamp, jobID] = jobName.split('-');
|
|
31
|
+
const {superseded, url, what} = targetLog;
|
|
32
|
+
// Get data about its report.
|
|
33
|
+
const data = await getReportData(timeStamp, jobID);
|
|
34
|
+
const {
|
|
35
|
+
creationDate,
|
|
36
|
+
daysAgo,
|
|
37
|
+
issueCount,
|
|
38
|
+
toolNames,
|
|
39
|
+
toolCount,
|
|
40
|
+
reporterNames,
|
|
41
|
+
reporterCount,
|
|
42
|
+
violatorCount,
|
|
43
|
+
preventedToolNames,
|
|
44
|
+
preventedToolCount
|
|
45
|
+
} = data;
|
|
46
|
+
availableReports.push({
|
|
47
|
+
identifier: jobName,
|
|
48
|
+
'creation date': creationDate,
|
|
49
|
+
'days since the creation date': daysAgo,
|
|
50
|
+
'tested web page': {
|
|
51
|
+
description: what,
|
|
52
|
+
URL: url
|
|
53
|
+
},
|
|
54
|
+
'whether a later report about the same page exists': !! superseded,
|
|
55
|
+
'number of issues reported': issueCount,
|
|
56
|
+
'number of HTML elements reported as exhibiting issues': violatorCount,
|
|
57
|
+
'tools that tried to test the page': {
|
|
58
|
+
number: toolCount,
|
|
59
|
+
names: toolNames
|
|
60
|
+
},
|
|
61
|
+
'tools that were unable to test the page': {
|
|
62
|
+
number: preventedToolCount,
|
|
63
|
+
names: preventedToolNames
|
|
64
|
+
},
|
|
65
|
+
'tools that reported issues': {
|
|
66
|
+
number: reporterCount,
|
|
67
|
+
names: reporterNames
|
|
68
|
+
},
|
|
69
|
+
'URLs for getting data on the reported issues': {
|
|
70
|
+
'for you': `${thisHost}/api/${agentID}/reportIssues/${timeStamp}/${jobID}`,
|
|
71
|
+
'for humans': `${thisHost}/reportIssues/${timeStamp}/${jobID}`
|
|
72
|
+
},
|
|
73
|
+
'URL for getting the full technical report as JSON': `${thisHost}/fullReport.html/${timeStamp}/${jobID}`
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// Get a response.
|
|
77
|
+
const response = {
|
|
78
|
+
summary: `This document fulfills a request made by an agent to the Kilotest service. The agent requested data about the web pages that Kilotest had tested for accessibility, usability, and standard-conformity and statistics for each page on the results of the tests. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten testing tools, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes API endpoints for agents and web UI URLs for humans to recommend web pages for testing and obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of tools, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), whose home page contains an introduction and a link to a tutorial.`,
|
|
79
|
+
'tool name': 'Kilotest',
|
|
80
|
+
request: {
|
|
81
|
+
'requesting agent': {
|
|
82
|
+
identifier: agentID,
|
|
83
|
+
name: researchAgents[agentID]
|
|
84
|
+
},
|
|
85
|
+
'type of request': {
|
|
86
|
+
identifier: 'targets',
|
|
87
|
+
description: 'Give me summary data about each available report.'
|
|
88
|
+
},
|
|
89
|
+
URLs: {
|
|
90
|
+
'URL of this request': `${thisHost}/api/${agentID}/targets`,
|
|
91
|
+
'equivalent URL for humans': `${thisHost}/targets.html`
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
'response metadata': {
|
|
95
|
+
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
96
|
+
'date and time': new Date().toISOString(),
|
|
97
|
+
},
|
|
98
|
+
'available reports': availableReports
|
|
99
|
+
};
|
|
100
|
+
return response;
|
|
101
|
+
};
|
package/targets/index.js
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
getAgoDays,
|
|
10
|
+
getCountString,
|
|
10
11
|
getJobNames,
|
|
11
12
|
getObject,
|
|
12
13
|
getPageDataStrings,
|
|
13
14
|
getRecs,
|
|
14
15
|
getToolNamesString,
|
|
15
16
|
getLogs,
|
|
16
|
-
|
|
17
|
+
getReportData,
|
|
17
18
|
isRecommendable,
|
|
18
19
|
jobsPath
|
|
19
20
|
} = require('../util');
|
|
@@ -22,8 +23,6 @@ const path = require('path');
|
|
|
22
23
|
|
|
23
24
|
// FUNCTIONS
|
|
24
25
|
|
|
25
|
-
// Returns a description of a tool count.
|
|
26
|
-
const getToolCountString = toolCount => toolCount === 1 ? '1 tool' : `${toolCount} tools`;
|
|
27
26
|
// Adds parameters to a query for the answer page.
|
|
28
27
|
const populateQuery = async query => {
|
|
29
28
|
const margin = ' '.repeat(8);
|
|
@@ -71,19 +70,26 @@ const populateQuery = async query => {
|
|
|
71
70
|
query.noQueued = lines.queue.length ? '' : 'No pages are queued for testing.';
|
|
72
71
|
// Add a no-claimed message, if applicable, to the query.
|
|
73
72
|
query.noClaimed = lines.claimed.length ? '' : 'No pages are being tested now.';
|
|
74
|
-
// Get the logs of the reports.
|
|
75
|
-
const targetLogs =
|
|
73
|
+
// Get the logs of the non-hidden reports.
|
|
74
|
+
const targetLogs = await getLogs();
|
|
76
75
|
query.which = targetLogs.length ? 'the following' : 'no';
|
|
77
76
|
query.some = (targetLogs.length || jobFileNames.queue.length || jobFileNames.claimed.length)
|
|
78
77
|
? 'another'
|
|
79
78
|
: 'a';
|
|
80
79
|
const multiReportTargets = new Set(targetLogs.filter(log => log.superseded).map(log => log.what));
|
|
81
|
-
// For each log
|
|
80
|
+
// For each log:
|
|
82
81
|
for (const targetLog of targetLogs) {
|
|
83
82
|
const {jobName, url, what} = targetLog;
|
|
84
83
|
const [timeStamp, jobID] = jobName.split('-');
|
|
85
|
-
const reportData = await
|
|
86
|
-
const {
|
|
84
|
+
const reportData = await getReportData(timeStamp, jobID);
|
|
85
|
+
const {
|
|
86
|
+
issueCount,
|
|
87
|
+
preventedToolCount,
|
|
88
|
+
preventedToolNames,
|
|
89
|
+
reporterNames,
|
|
90
|
+
reporterCount,
|
|
91
|
+
violatorCount
|
|
92
|
+
} = reportData;
|
|
87
93
|
lines.tested.push(`${margin}<details>`);
|
|
88
94
|
const daysAgo = getAgoDays(timeStamp);
|
|
89
95
|
const pageDataStrings = await getPageDataStrings(timeStamp, jobID, {what, url, daysAgo});
|
|
@@ -96,38 +102,32 @@ const populateQuery = async query => {
|
|
|
96
102
|
// Add facts about the report to the lines.
|
|
97
103
|
lines.tested.push(`${margin} <li>${testInfo}</li>`);
|
|
98
104
|
// If the page prevented any tool from performing its tests:
|
|
99
|
-
if (
|
|
105
|
+
if (preventedToolCount) {
|
|
100
106
|
// Add this to the lines.
|
|
101
|
-
const
|
|
102
|
-
const toolCountString = getToolCountString(preventedToolSet.size);
|
|
103
|
-
const toolsString = getToolNamesString(preventedToolSet);
|
|
107
|
+
const toolCountString = getCountString(preventedToolCount, 'tool', 'tools');
|
|
104
108
|
lines.tested.push(
|
|
105
|
-
`${margin} <li>Page not testable by ${toolCountString} (${
|
|
109
|
+
`${margin} <li>Page not testable by ${toolCountString} (${preventedToolNames.join(' + ')})</li>`,
|
|
106
110
|
);
|
|
107
111
|
}
|
|
108
112
|
// Add facts about the test results to the lines.
|
|
109
|
-
|
|
110
|
-
const reporterCountString = getToolCountString(reporterCount);
|
|
111
|
-
let reporterString = reporterCountString;
|
|
113
|
+
let reporterString = `${getCountString(reporterCount, 'tool', 'tools')} reported issues`;
|
|
112
114
|
if (reporterCount) {
|
|
113
|
-
const reporterNamesString =
|
|
114
|
-
reporterString = `${
|
|
115
|
+
const reporterNamesString = reporterNames.join(' + ');
|
|
116
|
+
reporterString = `${reporterString} (${reporterNamesString})`;
|
|
115
117
|
}
|
|
116
|
-
const issueCountString =
|
|
117
|
-
const violatorString =
|
|
118
|
-
|
|
119
|
-
: `${violatorSet.size} violators were`;
|
|
120
|
-
lines.tested.push(`${margin} <li>${reporterString} reported issues</li>`);
|
|
118
|
+
const issueCountString = getCountString(issueCount, 'issue was', 'issues were');
|
|
119
|
+
const violatorString = getCountString(violatorCount, 'violator was', 'violators were');
|
|
120
|
+
lines.tested.push(`${margin} <li>${reporterString}</li>`);
|
|
121
121
|
lines.tested.push(`${margin} <li>${issueCountString} reported</li>`);
|
|
122
122
|
lines.tested.push(`${margin} <li>${violatorString} reported</li>`);
|
|
123
123
|
lines.tested.push(`${margin} </ul>`);
|
|
124
124
|
lines.tested.push(`${margin}<ul class="nav">`);
|
|
125
125
|
// If any issues were reported:
|
|
126
|
-
if (
|
|
126
|
+
if (issueCount) {
|
|
127
127
|
// Add a question link about the reported issues to the lines.
|
|
128
128
|
const href = `href="reportIssues.html/${timeStamp}/${jobID}"`;
|
|
129
129
|
const label = `aria-label="What ${issueCountString} reported for the ${what} page?"`;
|
|
130
|
-
const questionString =
|
|
130
|
+
const questionString = issueCount === 1 ? 'was the issue' : 'were the issues';
|
|
131
131
|
const link = `<a ${href} ${label}>What ${questionString}?</a>`;
|
|
132
132
|
lines.tested.push(`${margin} <li>${link}</li>`);
|
|
133
133
|
}
|
package/util.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
const {sendAlert} = require('./alerts');
|
|
9
|
-
const
|
|
9
|
+
const issuesClassification = require('testilo/procs/score/tic').issues;
|
|
10
10
|
const fs = require('fs/promises');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const querystring = require('querystring');
|
|
@@ -35,6 +35,7 @@ const tools = exports.tools = {
|
|
|
35
35
|
qualWeb: ['QualWeb', 'University of Lisbon'],
|
|
36
36
|
testaro: ['Testaro', 'CVS Health'],
|
|
37
37
|
wave: ['WAVE', 'Utah State University'],
|
|
38
|
+
wax: ['WallyAX', 'Wally']
|
|
38
39
|
};
|
|
39
40
|
exports.researchAgents = {
|
|
40
41
|
'research-agent': 'Internal Research Agent'
|
|
@@ -146,7 +147,7 @@ const alphaCompare = (a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'});
|
|
|
146
147
|
// Sorts strings alphabetically and case-insensitively.
|
|
147
148
|
const alphaSort = strings => strings.sort((a, b) => alphaCompare(a, b));
|
|
148
149
|
// Sorts objects by a property value.
|
|
149
|
-
exports.objectSort = (objects, property, sortType) => objects
|
|
150
|
+
const objectSort = exports.objectSort = (objects, property, sortType) => objects
|
|
150
151
|
.sort((a, b) => {
|
|
151
152
|
// If the property values are numbers to be sorted in increasing order:
|
|
152
153
|
if (sortType === 'numericUp') {
|
|
@@ -174,8 +175,8 @@ const getRuleIDs = exports.getRuleIDs = () => {
|
|
|
174
175
|
// Initialize a validity checker.
|
|
175
176
|
const validityChecker = {};
|
|
176
177
|
// For each classified issue:
|
|
177
|
-
Object.keys(
|
|
178
|
-
const {tools, weight} =
|
|
178
|
+
Object.keys(issuesClassification).forEach(issueID => {
|
|
179
|
+
const {tools, weight} = issuesClassification[issueID];
|
|
179
180
|
// If the weight is invalid:
|
|
180
181
|
if (weight < 1 || weight > 4) {
|
|
181
182
|
// Report this.
|
|
@@ -324,9 +325,9 @@ exports.isHidden = async (timeStamp, jobID) => {
|
|
|
324
325
|
}
|
|
325
326
|
return !! log.hidden;
|
|
326
327
|
};
|
|
327
|
-
// Returns summary data on
|
|
328
|
-
exports.
|
|
329
|
-
//
|
|
328
|
+
// Returns summary data on a report.
|
|
329
|
+
exports.getReportData = async (timeStamp, jobID) => {
|
|
330
|
+
// Validate the report and annotate it if necessary.
|
|
330
331
|
const log = await getLog(timeStamp, jobID, true);
|
|
331
332
|
// If this failed:
|
|
332
333
|
if (typeof log === 'string') {
|
|
@@ -337,12 +338,22 @@ exports.getTargetData = async (timeStamp, jobID) => {
|
|
|
337
338
|
const data = {
|
|
338
339
|
what: log.what,
|
|
339
340
|
url: log.url,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
341
|
+
jobName: `${timeStamp}-${jobID}`,
|
|
342
|
+
creationDate: getDateTime(timeStamp),
|
|
343
|
+
daysAgo: getAgoDays(timeStamp),
|
|
344
|
+
issueCount: 0,
|
|
345
|
+
toolNames: [],
|
|
346
|
+
toolCount: 0,
|
|
347
|
+
reporterNames: [],
|
|
348
|
+
reporterCount: 0,
|
|
349
|
+
violatorCount: 0,
|
|
350
|
+
preventedToolNames: [],
|
|
351
|
+
preventedToolCount: 0
|
|
344
352
|
};
|
|
345
|
-
const
|
|
353
|
+
const issueIDSet = new Set();
|
|
354
|
+
const toolNameSet = new Set();
|
|
355
|
+
const reporterIDSet = new Set();
|
|
356
|
+
const violatorIndexSet = new Set();
|
|
346
357
|
// Get the report.
|
|
347
358
|
const report = await getReport(timeStamp, jobID);
|
|
348
359
|
// If this failed:
|
|
@@ -355,27 +366,44 @@ exports.getTargetData = async (timeStamp, jobID) => {
|
|
|
355
366
|
// If it is a test act:
|
|
356
367
|
if (act.type === 'test') {
|
|
357
368
|
const {result, which} = act;
|
|
369
|
+
// Ensure that the tool is in the temporary data.
|
|
370
|
+
toolNameSet.add(which);
|
|
358
371
|
const instances = result?.standardResult?.instances ?? [];
|
|
359
|
-
// For each standard instance:
|
|
372
|
+
// For each standard instance of the act:
|
|
360
373
|
instances.forEach(instance => {
|
|
361
374
|
const {catalogIndex, issueID} = instance;
|
|
362
375
|
// If it has a non-ignorable classified issue ID:
|
|
363
|
-
if (issueID &&
|
|
364
|
-
// Ensure that the tool is in the data.
|
|
365
|
-
|
|
366
|
-
// Ensure that the issue is in the data.
|
|
367
|
-
|
|
368
|
-
// If
|
|
376
|
+
if (issueID && issuesClassification[issueID] && issueID !== 'ignorable') {
|
|
377
|
+
// Ensure that the tool is in the temporary data.
|
|
378
|
+
reporterIDSet.add(which);
|
|
379
|
+
// Ensure that the issue is in the temporary data.
|
|
380
|
+
issueIDSet.add(issueID);
|
|
381
|
+
// If the violator has a catalog index:
|
|
369
382
|
if (catalogIndex) {
|
|
370
|
-
// Ensure that the violator is in the data.
|
|
371
|
-
|
|
383
|
+
// Ensure that the violator is in the temporary data.
|
|
384
|
+
violatorIndexSet.add(catalogIndex);
|
|
372
385
|
}
|
|
373
386
|
}
|
|
374
387
|
});
|
|
375
388
|
}
|
|
376
389
|
});
|
|
377
|
-
//
|
|
378
|
-
data.
|
|
390
|
+
// Populate the data with the act data.
|
|
391
|
+
data.issueCount = issueIDSet.size;
|
|
392
|
+
data.toolNames = Array
|
|
393
|
+
.from(toolNameSet)
|
|
394
|
+
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'}));
|
|
395
|
+
data.toolCount = toolNameSet.size;
|
|
396
|
+
data.reporterNames = Array
|
|
397
|
+
.from(reporterIDSet)
|
|
398
|
+
.map(id => tools[id][0])
|
|
399
|
+
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'}));
|
|
400
|
+
data.reporterCount = data.reporterNames.length;
|
|
401
|
+
data.violatorCount = violatorIndexSet.size;
|
|
402
|
+
// Add the names of any prevented tools to the data.
|
|
403
|
+
data.preventedToolNames = Object.keys(report.jobData?.preventions || {})
|
|
404
|
+
.map(toolID => tools[toolID][0])
|
|
405
|
+
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'}));
|
|
406
|
+
data.preventedToolCount = data.preventedToolNames.length;
|
|
379
407
|
// Return the data.
|
|
380
408
|
return data;
|
|
381
409
|
}
|
|
@@ -480,7 +508,7 @@ const getRecs = exports.getRecs = async () => {
|
|
|
480
508
|
};
|
|
481
509
|
// Returns a string of tool names.
|
|
482
510
|
exports.getToolNamesString = toolIDSet =>
|
|
483
|
-
alphaSort(Array.from(toolIDSet).map(toolID => tools[toolID][0])).join(' + ');
|
|
511
|
+
alphaSort(Array.from(toolIDSet).map(toolID => tools[toolID]?.[0] || toolID)).join(' + ');
|
|
484
512
|
// Converts a catalog item text to a text-fragment link destination.
|
|
485
513
|
exports.getTextFragmentHref = (text, url) => {
|
|
486
514
|
const fragmentList = text
|
|
@@ -490,7 +518,7 @@ exports.getTextFragmentHref = (text, url) => {
|
|
|
490
518
|
// Return a text-fragment link.
|
|
491
519
|
return `${url}#:~:text=${fragmentList}`;
|
|
492
520
|
};
|
|
493
|
-
// Returns
|
|
521
|
+
// Returns a sorted array of the logs, with job names added, of the non-hidden reports.
|
|
494
522
|
exports.getLogs = async () => {
|
|
495
523
|
// Initialize data on the tested targets.
|
|
496
524
|
const logs = [];
|
|
@@ -654,7 +682,7 @@ exports.getWCAGLink = numericID => {
|
|
|
654
682
|
// Return the link.
|
|
655
683
|
return `https://www.w3.org/WAI/WCAG22/Understanding/${wcagMap[numericID]}`;
|
|
656
684
|
};
|
|
657
|
-
//
|
|
685
|
+
// Returns page data from a report.
|
|
658
686
|
const getPageData = exports.getPageData = async (timeStamp, jobID) => {
|
|
659
687
|
// Get the log of the report.
|
|
660
688
|
const log = await getLog(timeStamp, jobID, false);
|
|
@@ -675,7 +703,7 @@ const getPageData = exports.getPageData = async (timeStamp, jobID) => {
|
|
|
675
703
|
};
|
|
676
704
|
// Gets HTML strings for page data from a report.
|
|
677
705
|
exports.getPageDataStrings = async (timeStamp, jobID, pageData) => {
|
|
678
|
-
// If the
|
|
706
|
+
// If the page data were not specified:
|
|
679
707
|
if (! pageData) {
|
|
680
708
|
// Get them.
|
|
681
709
|
pageData = await getPageData(timeStamp, jobID);
|
|
@@ -690,3 +718,35 @@ exports.getPageDataStrings = async (timeStamp, jobID, pageData) => {
|
|
|
690
718
|
testInfo: `Tested ${daysAgo} days ago by job <code>${jobID}</code> on ${when}`
|
|
691
719
|
};
|
|
692
720
|
};
|
|
721
|
+
// Returns tool data sorted by tool name.
|
|
722
|
+
const getToolsData = exports.getToolsData = toolIDs => objectSort(
|
|
723
|
+
Array.from(toolIDs).map(toolID => {
|
|
724
|
+
const toolData = tools[toolID];
|
|
725
|
+
return {
|
|
726
|
+
toolID,
|
|
727
|
+
toolName: toolData[0],
|
|
728
|
+
toolMaker: toolData[1]
|
|
729
|
+
}
|
|
730
|
+
}),
|
|
731
|
+
'toolName',
|
|
732
|
+
'alpha'
|
|
733
|
+
);
|
|
734
|
+
// Returns a +-delimited list of sorted tool names.
|
|
735
|
+
exports.getToolList = toolIDs => Array.from(toolIDs)
|
|
736
|
+
.map(toolID => tools[toolID][0])
|
|
737
|
+
.sort((a, b) => a.localeCompare(b, 'en', {sensitivity: 'base'}))
|
|
738
|
+
.join(' + ');
|
|
739
|
+
// Returns facts about tools.
|
|
740
|
+
exports.getToolsFacts = toolIDs => {
|
|
741
|
+
const crypticData = getToolsData(toolIDs);
|
|
742
|
+
return crypticData.map(tool => {
|
|
743
|
+
const {toolID, toolName, toolMaker} = tool;
|
|
744
|
+
return {
|
|
745
|
+
identifier: toolID,
|
|
746
|
+
name: toolName,
|
|
747
|
+
sponsor: toolMaker
|
|
748
|
+
};
|
|
749
|
+
});
|
|
750
|
+
};
|
|
751
|
+
// Returns a string describing a count.
|
|
752
|
+
exports.getCountString = (count, singular, plural) => count === 1 ? `1 ${singular}` : `${count} ${plural}`;
|