@jrpool/kilotest 27.0.0 → 28.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.html CHANGED
@@ -8,6 +8,51 @@
8
8
  <meta name="keywords" content="report,accessibility,a11y">
9
9
  <title>Kilotest</title>
10
10
  <link rel="stylesheet" href="/style.css">
11
+ <script type="application/ld+json">
12
+ {
13
+ "@context": "https://schema.org",
14
+ "@type": "WebApplication",
15
+ "name": "Kilotest",
16
+ "sameAs": "https://github.com/jrpool/kilotest",
17
+ "datePublished": "2025-10-14",
18
+ "applicationCategory": "Web accessibility, usability, and standard conformity testing and reporting service",
19
+ "isAccessibleForFree": true,
20
+ "description": "Testing of web pages for accessibility, usability, and standard conformity with an ensemble of 10 tools and provision of results at selectable levels of detail.",
21
+ "softwareHelp": {
22
+ "@type": "CreativeWork",
23
+ "url": "https://kilotest.com/tutorial.html"
24
+ },
25
+ "featureList": [
26
+ "Automated testing of web accessibility, usability, and standard conformity",
27
+ "WCAG 2.2 conformance evaluation",
28
+ "HTML5 conformance evaluation",
29
+ "CSS3 conformance evaluation",
30
+ "Ensemble of 10 testing tools performing more than a thousand tests",
31
+ "Multi-page, single-page, single-issue, and single-element reporting",
32
+ "Priority-ranked issue lists",
33
+ "Public API for agent integration"
34
+ ],
35
+ "url": "https://kilotest.com/",
36
+ "maintainer": {
37
+ "@type": "Person",
38
+ "name": "Jonathan Robert Pool",
39
+ "url": "https://github.com/jrpool"
40
+ },
41
+ "contributor": [
42
+ {
43
+ "@type": "Person",
44
+ "name": "Jonathan Robert Pool",
45
+ "url": "https://github.com/jrpool"
46
+ }
47
+ ],
48
+ "provider": {
49
+ "@type": "Project",
50
+ "name": "Kilotest",
51
+ "sameAs": "https://github.com/jrpool/kilotest"
52
+ },
53
+ "license": "https://github.com/jrpool/kilotest/blob/main/LICENSE"
54
+ }
55
+ </script>
11
56
  </head>
12
57
  <body>
13
58
  <main>
@@ -25,7 +70,7 @@
25
70
  <p>The Kilotest ensemble is <a href="https://github.com/jrpool/testaro?tab=readme-ov-file#dependencies">10 tools</a> developed by teams in the USA, Canada, Denmark, Australia, and Portugal.</p>
26
71
  <p>The tools perform about 1,300 tests in total. <a href="https://medium.com/cvs-health-tech-blog/how-to-run-a-thousand-accessibility-tests-63692ad120c3">Open-source software</a> developed initially at CVS Health drives the tools, tries to weed out invalid results, and distills the findings into about 350 <q>issues</q>.</p>
27
72
  <h2>Status</h2>
28
- <p>Kilotest is a proof of concept, launched in November 2025 and being actively developed. Your suggestions, bug reports, and collaboration proposals are welcome! Post them on <a href="https://github.com/jrpool/kilotest/issues">GitHub</a> or email them to <a href="mailto:info@kilotest.com">info@kilotest.com</a>.</p>
73
+ <p>Kilotest is a proof of concept, launched in October 2025 and being actively developed. Your suggestions, bug reports, and collaboration proposals are welcome! Post them on <a href="https://github.com/jrpool/kilotest/issues">GitHub</a> or email them to <a href="mailto:info@kilotest.com">info@kilotest.com</a>.</p>
29
74
  <p>In the current version, users can get results of already performed tests and can recommend new tests. Kilotest managers can approve recommendations.</p>
30
75
  </details>
