@jrpool/kilotest 60.1.11 → 60.2.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/.devin/config.local.json +2 -1
- package/lhm.plugin.json +1 -1
- package/openapi-3.0.yaml +1 -1
- package/openapi.yaml +1 -1
- package/package.json +1 -1
- package/server.json +1 -1
- package/style.css +3 -0
- package/web/listDiagnoses/index.html +2 -3
- package/web/listDiagnoses/index.ts +3 -6
- package/web/listIssues/index.html +1 -1
- package/web/listIssues/listIssues.test.ts +4 -10
- package/web/listViolators/index.html +2 -3
- package/web/listViolators/index.ts +3 -4
- package/web/listViolators/listViolators.test.ts +2 -2
- package/web/tutorialAI/index.html +52 -49
- package/web/util.test.ts +26 -30
- package/web/util.ts +21 -27
package/.devin/config.local.json
CHANGED
package/lhm.plugin.json
CHANGED
package/openapi-3.0.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.0.3
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 60.1
|
|
5
|
+
version: 60.2.1
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
url: https://github.com/jrpool/kilotest/blob/main/LICENSE
|
package/openapi.yaml
CHANGED
|
@@ -2,7 +2,7 @@ openapi: 3.1.0
|
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest API
|
|
4
4
|
description: 'Kilotest is an ensemble testing and reporting service for front-end web quality. It uses an ensemble of 12 rule engines to test web pages for front-end quality (accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG). Kilotest acts as a web server for human users, an MCP server for AI platforms, and an API for programmatic access. With this API you can request that a page be tested or retested, discover available reports, incrementally retrieve facts from a report: from what reports are available, to what issues were reported in one report, to what elements violated one issue, to what diagnoses rule engines gave for one violation.'
|
|
5
|
-
version: 60.1
|
|
5
|
+
version: 60.2.1
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
identifier: MIT
|
package/package.json
CHANGED
package/server.json
CHANGED
package/style.css
CHANGED
|
@@ -351,6 +351,9 @@ body.styled details[open] summary {
|
|
|
351
351
|
body.styled details > :not(summary) {
|
|
352
352
|
padding-inline: 1.25rem;
|
|
353
353
|
}
|
|
354
|
+
body.styled details > :is(ol, ul):not([class]) {
|
|
355
|
+
padding-inline-start: 2.75rem;
|
|
356
|
+
}
|
|
354
357
|
body.styled .hero details h2 {
|
|
355
358
|
margin: 1.5rem 0 0.5rem;
|
|
356
359
|
}
|
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
__header__
|
|
10
10
|
<main class="page">
|
|
11
11
|
<h1>Diagnoses of the <q>__issue__</q> violation by element __catalogIndex__ on the __target__ page</h1>
|
|
12
|
-
<h2>
|
|
13
|
-
<h3>About the page</h3>
|
|
12
|
+
<h2>About the page</h2>
|
|
14
13
|
__pageFacts__
|
|
15
|
-
<
|
|
14
|
+
<h2>About the issue</h2>
|
|
16
15
|
__issueFacts__
|
|
17
16
|
<h3>About the violator</h3>
|
|
18
17
|
__violatorFacts__
|
|
@@ -30,7 +30,6 @@ const populateQuery = async (
|
|
|
30
30
|
issueID: string,
|
|
31
31
|
reportID: string,
|
|
32
32
|
catalogIndex: string,
|
|
33
|
-
pathID: string | null,
|
|
34
33
|
query: Record<string, any>
|
|
35
34
|
) => {
|
|
36
35
|
// Get the report.
|
|
@@ -81,7 +80,7 @@ const populateQuery = async (
|
|
|
81
80
|
}
|
|
82
81
|
// Otherwise, i.e. if it succeeded, add the issue-facts and violator-facts lists to the query.
|
|
83
82
|
query.issueFacts = getIssueFactsLines(report, issueID, margin).join('\n');
|
|
84
|
-
query.violatorFacts = getViolatorFactsLines(report, issueID, catalogIndex,
|
|
83
|
+
query.violatorFacts = getViolatorFactsLines(report, issueID, catalogIndex, margin).join('\n');
|
|
85
84
|
// Initialize an array of diagnoses.
|
|
86
85
|
const diagnoses: any[] = [];
|
|
87
86
|
// For each violating standard instance that pertains to this combination of issue and violator:
|
|
@@ -111,13 +110,11 @@ const populateQuery = async (
|
|
|
111
110
|
query.diagnoses = lines.join('\n');
|
|
112
111
|
};
|
|
113
112
|
// Returns a page answering the diagnoses question.
|
|
114
|
-
export const answer = async (pageArgs: string
|
|
113
|
+
export const answer = async (pageArgs: string) => {
|
|
115
114
|
const [issueID, reportID, catalogIndex] = pageArgs.split('/') as [string, string, string];
|
|
116
|
-
const params = new URLSearchParams(search);
|
|
117
|
-
const pathID = params.get('pathID');
|
|
118
115
|
const query: Record<string, any> = {};
|
|
119
116
|
// Create a query to replace the placeholders.
|
|
120
|
-
await populateQuery(issueID, reportID, catalogIndex,
|
|
117
|
+
await populateQuery(issueID, reportID, catalogIndex, query);
|
|
121
118
|
// If this failed:
|
|
122
119
|
if (query.error) {
|
|
123
120
|
// Return why.
|
|
@@ -108,17 +108,13 @@ test('listIssues for the empty report shows no issue links', async () => {
|
|
|
108
108
|
test('listIssues counts the prevented rule engine as called but not able to test', async () => {
|
|
109
109
|
const result = await answer('260101T0006-prv');
|
|
110
110
|
assert.equal(result.status, 'ok');
|
|
111
|
-
assert.ok(result.answerPage.includes('<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>'));
|
|
111
|
+
assert.ok(result.answerPage.includes('<li>Rule engines: 2 called, 1 able to test, 1 reported any violations</li>'));
|
|
114
112
|
});
|
|
115
113
|
|
|
116
114
|
test('listIssues counts a rule engine that reported no violations as called and able to test', async () => {
|
|
117
115
|
const result = await answer('260101T0005-emp');
|
|
118
116
|
assert.equal(result.status, 'ok');
|
|
119
|
-
assert.ok(result.answerPage.includes('<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>'));
|
|
117
|
+
assert.ok(result.answerPage.includes('<li>Rule engines: 1 called, 1 able to test, 0 reported any violations</li>'));
|
|
122
118
|
});
|
|
123
119
|
|
|
124
120
|
test('listIssues counts an unknown prevented engine as called but not able to test', async () => {
|
|
@@ -131,8 +127,7 @@ test('listIssues counts an unknown prevented engine as called but not able to te
|
|
|
131
127
|
try {
|
|
132
128
|
const result = await answer('260103T0000-unk');
|
|
133
129
|
assert.equal(result.status, 'ok');
|
|
134
|
-
assert.ok(result.answerPage.includes('<li>
|
|
135
|
-
assert.ok(result.answerPage.includes('<li>Were able to test: 1</li>'));
|
|
130
|
+
assert.ok(result.answerPage.includes('<li>Rule engines: 3 called, 1 able to test, 1 reported any violations</li>'));
|
|
136
131
|
}
|
|
137
132
|
finally {
|
|
138
133
|
await fs.unlink(reportPath);
|
|
@@ -171,8 +166,7 @@ test('listIssues shows violation and violator counts for an issue with multiple
|
|
|
171
166
|
// The mul report has linkNoText with 4 violations by 3 violators.
|
|
172
167
|
const result = await answer('260101T0008-mul');
|
|
173
168
|
assert.equal(result.status, 'ok');
|
|
174
|
-
assert.ok(result.answerPage.includes('<li>
|
|
175
|
-
assert.ok(result.answerPage.includes('<li>Violators: 3</li>'));
|
|
169
|
+
assert.ok(result.answerPage.includes('<li>Results: 4 violations, 3 violators</li>'));
|
|
176
170
|
});
|
|
177
171
|
|
|
178
172
|
test('listIssues handles acts with no standardResult instances', async () => {
|
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
__header__
|
|
10
10
|
<main class="page">
|
|
11
11
|
<h1>Violators of the <q>__issue__</q> issue for the __target__ page</h1>
|
|
12
|
-
<h2>
|
|
13
|
-
<h3>About the page</h3>
|
|
12
|
+
<h2>About the page</h2>
|
|
14
13
|
__pageFacts__
|
|
15
|
-
<
|
|
14
|
+
<h2>About the issue</h2>
|
|
16
15
|
__issueFacts__
|
|
17
16
|
<h2>Violators</h2>
|
|
18
17
|
<p>Here are the HTML elements reported to violate rules belonging to the <q>__issue__</q> issue.</p>
|
|
@@ -92,11 +92,11 @@ const populateQuery = async (
|
|
|
92
92
|
let takeMeAdviceNeeded = false;
|
|
93
93
|
// For each violator:
|
|
94
94
|
violators.forEach((violator: any, index: number) => {
|
|
95
|
-
const {catalogIndex
|
|
95
|
+
const {catalogIndex} = violator;
|
|
96
96
|
// Add a heading to the lines.
|
|
97
97
|
lines.push(`${margin}<li><h3>Element ${catalogIndex}</h3>`);
|
|
98
98
|
// Add the violator facts to the lines.
|
|
99
|
-
lines.push(...getViolatorFactsLines(report, issueID, catalogIndex,
|
|
99
|
+
lines.push(...getViolatorFactsLines(report, issueID, catalogIndex, `${margin} `, 'pseudoTopLevel'));
|
|
100
100
|
if (catalogIndex) {
|
|
101
101
|
const catalogItem = catalog[catalogIndex];
|
|
102
102
|
if (catalogItem?.textLinkable) {
|
|
@@ -107,8 +107,7 @@ const populateQuery = async (
|
|
|
107
107
|
lines.push(`${margin} <p class="links">${takeMeLink}</p>`);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
const href
|
|
111
|
-
= `/listDiagnoses.html/${issueID}/${reportID}/${catalogIndex}?pathID=${encodeURIComponent(pathID)}`;
|
|
110
|
+
const href = `/listDiagnoses.html/${issueID}/${reportID}/${catalogIndex}`;
|
|
112
111
|
const questionString = 'What diagnoses were reported';
|
|
113
112
|
const labelString = `${questionString} for violator ${index + 1}?`;
|
|
114
113
|
lines.push(
|
|
@@ -86,7 +86,7 @@ test('listViolators includes links to listDiagnoses for violators', async () =>
|
|
|
86
86
|
test('listViolators excludes cantTell instances from the violator count', async () => {
|
|
87
87
|
const result = await answer('focusIndicationBad/260101T0000-mix');
|
|
88
88
|
assert.equal(result.status, 'ok');
|
|
89
|
-
assert.ok(result.answerPage.includes('<li>
|
|
89
|
+
assert.ok(result.answerPage.includes('<li>Results: 0 violations, 0 violators</li>'));
|
|
90
90
|
assert.ok(!result.answerPage.includes('<h3>Element'));
|
|
91
91
|
});
|
|
92
92
|
|
|
@@ -272,7 +272,7 @@ test('listViolators handles instances with missing catalogIndex and acts with no
|
|
|
272
272
|
const result = await answer('linkNoText/260101T0004-nvi');
|
|
273
273
|
assert.equal(result.status, 'ok');
|
|
274
274
|
// Should show plural violator count.
|
|
275
|
-
assert.ok(result.answerPage.includes('<li>
|
|
275
|
+
assert.ok(result.answerPage.includes('<li>Results: 2 violations, 2 violators</li>'));
|
|
276
276
|
}
|
|
277
277
|
finally {
|
|
278
278
|
await fs.unlink(reportPath).catch(() => {});
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
<meta name="description" content="Kilotest tutorial: how to connect an AI platform to Kilotest for technical analysis">
|
|
7
7
|
<title>Connect an AI Platform | Kilotest</title>
|
|
8
8
|
</head>
|
|
9
|
-
<body>
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<body class="styled">
|
|
10
|
+
__header__
|
|
11
|
+
<main class="page">
|
|
12
|
+
<h1>Connect an AI Platform</h1>
|
|
12
13
|
<h2>Purpose</h2>
|
|
13
14
|
<p>This tutorial shows you how to make Kilotest available to assistants powered by artificial intelligence. Suppose:</p>
|
|
14
15
|
<ul>
|
|
@@ -16,53 +17,55 @@
|
|
|
16
17
|
<li>You want to audit the quality of a web page.</li>
|
|
17
18
|
</ul>
|
|
18
19
|
<p>Then the models on your AI platform can use Kilotest <strong>if</strong> you make Kilotest available on the platform.</p>
|
|
19
|
-
<h2>
|
|
20
|
-
<
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
20
|
+
<h2>Platform instructions</h2>
|
|
21
|
+
<details id="claude">
|
|
22
|
+
<summary>Claude.ai in a browser or Claude Desktop</summary>
|
|
23
|
+
<p>These instructions apply to <a href="https://claude.ai">Claude.ai in a browser</a> and <a href="https://downloads.claude.com">Claude Desktop</a>.</p>
|
|
24
|
+
<ol>
|
|
25
|
+
<li><a href="https://claude.ai/new">Start a new task</a>.</li>
|
|
26
|
+
<li>Expand the left sidebar to see the labels.</li>
|
|
27
|
+
<li>Click the <q>Customize</q> button.</li>
|
|
28
|
+
<li>Click the <q>Connectors</q> button.</li>
|
|
29
|
+
<li>Enter <q>Kilotest</q> in the <q>Search</q> input.</li>
|
|
30
|
+
<li>Select <q>Kilotest</q> from the search results.</li>
|
|
31
|
+
<li>Click the <q>Connect to Claude</q> button.</li>
|
|
32
|
+
<li>If you wish, click the <q>Your tool permissions</q> button and adjust the settings for the Kilotest tools.</li>
|
|
33
|
+
</ol>
|
|
34
|
+
</details>
|
|
35
|
+
<details id="vibe">
|
|
36
|
+
<summary>Mistral Vibe</summary>
|
|
37
|
+
<ol>
|
|
38
|
+
<li>Choose the <a href="https://chat.mistral.ai/work">Work tab</a>.</li>
|
|
39
|
+
<li>Click <q>Connect your apps for better answers</q>.</li>
|
|
40
|
+
<li>Click the <q>+ Add connector</q> button.</li>
|
|
41
|
+
<li>Click the <q>Add a custom connector</q> button.</li>
|
|
42
|
+
<li>Enter <q>Kilotest</q> into the <q>Connector name</q> input.</li>
|
|
43
|
+
<li>Enter <q>https://kilotest.com/mcp</q> into the <q>Server</q> input.</li>
|
|
44
|
+
<li>Click the <q>Add connector</q> button.</li>
|
|
45
|
+
<li>Toggle the <q>Ask/Allow</q> buttons to show <q>Allow</q>.</li>
|
|
46
|
+
</ol>
|
|
47
|
+
</details>
|
|
48
|
+
<details id="perplexity">
|
|
49
|
+
<summary>Perplexity Pro in a browser</summary>
|
|
50
|
+
<p>These instructions apply to <a href="https://www.perplexity.ai/">Perplexity</a> Pro in a browser.</p>
|
|
51
|
+
<ol>
|
|
52
|
+
<li>After signing in, click the <q>Customize</q> button in the left sidebar.</li>
|
|
53
|
+
<li>Click the <q>+ Custom connector</q> menu button.</li>
|
|
54
|
+
<li>Click the <q>Add MCP connector</q> menu item.</li>
|
|
55
|
+
<li>Enter <q>Kilotest</q> into the <q>Name</q> input.</li>
|
|
56
|
+
<li>Enter <q>https://kilotest.com/mcp</q> into the <q>MCP server URL</q> input.</li>
|
|
57
|
+
<li>Click the <q>Advanced</q> menu button to open the advanced settings.</li>
|
|
58
|
+
<li>Under <q>Authentication</q>, change the authentication type to <q>None</q>.</li>
|
|
59
|
+
<li>Check the <q>I understand …</q> checkbox.</li>
|
|
60
|
+
<li>Click the <q>Add</q> button.</li>
|
|
61
|
+
</ol>
|
|
62
|
+
<p>Once you add Kilotest, it <strong>may</strong> become available in the Perplexity app on your computer or mobile device, too.</p>
|
|
63
|
+
</details>
|
|
61
64
|
<h2 id="other">Other AI platforms</h2>
|
|
62
65
|
<p>You can connect Kilotest to some other AI platforms, too. The instructions above are limited to consumer platforms that are either free or relatively inexpensive (US$20 per month or less). Business platforms such as ChatGPT Business, and developer platforms such as Devin Desktop, also permit you to enable Kilotest.</p>
|
|
63
66
|
<p>Feel free to submit a comment below about empowering any AI platform to use Kilotest.</p>
|
|
64
67
|
<p>You can also submit an issue on the <a href="https://github.com/jrpool/kilotest/issues">Kilotest GitHub repository</a>.</p>
|
|
65
|
-
<section id="suggest-improvement"
|
|
68
|
+
<section id="suggest-improvement">
|
|
66
69
|
<h2>Suggest improvements</h2>
|
|
67
70
|
<p>Please submit suggestions for improving this tutorial below. The Kilotest managers will review them.</p>
|
|
68
71
|
<form id="comment-form" novalidate>
|
|
@@ -139,7 +142,7 @@
|
|
|
139
142
|
#suggest-improvement {
|
|
140
143
|
margin-top: 2rem;
|
|
141
144
|
padding-top: 2rem;
|
|
142
|
-
border-top: solid 1px
|
|
145
|
+
border-top: solid 1px var(--line);
|
|
143
146
|
}
|
|
144
147
|
#comment-form {
|
|
145
148
|
margin: 1rem 0;
|
|
@@ -152,12 +155,12 @@
|
|
|
152
155
|
padding: 0.5rem;
|
|
153
156
|
border: solid 1px #ccc;
|
|
154
157
|
border-radius: 0.25rem;
|
|
155
|
-
font-family:
|
|
158
|
+
font-family: inherit;
|
|
156
159
|
font-size: 1rem;
|
|
157
160
|
}
|
|
158
161
|
.char-count {
|
|
159
162
|
font-size: 0.875rem;
|
|
160
|
-
color:
|
|
163
|
+
color: var(--muted);
|
|
161
164
|
margin: 0.25rem 0;
|
|
162
165
|
}
|
|
163
166
|
.char-count.low {
|
package/web/util.test.ts
CHANGED
|
@@ -215,10 +215,8 @@ test('getPageFactsLines returns the about-page list lines for a report', async (
|
|
|
215
215
|
const lines = getPageFactsLines(getPageDataStrings(report), getReportData(report), ' ');
|
|
216
216
|
const html = lines.join('\n');
|
|
217
217
|
assert.ok(html.includes('<li>URL: <a href="https://example.com/mixed">'));
|
|
218
|
-
assert.ok(html.includes('<li>
|
|
219
|
-
assert.ok(html.includes('<li>
|
|
220
|
-
assert.ok(html.includes('<li>Violations: 3</li>'));
|
|
221
|
-
assert.ok(html.includes('<li>Issues: 2</li>'));
|
|
218
|
+
assert.ok(html.includes('<li>Rule engines: 2 called, 2 able to test, 2 reported any violations</li>'));
|
|
219
|
+
assert.ok(html.includes('<li>Results: 3 violations, 2 violators, 2 issues</li>'));
|
|
222
220
|
});
|
|
223
221
|
|
|
224
222
|
test('getPageFactsLines ends with a link to download the full report', async () => {
|
|
@@ -227,7 +225,7 @@ test('getPageFactsLines ends with a link to download the full report', async ()
|
|
|
227
225
|
const lines = getPageFactsLines(getPageDataStrings(report), getReportData(report), '');
|
|
228
226
|
const topItems = parse(lines.join('\n')).querySelectorAll('ul:not(ul ul) > li');
|
|
229
227
|
const lastItem = topItems[topItems.length - 1]!;
|
|
230
|
-
assert.ok(topItems[topItems.length - 2]!.text.trim().startsWith('
|
|
228
|
+
assert.ok(topItems[topItems.length - 2]!.text.trim().startsWith('Results:'));
|
|
231
229
|
assert.equal(lastItem.querySelector('a')?.getAttribute('href'), '/fullReport.json/260101T0000-mix');
|
|
232
230
|
assert.equal(lastItem.text.trim(), 'Download the full technical report (JSON)');
|
|
233
231
|
});
|
|
@@ -237,28 +235,28 @@ const getResultFacts = async (reportID: string) => {
|
|
|
237
235
|
const {getReportData} = await import('./util.ts');
|
|
238
236
|
const report = await getFixtureReport(reportID);
|
|
239
237
|
const lines = getPageFactsLines(getPageDataStrings(report), getReportData(report), '');
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
238
|
+
const itemTexts: string[] = parse(lines.join('\n'))
|
|
239
|
+
.querySelectorAll('li')
|
|
240
|
+
.map(li => li.text.trim());
|
|
241
|
+
const enginesItem = itemTexts.find(t => t.startsWith('Rule engines: '));
|
|
242
|
+
assert.ok(enginesItem, 'Rule engines item exists');
|
|
243
|
+
const engineAmounts = enginesItem.match(
|
|
244
|
+
/^Rule engines: (\d+) called, (\d+) able to test, (\d+) reported any violations$/
|
|
245
|
+
);
|
|
246
|
+
assert.ok(engineAmounts, 'Rule engines item has the expected format');
|
|
247
|
+
const resultsItem = itemTexts.find(t => t.startsWith('Results: '));
|
|
248
|
+
assert.ok(resultsItem, 'Results item exists');
|
|
249
|
+
const resultAmounts = resultsItem.match(
|
|
250
|
+
/^Results: (\d+) violations?, (\d+) violators?, (\d+) issues?$/
|
|
251
|
+
);
|
|
252
|
+
assert.ok(resultAmounts, 'Results item has the expected format');
|
|
254
253
|
return {
|
|
255
|
-
called:
|
|
256
|
-
tested:
|
|
257
|
-
reporters:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
issues: getAmount('Issues')
|
|
254
|
+
called: Number(engineAmounts[1]),
|
|
255
|
+
tested: Number(engineAmounts[2]),
|
|
256
|
+
reporters: Number(engineAmounts[3]),
|
|
257
|
+
violations: Number(resultAmounts[1]),
|
|
258
|
+
violators: Number(resultAmounts[2]),
|
|
259
|
+
issues: Number(resultAmounts[3])
|
|
262
260
|
};
|
|
263
261
|
};
|
|
264
262
|
|
|
@@ -281,7 +279,6 @@ for (const [reportID, called, tested, reporters, violations, violators, issues]
|
|
|
281
279
|
assert.equal(facts.called, called);
|
|
282
280
|
assert.equal(facts.tested, tested);
|
|
283
281
|
assert.equal(facts.reporters, reporters);
|
|
284
|
-
assert.deepEqual(facts.amountLabels, ['Rule engines', 'Violations', 'Violators', 'Issues']);
|
|
285
282
|
assert.equal(facts.violations, violations);
|
|
286
283
|
assert.equal(facts.violators, violators);
|
|
287
284
|
assert.equal(facts.issues, issues);
|
|
@@ -297,14 +294,13 @@ test('getIssueFactsLines returns the about-issue list lines for a report', async
|
|
|
297
294
|
assert.ok(html.includes('<li>Priority:'));
|
|
298
295
|
assert.ok(html.includes('<li>Related WCAG standard: <a href='));
|
|
299
296
|
assert.ok(html.includes('<li>Reported by'));
|
|
300
|
-
assert.ok(
|
|
301
|
-
assert.ok(html.includes('<li>Violators:'));
|
|
297
|
+
assert.ok(/<li>Results: \d+ violations?, \d+ violators?<\/li>/.test(html));
|
|
302
298
|
});
|
|
303
299
|
|
|
304
300
|
test('getViolatorFactsLines returns the about-violator list lines for a report', async () => {
|
|
305
301
|
const {getViolatorFactsLines} = await import('./util.ts');
|
|
306
302
|
const report = await getFixtureReport('260101T0000-mix');
|
|
307
|
-
const lines = getViolatorFactsLines(report, 'linkNoText', '0',
|
|
303
|
+
const lines = getViolatorFactsLines(report, 'linkNoText', '0', ' ');
|
|
308
304
|
const html = lines.join('\n');
|
|
309
305
|
assert.ok(html.includes('<li>Tag name: <code>'));
|
|
310
306
|
assert.ok(html.includes('<li>Text:'));
|
package/web/util.ts
CHANGED
|
@@ -252,7 +252,7 @@ export const getReportData = (report: UsableReport): ReportData => {
|
|
|
252
252
|
export const getPageData = (report: UsableReport): PageData => {
|
|
253
253
|
const {what: description, url} = report.target;
|
|
254
254
|
// Get the elapsed time in days since the report was completed.
|
|
255
|
-
const daysAgo = getAgoDays(getReportEndTime(report))
|
|
255
|
+
const daysAgo = getAgoDays(getReportEndTime(report))!;
|
|
256
256
|
// Return the data.
|
|
257
257
|
return {
|
|
258
258
|
description,
|
|
@@ -270,7 +270,7 @@ export const getPageDataStrings = (report: UsableReport): PageDataStrings => {
|
|
|
270
270
|
description,
|
|
271
271
|
url,
|
|
272
272
|
urlLink: `<a href="${url}">${url}</a>`,
|
|
273
|
-
testInfo: `Tested ${daysAgo
|
|
273
|
+
testInfo: `Tested ${getCountString(daysAgo!, 'day', 'days')} ago on ${when} by job <code>${report.id}</code>`,
|
|
274
274
|
fullReportLink: `<a href="/fullReport.json/${report.id}">Download the full technical report</a> (JSON)`
|
|
275
275
|
};
|
|
276
276
|
};
|
|
@@ -279,27 +279,20 @@ export const getPageFactsLines = (
|
|
|
279
279
|
strings: PageDataStrings,
|
|
280
280
|
summary: ResultsSummary,
|
|
281
281
|
margin: string
|
|
282
|
-
): string[] =>
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
`${margin} <li>Violators: ${summary.violatorCount}</li>`,
|
|
297
|
-
`${margin} <li>Issues: ${summary.issueCount}</li>`,
|
|
298
|
-
`${margin} </ul>`,
|
|
299
|
-
`${margin} </li>`,
|
|
300
|
-
`${margin} <li>${strings.fullReportLink}</li>`,
|
|
301
|
-
`${margin}</ul>`
|
|
302
|
-
];
|
|
282
|
+
): string[] => {
|
|
283
|
+
const {
|
|
284
|
+
engineCount, issueCount, reporterCount, testedEngineCount, violationCount, violatorCount
|
|
285
|
+
} = summary;
|
|
286
|
+
return [
|
|
287
|
+
`${margin}<ul>`,
|
|
288
|
+
`${margin} <li>URL: ${strings.urlLink}</li>`,
|
|
289
|
+
`${margin} <li>${strings.testInfo}</li>`,
|
|
290
|
+
`${margin} <li>Rule engines: ${engineCount} called, ${testedEngineCount} able to test, ${reporterCount} reported any violations</li>`,
|
|
291
|
+
`${margin} <li>Results: ${getCountString(violationCount, 'violation', 'violations')}, ${getCountString(violatorCount, 'violator', 'violators')}, ${getCountString(issueCount, 'issue', 'issues')}</li>`,
|
|
292
|
+
`${margin} <li>${strings.fullReportLink}</li>`,
|
|
293
|
+
`${margin}</ul>`
|
|
294
|
+
];
|
|
295
|
+
};
|
|
303
296
|
// Returns the lines of a list of facts about an issue in an available report.
|
|
304
297
|
export const getIssueFactsLines = (
|
|
305
298
|
report: UsableReport,
|
|
@@ -328,8 +321,7 @@ export const getIssueFactsLines = (
|
|
|
328
321
|
`${margin} <li>Priority: ${getWeightName(weight)}</li>`,
|
|
329
322
|
`${margin} <li>Related WCAG standard: <a href="${getWCAGLink(wcag)}">${wcag}</a></li>`,
|
|
330
323
|
`${margin} <li>Reported by ${reporterString} (${getEngineNamesString(reporters)})</li>`,
|
|
331
|
-
`${margin} <li>
|
|
332
|
-
`${margin} <li>Violators: ${violatorIndexes.size}</li>`,
|
|
324
|
+
`${margin} <li>Results: ${getCountString(violationCount, 'violation', 'violations')}, ${getCountString(violatorIndexes.size, 'violator', 'violators')}</li>`,
|
|
333
325
|
`${margin}</ul>`
|
|
334
326
|
];
|
|
335
327
|
};
|
|
@@ -338,7 +330,6 @@ export const getViolatorFactsLines = (
|
|
|
338
330
|
report: UsableReport,
|
|
339
331
|
issueID: string,
|
|
340
332
|
catalogIndex: string,
|
|
341
|
-
pathID: string | null,
|
|
342
333
|
margin: string,
|
|
343
334
|
listClass?: string
|
|
344
335
|
): string[] => {
|
|
@@ -350,9 +341,12 @@ export const getViolatorFactsLines = (
|
|
|
350
341
|
const text = catalogItem?.text;
|
|
351
342
|
// Identify the rule engines that reported the violator for the issue.
|
|
352
343
|
const reporters = new Set<string>();
|
|
353
|
-
|
|
344
|
+
let instancePathID: string | undefined;
|
|
345
|
+
getTestActInstances(report, {violationsOnly: true, issueID, catalogIndex}).forEach(({act, instance}) => {
|
|
354
346
|
reporters.add(act.which!);
|
|
347
|
+
instancePathID ??= instance.pathID;
|
|
355
348
|
});
|
|
349
|
+
const pathID = catalogItem?.pathID || instancePathID;
|
|
356
350
|
const lines: string[] = [
|
|
357
351
|
`${margin}<ul${listClass ? ` class="${listClass}"` : ''}>`,
|
|
358
352
|
`${margin} <li>Tag name: <code>${tagName || 'HTML'}</code></li>`
|