@jrpool/kilotest 57.2.3 → 58.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/api/orderNewTest.test.ts +1 -1
- package/api/orderNewTest.ts +1 -1
- package/api/requestNewTest.test.ts +1 -1
- package/api/requestNewTest.ts +1 -1
- package/index.test.ts +1 -1
- package/index.ts +11 -20
- package/package.json +1 -1
- package/test/fixtures/buildFixtures.ts +10 -1
- package/test/fixtures/db/reports/260101T0006-prv.json +10 -0
- package/util.test.ts +46 -0
- package/util.ts +16 -18
- package/web/listDiagnoses/index.html +6 -19
- package/web/listDiagnoses/index.ts +21 -43
- package/web/listDiagnoses/listDiagnoses.test.ts +14 -2
- package/web/listIssues/index.html +12 -12
- package/web/listIssues/index.ts +112 -168
- package/web/listIssues/listIssues.test.ts +33 -10
- package/web/listReports/index.ts +21 -50
- package/web/listReports/listReports.test.ts +82 -0
- package/web/listViolators/index.html +4 -13
- package/web/listViolators/index.ts +30 -64
- package/web/listViolators/listViolators.test.ts +16 -4
- package/web/reportDeletion.ts +8 -4
- package/web/requestNewTest/index.ts +2 -2
- package/web/requestNewTest/requestNewTest.test.ts +1 -1
- package/web/tutorialAI/index.html +6 -6
- package/web/util.test.ts +123 -27
- package/web/util.ts +163 -46
package/web/listIssues/index.ts
CHANGED
|
@@ -8,147 +8,132 @@
|
|
|
8
8
|
import {
|
|
9
9
|
getReport,
|
|
10
10
|
getTestActInstances,
|
|
11
|
-
|
|
12
|
-
isUsableReport,
|
|
11
|
+
isReportError,
|
|
13
12
|
objectSort,
|
|
14
|
-
populateTemplate
|
|
15
|
-
ruleEngines
|
|
13
|
+
populateTemplate
|
|
16
14
|
} from '../../util.ts';
|
|
15
|
+
import type {UsableReport} from '../../util.ts';
|
|
17
16
|
import {
|
|
18
|
-
|
|
17
|
+
getIssueFactsLines,
|
|
19
18
|
getPageDataStrings,
|
|
19
|
+
getPageFactsLines,
|
|
20
|
+
getEngineIDs,
|
|
20
21
|
getEngineNamesString,
|
|
21
|
-
getWCAGLink,
|
|
22
22
|
getWeightName
|
|
23
23
|
} from '../util.ts';
|
|
24
|
+
import type {ResultsSummary} from '../util.ts';
|
|
24
25
|
import {issues as issueSpecs} from 'testaro-issues';
|
|
25
26
|
|
|
26
27
|
// FUNCTIONS
|
|
27
28
|
|
|
28
29
|
// Returns data on the issues reported by a report.
|
|
29
|
-
const getIssuesData =
|
|
30
|
-
//
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
30
|
+
const getIssuesData = (report: UsableReport): ResultsSummary & Record<string, any> => {
|
|
31
|
+
// Initialize the temporary data.
|
|
32
|
+
const temp = {
|
|
33
|
+
issues: {} as Record<string, any>,
|
|
34
|
+
reporters: new Set<string>(),
|
|
35
|
+
violators: new Set<string>()
|
|
36
|
+
};
|
|
37
|
+
// Initialize the final data.
|
|
38
|
+
const final: ResultsSummary & Record<string, any> = {
|
|
39
|
+
reporters: [],
|
|
40
|
+
reporterList: '',
|
|
41
|
+
reporterCount: 0,
|
|
42
|
+
engineCount: 0,
|
|
43
|
+
testedEngineCount: 0,
|
|
44
|
+
violationCount: 0,
|
|
45
|
+
violatorCount: 0,
|
|
46
|
+
issues: {
|
|
47
|
+
4: [],
|
|
48
|
+
3: [],
|
|
49
|
+
2: [],
|
|
50
|
+
1: []
|
|
51
|
+
},
|
|
52
|
+
issueCount: 0
|
|
53
|
+
};
|
|
54
|
+
// For each violating standard instance of each test act:
|
|
55
|
+
getTestActInstances(report, {violationsOnly: true}).forEach(({act, instance}) => {
|
|
56
|
+
const {catalogIndex, issueID} = instance;
|
|
57
|
+
const which = act.which!;
|
|
58
|
+
// If it identifies a non-ignorable issue:
|
|
59
|
+
if (issueID && issueID !== 'ignorable') {
|
|
60
|
+
const issueClassification = issueSpecs[issueID];
|
|
61
|
+
// If the issue has a current weighted classification:
|
|
62
|
+
if (issueClassification && [1, 2, 3, 4].includes(issueClassification.weight)) {
|
|
63
|
+
const {summary, wcag, weight, why} = issueClassification;
|
|
64
|
+
// Initialize the temporary data on the issue if necessary.
|
|
65
|
+
temp.issues[issueID] ??= {
|
|
66
|
+
issueID,
|
|
67
|
+
summary,
|
|
68
|
+
wcag,
|
|
69
|
+
why,
|
|
70
|
+
weight,
|
|
71
|
+
reporters: new Set(),
|
|
72
|
+
reporterList: '',
|
|
73
|
+
violationCount: 0,
|
|
74
|
+
violators: new Set()
|
|
75
|
+
};
|
|
76
|
+
// Ensure the rule engine is in the temporary data.
|
|
77
|
+
temp.issues[issueID].reporters.add(which);
|
|
78
|
+
temp.reporters.add(which);
|
|
79
|
+
// Increment the violation counts.
|
|
80
|
+
temp.issues[issueID].violationCount++;
|
|
81
|
+
final.violationCount++;
|
|
82
|
+
// If the instance has a catalog index:
|
|
83
|
+
if (catalogIndex) {
|
|
84
|
+
// Ensure the violator is in the temporary data.
|
|
85
|
+
temp.issues[issueID].violators.add(catalogIndex);
|
|
86
|
+
temp.violators.add(String(catalogIndex));
|
|
85
87
|
}
|
|
86
88
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const getData = async (timeStamp: string, jobID: string) => {
|
|
122
|
-
const pageData = await getPageData(timeStamp, jobID);
|
|
123
|
-
const issuesData: any = await getIssuesData(timeStamp, jobID);
|
|
124
|
-
const pageError = ('error' in pageData ? pageData.error : '');
|
|
125
|
-
const issuesError = issuesData.error || '';
|
|
126
|
-
const errors = [pageError, issuesError].filter(Boolean).join('; ');
|
|
127
|
-
// If the data of either type are missing or invalid:
|
|
128
|
-
if (errors) {
|
|
129
|
-
// Return this.
|
|
130
|
-
return {error: errors};
|
|
131
|
-
}
|
|
132
|
-
// Otherwise, return the data.
|
|
133
|
-
return {
|
|
134
|
-
pageData,
|
|
135
|
-
issuesData
|
|
136
|
-
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
// Finish populating the final data.
|
|
92
|
+
const {calledIDs, preventedIDs} = getEngineIDs(report);
|
|
93
|
+
final.engineCount = calledIDs.length;
|
|
94
|
+
final.testedEngineCount = calledIDs.length - preventedIDs.length;
|
|
95
|
+
final.reporterList = getEngineNamesString(temp.reporters);
|
|
96
|
+
final.reporterCount = temp.reporters.size;
|
|
97
|
+
final.violatorCount = temp.violators.size;
|
|
98
|
+
Object.values(temp.issues).forEach(issue => {
|
|
99
|
+
const {issueID, summary, wcag, why, weight} = issue;
|
|
100
|
+
const finalIssue: Record<string, any> = {
|
|
101
|
+
issueID,
|
|
102
|
+
summary,
|
|
103
|
+
wcag,
|
|
104
|
+
why,
|
|
105
|
+
weight
|
|
106
|
+
};
|
|
107
|
+
finalIssue.reporterList = getEngineNamesString(issue.reporters);
|
|
108
|
+
finalIssue.reporterCount = issue.reporters.size;
|
|
109
|
+
finalIssue.violationCount = issue.violationCount;
|
|
110
|
+
finalIssue.violatorCount = issue.violators.size;
|
|
111
|
+
final.issues[issue.weight].push(finalIssue);
|
|
112
|
+
});
|
|
113
|
+
final.issueCount = Object.keys(temp.issues).length;
|
|
114
|
+
// For each weight:
|
|
115
|
+
[4, 3, 2, 1].forEach(weight => {
|
|
116
|
+
// Sort its issues in the final data alphabetically by reporter names.
|
|
117
|
+
objectSort(final.issues[weight], 'reporterList', 'alpha');
|
|
118
|
+
// Sort the issues again in descending reporter-count order, making this the primary order.
|
|
119
|
+
objectSort(final.issues[weight], 'reporterCount', 'numericDown');
|
|
120
|
+
});
|
|
121
|
+
// Return the data.
|
|
122
|
+
return final;
|
|
137
123
|
};
|
|
138
124
|
// Adds parameters to a query for the answer page.
|
|
139
125
|
const populateQuery = async (timeStamp: string, jobID: string, query: Record<string, any>) => {
|
|
140
|
-
// Get
|
|
141
|
-
const
|
|
142
|
-
const {pageData, issuesData} = data;
|
|
126
|
+
// Get the report.
|
|
127
|
+
const report = await getReport(timeStamp, jobID);
|
|
143
128
|
// If this failed:
|
|
144
|
-
if (
|
|
129
|
+
if (isReportError(report)) {
|
|
145
130
|
// Populate the query with the reason.
|
|
146
|
-
query.error =
|
|
131
|
+
query.error = report.error;
|
|
147
132
|
// Stop populating the query.
|
|
148
133
|
return;
|
|
149
134
|
}
|
|
150
135
|
// Otherwise, i.e. if it succeeded, get fact descriptions for the target.
|
|
151
|
-
const pageInfo =
|
|
136
|
+
const pageInfo = getPageDataStrings(report);
|
|
152
137
|
// If this failed:
|
|
153
138
|
if (pageInfo.error !== undefined) {
|
|
154
139
|
// Populate the query with the reason.
|
|
@@ -156,45 +141,24 @@ const populateQuery = async (timeStamp: string, jobID: string, query: Record<str
|
|
|
156
141
|
// Stop populating the query.
|
|
157
142
|
return;
|
|
158
143
|
}
|
|
159
|
-
|
|
160
|
-
|
|
144
|
+
// Otherwise, i.e. if it succeeded, get data on the issues.
|
|
145
|
+
const issuesData = getIssuesData(report);
|
|
146
|
+
const {testInfo, description} = pageInfo;
|
|
147
|
+
// Add target data to the query.
|
|
161
148
|
query.target = description;
|
|
162
|
-
query.urlLink = urlLink;
|
|
163
149
|
query.testInfo = testInfo;
|
|
164
|
-
const {
|
|
165
|
-
reporterList,
|
|
166
|
-
reporterCount,
|
|
167
|
-
violatorCount,
|
|
168
|
-
issueCount,
|
|
169
|
-
preventions,
|
|
170
|
-
issues
|
|
171
|
-
} = issuesData;
|
|
172
|
-
// Initialize strings for the prevention notices query property.
|
|
173
|
-
const preventionStrings: string[] = [];
|
|
174
150
|
const margin = ' '.repeat(6);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const causeString = htmlSafe(preventions[preventedEngineID]);
|
|
179
|
-
const preventionString = `${margin}<li>Page not testable by ${engineNameString}: ${causeString}</li>`;
|
|
180
|
-
preventionStrings.push(preventionString);
|
|
181
|
-
});
|
|
182
|
-
// Add prevention notices to the query.
|
|
183
|
-
query.preventions = preventionStrings.join('\n');
|
|
151
|
+
// Add the page-facts list to the query.
|
|
152
|
+
query.pageFacts = getPageFactsLines(pageInfo, issuesData, margin).join('\n');
|
|
153
|
+
const {issues} = issuesData;
|
|
184
154
|
// Add report data to the query.
|
|
185
155
|
query.timeStamp = timeStamp;
|
|
186
156
|
query.jobID = jobID;
|
|
187
|
-
// Add reporter information to the query.
|
|
188
|
-
query.reporterCount = reporterCount === 1 ? '1 rule engine' : `${reporterCount} rule engines`;
|
|
189
|
-
query.reporters = reporterList;
|
|
190
157
|
// Add a summary of the issues to the query.
|
|
191
|
-
query.issueCount = issueCount === 1 ? '1 issue was' : `${issueCount} issues were`;
|
|
192
158
|
query.highestCount = issues[4].length;
|
|
193
159
|
query.highCount = issues[3].length;
|
|
194
160
|
query.lowCount = issues[2].length;
|
|
195
161
|
query.lowestCount = issues[1].length;
|
|
196
|
-
// Add a violator count to the query.
|
|
197
|
-
query.violatorCount = violatorCount === 1 ? '1 violator was' : `${violatorCount} violators were`;
|
|
198
162
|
// For each weight:
|
|
199
163
|
[4, 3, 2, 1].forEach(weight => {
|
|
200
164
|
const weightName = getWeightName(weight);
|
|
@@ -208,35 +172,15 @@ const populateQuery = async (timeStamp: string, jobID: string, query: Record<str
|
|
|
208
172
|
const weightIssueCount = weightIssues.length;
|
|
209
173
|
// Add the issue count to the query.
|
|
210
174
|
query[`${weightName}Count`] = weightIssueCount;
|
|
211
|
-
const {
|
|
212
|
-
issueID,
|
|
213
|
-
reporterCount,
|
|
214
|
-
reporterList,
|
|
215
|
-
summary,
|
|
216
|
-
violatorCount,
|
|
217
|
-
wcag,
|
|
218
|
-
why
|
|
219
|
-
} = issueData;
|
|
220
|
-
const wcagLink = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
|
|
175
|
+
const {issueID, summary} = issueData;
|
|
221
176
|
// Add the start of a list item to the lines.
|
|
222
177
|
detailsLines.push(`${margin} <li>`);
|
|
223
178
|
// Add a heading summarizing the issue to the lines.
|
|
224
179
|
detailsLines.push(`${margin} <h5>${summary}</h5>`);
|
|
225
|
-
// Add the start of a fact list about the issue to the lines.
|
|
226
|
-
detailsLines.push(`${margin} <ul class="pseudoTopLevel">`);
|
|
227
180
|
// Add the issue facts to the lines.
|
|
228
|
-
detailsLines.push(`${margin} <li>Why it matters: ${why}`);
|
|
229
|
-
detailsLines.push(`${margin} <li>Related WCAG standard: ${wcagLink}`);
|
|
230
|
-
const reporterCountString = reporterCount === 1 ? '1 rule engine' : `${reporterCount} rule engines`;
|
|
231
181
|
detailsLines.push(
|
|
232
|
-
`${margin}
|
|
182
|
+
...getIssueFactsLines(report, issueID, `${margin} `, 'pseudoTopLevel')
|
|
233
183
|
);
|
|
234
|
-
const violatorCountString = violatorCount === 1
|
|
235
|
-
? '1 violator was'
|
|
236
|
-
: `${violatorCount} violators were`;
|
|
237
|
-
detailsLines.push(`${margin} <li>${violatorCountString} reported</li>`);
|
|
238
|
-
// Add the end of the fact list to the lines.
|
|
239
|
-
detailsLines.push(`${margin} </ul>`);
|
|
240
184
|
// Add the start of a link list to the lines.
|
|
241
185
|
detailsLines.push(`${margin} <ul class="nav">`);
|
|
242
186
|
const whereQuestionString = 'Where was the issue found?';
|
|
@@ -18,7 +18,7 @@ let pageDataStringsOverride: any = null;
|
|
|
18
18
|
mock.module('../util.ts', {
|
|
19
19
|
exports: {
|
|
20
20
|
...realWebUtil,
|
|
21
|
-
getPageDataStrings:
|
|
21
|
+
getPageDataStrings: (...args: any[]) => {
|
|
22
22
|
if (pageDataStringsOverride !== null) {
|
|
23
23
|
return pageDataStringsOverride;
|
|
24
24
|
}
|
|
@@ -61,6 +61,18 @@ test('listIssues includes the page description in the HTML for the mixed report'
|
|
|
61
61
|
assert.ok(result.answerPage.includes('Mixed Outcomes Page'));
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
+
test('listIssues interpolates the page description into the AI prompt block quote', async () => {
|
|
65
|
+
const result = await answer('260101T0000/mix');
|
|
66
|
+
const html = parse(result.answerPage);
|
|
67
|
+
const blockQuote = html.querySelector('details blockquote');
|
|
68
|
+
assert.ok(blockQuote);
|
|
69
|
+
assert.ok(
|
|
70
|
+
blockQuote.textContent.startsWith('Kilotest reports defects on the Mixed Outcomes Page web page.')
|
|
71
|
+
);
|
|
72
|
+
// No placeholder may remain uninterpolated.
|
|
73
|
+
assert.ok(!blockQuote.textContent.includes('__target__'));
|
|
74
|
+
});
|
|
75
|
+
|
|
64
76
|
test('listIssues includes links to listViolators for issues in the mixed report', async () => {
|
|
65
77
|
const result = await answer('260101T0000/mix');
|
|
66
78
|
const html = parse(result.answerPage);
|
|
@@ -93,14 +105,23 @@ test('listIssues for the empty report shows no issue links', async () => {
|
|
|
93
105
|
});
|
|
94
106
|
|
|
95
107
|
|
|
96
|
-
test('listIssues
|
|
108
|
+
test('listIssues counts the prevented rule engine as called but not able to test', async () => {
|
|
97
109
|
const result = await answer('260101T0006/prv');
|
|
98
110
|
assert.equal(result.status, 'ok');
|
|
99
|
-
assert.ok(result.answerPage.includes('
|
|
100
|
-
assert.ok(result.answerPage.includes('
|
|
111
|
+
assert.ok(result.answerPage.includes('<li>Called: 2</li>'));
|
|
112
|
+
assert.ok(result.answerPage.includes('<li>Were able to test: 1</li>'));
|
|
113
|
+
assert.ok(result.answerPage.includes('<li>Reported any rule violations: 1</li>'));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('listIssues counts a rule engine that reported no violations as called and able to test', async () => {
|
|
117
|
+
const result = await answer('260101T0005/emp');
|
|
118
|
+
assert.equal(result.status, 'ok');
|
|
119
|
+
assert.ok(result.answerPage.includes('<li>Called: 1</li>'));
|
|
120
|
+
assert.ok(result.answerPage.includes('<li>Were able to test: 1</li>'));
|
|
121
|
+
assert.ok(result.answerPage.includes('<li>Reported any rule violations: 0</li>'));
|
|
101
122
|
});
|
|
102
123
|
|
|
103
|
-
test('listIssues
|
|
124
|
+
test('listIssues counts an unknown prevented engine as called but not able to test', async () => {
|
|
104
125
|
const fs = await import('node:fs/promises');
|
|
105
126
|
const prvJSON = await fs.readFile(path.join(realUtil.reportsPath(), '260101T0006-prv.json'), 'utf8');
|
|
106
127
|
const report = JSON.parse(prvJSON);
|
|
@@ -110,14 +131,15 @@ test('listIssues falls back to the engine ID for an unknown prevented engine', a
|
|
|
110
131
|
try {
|
|
111
132
|
const result = await answer('260103T0000/unk');
|
|
112
133
|
assert.equal(result.status, 'ok');
|
|
113
|
-
assert.ok(result.answerPage.includes('
|
|
134
|
+
assert.ok(result.answerPage.includes('<li>Called: 3</li>'));
|
|
135
|
+
assert.ok(result.answerPage.includes('<li>Were able to test: 1</li>'));
|
|
114
136
|
}
|
|
115
137
|
finally {
|
|
116
138
|
await fs.unlink(reportPath);
|
|
117
139
|
}
|
|
118
140
|
});
|
|
119
141
|
|
|
120
|
-
test('listIssues returns an error when getPageDataStrings fails after
|
|
142
|
+
test('listIssues returns an error when getPageDataStrings fails after getReport succeeds', async () => {
|
|
121
143
|
pageDataStringsOverride = {error: 'Page data strings error'};
|
|
122
144
|
try {
|
|
123
145
|
const result = await answer('260101T0000/mix');
|
|
@@ -145,11 +167,12 @@ test('listIssues returns an error when report facts are not obtained', async ()
|
|
|
145
167
|
}
|
|
146
168
|
});
|
|
147
169
|
|
|
148
|
-
test('listIssues shows
|
|
149
|
-
// The mul report has linkNoText with 3 violators.
|
|
170
|
+
test('listIssues shows violation and violator counts for an issue with multiple violators', async () => {
|
|
171
|
+
// The mul report has linkNoText with 4 violations by 3 violators.
|
|
150
172
|
const result = await answer('260101T0008/mul');
|
|
151
173
|
assert.equal(result.status, 'ok');
|
|
152
|
-
assert.ok(result.answerPage.includes('
|
|
174
|
+
assert.ok(result.answerPage.includes('<li>Violations: 4</li>'));
|
|
175
|
+
assert.ok(result.answerPage.includes('<li>Violators: 3</li>'));
|
|
153
176
|
});
|
|
154
177
|
|
|
155
178
|
test('listIssues handles acts with no standardResult instances', async () => {
|
package/web/listReports/index.ts
CHANGED
|
@@ -6,19 +6,20 @@
|
|
|
6
6
|
// IMPORTS
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
getAgoDays,
|
|
10
9
|
getJobNames,
|
|
11
10
|
getObject,
|
|
12
|
-
|
|
11
|
+
getReport,
|
|
13
12
|
getReportExtracts,
|
|
13
|
+
getTestRequests,
|
|
14
|
+
isReportError,
|
|
14
15
|
jobsPath,
|
|
15
16
|
objectSort,
|
|
16
17
|
populateTemplate
|
|
17
18
|
} from '../../util.ts';
|
|
18
19
|
import {
|
|
19
|
-
getCountString,
|
|
20
20
|
getMultiReportWhats,
|
|
21
21
|
getPageDataStrings,
|
|
22
|
+
getPageFactsLines,
|
|
22
23
|
getReportData
|
|
23
24
|
} from '../util.ts';
|
|
24
25
|
import path from 'node:path';
|
|
@@ -103,64 +104,34 @@ const populateQuery = async (query: Record<string, any>) => {
|
|
|
103
104
|
// For each report:
|
|
104
105
|
for (const extract of sortedExtracts) {
|
|
105
106
|
const {jobID, timeStamp, url, description, superseded} = extract;
|
|
106
|
-
// Get
|
|
107
|
-
const
|
|
107
|
+
// Get the report.
|
|
108
|
+
const report = await getReport(timeStamp, jobID);
|
|
108
109
|
// If this failed:
|
|
109
|
-
if (
|
|
110
|
-
console.error(
|
|
110
|
+
if (isReportError(report)) {
|
|
111
|
+
console.error(report.error);
|
|
111
112
|
// Populate the query with the reason.
|
|
112
|
-
query.error =
|
|
113
|
+
query.error = report.error;
|
|
113
114
|
// Stop populating the query.
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
violatorCount
|
|
123
|
-
} = reportData;
|
|
124
|
-
// Otherwise, i.e. if it succeeded, add lines about the report.
|
|
117
|
+
// Otherwise, i.e. if it succeeded, get data about the report.
|
|
118
|
+
const reportData = getReportData(report);
|
|
119
|
+
const pageDataStrings = getPageDataStrings(report);
|
|
120
|
+
const {issueCount} = reportData;
|
|
121
|
+
const {testInfo} = pageDataStrings;
|
|
122
|
+
// Add lines about the report.
|
|
125
123
|
lines.tested.push(`${margin}<details>`);
|
|
126
|
-
const daysAgo = getAgoDays(timeStamp);
|
|
127
|
-
const pageDataStrings = await getPageDataStrings(timeStamp, jobID, {description, url, daysAgo});
|
|
128
|
-
const {urlLink, testInfo}: any = pageDataStrings;
|
|
129
124
|
const testText = multiReportWhats.includes(description) ? ` (${testInfo.toLowerCase()})` : '';
|
|
130
125
|
lines.tested.push(`${margin} <summary>${description}${testText}</summary>`);
|
|
131
|
-
lines.
|
|
132
|
-
|
|
133
|
-
lines.tested.push(`${margin}
|
|
134
|
-
// Add facts about the report to the lines.
|
|
135
|
-
lines.tested.push(`${margin} <li>${testInfo}</li>`);
|
|
136
|
-
// If the page prevented any rule engine from performing its tests:
|
|
137
|
-
if (preventedEngineCount) {
|
|
138
|
-
// Add this to the lines.
|
|
139
|
-
const engineCountString = getCountString(preventedEngineCount, 'rule engine', 'rule engines');
|
|
140
|
-
lines.tested.push(
|
|
141
|
-
`${margin} <li>Page not testable by ${engineCountString} (${preventedEngineNames.join(' + ')})</li>`,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
// Add facts about the test results to the lines.
|
|
145
|
-
let reporterString = `${getCountString(reporterCount, 'rule engine', 'rule engines')} reported issues`;
|
|
146
|
-
if (reporterCount) {
|
|
147
|
-
const reporterNamesString = reporterNames.join(' + ');
|
|
148
|
-
reporterString = `${reporterString} (${reporterNamesString})`;
|
|
149
|
-
}
|
|
150
|
-
const issueCountString = getCountString(issueCount, 'issue was', 'issues were');
|
|
151
|
-
const violatorString = getCountString(violatorCount, 'violator was', 'violators were');
|
|
152
|
-
lines.tested.push(`${margin} <li>${reporterString}</li>`);
|
|
153
|
-
lines.tested.push(`${margin} <li>${issueCountString} reported</li>`);
|
|
154
|
-
lines.tested.push(`${margin} <li>${violatorString} reported</li>`);
|
|
155
|
-
lines.tested.push(`${margin} </ul>`);
|
|
156
|
-
lines.tested.push(`${margin}<ul class="nav">`);
|
|
126
|
+
// Add the page facts to the lines.
|
|
127
|
+
lines.tested.push(...getPageFactsLines(pageDataStrings, reportData, `${margin} `));
|
|
128
|
+
lines.tested.push(`${margin} <ul class="nav">`);
|
|
157
129
|
// If any issues were reported:
|
|
158
130
|
if (issueCount) {
|
|
159
|
-
// Add a
|
|
131
|
+
// Add a link to issue details to the lines.
|
|
160
132
|
const href = `href="listIssues.html/${timeStamp}/${jobID}"`;
|
|
161
|
-
const label = `aria-label="
|
|
162
|
-
const
|
|
163
|
-
const link = `<a ${href} ${label}>What ${questionString}?</a>`;
|
|
133
|
+
const label = `aria-label="issue details for the ${description} page"`;
|
|
134
|
+
const link = `<a ${href} ${label}>Issue details</a>`;
|
|
164
135
|
lines.tested.push(`${margin} <li>${link}</li>`);
|
|
165
136
|
}
|
|
166
137
|
let retestString: string = '';
|
|
@@ -216,3 +216,85 @@ test('listReports shows no-reports message when the database is empty', async ()
|
|
|
216
216
|
await fs.rm(tmpDir, {recursive: true}).catch(() => {});
|
|
217
217
|
}
|
|
218
218
|
});
|
|
219
|
+
|
|
220
|
+
// Returns the details element of the report whose summary starts with a description and whose issue link, if required, contains a fragment.
|
|
221
|
+
const getReportDetails = (html: any, description: string, hrefFragment?: string) => {
|
|
222
|
+
const matches = html.querySelectorAll('details').filter((d: any) => {
|
|
223
|
+
const summary = d.querySelector('summary')?.text ?? '';
|
|
224
|
+
if (!summary.startsWith(description)) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return hrefFragment === undefined
|
|
228
|
+
|| d.querySelectorAll('a').some((a: any) => a.getAttribute('href')?.includes(hrefFragment));
|
|
229
|
+
});
|
|
230
|
+
assert.equal(matches.length, 1, `exactly one report matches ${description} ${hrefFragment ?? ''}`);
|
|
231
|
+
return matches[0];
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Returns the 3 rule-engine amounts and the 3 summary amounts of a details element.
|
|
235
|
+
const getResultFacts = (details: any) => {
|
|
236
|
+
const items = details.querySelectorAll('li');
|
|
237
|
+
const itemTexts: string[] = items.map((li: any) => li.text.trim());
|
|
238
|
+
const getAmount = (label: string) => {
|
|
239
|
+
const text = itemTexts.find(t => t.startsWith(`${label}: `));
|
|
240
|
+
assert.ok(text, `${label} item exists`);
|
|
241
|
+
return Number(text.slice(label.length + 2));
|
|
242
|
+
};
|
|
243
|
+
// The summary item is the parent of the rule-engine item and the 3 amount items.
|
|
244
|
+
const summaryItem = items.find((li: any) => li.text.trim().startsWith('Summary of results:'));
|
|
245
|
+
assert.ok(summaryItem, 'Summary of results item exists');
|
|
246
|
+
const summaryList = summaryItem.querySelector('ul');
|
|
247
|
+
const amountLabels = summaryList.childNodes
|
|
248
|
+
.filter((node: any) => node.tagName === 'LI')
|
|
249
|
+
.map((li: any) => li.text.trim().split(':')[0]);
|
|
250
|
+
return {
|
|
251
|
+
called: getAmount('Called'),
|
|
252
|
+
tested: getAmount('Were able to test'),
|
|
253
|
+
reporters: getAmount('Reported any rule violations'),
|
|
254
|
+
amountLabels,
|
|
255
|
+
violations: getAmount('Violations'),
|
|
256
|
+
violators: getAmount('Violators'),
|
|
257
|
+
issues: getAmount('Issues')
|
|
258
|
+
};
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Expected facts, hand-computed from the fixtures.
|
|
262
|
+
const expectedFacts: [string, string | undefined, number, number, number, number, number, number][] = [
|
|
263
|
+
// Description, issue-link fragment, engines called, engines able to test, reporting engines, violations, violators, issues.
|
|
264
|
+
['Mixed Outcomes Page', '260101T0000/mix', 2, 2, 2, 3, 2, 2],
|
|
265
|
+
['Mixed Outcomes Page', '260202T0000/new', 1, 1, 1, 1, 1, 1],
|
|
266
|
+
['All CantTell Page', undefined, 1, 1, 0, 0, 0, 0],
|
|
267
|
+
['No Outcomes Page', undefined, 1, 1, 1, 1, 1, 1],
|
|
268
|
+
['Empty Results Page', undefined, 1, 1, 0, 0, 0, 0],
|
|
269
|
+
['Prevented Page', undefined, 2, 1, 1, 1, 1, 1],
|
|
270
|
+
['Multi Violator Page', undefined, 2, 2, 2, 4, 3, 1],
|
|
271
|
+
['Branch Coverage Page', undefined, 3, 3, 1, 4, 4, 1]
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
for (const [description, fragment, called, tested, reporters, violations, violators, issues] of expectedFacts) {
|
|
275
|
+
test(`listReports gives correct rule-engine and summary amounts for ${description}${fragment ? ` (${fragment})` : ''}`, async () => {
|
|
276
|
+
const result: any = await answer();
|
|
277
|
+
const html = parse(result.answerPage);
|
|
278
|
+
const facts = getResultFacts(getReportDetails(html, description, fragment));
|
|
279
|
+
assert.equal(facts.called, called);
|
|
280
|
+
assert.equal(facts.tested, tested);
|
|
281
|
+
assert.equal(facts.reporters, reporters);
|
|
282
|
+
assert.deepEqual(facts.amountLabels, ['Rule engines', 'Violations', 'Violators', 'Issues']);
|
|
283
|
+
assert.equal(facts.violations, violations);
|
|
284
|
+
assert.equal(facts.violators, violators);
|
|
285
|
+
assert.equal(facts.issues, issues);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
test('listReports counts a prevented rule engine as called but not able to test', async () => {
|
|
290
|
+
const result: any = await answer();
|
|
291
|
+
const html = parse(result.answerPage);
|
|
292
|
+
const prevented = getResultFacts(getReportDetails(html, 'Prevented Page'));
|
|
293
|
+
assert.equal(prevented.called - prevented.tested, 1);
|
|
294
|
+
const others = html.querySelectorAll('details').filter((d: any) => !d.text.includes('Prevented Page'));
|
|
295
|
+
assert.equal(others.length, 7);
|
|
296
|
+
assert.ok(others.every((d: any) => {
|
|
297
|
+
const facts = getResultFacts(d);
|
|
298
|
+
return facts.called === facts.tested;
|
|
299
|
+
}));
|
|
300
|
+
});
|
|
@@ -14,19 +14,10 @@
|
|
|
14
14
|
<main>
|
|
15
15
|
<h1><a href="/">Kilotest</a>: Violations of <q>__issue__</q> issue for __target__ page</h1>
|
|
16
16
|
<h2>Basics</h2>
|
|
17
|
-
<h3>About the
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</ul>
|
|
22
|
-
<h3>About the <q>__issue__</q> issue</h3>
|
|
23
|
-
<ul>
|
|
24
|
-
<li>Why it matters: __why__</li>
|
|
25
|
-
<li>Priority: __priority__</li>
|
|
26
|
-
<li>Related WCAG standard: __wcag__</li>
|
|
27
|
-
<li>Reported by __reporterCount__ (__reporters__)</li>
|
|
28
|
-
<li>__violatorCount__ reported</li>
|
|
29
|
-
</ul>
|
|
17
|
+
<h3>About the page</h3>
|
|
18
|
+
__pageFacts__
|
|
19
|
+
<h3>About the issue</h3>
|
|
20
|
+
__issueFacts__
|
|
30
21
|
<h2>Violators</h2>
|
|
31
22
|
<p>Here are the HTML elements reported to violate rules belonging to the <q>__issue__</q> issue.</p>
|
|
32
23
|
__takeMeThere__
|