31
76
  <h2>What do you want to know?</h2>
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 pageArgs = pathname.split('/').slice(2).join('/');
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
- response.setHeader('content-type', 'text/html; charset=utf-8');
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
- response.setHeader('content-type', 'application/yaml; charset=utf-8');
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] = pageArgs.split('/');
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
- response.setHeader('content-type', 'application/json; charset=utf-8');
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
- // Serve response headers, including one allowing requests from other applications.
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](pageArgs, search);
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 = {'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml'};
289
- response.setHeader('content-type', mimeTypes[ext] || 'application/octet-stream');
290
- response.setHeader('cache-control', 'public, max-age=3600');
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
- response.setHeader('content-type', 'image/x-icon');
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
- response.writeHead(200, {
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] = pageArgs.split('/');
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, including one allowing requests from other applications.
340
- response.setHeader('content-type', 'text/html; charset=utf-8');
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(pageArgs, why);
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
- response.setHeader('content-type', 'text/html; charset=utf-8');
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
- // Serve a content-type header for a response.
398
- response.setHeader('content-type', 'text/html; charset=utf-8');
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
- // Serve a location header for a response.
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
- // Serve a location header for a response.
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
- // Serve headers for a response.
443
- response.setHeader('content-type', 'text/html; charset=utf-8');
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
- // Serve headers for a response.
463
- response.setHeader('content-type', 'text/html; charset=utf-8');
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 agent ID, the service, and any service specifications from the path.
482
- const [agentID, service, ... specs] = pageArgs.split('/');
483
- // If the agent is the authorized Testaro instance and it is authenticated:
484
- if (agentID === testaroAgent && postData.agentPW === testaroAgentPW) {
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
- response.setHeader('content-type', 'application/json; charset=utf-8');
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/{agentId}
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/TargetsResponse'
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
- Get element-level detail for a specific issue in a specific report
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: Element-level detail for the specified issue
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
- TargetsResponse:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "27.0.0",
3
+ "version": "28.0.1",
4
4
  "description": "An ensemble testing service with a focus on accessibility",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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, agentID, timeStamp, jobID, issue) => {
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/${agentID}/reportIssue/${issueID}/${timeStamp}/${jobID}`,
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 [agentID, timeStamp, jobID] = args;
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/${agentID}/reportIssues/${timeStamp}/${jobID}`,
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/${agentID}/targets.html`,
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, agentID, timeStamp, jobID, issue)),
116
+ .map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
122
117
  'high priority': issues[3]
123
- .map(issue => getIssueFacts(thisHost, agentID, timeStamp, jobID, issue)),
118
+ .map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
124
119
  'low priority': issues[2]
125
- .map(issue => getIssueFacts(thisHost, agentID, timeStamp, jobID, issue)),
120
+ .map(issue => getIssueFacts(thisHost, timeStamp, jobID, issue)),
126
121
  'lowest priority': issues[1]
127
- .map(issue => getIssueFacts(thisHost, agentID, timeStamp, jobID, issue))
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: 'POST',
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/${agent}/targets`;
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/${agent}/reportIssues/${timeStamp}/${jobID}`;
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(JSON.stringify({
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(JSON.stringify({
124
- agentPW
125
- }));
119
+ .end();
126
120
  };
127
121
 
128
122
  // EXECUTION
package/robots.txt ADDED
@@ -0,0 +1,4 @@
1
+ # robots.txt
2
+ User-agent: *
3
+ Allow: /
4
+ Sitemap: https://kilotest.com/sitemap.xml
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 args => {
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
- 'URLs for getting data on the reported issues': {
71
- 'for you': `${thisHost}/api/${agentID}/reportIssues/${timeStamp}/${jobID}`,
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 response = {
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 statistics for each page on the results of the tests. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten testing tools, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes API endpoints for agents and web UI URLs for humans to recommend web pages for testing and obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of tools, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), whose home page contains an introduction and a link to a tutorial.`,
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/${agentID}/targets`,
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 response;
96
+ return content;
102
97
  };