@jrpool/kilotest 60.4.1 → 60.6.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/.devin/config.local.json +2 -1
- package/docs/SERVICE.md +1 -1
- package/error.html +1 -0
- package/index.test.ts +26 -0
- package/index.ts +31 -0
- package/lhm.plugin.json +5 -5
- package/linkValidation.test.ts +1 -0
- package/mcp.ts +4 -4
- package/media/demo-chatgpt.mp4 +0 -0
- package/openapi-3.0.yaml +1 -1
- package/openapi.yaml +1 -1
- package/package.json +1 -1
- package/server.json +1 -1
- package/sitemap.xml +3 -0
- package/smokeTest.ts +1 -0
- package/style.css +41 -0
- package/util.ts +4 -2
- package/web/accessibility/accessibility.test.ts +18 -0
- package/web/accessibility/index.html +29 -0
- package/web/accessibility/index.ts +19 -0
- package/web/ai0BalanceForm/index.html +1 -0
- package/web/deleteNotesForm/index.html +1 -0
- package/web/demo-chatgpt/demo-chatgpt.test.ts +18 -0
- package/web/demo-chatgpt/index.html +279 -0
- package/web/demo-chatgpt/index.ts +19 -0
- package/web/enqueue/index.html +1 -0
- package/web/enqueueForm/index.html +1 -0
- package/web/expungeReportsForm/index.html +1 -0
- package/web/footer.partial +9 -0
- package/web/hideReportForm/index.html +1 -0
- package/web/home/index.html +1 -1
- package/web/listDiagnoses/index.html +1 -0
- package/web/listIssues/index.html +1 -0
- package/web/listPages/index.html +1 -0
- package/web/listRules/index.html +1 -0
- package/web/listTopIssues/index.html +1 -0
- package/web/listViolators/index.html +1 -0
- package/web/manage/index.html +1 -0
- package/web/metrics/index.html +1 -0
- package/web/privacy/index.html +5 -3
- package/web/pruneReportsForm/index.html +1 -0
- package/web/reannotate/index.html +1 -0
- package/web/reannotateForm/index.html +1 -0
- package/web/renewWCAG/index.html +1 -0
- package/web/renewWCAGForm/index.html +1 -0
- package/web/requestNewTest/index.html +1 -0
- package/web/requestNewTestForm/index.html +1 -0
- package/web/requestRetest/index.html +1 -0
- package/web/requestRetestForm/index.html +1 -0
- package/web/rewindReportsForm/index.html +1 -0
- package/web/showHiddenReportsForm/index.html +1 -0
- package/web/terms/index.html +27 -0
- package/web/terms/index.ts +19 -0
- package/web/terms/terms.test.ts +18 -0
- package/web/tutorialAI/index.html +3 -0
- package/web/tutorialWeb/index.html +1 -0
- package/web/unhideReportForm/index.html +1 -0
package/.devin/config.local.json
CHANGED
package/docs/SERVICE.md
CHANGED
|
@@ -259,7 +259,7 @@ kilotest.com {
|
|
|
259
259
|
# Specify the only paths of forwardable requests.
|
|
260
260
|
@allowedGET {
|
|
261
261
|
method GET
|
|
262
|
-
path /mcp / /index.html /robots.txt /openapi.yaml /openapi.json /swagger.yaml /swagger.json /api-docs /llms.txt /llms-full.txt /capability.md *.html* /fullReport.json/* /api/* /tutorialWeb/images/* /tutorialAI/images/* /qai /qai/comments /favicon.* /style.css /sitemap.xml
|
|
262
|
+
path /mcp / /index.html /robots.txt /openapi.yaml /openapi.json /swagger.yaml /swagger.json /api-docs /llms.txt /llms-full.txt /capability.md *.html* /fullReport.json/* /api/* /tutorialWeb/images/* /tutorialAI/images/* /media/* /qai /qai/comments /favicon.* /style.css /sitemap.xml
|
|
263
263
|
}
|
|
264
264
|
# UptimeRobot's uptime check is the only known HEAD caller, and it targets only the site
|
|
265
265
|
# root (see "Health monitoring" below), so HEAD is forwarded only for that path, not the
|
package/error.html
CHANGED
package/index.test.ts
CHANGED
|
@@ -451,6 +451,32 @@ test('GET /tutorialAI/images/nonexistent.png returns an error page', async () =>
|
|
|
451
451
|
assert.ok(res.body.includes('Image not found'));
|
|
452
452
|
});
|
|
453
453
|
|
|
454
|
+
test('GET /media/demo-chatgpt.mp4 serves the video', async () => {
|
|
455
|
+
const res = await request('GET', '/media/demo-chatgpt.mp4');
|
|
456
|
+
assert.equal(res.statusCode, 200);
|
|
457
|
+
assert.ok(res.headers['content-type'].includes('video/mp4'));
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
test('GET /media with an unknown extension serves octet-stream', async () => {
|
|
461
|
+
// Create a temporary media file with an unknown extension.
|
|
462
|
+
const mediaPath = path.join(import.meta.dirname, 'media', 'test.xyz');
|
|
463
|
+
await fs.writeFile(mediaPath, 'fake media data');
|
|
464
|
+
try {
|
|
465
|
+
const res = await request('GET', '/media/test.xyz');
|
|
466
|
+
assert.equal(res.statusCode, 200);
|
|
467
|
+
assert.ok(res.headers['content-type'].includes('application/octet-stream'));
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
await fs.unlink(mediaPath).catch(() => {});
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('GET /media/nonexistent.mp4 returns an error page', async () => {
|
|
475
|
+
const res = await request('GET', '/media/nonexistent.mp4');
|
|
476
|
+
assert.equal(res.statusCode, 400);
|
|
477
|
+
assert.ok(res.body.includes('Media file not found'));
|
|
478
|
+
});
|
|
479
|
+
|
|
454
480
|
test('GET /nonexistent.html returns an abuse error', {timeout: 500}, async () => {
|
|
455
481
|
const res = await request('GET', '/nonexistent.html');
|
|
456
482
|
assert.equal(res.statusCode, 400);
|
package/index.ts
CHANGED
|
@@ -41,8 +41,11 @@ import {handleMCP, mcpPath} from './mcp.ts';
|
|
|
41
41
|
import fs from 'node:fs/promises';
|
|
42
42
|
import {handleComment as handleTutorialWebComment} from './web/tutorialWeb/index.ts';
|
|
43
43
|
import {answer as tutorialWeb} from './web/tutorialWeb/index.ts';
|
|
44
|
+
import {answer as accessibility} from './web/accessibility/index.ts';
|
|
45
|
+
import {answer as demoChatgpt} from './web/demo-chatgpt/index.ts';
|
|
44
46
|
import {answer as home} from './web/home/index.ts';
|
|
45
47
|
import {answer as privacy} from './web/privacy/index.ts';
|
|
48
|
+
import {answer as terms} from './web/terms/index.ts';
|
|
46
49
|
import {answer as tutorialAI, handleComment as handleTutorialAIComment} from './web/tutorialAI/index.ts';
|
|
47
50
|
import http, {type IncomingMessage, type ServerResponse} from 'node:http';
|
|
48
51
|
import https from 'node:https';
|
|
@@ -106,8 +109,10 @@ type AnswerData = {
|
|
|
106
109
|
type PageHandler = (...args: any[]) => Promise<AnswerData>;
|
|
107
110
|
|
|
108
111
|
const answer: {
|
|
112
|
+
accessibility: PageHandler;
|
|
109
113
|
ai0BalanceForm: PageHandler;
|
|
110
114
|
deleteNotesForm: PageHandler;
|
|
115
|
+
'demo-chatgpt': PageHandler;
|
|
111
116
|
enqueue: PageHandler;
|
|
112
117
|
enqueueForm: PageHandler;
|
|
113
118
|
expungeReportsForm: PageHandler;
|
|
@@ -134,11 +139,14 @@ const answer: {
|
|
|
134
139
|
requestNewTestForm: PageHandler;
|
|
135
140
|
rewindReportsForm: PageHandler;
|
|
136
141
|
showHiddenReportsForm: PageHandler;
|
|
142
|
+
terms: PageHandler;
|
|
137
143
|
unhideReportForm: PageHandler;
|
|
138
144
|
[key: string]: PageHandler | undefined;
|
|
139
145
|
} = {
|
|
146
|
+
accessibility,
|
|
140
147
|
ai0BalanceForm,
|
|
141
148
|
deleteNotesForm,
|
|
149
|
+
'demo-chatgpt': demoChatgpt,
|
|
142
150
|
enqueue,
|
|
143
151
|
enqueueForm,
|
|
144
152
|
expungeReportsForm,
|
|
@@ -165,6 +173,7 @@ const answer: {
|
|
|
165
173
|
requestNewTestForm,
|
|
166
174
|
rewindReportsForm,
|
|
167
175
|
showHiddenReportsForm,
|
|
176
|
+
terms,
|
|
168
177
|
unhideReportForm
|
|
169
178
|
};
|
|
170
179
|
// Response functions of the API services.
|
|
@@ -211,6 +220,7 @@ export const routes = {
|
|
|
211
220
|
'/llms-full.txt',
|
|
212
221
|
'/llms.txt',
|
|
213
222
|
'/mcp',
|
|
223
|
+
'/media/*',
|
|
214
224
|
'/openapi.json',
|
|
215
225
|
'/openapi.yaml',
|
|
216
226
|
'/qai',
|
|
@@ -908,6 +918,27 @@ const handleRequest = async (request: IncomingMessage, response: ServerResponse)
|
|
|
908
918
|
await serveError({message: 'Invalid service request'}, response, false);
|
|
909
919
|
}
|
|
910
920
|
}
|
|
921
|
+
// Otherwise, if it is for a media file (e.g. a demonstration video):
|
|
922
|
+
else if (pathname.startsWith('/media/')) {
|
|
923
|
+
const mediaFile = pathname.slice('/media/'.length);
|
|
924
|
+
const mediaPath = path.join(import.meta.dirname, 'media', mediaFile);
|
|
925
|
+
try {
|
|
926
|
+
const media = await fs.readFile(mediaPath);
|
|
927
|
+
const ext = path.extname(mediaFile).toLowerCase();
|
|
928
|
+
const mimeTypes: Record<string, string> = {
|
|
929
|
+
'.mp4': 'video/mp4',
|
|
930
|
+
'.webm': 'video/webm',
|
|
931
|
+
'.mov': 'video/quicktime',
|
|
932
|
+
'.m4v': 'video/mp4'
|
|
933
|
+
};
|
|
934
|
+
const mimeType = mimeTypes[ext] || 'application/octet-stream';
|
|
935
|
+
setHeaders(mimeType, null, 'low');
|
|
936
|
+
response.end(media);
|
|
937
|
+
}
|
|
938
|
+
catch {
|
|
939
|
+
await serveError({message: 'ERROR: Media file not found'}, response, true);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
911
942
|
// Otherwise, if it is for a tutorial image:
|
|
912
943
|
else if (pathname.startsWith('/tutorialWeb/images/') || pathname.startsWith('/tutorialAI/images/')) {
|
|
913
944
|
let imgFile: string;
|
package/lhm.plugin.json
CHANGED
|
@@ -2778,7 +2778,7 @@
|
|
|
2778
2778
|
"readOnlyHint": false,
|
|
2779
2779
|
"destructiveHint": false,
|
|
2780
2780
|
"idempotentHint": false,
|
|
2781
|
-
"openWorldHint":
|
|
2781
|
+
"openWorldHint": true
|
|
2782
2782
|
},
|
|
2783
2783
|
"execution": {
|
|
2784
2784
|
"taskSupport": "forbidden"
|
|
@@ -3122,7 +3122,7 @@
|
|
|
3122
3122
|
"readOnlyHint": false,
|
|
3123
3123
|
"destructiveHint": false,
|
|
3124
3124
|
"idempotentHint": false,
|
|
3125
|
-
"openWorldHint":
|
|
3125
|
+
"openWorldHint": true
|
|
3126
3126
|
},
|
|
3127
3127
|
"execution": {
|
|
3128
3128
|
"taskSupport": "forbidden"
|
|
@@ -3763,7 +3763,7 @@
|
|
|
3763
3763
|
"readOnlyHint": false,
|
|
3764
3764
|
"destructiveHint": false,
|
|
3765
3765
|
"idempotentHint": false,
|
|
3766
|
-
"openWorldHint":
|
|
3766
|
+
"openWorldHint": true
|
|
3767
3767
|
},
|
|
3768
3768
|
"execution": {
|
|
3769
3769
|
"taskSupport": "forbidden"
|
|
@@ -4115,7 +4115,7 @@
|
|
|
4115
4115
|
"readOnlyHint": false,
|
|
4116
4116
|
"destructiveHint": false,
|
|
4117
4117
|
"idempotentHint": false,
|
|
4118
|
-
"openWorldHint":
|
|
4118
|
+
"openWorldHint": true
|
|
4119
4119
|
},
|
|
4120
4120
|
"execution": {
|
|
4121
4121
|
"taskSupport": "forbidden"
|
|
@@ -4423,5 +4423,5 @@
|
|
|
4423
4423
|
}
|
|
4424
4424
|
}
|
|
4425
4425
|
],
|
|
4426
|
-
"version": "60.
|
|
4426
|
+
"version": "60.6.0"
|
|
4427
4427
|
}
|
package/linkValidation.test.ts
CHANGED
|
@@ -35,6 +35,7 @@ validPathPatterns.add('/api/*');
|
|
|
35
35
|
validPathPatterns.add('/fullReport.json/*');
|
|
36
36
|
validPathPatterns.add('/tutorialWeb/images/*');
|
|
37
37
|
validPathPatterns.add('/tutorialAI/images/*');
|
|
38
|
+
validPathPatterns.add('/media/*');
|
|
38
39
|
|
|
39
40
|
// Specific page routes (those that end in .html)
|
|
40
41
|
const htmlRoutes = [
|
package/mcp.ts
CHANGED
|
@@ -208,7 +208,7 @@ export const createMCPServer = (): McpServer => {
|
|
|
208
208
|
readOnlyHint: false,
|
|
209
209
|
idempotentHint: false,
|
|
210
210
|
destructiveHint: false,
|
|
211
|
-
openWorldHint:
|
|
211
|
+
openWorldHint: true
|
|
212
212
|
}
|
|
213
213
|
},
|
|
214
214
|
async ({description, URL, reason}) => {
|
|
@@ -228,7 +228,7 @@ export const createMCPServer = (): McpServer => {
|
|
|
228
228
|
readOnlyHint: false,
|
|
229
229
|
idempotentHint: false,
|
|
230
230
|
destructiveHint: false,
|
|
231
|
-
openWorldHint:
|
|
231
|
+
openWorldHint: true
|
|
232
232
|
}
|
|
233
233
|
},
|
|
234
234
|
async ({reportID, reason}) => {
|
|
@@ -268,7 +268,7 @@ export const createMCPServer = (): McpServer => {
|
|
|
268
268
|
readOnlyHint: false,
|
|
269
269
|
idempotentHint: false,
|
|
270
270
|
destructiveHint: false,
|
|
271
|
-
openWorldHint:
|
|
271
|
+
openWorldHint: true
|
|
272
272
|
}
|
|
273
273
|
},
|
|
274
274
|
async ({description, URL, reason}) => {
|
|
@@ -288,7 +288,7 @@ export const createMCPServer = (): McpServer => {
|
|
|
288
288
|
readOnlyHint: false,
|
|
289
289
|
idempotentHint: false,
|
|
290
290
|
destructiveHint: false,
|
|
291
|
-
openWorldHint:
|
|
291
|
+
openWorldHint: true
|
|
292
292
|
}
|
|
293
293
|
},
|
|
294
294
|
async ({reportID, reason}) => {
|
|
Binary file
|
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.
|
|
5
|
+
version: 60.6.0
|
|
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.
|
|
5
|
+
version: 60.6.0
|
|
6
6
|
license:
|
|
7
7
|
name: MIT
|
|
8
8
|
identifier: MIT
|
package/package.json
CHANGED
package/server.json
CHANGED
package/sitemap.xml
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
<url><loc>https://kilotest.com/manage.html</loc></url>
|
|
8
8
|
<url><loc>https://kilotest.com/tutorial.html</loc></url>
|
|
9
9
|
<url><loc>https://kilotest.com/privacy.html</loc></url>
|
|
10
|
+
<url><loc>https://kilotest.com/terms.html</loc></url>
|
|
11
|
+
<url><loc>https://kilotest.com/accessibility.html</loc></url>
|
|
12
|
+
<url><loc>https://kilotest.com/demo-chatgpt.html</loc></url>
|
|
10
13
|
<url><loc>https://kilotest.com/openapi.yaml</loc></url>
|
|
11
14
|
<url><loc>https://kilotest.com/llms.txt</loc></url>
|
|
12
15
|
<url><loc>https://kilotest.com/llms-full.txt</loc></url>
|
package/smokeTest.ts
CHANGED
|
@@ -25,6 +25,7 @@ const concretePaths: Record<string, Record<string, string>> = {
|
|
|
25
25
|
'/llms-full.txt': '/llms-full.txt',
|
|
26
26
|
'/llms.txt': '/llms.txt',
|
|
27
27
|
'/mcp': '/mcp',
|
|
28
|
+
'/media/*': '/media/demo-chatgpt.mp4',
|
|
28
29
|
'/openapi.json': '/openapi.json',
|
|
29
30
|
'/openapi.yaml': '/openapi.yaml',
|
|
30
31
|
'/qai': '/qai',
|
package/style.css
CHANGED
|
@@ -74,6 +74,25 @@ fieldset {
|
|
|
74
74
|
form {
|
|
75
75
|
margin-top: 1rem;
|
|
76
76
|
}
|
|
77
|
+
footer {
|
|
78
|
+
margin-top: 3rem;
|
|
79
|
+
padding-top: 1rem;
|
|
80
|
+
border-top: solid 0.1rem #000;
|
|
81
|
+
}
|
|
82
|
+
.footer-inner ul {
|
|
83
|
+
margin: 0;
|
|
84
|
+
padding: 0;
|
|
85
|
+
list-style: none;
|
|
86
|
+
}
|
|
87
|
+
.footer-inner li {
|
|
88
|
+
display: inline-block;
|
|
89
|
+
}
|
|
90
|
+
.footer-inner a {
|
|
91
|
+
display: inline-block;
|
|
92
|
+
min-height: 44px;
|
|
93
|
+
padding: 0.5rem 0.25rem;
|
|
94
|
+
margin-right: 1.5rem;
|
|
95
|
+
}
|
|
77
96
|
h4, h5, h6 {
|
|
78
97
|
margin-top: 1rem;
|
|
79
98
|
margin-bottom: 0;
|
|
@@ -469,3 +488,25 @@ body.styled .warn {
|
|
|
469
488
|
border: 1px solid #e8d48b;
|
|
470
489
|
border-radius: 0.5rem;
|
|
471
490
|
}
|
|
491
|
+
body.styled footer {
|
|
492
|
+
background: var(--card);
|
|
493
|
+
border-top: 1px solid var(--line);
|
|
494
|
+
}
|
|
495
|
+
body.styled .footer-inner {
|
|
496
|
+
max-width: 64rem;
|
|
497
|
+
margin-inline: auto;
|
|
498
|
+
padding: 1rem 1.5rem;
|
|
499
|
+
}
|
|
500
|
+
body.styled .footer-inner ul {
|
|
501
|
+
display: flex;
|
|
502
|
+
flex-wrap: wrap;
|
|
503
|
+
gap: 0.25rem 1.75rem;
|
|
504
|
+
}
|
|
505
|
+
body.styled .footer-inner a {
|
|
506
|
+
font-size: 0.9rem;
|
|
507
|
+
color: var(--muted);
|
|
508
|
+
}
|
|
509
|
+
body.styled .footer-inner a:hover {
|
|
510
|
+
color: var(--accent);
|
|
511
|
+
background-color: transparent;
|
|
512
|
+
}
|
package/util.ts
CHANGED
|
@@ -376,9 +376,9 @@ export const getPlainText = (string: string): string => string
|
|
|
376
376
|
.replace(/&/g, '+')
|
|
377
377
|
.replace(/[<>"']/g, ' ');
|
|
378
378
|
// Markup shared by populated templates, lazily read once from files.
|
|
379
|
-
let sharedContent: {head: string, header: string, headerNL: string, logo: string} | undefined;
|
|
379
|
+
let sharedContent: {head: string, header: string, headerNL: string, footer: string, logo: string} | undefined;
|
|
380
380
|
// Returns the markup shared by populated templates.
|
|
381
|
-
const getSharedContent = async (): Promise<{head: string, header: string, headerNL: string, logo: string}> => {
|
|
381
|
+
const getSharedContent = async (): Promise<{head: string, header: string, headerNL: string, footer: string, logo: string}> => {
|
|
382
382
|
// Trailing newlines are stripped so injection does not add blank lines.
|
|
383
383
|
if (!sharedContent) {
|
|
384
384
|
const webDir = path.join(import.meta.dirname, 'web');
|
|
@@ -390,6 +390,7 @@ const getSharedContent = async (): Promise<{head: string, header: string, header
|
|
|
390
390
|
header,
|
|
391
391
|
// The home page gets the same header with its brand as text, not a link to itself.
|
|
392
392
|
headerNL: header.replace('<a class="brand" href="/">', '<span class="brand">').replace('</a>', '</span>'),
|
|
393
|
+
footer: (await fs.readFile(path.join(webDir, 'footer.partial'), 'utf8')).trimEnd(),
|
|
393
394
|
logo
|
|
394
395
|
};
|
|
395
396
|
}
|
|
@@ -405,6 +406,7 @@ export const populateFile = async (filePath: string, query: Record<string, strin
|
|
|
405
406
|
.replace(/__head__/g, () => shared.head)
|
|
406
407
|
.replace(/__headerNL__/g, () => shared.headerNL)
|
|
407
408
|
.replace(/__header__/g, () => shared.header)
|
|
409
|
+
.replace(/__footer__/g, () => shared.footer)
|
|
408
410
|
.replace(/__logo__/g, () => shared.logo);
|
|
409
411
|
// Replace its placeholders.
|
|
410
412
|
Object.keys(query).forEach(param => {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
accessibility.test.ts
|
|
3
|
+
Unit tests for web/accessibility/index.ts, covering the answer export.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {test} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import {answer} from './index.ts';
|
|
11
|
+
|
|
12
|
+
// TESTS
|
|
13
|
+
|
|
14
|
+
test('answer returns the accessibility statement page', async () => {
|
|
15
|
+
const result = await answer();
|
|
16
|
+
assert.equal(result.status, 'ok');
|
|
17
|
+
assert.ok(result.answerPage.includes('WCAG'));
|
|
18
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
__head__
|
|
5
|
+
<meta name="description" content="Kilotest accessibility statement: the accessibility goals, testing, and feedback channel for kilotest.com">
|
|
6
|
+
<title>Accessibility statement | Kilotest</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body class="styled">
|
|
9
|
+
__header__
|
|
10
|
+
<main class="page">
|
|
11
|
+
<h1>Accessibility statement</h1>
|
|
12
|
+
<p>This statement applies to the Kilotest web site at <code>kilotest.com</code>. It was last updated in October 2026.</p>
|
|
13
|
+
<h2>Policy</h2>
|
|
14
|
+
<p>Kilotest intends to have a simple, intuitive, and accessible web interface that conforms to Level AAA of Web Content Accessibility Guidelines (WCAG) 2.2 and best practices defined by various accessibility experts. One requirement that Kilotest imposes on itself is that no page exhibits any issues other than lowest-priority issues when tested by Kilotest itself. Any lowest-priority issues will be remediated or their justifications will be documented on this page.</p>
|
|
15
|
+
<h2>Unremediated issues</h2>
|
|
16
|
+
<p>Kilotest has reported these lowest-priority issues:</p>
|
|
17
|
+
<ul>
|
|
18
|
+
<li><strong>large visible-element count</strong> (<code>visibleBulk</code>) on the list-pages page: justified by the existence of more than 200 tested pages</li>
|
|
19
|
+
<li><strong>inconsistent heading, link, or button styles</strong> (<code>incnsistentStyles</code>) on the list-pages page: under investigation</li>
|
|
20
|
+
</ul>
|
|
21
|
+
<p>The testing of Kilotest pages is incomplete, so additional unremediated issues may remain.</p>
|
|
22
|
+
<h2>Known limitations</h2>
|
|
23
|
+
<p>Reports about third-party pages quote elements of those pages, such as tag names, attributes, and text excerpts. Kilotest does not control the accessibility of quoted content or of the tested pages themselves.</p>
|
|
24
|
+
<h2>Comments</h2>
|
|
25
|
+
<p>If you encounter an accessibility barrier on this site, or if any content is hard for you to perceive or operate, email <a href="mailto:info@kilotest.com">info@kilotest.com</a> or submit an issue on <a href="https://github.com/jrpool/kilotest/issues">GitHub</a>. Reported barriers are treated as defects and prioritized for remediation.</p>
|
|
26
|
+
</main>
|
|
27
|
+
__footer__
|
|
28
|
+
</body>
|
|
29
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.ts
|
|
3
|
+
Serves the accessibility statement.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {populateTemplate} from '../../util.ts';
|
|
9
|
+
|
|
10
|
+
// FUNCTIONS
|
|
11
|
+
|
|
12
|
+
// Returns the accessibility statement page.
|
|
13
|
+
export const answer = async () => {
|
|
14
|
+
const answerPage = await populateTemplate(import.meta.dirname, {});
|
|
15
|
+
return {
|
|
16
|
+
status: 'ok',
|
|
17
|
+
answerPage
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
demo.test.ts
|
|
3
|
+
Unit tests for web/demo/index.ts, covering the answer export.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {test} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import {answer} from './index.ts';
|
|
11
|
+
|
|
12
|
+
// TESTS
|
|
13
|
+
|
|
14
|
+
test('answer returns the demonstration page', async () => {
|
|
15
|
+
const result = await answer();
|
|
16
|
+
assert.equal(result.status, 'ok');
|
|
17
|
+
assert.ok(result.answerPage.includes('ChatGPT demonstration'));
|
|
18
|
+
});
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
__head__
|
|
5
|
+
<meta name="description" content="Kilotest demonstration: a step-through walkthrough of ChatGPT using the Kilotest tools">
|
|
6
|
+
<title>ChatGPT demonstration | Kilotest</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body class="styled">
|
|
9
|
+
__header__
|
|
10
|
+
<main class="page">
|
|
11
|
+
<h1>ChatGPT demonstration</h1>
|
|
12
|
+
<p>This page shows how ChatGPT uses Kilotest to answer questions about the front-end quality of web pages. Each of the five scenarios below reproduces a real interaction. Step through them at your own pace with the Previous and Next buttons, or press Play to advance automatically. The same interaction is also available as a <a href="/media/demo-chatgpt.mp4">video recording</a>.</p>
|
|
13
|
+
<div class="demo" id="demo">
|
|
14
|
+
<div class="demo-controls">
|
|
15
|
+
<button type="button" id="demo-play" aria-pressed="false"><span aria-hidden="true">▶</span> Play</button>
|
|
16
|
+
<button type="button" id="demo-prev"><span aria-hidden="true">◀</span> Previous</button>
|
|
17
|
+
<button type="button" id="demo-next">Next <span aria-hidden="true">▶</span></button>
|
|
18
|
+
<span id="demo-status" role="status">Scene 1 of 5</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="demo-stage" id="demo-stage" aria-live="polite">
|
|
21
|
+
|
|
22
|
+
<section class="frame" data-scene="1">
|
|
23
|
+
<h2>1. Which of its own pages has Kilotest tested?</h2>
|
|
24
|
+
<h3>Prompt 1</h3>
|
|
25
|
+
<div class="msg user"><p><span class="mention">@ Kilotest</span> Name all pages of Kilotest that Kilotest has tested. I need only their names and not their URLs, test dates, test results, or any other information.</p></div>
|
|
26
|
+
<div class="msg tool"><p>Used Kilotest — worked for a few seconds</p></div>
|
|
27
|
+
<h3>Response 1</h3>
|
|
28
|
+
<div class="msg assistant"><p>The assistant at first listed every tested page, not only the Kilotest pages.</p></div>
|
|
29
|
+
<h3>Corrective prompt 1</h3>
|
|
30
|
+
<div class="msg user"><p>You answered the wrong question. I asked you to name all pages of Kilotest that Kilotest has tested. I did not ask you to name all pages that Kilotest has tested.</p></div>
|
|
31
|
+
<div class="msg tool"><p>Used Kilotest — worked for 13s</p></div>
|
|
32
|
+
<h3>Corrected response 1</h3>
|
|
33
|
+
<div class="msg assistant"><ul><li>Kilotest</li><li>Kilotest list diagnoses</li><li>Kilotest list issues</li><li>Kilotest list pages</li><li>Kilotest list violators</li></ul></div>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
<section class="frame" data-scene="2" hidden>
|
|
37
|
+
<h2>2. What issues were reported in a page’s latest report?</h2>
|
|
38
|
+
<h3>Prompt 2</h3>
|
|
39
|
+
<div class="msg user"><p><span class="mention">@ Kilotest</span> Name the issues reported in the latest report about the Human Rights Watch contact page. I need only their names and not their descriptions, priorities, or any other information.</p></div>
|
|
40
|
+
<div class="msg tool"><p>Used Kilotest — worked for 9s</p></div>
|
|
41
|
+
<h3>Response 2</h3>
|
|
42
|
+
<div class="msg assistant">
|
|
43
|
+
<ul class="issue-list">
|
|
44
|
+
<li>abbr title missing</li>
|
|
45
|
+
<li>all-capital text</li>
|
|
46
|
+
<li>aria reference invalid</li>
|
|
47
|
+
<li>attribute invalid</li>
|
|
48
|
+
<li>attribute obsolete</li>
|
|
49
|
+
<li>attribute value invalid</li>
|
|
50
|
+
<li>autocomplete invalid</li>
|
|
51
|
+
<li>box size absolute</li>
|
|
52
|
+
<li>button name not visible</li>
|
|
53
|
+
<li>code invalid</li>
|
|
54
|
+
<li>contrast poor</li>
|
|
55
|
+
<li>double br use dubious</li>
|
|
56
|
+
<li>element focusability and operability conflict</li>
|
|
57
|
+
<li>fieldset missing?</li>
|
|
58
|
+
<li>focus indication improvable</li>
|
|
59
|
+
<li>focusable element hidden</li>
|
|
60
|
+
<li>forms not distinctly named</li>
|
|
61
|
+
<li>heading level skipped</li>
|
|
62
|
+
<li>heading structure illogical</li>
|
|
63
|
+
<li>hovering changes content</li>
|
|
64
|
+
<li>ID empty</li>
|
|
65
|
+
<li>ID not unique</li>
|
|
66
|
+
<li>iframe not named</li>
|
|
67
|
+
<li>image not named</li>
|
|
68
|
+
<li>inconsistent heading, link, or button styles</li>
|
|
69
|
+
<li>itemid invalid</li>
|
|
70
|
+
<li>label location dubious</li>
|
|
71
|
+
<li>large visible-element count</li>
|
|
72
|
+
<li>line height low</li>
|
|
73
|
+
<li>link destination not URL</li>
|
|
74
|
+
<li>link href missing</li>
|
|
75
|
+
<li>link indication poor</li>
|
|
76
|
+
<li>link not named</li>
|
|
77
|
+
<li>links named identically</li>
|
|
78
|
+
<li>links not grouped as nav</li>
|
|
79
|
+
<li>list-like br use dubious</li>
|
|
80
|
+
<li>menu navigation nonstandard</li>
|
|
81
|
+
<li>parent invalid</li>
|
|
82
|
+
<li>requirement invalid</li>
|
|
83
|
+
<li>role assigned instead of implicit</li>
|
|
84
|
+
<li>role redundant</li>
|
|
85
|
+
<li>small targets too near to each other</li>
|
|
86
|
+
<li>summary has explicit role</li>
|
|
87
|
+
<li>svg image not named</li>
|
|
88
|
+
<li>tab-opening link action unstated?</li>
|
|
89
|
+
<li>target small</li>
|
|
90
|
+
<li>target very small</li>
|
|
91
|
+
<li>text contrast improvable</li>
|
|
92
|
+
<li>text contrast not testable</li>
|
|
93
|
+
<li>title attribute invalid</li>
|
|
94
|
+
<li>z-index not zero</li>
|
|
95
|
+
</ul>
|
|
96
|
+
</div>
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<section class="frame" data-scene="3" hidden>
|
|
100
|
+
<h2>3. Which issue did the most rule engines report, and what did they find?</h2>
|
|
101
|
+
<h3>Prompt 3</h3>
|
|
102
|
+
<div class="msg user"><p><span class="mention">@ Kilotest</span> In the latest report about the Wikimedia Foundation (home) page, tell me which highest-priority issue was reported by the largest number of rule engines. For that issue, give me the XPath of the element that was reported as a violator by the largest number of rule engines. Finally, for that element and that issue, give me a one-sentence summary of the diagnoses. I do not need an issue explanation, additional information about the element, the number of diagnoses, their full texts, or the number or names of the rule engines that issued them.</p></div>
|
|
103
|
+
<div class="msg tool"><p>Used Kilotest — worked for 21s</p></div>
|
|
104
|
+
<h3>Response 3</h3>
|
|
105
|
+
<div class="msg assistant">
|
|
106
|
+
<p><strong>Highest-priority issue:</strong> Link not named</p>
|
|
107
|
+
<p><strong>XPath(s) tied for the most rule-engine reports:</strong></p>
|
|
108
|
+
<ul>
|
|
109
|
+
<li><code>/html/body/div[2]/footer[1]/div[1]/div[2]/div[1]/figure[1]/a[1]</code></li>
|
|
110
|
+
<li><code>/html/body/div[2]/footer[1]/div[1]/div[2]/div[1]/figure[2]/a[1]</code></li>
|
|
111
|
+
</ul>
|
|
112
|
+
<p><strong>Diagnosis summary:</strong> Both links lack an accessible name, such as discernible text or an equivalent text alternative, making their purpose unclear to screen-reader users.</p>
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
|
|
116
|
+
<section class="frame" data-scene="4" hidden>
|
|
117
|
+
<h2>4. How do I get an untested page tested?</h2>
|
|
118
|
+
<h3>Prompt 4</h3>
|
|
119
|
+
<div class="msg user"><p><span class="mention">@ Kilotest</span> Request a Kilotest test of Example12345 (<a href="https://example12345.info">https://example12345.info</a>) and let me know whether the request was accepted. If it was, tell me how long I should expect to wait before the results will be available.</p></div>
|
|
120
|
+
<div class="msg tool"><p>Used Kilotest — worked for 12s</p></div>
|
|
121
|
+
<h3>Response 4</h3>
|
|
122
|
+
<div class="msg assistant"><p>Kilotest <strong>did not accept the request</strong> to test Example12345 (<code>https://example12345.info</code>).</p>
|
|
123
|
+
<p>The reason given was that the URL does not resolve to a page that this Kilotest deployment can test.</p>
|
|
124
|
+
<p>Since the request was rejected, there is no expected wait time for results.</p></div>
|
|
125
|
+
</section>
|
|
126
|
+
|
|
127
|
+
<section class="frame" data-scene="5" hidden>
|
|
128
|
+
<h2>5. Can a page be retested after it has been remediated?</h2>
|
|
129
|
+
<h3>Prompt 5</h3>
|
|
130
|
+
<div class="msg user"><p><span class="mention">@ Kilotest</span> Order a Kilotest retest of the Example page. When the report is completed, compare it with the previous report about that page and give me two lists: one naming any eliminated issues and the other naming any newly reported issues. I do not need the report dates, the issue counts, issue explanations, or any other information. If the order is rejected, or if the report is not completed within 4 minutes, stop waiting and tell me what happened.</p></div>
|
|
131
|
+
<div class="msg tool"><p>Used Kilotest — worked for 1m 12s</p></div>
|
|
132
|
+
<h3>Response 5</h3>
|
|
133
|
+
<div class="msg assistant">
|
|
134
|
+
<p>Kilotest accepted the retest order, and the report was completed within 4 minutes.</p>
|
|
135
|
+
<p><strong>Eliminated issues</strong></p>
|
|
136
|
+
<p>– None</p>
|
|
137
|
+
<p><strong>Newly reported issues</strong></p>
|
|
138
|
+
<p>– None</p>
|
|
139
|
+
</div>
|
|
140
|
+
</section>
|
|
141
|
+
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<p>Each scenario corresponds to a real capability of the Kilotest tools: discovering tested pages, listing issues, drilling down to elements and diagnoses, requesting tests, and ordering retests. The responses above reproduce real Kilotest report data.</p>
|
|
145
|
+
</main>
|
|
146
|
+
<script>
|
|
147
|
+
const frames = Array.from(document.querySelectorAll('#demo-stage .frame'));
|
|
148
|
+
const playBtn = document.getElementById('demo-play');
|
|
149
|
+
const prevBtn = document.getElementById('demo-prev');
|
|
150
|
+
const nextBtn = document.getElementById('demo-next');
|
|
151
|
+
const status = document.getElementById('demo-status');
|
|
152
|
+
let current = 0;
|
|
153
|
+
let timer = null;
|
|
154
|
+
|
|
155
|
+
function show(index) {
|
|
156
|
+
current = Math.max(0, Math.min(frames.length - 1, index));
|
|
157
|
+
frames.forEach((frame, i) => { frame.hidden = i !== current; });
|
|
158
|
+
status.textContent = 'Scene ' + (current + 1) + ' of ' + frames.length;
|
|
159
|
+
prevBtn.disabled = current === 0;
|
|
160
|
+
nextBtn.disabled = current === frames.length - 1;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function stop() {
|
|
164
|
+
if (timer) { clearInterval(timer); timer = null; }
|
|
165
|
+
playBtn.innerHTML = '<span aria-hidden="true">▶</span> Play';
|
|
166
|
+
playBtn.setAttribute('aria-pressed', 'false');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
playBtn.addEventListener('click', function () {
|
|
170
|
+
if (timer) { stop(); return; }
|
|
171
|
+
if (current === frames.length - 1) { show(0); }
|
|
172
|
+
playBtn.innerHTML = '<span aria-hidden="true">⏸</span> Pause';
|
|
173
|
+
playBtn.setAttribute('aria-pressed', 'true');
|
|
174
|
+
timer = setInterval(function () {
|
|
175
|
+
if (current >= frames.length - 1) { stop(); return; }
|
|
176
|
+
show(current + 1);
|
|
177
|
+
}, 6000);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
prevBtn.addEventListener('click', function () { stop(); show(current - 1); });
|
|
181
|
+
nextBtn.addEventListener('click', function () { stop(); show(current + 1); });
|
|
182
|
+
show(0);
|
|
183
|
+
</script>
|
|
184
|
+
<style>
|
|
185
|
+
.demo-controls {
|
|
186
|
+
display: flex;
|
|
187
|
+
flex-wrap: wrap;
|
|
188
|
+
gap: 0.5rem;
|
|
189
|
+
align-items: center;
|
|
190
|
+
margin-bottom: 1rem;
|
|
191
|
+
}
|
|
192
|
+
.demo-controls button {
|
|
193
|
+
min-height: 44px;
|
|
194
|
+
padding: 0.5rem 1rem;
|
|
195
|
+
font-size: 1rem;
|
|
196
|
+
font-family: inherit;
|
|
197
|
+
border: solid 1px var(--line);
|
|
198
|
+
border-radius: 0.375rem;
|
|
199
|
+
background: var(--card);
|
|
200
|
+
color: inherit;
|
|
201
|
+
cursor: pointer;
|
|
202
|
+
}
|
|
203
|
+
.demo-controls button:hover {
|
|
204
|
+
border-color: var(--accent);
|
|
205
|
+
}
|
|
206
|
+
.demo-controls button:disabled {
|
|
207
|
+
opacity: 0.45;
|
|
208
|
+
cursor: default;
|
|
209
|
+
}
|
|
210
|
+
.demo-controls button:disabled:hover {
|
|
211
|
+
border-color: var(--line);
|
|
212
|
+
}
|
|
213
|
+
#demo-status {
|
|
214
|
+
font-size: 0.9rem;
|
|
215
|
+
color: var(--muted);
|
|
216
|
+
}
|
|
217
|
+
.demo-stage .frame {
|
|
218
|
+
border: solid 1px var(--line);
|
|
219
|
+
border-radius: 0.5rem;
|
|
220
|
+
padding: 1rem 1.25rem;
|
|
221
|
+
background: var(--card);
|
|
222
|
+
}
|
|
223
|
+
.demo-stage .frame h2 {
|
|
224
|
+
margin-top: 0;
|
|
225
|
+
font-size: 1.15rem;
|
|
226
|
+
}
|
|
227
|
+
.demo-stage .frame h3 {
|
|
228
|
+
margin: 1rem 0 0.25rem;
|
|
229
|
+
font-size: 0.95rem;
|
|
230
|
+
color: var(--accent);
|
|
231
|
+
}
|
|
232
|
+
.msg {
|
|
233
|
+
margin: 0.75rem 0;
|
|
234
|
+
max-width: 52rem;
|
|
235
|
+
}
|
|
236
|
+
.msg p {
|
|
237
|
+
margin: 0;
|
|
238
|
+
}
|
|
239
|
+
.msg.user {
|
|
240
|
+
display: flex;
|
|
241
|
+
justify-content: flex-end;
|
|
242
|
+
}
|
|
243
|
+
.msg.user p {
|
|
244
|
+
max-width: 42rem;
|
|
245
|
+
padding: 0.55rem 1.1rem;
|
|
246
|
+
background: #e9e9e9;
|
|
247
|
+
border-radius: 1.4rem;
|
|
248
|
+
}
|
|
249
|
+
.msg.tool {
|
|
250
|
+
font-size: 0.9rem;
|
|
251
|
+
color: #888;
|
|
252
|
+
}
|
|
253
|
+
.msg.tool p {
|
|
254
|
+
padding: 0.2rem 0;
|
|
255
|
+
}
|
|
256
|
+
.msg.tool p::after {
|
|
257
|
+
content: " \203A";
|
|
258
|
+
}
|
|
259
|
+
.mention {
|
|
260
|
+
display: inline-block;
|
|
261
|
+
padding: 0.05rem 0.5rem;
|
|
262
|
+
margin-right: 0.25rem;
|
|
263
|
+
font-size: 0.85rem;
|
|
264
|
+
background: #ddf0e8;
|
|
265
|
+
border-radius: 0.4rem;
|
|
266
|
+
}
|
|
267
|
+
.msg.assistant {
|
|
268
|
+
padding: 0.6rem 0;
|
|
269
|
+
}
|
|
270
|
+
.issue-list {
|
|
271
|
+
columns: 14rem 3;
|
|
272
|
+
column-gap: 2rem;
|
|
273
|
+
font-size: 0.9rem;
|
|
274
|
+
margin: 0.5rem 0 0;
|
|
275
|
+
}
|
|
276
|
+
</style>
|
|
277
|
+
__footer__
|
|
278
|
+
</body>
|
|
279
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.ts
|
|
3
|
+
Serves the Kilotest demonstration page.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {populateTemplate} from '../../util.ts';
|
|
9
|
+
|
|
10
|
+
// FUNCTIONS
|
|
11
|
+
|
|
12
|
+
// Returns the demonstration page.
|
|
13
|
+
export const answer = async () => {
|
|
14
|
+
const answerPage = await populateTemplate(import.meta.dirname, {});
|
|
15
|
+
return {
|
|
16
|
+
status: 'ok',
|
|
17
|
+
answerPage
|
|
18
|
+
};
|
|
19
|
+
};
|
package/web/enqueue/index.html
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<footer>
|
|
2
|
+
<nav class="footer-inner" aria-label="Site">
|
|
3
|
+
<ul>
|
|
4
|
+
<li><a href="/privacy.html">Privacy policy</a></li>
|
|
5
|
+
<li><a href="/terms.html">Terms of service</a></li>
|
|
6
|
+
<li><a href="/accessibility.html">Accessibility statement</a></li>
|
|
7
|
+
</ul>
|
|
8
|
+
</nav>
|
|
9
|
+
</footer>
|
package/web/home/index.html
CHANGED
|
@@ -99,9 +99,9 @@
|
|
|
99
99
|
<h2 id="more-heading">More …</h2>
|
|
100
100
|
<ul class="nav">
|
|
101
101
|
<li><a href="manage.html"><span class="card-title">Management actions</span><span class="card-desc">Approval, queue, and report administration for managers.</span></a></li>
|
|
102
|
-
<li><a href="privacy.html"><span class="card-title">Privacy policy</span><span class="card-desc">What Kilotest collects, stores, and shares.</span></a></li>
|
|
103
102
|
</ul>
|
|
104
103
|
</section>
|
|
105
104
|
</main>
|
|
105
|
+
__footer__
|
|
106
106
|
</body>
|
|
107
107
|
</html>
|
|
@@ -47,5 +47,6 @@ __history__
|
|
|
47
47
|
<p><a href="/tutorialAI.html">Make Kilotest available to an AI assistant</a>. Then give it a prompt like this:</p>
|
|
48
48
|
<blockquote><q>Kilotest reports issues on the __target__ web page. Describe them and propose a plan to resolve the genuine defects.</q></blockquote>
|
|
49
49
|
</main>
|
|
50
|
+
__footer__
|
|
50
51
|
</body>
|
|
51
52
|
</html>
|
package/web/listPages/index.html
CHANGED
package/web/listRules/index.html
CHANGED
package/web/manage/index.html
CHANGED
package/web/metrics/index.html
CHANGED
package/web/privacy/index.html
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
<meta name="description" content="Kilotest privacy policy: what information Kilotest collects, uses, shares, and keeps">
|
|
6
6
|
<title>Privacy policy | Kilotest</title>
|
|
7
7
|
</head>
|
|
8
|
-
<body>
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
<body class="styled">
|
|
9
|
+
__header__
|
|
10
|
+
<main class="page">
|
|
11
|
+
<h1>Privacy policy</h1>
|
|
11
12
|
<p>This policy applies to the Kilotest web site, API, and MCP server at <code>kilotest.com</code>, and does not apply to any other deployment of Kilotest. Kilotest does not require an account, does not ask for your name or email address, and does not use advertising or tracking cookies.</p>
|
|
12
13
|
<h2>What Kilotest collects</h2>
|
|
13
14
|
<ul>
|
|
@@ -28,5 +29,6 @@
|
|
|
28
29
|
<h2>Changes and contact</h2>
|
|
29
30
|
<p>Changes to this policy are recorded in the <a href="https://github.com/jrpool/kilotest">public repository</a>. Questions can be sent to <a href="mailto:info@kilotest.com">info@kilotest.com</a>.</p>
|
|
30
31
|
</main>
|
|
32
|
+
__footer__
|
|
31
33
|
</body>
|
|
32
34
|
</html>
|
package/web/renewWCAG/index.html
CHANGED
|
@@ -14,5 +14,6 @@
|
|
|
14
14
|
<p>If a <a href="/enqueueForm.html">Kilotest manager approves this request</a>, a test order will be added to the queue.</p>
|
|
15
15
|
<p>In most cases, if you check Kilotest again in 1 day, you will find that the requested job has been performed.</p>
|
|
16
16
|
</main>
|
|
17
|
+
__footer__
|
|
17
18
|
</body>
|
|
18
19
|
</html>
|
|
@@ -14,5 +14,6 @@
|
|
|
14
14
|
<p>If a <a href="/enqueueForm.html">Kilotest manager approves this request</a>, a test order will be added to the queue.</p>
|
|
15
15
|
<p>In most cases, if you check Kilotest again in 1 day, you will find that the requested test has been performed.</p>
|
|
16
16
|
</main>
|
|
17
|
+
__footer__
|
|
17
18
|
</body>
|
|
18
19
|
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en-US">
|
|
3
|
+
<head>
|
|
4
|
+
__head__
|
|
5
|
+
<meta name="description" content="Kilotest terms of service: rules for using the Kilotest web site, API, and MCP server">
|
|
6
|
+
<title>Terms of service | Kilotest</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body class="styled">
|
|
9
|
+
__header__
|
|
10
|
+
<main class="page">
|
|
11
|
+
<h1>Terms of service</h1>
|
|
12
|
+
<p>These terms apply to the Kilotest web site, API, and MCP server at <code>kilotest.com</code>, and do not apply to any other deployment of Kilotest. By using Kilotest you accept these terms. Kilotest is free and does not require an account.</p>
|
|
13
|
+
<h2>What Kilotest does</h2>
|
|
14
|
+
<p>Kilotest tests publicly reachable web pages for front-end quality (accessibility, usability, and web standards) with an ensemble of rule engines, stores the results as reports, and makes the reports publicly available through its web site, API, and MCP server. Owners of tested pages can request deletion of reports under the <a href="/privacy.html">privacy policy</a>.</p>
|
|
15
|
+
<h2>What you submit</h2>
|
|
16
|
+
<p>When you request or order a test or retest, you ask Kilotest to fetch the page and publish a report about it. You do not need the page owner’s permission to do so. Submit only publicly reachable URLs, and do not submit URLs that require credentials or that would put secrets into a public report. Kilotest may reject any request or order.</p>
|
|
17
|
+
<h2>Acceptable use</h2>
|
|
18
|
+
<p>Do not use Kilotest to attack, probe, or overload web sites, including this one, and do not submit unlawful or deceptive content. Kilotest may rate-limit, refuse, or block any use.</p>
|
|
19
|
+
<p>Reports attribute results to third-party rule engines, whose own terms may restrict the use of those results. In particular, results derived from WAVE are subject to the <a href="https://wave.webaim.org/terms">WAVE terms of use</a>, which prohibit their use for any purpose related to a legal action without written permission from WebAIM.</p>
|
|
20
|
+
<h2>No warranty</h2>
|
|
21
|
+
<p>Kilotest and its reports are provided as is. Automated rule engines can produce incomplete or incorrect results. Kilotest runs multiple rule engines and discloses selected results from them with attribution to them, but results disclosed from any particular rule engine differ in detail from the results that the same rule engine would produce if run directly. A Kilotest report is not a certification of conformance with WCAG or any other standard or law, and it is not legal advice. Kilotest is not liable for decisions made on the basis of a report.</p>
|
|
22
|
+
<h2>Changes and contact</h2>
|
|
23
|
+
<p>Changes to these terms are recorded in the <a href="https://github.com/jrpool/kilotest">public repository</a>. Questions can be sent to <a href="mailto:info@kilotest.com">info@kilotest.com</a>.</p>
|
|
24
|
+
</main>
|
|
25
|
+
__footer__
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
index.ts
|
|
3
|
+
Serves the terms of service.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {populateTemplate} from '../../util.ts';
|
|
9
|
+
|
|
10
|
+
// FUNCTIONS
|
|
11
|
+
|
|
12
|
+
// Returns the terms of service page.
|
|
13
|
+
export const answer = async () => {
|
|
14
|
+
const answerPage = await populateTemplate(import.meta.dirname, {});
|
|
15
|
+
return {
|
|
16
|
+
status: 'ok',
|
|
17
|
+
answerPage
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
terms.test.ts
|
|
3
|
+
Unit tests for web/terms/index.ts, covering the answer export.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
import {test} from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import {answer} from './index.ts';
|
|
11
|
+
|
|
12
|
+
// TESTS
|
|
13
|
+
|
|
14
|
+
test('answer returns the terms of service page', async () => {
|
|
15
|
+
const result = await answer();
|
|
16
|
+
assert.equal(result.status, 'ok');
|
|
17
|
+
assert.ok(result.answerPage.includes('provided as is'));
|
|
18
|
+
});
|
|
@@ -111,6 +111,8 @@
|
|
|
111
111
|
<li>Toggle the <q>Ask/Allow</q> buttons to show <q>Allow</q>.</li>
|
|
112
112
|
</ol>
|
|
113
113
|
</details>
|
|
114
|
+
<h2>Demonstration</h2>
|
|
115
|
+
<p>A <a href="/demo-chatgpt.html">step-through demonstration</a> shows ChatGPT using Kilotest to discover tested pages, inspect reported issues, and request tests and retests.</p>
|
|
114
116
|
<h2 id="other">Other AI platforms</h2>
|
|
115
117
|
<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>
|
|
116
118
|
<p>Feel free to submit a comment below about empowering any AI platform to use Kilotest.</p>
|
|
@@ -234,5 +236,6 @@
|
|
|
234
236
|
color: #1a4a1a;
|
|
235
237
|
}
|
|
236
238
|
</style>
|
|
239
|
+
__footer__
|
|
237
240
|
</body>
|
|
238
241
|
</html>
|