@jrpool/kilotest 27.0.0 → 28.0.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/index.js +112 -92
- package/llms.txt +16 -0
- package/openapi.yaml +23 -77
- package/package.json +1 -1
- package/reportIssues/api.js +9 -14
- package/researchAgent.js +5 -11
- package/robots.txt +4 -0
- package/sitemap.xml +9 -0
- package/targets/api.js +9 -14
package/index.js
CHANGED
|
@@ -87,6 +87,9 @@ const serveError = async (error, response, isHumanUser = true) => {
|
|
|
87
87
|
if (isHumanUser) {
|
|
88
88
|
// Serve an HTML page containing the message property of the error.
|
|
89
89
|
response.setHeader('content-type', 'text/html; charset=utf-8');
|
|
90
|
+
response.setHeader('content-location', '/error.html');
|
|
91
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
92
|
+
response.setHeader('Cache-Control', 'public, max-age=300, stale-while-revalidate=3000');
|
|
90
93
|
const errorTemplate = await fs.readFile('error.html', 'utf8');
|
|
91
94
|
const errorPage = errorTemplate.replace(/__error__/, error.message || 'ERROR');
|
|
92
95
|
response.end(errorPage);
|
|
@@ -168,11 +171,28 @@ const checkBalancesForAlerts = async report => {
|
|
|
168
171
|
};
|
|
169
172
|
// Handles a request.
|
|
170
173
|
const requestHandler = async (request, response) => {
|
|
174
|
+
// Sets response headers.
|
|
175
|
+
const setHeaders = (contentType, location, volatility = 'high') => {
|
|
176
|
+
response.setHeader('content-type', `${contentType}; charset=utf-8`);
|
|
177
|
+
if (location) {
|
|
178
|
+
response.setHeader('content-location', location);
|
|
179
|
+
}
|
|
180
|
+
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
181
|
+
const lives = {
|
|
182
|
+
high: [300, 3000],
|
|
183
|
+
medium: [1000, 10000],
|
|
184
|
+
low: [5000, 50000]
|
|
185
|
+
};
|
|
186
|
+
response.setHeader(
|
|
187
|
+
'Cache-Control',
|
|
188
|
+
`public, max-age=${lives[volatility][0]}, stale-while-revalidate=${lives[volatility][1]}`
|
|
189
|
+
);
|
|
190
|
+
};
|
|
171
191
|
const {method, url} = request;
|
|
172
192
|
const requestURL = new URL(url, 'https://localhost:3000');
|
|
173
193
|
const {pathname, search} = requestURL;
|
|
174
194
|
const pageName = pathname.split('/')[1];
|
|
175
|
-
const
|
|
195
|
+
const pathTail = pathname.split('/').slice(2).join('/');
|
|
176
196
|
// If the request is an OPTIONS request:
|
|
177
197
|
if (method === 'OPTIONS') {
|
|
178
198
|
// Serve response headers, including one allowing requests from other applications.
|
|
@@ -189,22 +209,30 @@ const requestHandler = async (request, response) => {
|
|
|
189
209
|
// Get the home page.
|
|
190
210
|
const homePage = await fs.readFile('index.html', 'utf8');
|
|
191
211
|
// Serve it.
|
|
192
|
-
|
|
193
|
-
response.setHeader('content-location', '/index.html');
|
|
194
|
-
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
212
|
+
setHeaders('text/html', '/index.html', 'medium');
|
|
195
213
|
response.end(homePage);
|
|
196
214
|
}
|
|
215
|
+
// Otherwise, if it is for the the crawler specification:
|
|
216
|
+
else if (pageName === 'robots.txt') {
|
|
217
|
+
const robots = await fs.readFile('robots.txt', 'utf8');
|
|
218
|
+
setHeaders('text/plain', '/robots.txt', 'low');
|
|
219
|
+
response.end(robots);
|
|
220
|
+
}
|
|
197
221
|
// Otherwise, if it is for the the OpenAPI specification:
|
|
198
222
|
else if (pageName === 'openapi.yaml') {
|
|
199
223
|
const openapi = await fs.readFile('openapi.yaml', 'utf8');
|
|
200
|
-
|
|
201
|
-
response.setHeader('content-location', '/openapi.yaml');
|
|
202
|
-
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
224
|
+
setHeaders('text/yaml', '/openapi.yaml', 'high');
|
|
203
225
|
response.end(openapi);
|
|
204
226
|
}
|
|
227
|
+
// Otherwise, if it is for the the large-language-model specification:
|
|
228
|
+
else if (pageName === 'llms.txt') {
|
|
229
|
+
const llms = await fs.readFile('llms.txt', 'utf8');
|
|
230
|
+
setHeaders('text/plain', '/llms.txt', 'high');
|
|
231
|
+
response.end(llms);
|
|
232
|
+
}
|
|
205
233
|
// Otherwise, if it is for a full report download:
|
|
206
234
|
else if (pageName === 'fullReport.json') {
|
|
207
|
-
const [timeStamp, jobID] =
|
|
235
|
+
const [timeStamp, jobID] = pathTail.split('/');
|
|
208
236
|
// If the request is syntactically valid:
|
|
209
237
|
if (isTimeStamp(timeStamp) && isJobID(jobID)) {
|
|
210
238
|
const reportHidden = await isHidden(timeStamp, jobID);
|
|
@@ -230,11 +258,10 @@ const requestHandler = async (request, response) => {
|
|
|
230
258
|
// If it exists and is valid:
|
|
231
259
|
if (typeof report === 'object') {
|
|
232
260
|
// Serve response headers for a JSON download.
|
|
233
|
-
|
|
261
|
+
setHeaders('application/json', null, 'low');
|
|
234
262
|
response.setHeader(
|
|
235
263
|
'content-disposition', `attachment; filename="${timeStamp}-${jobID}.json"`,
|
|
236
264
|
);
|
|
237
|
-
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
238
265
|
// Download the report.
|
|
239
266
|
response.end(getJSON(report));
|
|
240
267
|
}
|
|
@@ -256,11 +283,9 @@ const requestHandler = async (request, response) => {
|
|
|
256
283
|
const topic = pageName.slice(0, -5);
|
|
257
284
|
// If the page can be generated:
|
|
258
285
|
if (answer[topic]) {
|
|
259
|
-
|
|
260
|
-
response.setHeader('content-type', 'text/html; charset=utf-8');
|
|
261
|
-
response.setHeader('content-location', `${pathname}${search}`);
|
|
286
|
+
setHeaders('text/html', `${pathname}${search}`, 'medium');
|
|
262
287
|
// Get the answer data.
|
|
263
|
-
const answerData = await answer[topic](
|
|
288
|
+
const answerData = await answer[topic](pathTail, search);
|
|
264
289
|
// If they are valid:
|
|
265
290
|
if (answerData.status === 'ok') {
|
|
266
291
|
// Serve the answer page.
|
|
@@ -278,6 +303,47 @@ const requestHandler = async (request, response) => {
|
|
|
278
303
|
await serveError({message: 'ERROR: Invalid request'}, response, true);
|
|
279
304
|
}
|
|
280
305
|
}
|
|
306
|
+
// Otherwise, if it is for an API service:
|
|
307
|
+
else if (pageName === 'api') {
|
|
308
|
+
const [service, ... specs] = pathTail.split('/');
|
|
309
|
+
// If the service is provision of facts about the available reports:
|
|
310
|
+
if (service === 'targets') {
|
|
311
|
+
// Get the response (potentially error) data.
|
|
312
|
+
const responseData = await require(path.join(__dirname, 'targets', 'api')).response(specs);
|
|
313
|
+
// Send them.
|
|
314
|
+
setHeaders('application/json', null, 'high');
|
|
315
|
+
response.end(JSON.stringify(responseData));
|
|
316
|
+
}
|
|
317
|
+
// Otherwise, if the service is provision of issue statistics for a report:
|
|
318
|
+
else if (service === 'reportIssues') {
|
|
319
|
+
// Get the report identifiers from the path.
|
|
320
|
+
const [timeStamp, jobID] = specs;
|
|
321
|
+
const reportSpecsBad = await isHidden(timeStamp, jobID);
|
|
322
|
+
// If the report is nonexistent or hidden:
|
|
323
|
+
if (reportSpecsBad) {
|
|
324
|
+
// Report this.
|
|
325
|
+
await serveError(
|
|
326
|
+
{message: reportSpecsBad === true ? 'Report nonexistent or hidden' : reportSpecsBad},
|
|
327
|
+
response,
|
|
328
|
+
false
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
// Otherwise, i.e. if the report is available:
|
|
332
|
+
else {
|
|
333
|
+
// Get the response (potentially error) data.
|
|
334
|
+
const responseData = await require(path.join(__dirname, 'reportIssues', 'api'))
|
|
335
|
+
.response(specs);
|
|
336
|
+
// Send them.
|
|
337
|
+
setHeaders('application/json', null, 'high');
|
|
338
|
+
response.end(JSON.stringify(responseData));
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// Otherwise, i.e. if the service is invalid:
|
|
342
|
+
else {
|
|
343
|
+
// Report this.
|
|
344
|
+
await serveError({message: 'ERROR: Invalid service request'}, response, false);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
281
347
|
// Otherwise, if it is for a tutorial image:
|
|
282
348
|
else if (pathname.startsWith('/tutorial/images/')) {
|
|
283
349
|
const imgFile = pathname.slice('/tutorial/images/'.length);
|
|
@@ -285,9 +351,15 @@ const requestHandler = async (request, response) => {
|
|
|
285
351
|
try {
|
|
286
352
|
const img = await fs.readFile(imgPath);
|
|
287
353
|
const ext = path.extname(imgFile).toLowerCase();
|
|
288
|
-
const mimeTypes = {
|
|
289
|
-
|
|
290
|
-
|
|
354
|
+
const mimeTypes = {
|
|
355
|
+
'.png': 'image/png',
|
|
356
|
+
'.jpg': 'image/jpeg',
|
|
357
|
+
'.jpeg': 'image/jpeg',
|
|
358
|
+
'.gif': 'image/gif',
|
|
359
|
+
'.webp': 'image/webp',
|
|
360
|
+
'.svg': 'image/svg+xml'
|
|
361
|
+
};
|
|
362
|
+
setHeaders(mimeTypes[ext] || 'application/octet-stream', null, 'low');
|
|
291
363
|
response.end(img);
|
|
292
364
|
}
|
|
293
365
|
catch (_) {
|
|
@@ -299,7 +371,7 @@ const requestHandler = async (request, response) => {
|
|
|
299
371
|
// Get the site icon.
|
|
300
372
|
const icon = await fs.readFile(path.join(__dirname, 'favicon.ico'));
|
|
301
373
|
// Serve it.
|
|
302
|
-
|
|
374
|
+
setHeaders('image/x-icon', null, 'low');
|
|
303
375
|
response.write(icon, 'binary');
|
|
304
376
|
response.end('');
|
|
305
377
|
}
|
|
@@ -308,10 +380,7 @@ const requestHandler = async (request, response) => {
|
|
|
308
380
|
try {
|
|
309
381
|
// Serve it.
|
|
310
382
|
const styleSheet = await fs.readFile('style.css', 'utf8');
|
|
311
|
-
|
|
312
|
-
'content-type': 'text/css; charset=utf-8',
|
|
313
|
-
'cache-control': 'public, max-age=600'
|
|
314
|
-
});
|
|
383
|
+
setHeaders('text/css', null, 'low');
|
|
315
384
|
response.end(styleSheet);
|
|
316
385
|
}
|
|
317
386
|
catch (error) {
|
|
@@ -333,16 +402,14 @@ const requestHandler = async (request, response) => {
|
|
|
333
402
|
// If the request is a retest recommendation:
|
|
334
403
|
if (pageName === 'retestRec.html') {
|
|
335
404
|
const {why} = postData;
|
|
336
|
-
const [timeStamp, jobID] =
|
|
405
|
+
const [timeStamp, jobID] = pathTail.split('/');
|
|
337
406
|
// If the request is valid:
|
|
338
407
|
if (isTimeStamp(timeStamp) && isJobID(jobID) && why) {
|
|
339
|
-
// Serve response headers
|
|
340
|
-
|
|
341
|
-
response.setHeader('content-location', `${pathname}${search}`);
|
|
342
|
-
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
408
|
+
// Serve response headers.
|
|
409
|
+
setHeaders('text/html', `${pathname}${search}`, 'high');
|
|
343
410
|
// Get the answer data.
|
|
344
411
|
const answerData = await require(path.join(__dirname, 'retestRec', 'index'))
|
|
345
|
-
.answer(
|
|
412
|
+
.answer(pathTail, why);
|
|
346
413
|
// If they are valid:
|
|
347
414
|
if (answerData.status === 'ok') {
|
|
348
415
|
// Serve the answer page.
|
|
@@ -366,8 +433,7 @@ const requestHandler = async (request, response) => {
|
|
|
366
433
|
// If the request is valid:
|
|
367
434
|
if (what && url.startsWith('https://') && why) {
|
|
368
435
|
// Serve headers for a response.
|
|
369
|
-
|
|
370
|
-
response.setHeader('content-location', `${pathname}${search}`);
|
|
436
|
+
setHeaders('text/html', `${pathname}${search}`, 'high');
|
|
371
437
|
// Get the answer data.
|
|
372
438
|
const answerData = await require(path.join(__dirname, 'testRec', 'index'))
|
|
373
439
|
.answer(what, url, why);
|
|
@@ -394,11 +460,11 @@ const requestHandler = async (request, response) => {
|
|
|
394
460
|
const [url, what] = target.split('\t');
|
|
395
461
|
// If the request is valid:
|
|
396
462
|
if (url.startsWith('https://') && authCode === process.env.AUTH_CODE) {
|
|
397
|
-
//
|
|
398
|
-
|
|
463
|
+
// Set the non-location headers for a response.
|
|
464
|
+
setHeaders('text/html', null, 'high');
|
|
399
465
|
// If the request is an approval:
|
|
400
466
|
if (what) {
|
|
401
|
-
//
|
|
467
|
+
// Set a location header for a response.
|
|
402
468
|
response.setHeader('content-location', `${pathname}${search}`);
|
|
403
469
|
// Get the answer data.
|
|
404
470
|
const answerData = await require(path.join(__dirname, 'testOrder', 'index'))
|
|
@@ -422,7 +488,7 @@ const requestHandler = async (request, response) => {
|
|
|
422
488
|
delete recs[url];
|
|
423
489
|
// Save the revised recommendations.
|
|
424
490
|
await fs.writeFile(path.join(__dirname, 'jobs', 'recs.json'), getJSON(recs));
|
|
425
|
-
//
|
|
491
|
+
// Set a location header for a response.
|
|
426
492
|
response.setHeader('content-location', '/recActionForm.html');
|
|
427
493
|
// Get the answer data.
|
|
428
494
|
const answerData = await require(path.join(__dirname, 'recActionForm', 'index')).answer();
|
|
@@ -439,9 +505,8 @@ const requestHandler = async (request, response) => {
|
|
|
439
505
|
// Otherwise, if it is a reannotation order:
|
|
440
506
|
else if (pageName === 'reannotate.html') {
|
|
441
507
|
const {authCode} = postData;
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
response.setHeader('content-location', `${pathname}${search}`);
|
|
508
|
+
// Set headers for a response.
|
|
509
|
+
setHeaders('text/html', `${pathname}${search}`, 'high');
|
|
445
510
|
// Get the answer data.
|
|
446
511
|
const answerData = await require(path.join(__dirname, 'reannotate', 'index'))
|
|
447
512
|
.answer(authCode);
|
|
@@ -459,9 +524,8 @@ const requestHandler = async (request, response) => {
|
|
|
459
524
|
// Otherwise, if it is a WCAG map renewal:
|
|
460
525
|
else if (pageName === 'wcagRenew.html') {
|
|
461
526
|
const {authCode} = postData;
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
response.setHeader('content-location', `${pathname}${search}`);
|
|
527
|
+
// Set headers for a response.
|
|
528
|
+
setHeaders('text/html', `${pathname}${search}`, 'low');
|
|
465
529
|
// Get the answer data.
|
|
466
530
|
const answerData = await require(path.join(__dirname, 'wcagRenew', 'index'))
|
|
467
531
|
.answer(authCode);
|
|
@@ -478,10 +542,13 @@ const requestHandler = async (request, response) => {
|
|
|
478
542
|
}
|
|
479
543
|
// Otherwise, if it is a request from an agent:
|
|
480
544
|
else if (pageName === 'api') {
|
|
481
|
-
// Get the
|
|
482
|
-
const
|
|
483
|
-
// If the
|
|
484
|
-
if (
|
|
545
|
+
// Get the segments of the path after api.
|
|
546
|
+
const segments = pathTail.split('/');
|
|
547
|
+
// If the first segment is the ID of the Testaro agent and the agent is authenticated:
|
|
548
|
+
if (specs[0] === testaroAgent && postData.agentPW === testaroAgentPW) {
|
|
549
|
+
const agentID = specs[0];
|
|
550
|
+
// Get the requested service from the path.
|
|
551
|
+
const service = specs[1];
|
|
485
552
|
// If the service is job assignment:
|
|
486
553
|
if (service === 'job') {
|
|
487
554
|
let clean = true;
|
|
@@ -588,58 +655,11 @@ const requestHandler = async (request, response) => {
|
|
|
588
655
|
);
|
|
589
656
|
}
|
|
590
657
|
}
|
|
591
|
-
// Otherwise, if the agent is the authorized research agent and it is authenticated:
|
|
592
|
-
else if (agentID === researchAgent && postData.agentPW === researchAgentPW) {
|
|
593
|
-
// If the service is provision of facts about the available reports:
|
|
594
|
-
if (service === 'targets') {
|
|
595
|
-
// Get the agent ID from the path.
|
|
596
|
-
const args = [agentID];
|
|
597
|
-
// Get the response (potentially error) data.
|
|
598
|
-
const responseData = await require(path.join(__dirname, 'targets', 'api')).response(args);
|
|
599
|
-
// Send them.
|
|
600
|
-
response.end(JSON.stringify(responseData));
|
|
601
|
-
}
|
|
602
|
-
// Otherwise, if the service is provision of facts about issues in a report:
|
|
603
|
-
else if (service === 'reportIssues') {
|
|
604
|
-
// Get the agent ID and report identifiers from the path.
|
|
605
|
-
const [timeStamp, jobID] = specs;
|
|
606
|
-
const args = [agentID, timeStamp, jobID];
|
|
607
|
-
const reportSpecsBad = await isHidden(timeStamp, jobID);
|
|
608
|
-
// If the report is nonexistent or hidden:
|
|
609
|
-
if (reportSpecsBad) {
|
|
610
|
-
// Report this.
|
|
611
|
-
await serveError(
|
|
612
|
-
{message: reportSpecsBad === true ? 'Report nonexistent or hidden' : reportSpecsBad},
|
|
613
|
-
response,
|
|
614
|
-
false
|
|
615
|
-
);
|
|
616
|
-
}
|
|
617
|
-
// Otherwise, i.e. if the report is available:
|
|
618
|
-
else {
|
|
619
|
-
// Get the response (potentially error) data.
|
|
620
|
-
const responseData = await require(path.join(__dirname, 'reportIssues', 'api'))
|
|
621
|
-
.response(args);
|
|
622
|
-
// Send them.
|
|
623
|
-
response.end(JSON.stringify(responseData));
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
// Otherwise, i.e. if the service is invalid:
|
|
627
|
-
else {
|
|
628
|
-
// Report this.
|
|
629
|
-
await serveError({message: 'ERROR: Invalid service request from research agent'}, response, false);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
// Otherwise, i.e. if the agent is not authorized or not authenticated:
|
|
633
|
-
else {
|
|
634
|
-
// Report this.
|
|
635
|
-
await serveError({message: 'ERROR: Invalid agent'}, response, false);
|
|
636
|
-
}
|
|
637
658
|
}
|
|
638
659
|
// Otherwise, if it is a tutorial comment:
|
|
639
660
|
else if (pageName === 'tutorialComment.html') {
|
|
640
661
|
const {content} = postData;
|
|
641
|
-
|
|
642
|
-
response.setHeader('Access-Control-Allow-Origin', '*');
|
|
662
|
+
setHeaders('application/json', null, 'low');
|
|
643
663
|
const answerData = await require(path.join(__dirname, 'tutorial', 'index')).saveComment(content);
|
|
644
664
|
if (answerData.status === 'ok') {
|
|
645
665
|
response.end(JSON.stringify({status: 'ok'}));
|
package/llms.txt
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Kilotest
|
|
2
|
+
Kilotest helps AI agents give accurate and truthful answers when users ask how accessible, usable, or standard conformant web pages are. Kilotest integrates ten independent rule engines to provide comprehensive multipage, single-page, and element-level statistics and details via its API and web UI.
|
|
3
|
+
|
|
4
|
+
## API
|
|
5
|
+
- GET /api/targets: summarize test results for all tested pages
|
|
6
|
+
- GET /api/reportIssues/{timeStamp}/{jobID}: statistics on issues reported in a job
|
|
7
|
+
- GET /api/reportIssue/{issueID}/{timeStamp}/{jobID}: details on issues reported in a job
|
|
8
|
+
- GET /api/issues: statistics on frequently reported issues across all tested pages
|
|
9
|
+
- OpenAPI spec: https://kilotest.com/openapi.yaml
|
|
10
|
+
|
|
11
|
+
## Web UI
|
|
12
|
+
- https://kilotest.com/targets.html: summarize test results for all tested pages
|
|
13
|
+
- https://kilotest.com/reportIssues.html/{timeStamp}/{jobID}: details on issues reported in a job
|
|
14
|
+
- https://kilotest.com/reportIssue.html/{issueID}/{timeStamp}/{jobID}: details on issues reported in a job
|
|
15
|
+
- https://kilotest.com/issues.html: statistics on frequently reported issues across all tested pages
|
|
16
|
+
- https://kilotest.com/tutorial.html: tutorial, “Accessibility testing strategies”
|
package/openapi.yaml
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
openapi: 3.1.0
|
|
2
2
|
info:
|
|
3
3
|
title: Kilotest Agent API
|
|
4
|
-
description:
|
|
5
|
-
Kilotest tests web pages for accessibility, usability, and standard
|
|
6
|
-
conformity using an ensemble of ten independent tools that employ
|
|
7
|
-
rule-based and machine-learning-based methods. This API enables AI
|
|
8
|
-
agents to recommend web pages for testing, discover available test
|
|
9
|
-
reports, and retrieve data from reports at multiple levels of detail.
|
|
10
|
-
For background on Kilotest and the advantages of ensemble testing,
|
|
11
|
-
visit https://kilotest.com.
|
|
4
|
+
description: Kilotest tests web pages for accessibility, usability, and standard conformity using an ensemble of ten independent tools that employ rule-based and machine-learning-based methods. This API enables AI agents to recommend web pages for testing (not yet implemented), discover available test reports, and retrieve data from reports at multiple levels of detail. For background on Kilotest and the advantages of ensemble testing, visit https://kilotest.com.
|
|
12
5
|
version: 1.0.0
|
|
13
6
|
contact:
|
|
14
7
|
url: https://kilotest.com
|
|
@@ -16,52 +9,33 @@ info:
|
|
|
16
9
|
repository: https://github.com/jrpool/kilotest
|
|
17
10
|
|
|
18
11
|
servers:
|
|
19
|
-
- url: https://kilotest.com/api
|
|
12
|
+
- url: https://kilotest.com/api
|
|
20
13
|
description: Kilotest production server
|
|
21
|
-
variables:
|
|
22
|
-
agentId:
|
|
23
|
-
description: >-
|
|
24
|
-
Identifier assigned to the requesting agent by the Kilotest
|
|
25
|
-
operator. Contact the operator via info@kilotest.com to
|
|
26
|
-
obtain an identifier and a password.
|
|
27
|
-
default: research-agent
|
|
28
14
|
|
|
29
15
|
paths:
|
|
30
|
-
/targets:
|
|
16
|
+
/api/targets:
|
|
31
17
|
post:
|
|
32
18
|
operationId: getReportSummaries
|
|
33
19
|
summary: Summarize all available reports
|
|
34
|
-
description:
|
|
35
|
-
Returns summary data about every non-hidden report available from
|
|
36
|
-
Kilotest, including the name and URL of the tested web page, when
|
|
37
|
-
the testing was performed, how many issues were reported, and URLs
|
|
38
|
-
for retrieving more detailed data from the report.
|
|
20
|
+
description: Returns summary data about every non-hidden report available from Kilotest, including the name and URL of the tested web page, when the testing was performed, how many issues were reported, and URLs for retrieving more detailed data from the report.
|
|
39
21
|
responses:
|
|
40
22
|
'200':
|
|
41
23
|
description: Summaries of available reports
|
|
42
24
|
content:
|
|
43
25
|
application/json:
|
|
44
26
|
schema:
|
|
45
|
-
$ref: '#/components/schemas/
|
|
27
|
+
$ref: '#/components/schemas/ReportSummariesResponse'
|
|
46
28
|
|
|
47
|
-
/reportIssues/{timeStamp}/{jobID}:
|
|
29
|
+
/api/reportIssues/{timeStamp}/{jobID}:
|
|
48
30
|
post:
|
|
49
31
|
operationId: getReportIssues
|
|
50
32
|
summary: Get data on issues from a specific report
|
|
51
|
-
description:
|
|
52
|
-
Returns data about the issues reported in a specific Kilotest
|
|
53
|
-
report, grouped by priority. The data on each issue include the
|
|
54
|
-
tools that reported it, the number of HTML elements exhibiting
|
|
55
|
-
it, and URLs for retrieving element-level detail. The timeStamp
|
|
56
|
-
and jobID components identify the report and are available in
|
|
57
|
-
the getReportSummaries response.
|
|
33
|
+
description: Returns data about the issues reported in a specific Kilotest report, grouped by priority. The data on each issue include the tools that reported it, the number of HTML elements exhibiting it, and URLs for retrieving element-level detail. The timeStamp and jobID components identify the report and are available in the getReportSummaries response.
|
|
58
34
|
parameters:
|
|
59
35
|
- name: timeStamp
|
|
60
36
|
in: path
|
|
61
37
|
required: true
|
|
62
|
-
description:
|
|
63
|
-
timeStamp component of the report identifier, in the format
|
|
64
|
-
YYMMDDTHHMM (e.g., 260503T0432).
|
|
38
|
+
description: timeStamp component of the report identifier, in the format YYMMDDTHHMM (e.g., 260503T0432).
|
|
65
39
|
schema:
|
|
66
40
|
type: string
|
|
67
41
|
pattern: '^\d{6}T\d{4}$'
|
|
@@ -69,8 +43,7 @@ paths:
|
|
|
69
43
|
- name: jobID
|
|
70
44
|
in: path
|
|
71
45
|
required: true
|
|
72
|
-
description:
|
|
73
|
-
Job identifier component of the report identifier (e.g., xx0).
|
|
46
|
+
description: Job identifier component of the report identifier (e.g., xx0).
|
|
74
47
|
schema:
|
|
75
48
|
type: string
|
|
76
49
|
example: 'xx0'
|
|
@@ -82,28 +55,18 @@ paths:
|
|
|
82
55
|
schema:
|
|
83
56
|
$ref: '#/components/schemas/ReportIssuesResponse'
|
|
84
57
|
'404':
|
|
85
|
-
description:
|
|
86
|
-
No report with the specified identifier is available
|
|
58
|
+
description: No report with the specified identifier is available
|
|
87
59
|
|
|
88
|
-
/reportIssue/{issueID}/{timeStamp}/{jobID}:
|
|
60
|
+
/api/reportIssue/{issueID}/{timeStamp}/{jobID}:
|
|
89
61
|
post:
|
|
90
62
|
operationId: getReportIssue
|
|
91
|
-
summary:
|
|
92
|
-
|
|
93
|
-
description: >-
|
|
94
|
-
Returns element-level detail for a single issue within a specific
|
|
95
|
-
report, including which HTML elements exhibit the issue and
|
|
96
|
-
diagnostic information from each tool that reported it. This is
|
|
97
|
-
the level at which tool-provided remediation guidance becomes
|
|
98
|
-
available. The issueID is available in the getReportIssues response.
|
|
63
|
+
summary: Get details about a specific issue in a specific report
|
|
64
|
+
description: Returns details about a single issue within a specific report, including which HTML elements exhibit the issue and, for each such element, URLs for retrieving tool-by-tool diagnoses of the issue on the element.
|
|
99
65
|
parameters:
|
|
100
66
|
- name: issueID
|
|
101
67
|
in: path
|
|
102
68
|
required: true
|
|
103
|
-
description:
|
|
104
|
-
Issue identifier (e.g., imageNoText). Available under
|
|
105
|
-
"issues reported" > priority level > "identifier" in the
|
|
106
|
-
getReportIssues response.
|
|
69
|
+
description: Issue identifier (e.g., imageNoText). Available under "issues reported" > priority level > "identifier" in the getReportIssues response.
|
|
107
70
|
schema:
|
|
108
71
|
type: string
|
|
109
72
|
example: 'imageNoText'
|
|
@@ -119,7 +82,7 @@ paths:
|
|
|
119
82
|
type: string
|
|
120
83
|
responses:
|
|
121
84
|
'200':
|
|
122
|
-
description:
|
|
85
|
+
description: Details about the specified issue in the specified report
|
|
123
86
|
content:
|
|
124
87
|
application/json:
|
|
125
88
|
schema:
|
|
@@ -135,10 +98,7 @@ components:
|
|
|
135
98
|
properties:
|
|
136
99
|
summary:
|
|
137
100
|
type: string
|
|
138
|
-
description:
|
|
139
|
-
Natural-language description of this response and of Kilotest.
|
|
140
|
-
Provides context for an agent encountering Kilotest for the
|
|
141
|
-
first time.
|
|
101
|
+
description: Natural-language description of this response and of Kilotest. Provides context for an agent encountering Kilotest for the first time.
|
|
142
102
|
tool name:
|
|
143
103
|
type: string
|
|
144
104
|
example: Kilotest
|
|
@@ -245,9 +205,7 @@ components:
|
|
|
245
205
|
format: uri
|
|
246
206
|
whether a later report about the same page exists:
|
|
247
207
|
type: boolean
|
|
248
|
-
description:
|
|
249
|
-
If true, a more recent report about this page is available.
|
|
250
|
-
If you want the latest results, use that report instead.
|
|
208
|
+
description: If true, a more recent report about this page is available. If you want the latest results, use that report instead.
|
|
251
209
|
number of issues reported:
|
|
252
210
|
type: integer
|
|
253
211
|
number of HTML elements reported as exhibiting issues:
|
|
@@ -264,27 +222,20 @@ components:
|
|
|
264
222
|
type: string
|
|
265
223
|
format: uri
|
|
266
224
|
|
|
267
|
-
|
|
225
|
+
ReportSummariesResponse:
|
|
268
226
|
allOf:
|
|
269
227
|
- $ref: '#/components/schemas/CommonResponseFields'
|
|
270
228
|
- type: object
|
|
271
229
|
properties:
|
|
272
230
|
available reports:
|
|
273
231
|
type: array
|
|
274
|
-
description:
|
|
275
|
-
One entry per available Kilotest report, in alphabetical
|
|
276
|
-
order by page description and, in case of multiple reports
|
|
277
|
-
per page, in order of creation date and time. Reports are
|
|
278
|
-
matched by page description, not page URL, so, if only the
|
|
279
|
-
URL of a page has changed between reports, the reports are
|
|
280
|
-
treated as reports about the same page.
|
|
232
|
+
description: One entry per available Kilotest report, in alphabetical order by page description and, in case of multiple reports per page, in order of creation date and time. Reports are matched by page description, not page URL, so, if only the URL of a page has changed between reports, the reports are treated as reports about the same page.
|
|
281
233
|
items:
|
|
282
234
|
$ref: '#/components/schemas/ReportSummaryItem'
|
|
283
235
|
|
|
284
236
|
IssueEntry:
|
|
285
237
|
type: object
|
|
286
|
-
description:
|
|
287
|
-
Details about a specific accessibility issue found on a page.
|
|
238
|
+
description: Details about a specific accessibility issue found on a page.
|
|
288
239
|
properties:
|
|
289
240
|
identifier:
|
|
290
241
|
type: string
|
|
@@ -295,9 +246,7 @@ components:
|
|
|
295
246
|
example: image not named
|
|
296
247
|
related WCAG 2.2 standard:
|
|
297
248
|
type: object
|
|
298
|
-
description:
|
|
299
|
-
The WCAG 2.2 success criterion or guideline most closely
|
|
300
|
-
related to this issue.
|
|
249
|
+
description: The WCAG 2.2 success criterion or guideline most closely related to this issue.
|
|
301
250
|
properties:
|
|
302
251
|
layer:
|
|
303
252
|
type: string
|
|
@@ -308,8 +257,7 @@ components:
|
|
|
308
257
|
example: '1.1.1'
|
|
309
258
|
impact on a user:
|
|
310
259
|
type: string
|
|
311
|
-
description:
|
|
312
|
-
How this issue is likely to affect users.
|
|
260
|
+
description: How this issue is likely to affect users.
|
|
313
261
|
tools reporting the issue:
|
|
314
262
|
$ref: '#/components/schemas/ToolsSummary'
|
|
315
263
|
number of HTML elements reported as exhibiting the issue:
|
|
@@ -395,6 +343,4 @@ components:
|
|
|
395
343
|
allOf:
|
|
396
344
|
- $ref: '#/components/schemas/CommonResponseFields'
|
|
397
345
|
- type: object
|
|
398
|
-
description:
|
|
399
|
-
Element-level detail for a specific issue. Schema to be
|
|
400
|
-
completed once the tier-3 service is implemented.
|
|
346
|
+
description: Element-level detail for a specific issue. Schema to be completed once the tier-3 service is implemented.
|
package/package.json
CHANGED
package/reportIssues/api.js
CHANGED
|
@@ -12,14 +12,13 @@ const {
|
|
|
12
12
|
getRandomString,
|
|
13
13
|
getToolsFacts,
|
|
14
14
|
isHidden,
|
|
15
|
-
researchAgents,
|
|
16
15
|
tools
|
|
17
16
|
} = require('../util');
|
|
18
17
|
|
|
19
18
|
// FUNCTIONS
|
|
20
19
|
|
|
21
20
|
// Gets facts about an issue.
|
|
22
|
-
const getIssueFacts = (thisHost,
|
|
21
|
+
const getIssueFacts = (thisHost, timeStamp, jobID, issue) => {
|
|
23
22
|
const {issueID, reporterCount, reporters, summary, violatorCount, wcag, why} = issue;
|
|
24
23
|
const wcagType = wcag.length === 3 ? 'guideline' : 'success criterion';
|
|
25
24
|
return {
|
|
@@ -36,14 +35,14 @@ const getIssueFacts = (thisHost, agentID, timeStamp, jobID, issue) => {
|
|
|
36
35
|
},
|
|
37
36
|
'number of HTML elements reported as exhibiting the issue': violatorCount,
|
|
38
37
|
'URLs for details about the issue on the page': {
|
|
39
|
-
'for you': `${thisHost}/api
|
|
38
|
+
'for you': `${thisHost}/api/reportIssue/${issueID}/${timeStamp}/${jobID}`,
|
|
40
39
|
'for humans': `${thisHost}/reportIssue.html/${issueID}/${timeStamp}/${jobID}`
|
|
41
40
|
}
|
|
42
41
|
};
|
|
43
42
|
};
|
|
44
43
|
// Returns a response to a target-issues request.
|
|
45
44
|
exports.response = async args => {
|
|
46
|
-
const [
|
|
45
|
+
const [timeStamp, jobID] = args;
|
|
47
46
|
const reportIsHidden = await isHidden(timeStamp, jobID);
|
|
48
47
|
// If the report is not available:
|
|
49
48
|
if (reportIsHidden) {
|
|
@@ -67,22 +66,18 @@ exports.response = async args => {
|
|
|
67
66
|
summary: `This document fulfills a request made by an agent to the Kilotest service. The agent requested data from a Kilotest report about the accessibility, usability, and standard-conformity of a web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten testing tools, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes several API endpoints for agents and several web UI URLs for humans to obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of tools, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
|
|
68
67
|
'tool name': 'Kilotest',
|
|
69
68
|
request: {
|
|
70
|
-
'requesting agent': {
|
|
71
|
-
identifier: agentID,
|
|
72
|
-
name: researchAgents[agentID]
|
|
73
|
-
},
|
|
74
69
|
'type of request': {
|
|
75
70
|
identifier: 'reportIssues',
|
|
76
71
|
description: 'What issues does the specified report describe?'
|
|
77
72
|
},
|
|
78
73
|
URLs: {
|
|
79
|
-
'URL of your request': `${thisHost}/api
|
|
74
|
+
'URL of your request': `${thisHost}/api/reportIssues/${timeStamp}/${jobID}`,
|
|
80
75
|
'equivalent URL for humans': `${thisHost}/reportIssues.html/${timeStamp}/${jobID}`
|
|
81
76
|
},
|
|
82
77
|
'closest ancestor request': {
|
|
83
78
|
description: 'Which web pages are reports available about, and what are the statistics about the issues reported for each page?',
|
|
84
79
|
URLs: {
|
|
85
|
-
'for you': `${thisHost}/api
|
|
80
|
+
'for you': `${thisHost}/api/targets.html`,
|
|
86
81
|
'for humans': `${thisHost}/targets.html`
|
|
87
82
|
}
|
|
88
83
|
}
|
|
@@ -118,13 +113,13 @@ exports.response = async args => {
|
|
|
118
113
|
'number of HTML elements reported as exhibiting issues': violatorCount,
|
|
119
114
|
'issues reported': {
|
|
120
115
|
'highest priority': issues[4]
|
|
121
|
-
.map(issue => getIssueFacts(thisHost,
|
|
116
|
+
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
|
|
122
117
|
'high priority': issues[3]
|
|
123
|
-
.map(issue => getIssueFacts(thisHost,
|
|
118
|
+
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
|
|
124
119
|
'low priority': issues[2]
|
|
125
|
-
.map(issue => getIssueFacts(thisHost,
|
|
120
|
+
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
|
|
126
121
|
'lowest priority': issues[1]
|
|
127
|
-
.map(issue => getIssueFacts(thisHost,
|
|
122
|
+
.map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue))
|
|
128
123
|
}
|
|
129
124
|
};
|
|
130
125
|
return response;
|
package/researchAgent.js
CHANGED
|
@@ -20,8 +20,6 @@ const httpsClient = require('https');
|
|
|
20
20
|
|
|
21
21
|
// CONSTANTS
|
|
22
22
|
|
|
23
|
-
const agent = process.env.RESEARCH_AGENT;
|
|
24
|
-
const agentPW = process.env.RESEARCH_AGENT_PW;
|
|
25
23
|
const kilotestHosts = [process.env.LOCAL_KILOTEST_HOST, process.env.DEPLOYED_KILOTEST_HOST];
|
|
26
24
|
// Randomly chosen Kilotest host.
|
|
27
25
|
const kilotestHost = kilotestHosts[Math.random() < 0.5 ? 0 : 1];
|
|
@@ -36,7 +34,7 @@ const port = hostParts[2] || (scheme === 'https' ? 443 : 80);
|
|
|
36
34
|
const requestService = async () => {
|
|
37
35
|
const client = scheme === 'https' ? httpsClient : httpClient;
|
|
38
36
|
const getRequestOptions = path => ({
|
|
39
|
-
method: '
|
|
37
|
+
method: 'GET',
|
|
40
38
|
host,
|
|
41
39
|
port,
|
|
42
40
|
path,
|
|
@@ -44,7 +42,7 @@ const requestService = async () => {
|
|
|
44
42
|
'content-type': 'application/json; charset=utf-8'
|
|
45
43
|
}
|
|
46
44
|
});
|
|
47
|
-
const path = `/api
|
|
45
|
+
const path = `/api/targets`;
|
|
48
46
|
console.log(`About to submit ${scheme} request as JSON on port ${port} to ${host}${path}`);
|
|
49
47
|
// Submit a targets request.
|
|
50
48
|
client.request(getRequestOptions(path), response => {
|
|
@@ -75,7 +73,7 @@ const requestService = async () => {
|
|
|
75
73
|
const [timeStamp, jobID] = reportIDs[Math.floor(Math.random() * reportIDs.length)]
|
|
76
74
|
.split('-');
|
|
77
75
|
console.log('======================');
|
|
78
|
-
const path = `/api
|
|
76
|
+
const path = `/api/reportIssues/${timeStamp}/${jobID}`;
|
|
79
77
|
console.log(`About to submit ${scheme} request as JSON on port ${port} to ${host}${path}`);
|
|
80
78
|
const requestOptions = getRequestOptions(path);
|
|
81
79
|
// Submit an issues request for it.
|
|
@@ -109,9 +107,7 @@ const requestService = async () => {
|
|
|
109
107
|
});
|
|
110
108
|
})
|
|
111
109
|
// Finish sending the issues request.
|
|
112
|
-
.end(
|
|
113
|
-
agentPW
|
|
114
|
-
}));
|
|
110
|
+
.end();
|
|
115
111
|
}
|
|
116
112
|
catch (error) {
|
|
117
113
|
console.log(error.message);
|
|
@@ -120,9 +116,7 @@ const requestService = async () => {
|
|
|
120
116
|
});
|
|
121
117
|
})
|
|
122
118
|
// Finish sending the targets request.
|
|
123
|
-
.end(
|
|
124
|
-
agentPW
|
|
125
|
-
}));
|
|
119
|
+
.end();
|
|
126
120
|
};
|
|
127
121
|
|
|
128
122
|
// EXECUTION
|
package/robots.txt
ADDED
package/sitemap.xml
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- sitemap.xml -->
|
|
2
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
|
+
<url><loc>https://kilotest.com/</loc></url>
|
|
4
|
+
<url><loc>https://kilotest.com/targets.html</loc></url>
|
|
5
|
+
<url><loc>https://kilotest.com/issues.html</loc></url>
|
|
6
|
+
<url><loc>https://kilotest.com/tutorial.html</loc></url>
|
|
7
|
+
<url><loc>https://kilotest.com/openapi.yaml</loc></url>
|
|
8
|
+
<url><loc>https://kilotest.com/llms.txt</loc></url>
|
|
9
|
+
</urlset>
|
package/targets/api.js
CHANGED
|
@@ -20,8 +20,7 @@ const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
|
20
20
|
// FUNCTIONS
|
|
21
21
|
|
|
22
22
|
// Returns a response to a targets request.
|
|
23
|
-
exports.response = async
|
|
24
|
-
const [agentID] = args;
|
|
23
|
+
exports.response = async () => {
|
|
25
24
|
const availableReports = [];
|
|
26
25
|
// Get the non-hidden logs.
|
|
27
26
|
const targetLogs = await getLogs();
|
|
@@ -53,8 +52,6 @@ exports.response = async args => {
|
|
|
53
52
|
URL: url
|
|
54
53
|
},
|
|
55
54
|
'whether a later report about the same page exists': !! superseded,
|
|
56
|
-
'number of issues reported': issueCount,
|
|
57
|
-
'number of HTML elements reported as exhibiting issues': violatorCount,
|
|
58
55
|
'tools that tried to test the page': {
|
|
59
56
|
number: toolCount,
|
|
60
57
|
names: toolNames
|
|
@@ -67,28 +64,26 @@ exports.response = async args => {
|
|
|
67
64
|
number: reporterCount,
|
|
68
65
|
names: reporterNames
|
|
69
66
|
},
|
|
70
|
-
'
|
|
71
|
-
|
|
67
|
+
'number of issues reported': issueCount,
|
|
68
|
+
'number of HTML elements reported as exhibiting issues': violatorCount,
|
|
69
|
+
'URLs for getting data about the reported issues': {
|
|
70
|
+
'for you': `${thisHost}/api/reportIssues/${timeStamp}/${jobID}`,
|
|
72
71
|
'for humans': `${thisHost}/reportIssues/${timeStamp}/${jobID}`
|
|
73
72
|
},
|
|
74
73
|
'URL for getting the full technical report as JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
|
|
75
74
|
});
|
|
76
75
|
}
|
|
77
76
|
// Get a response.
|
|
78
|
-
const
|
|
79
|
-
summary: `This document fulfills a request made by an agent to the Kilotest service. The agent requested data about the web pages that Kilotest had tested for accessibility, usability, and standard-conformity and
|
|
77
|
+
const content = {
|
|
78
|
+
summary: `This document fulfills a request made by an agent to the Kilotest service. The agent requested data about the web pages that Kilotest had tested for accessibility, usability, and standard-conformity and, for each page, statistics about the results of the tests. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten testing tools, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes API endpoints for agents and web UI URLs for humans to recommend web pages for testing and to obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of tools, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), whose home page contains an introduction and a link to a tutorial.`,
|
|
80
79
|
'tool name': 'Kilotest',
|
|
81
80
|
request: {
|
|
82
|
-
'requesting agent': {
|
|
83
|
-
identifier: agentID,
|
|
84
|
-
name: researchAgents[agentID]
|
|
85
|
-
},
|
|
86
81
|
'type of request': {
|
|
87
82
|
identifier: 'targets',
|
|
88
83
|
description: 'Give me summary data about each available report.'
|
|
89
84
|
},
|
|
90
85
|
URLs: {
|
|
91
|
-
'URL of your request': `${thisHost}/api
|
|
86
|
+
'URL of your request': `${thisHost}/api/targets`,
|
|
92
87
|
'equivalent URL for humans': `${thisHost}/targets.html`
|
|
93
88
|
}
|
|
94
89
|
},
|
|
@@ -98,5 +93,5 @@ exports.response = async args => {
|
|
|
98
93
|
},
|
|
99
94
|
'available reports': availableReports
|
|
100
95
|
};
|
|
101
|
-
return
|
|
96
|
+
return content;
|
|
102
97
|
};
|