@jrpool/kilotest 24.0.4

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.
Files changed (69) hide show
  1. package/.claude/settings.local.json +9 -0
  2. package/DEVELOPMENT.md +5860 -0
  3. package/LICENSE +21 -0
  4. package/README.md +44 -0
  5. package/SERVICE.md +268 -0
  6. package/aceconfig.js +28 -0
  7. package/ai0BalanceForm/index.html +30 -0
  8. package/ai0BalanceForm/index.js +79 -0
  9. package/alerts.js +73 -0
  10. package/diagnoses/index.html +46 -0
  11. package/diagnoses/index.js +140 -0
  12. package/env.example +21 -0
  13. package/env.testaro +17 -0
  14. package/error.html +18 -0
  15. package/eslint.config.mjs +53 -0
  16. package/favicon.ico +0 -0
  17. package/index.html +39 -0
  18. package/index.js +639 -0
  19. package/issues/index.html +20 -0
  20. package/issues/index.js +173 -0
  21. package/job.json +100 -0
  22. package/manage/index.html +32 -0
  23. package/manage/index.js +22 -0
  24. package/package.json +38 -0
  25. package/pm2.config.js +15 -0
  26. package/reannotate/index.html +19 -0
  27. package/reannotate/index.js +39 -0
  28. package/reannotateForm/index.html +29 -0
  29. package/reannotateForm/index.js +114 -0
  30. package/recActionForm/index.html +33 -0
  31. package/recActionForm/index.js +49 -0
  32. package/reportHideForm/index.html +29 -0
  33. package/reportHideForm/index.js +89 -0
  34. package/reportIssue/index.html +38 -0
  35. package/reportIssue/index.js +181 -0
  36. package/reportIssues/index.html +47 -0
  37. package/reportIssues/index.js +259 -0
  38. package/reportUnhideForm/index.html +29 -0
  39. package/reportUnhideForm/index.js +89 -0
  40. package/reportsExpungeForm/index.html +29 -0
  41. package/reportsExpungeForm/index.js +105 -0
  42. package/reportsPruneForm/index.html +29 -0
  43. package/reportsPruneForm/index.js +105 -0
  44. package/reportsRewindForm/index.html +29 -0
  45. package/reportsRewindForm/index.js +105 -0
  46. package/retestRec/index.html +23 -0
  47. package/retestRec/index.js +19 -0
  48. package/retestRecForm/index.html +27 -0
  49. package/retestRecForm/index.js +36 -0
  50. package/rules/index.html +28 -0
  51. package/rules/index.js +71 -0
  52. package/style.css +196 -0
  53. package/targets/index.html +37 -0
  54. package/targets/index.js +170 -0
  55. package/testOrder/index.html +23 -0
  56. package/testOrder/index.js +62 -0
  57. package/testRec/index.html +23 -0
  58. package/testRec/index.js +25 -0
  59. package/testRecForm/index.html +34 -0
  60. package/testRecForm/index.js +22 -0
  61. package/tutorial/images/newsletter-form.png +0 -0
  62. package/tutorial/index.html +796 -0
  63. package/tutorial/index.js +53 -0
  64. package/util.js +686 -0
  65. package/wcagMap.json +102 -0
  66. package/wcagRenew/index.html +19 -0
  67. package/wcagRenew/index.js +70 -0
  68. package/wcagRenewForm/index.html +25 -0
  69. package/wcagRenewForm/index.js +22 -0
@@ -0,0 +1,140 @@
1
+ /*
2
+ index.js
3
+ Answers the report-issues question.
4
+ */
5
+
6
+ // IMPORTS
7
+
8
+ const {
9
+ getPageDataStrings,
10
+ getReport,
11
+ getTextFragmentHref,
12
+ getWCAGLink,
13
+ getWeightName,
14
+ htmlSafe,
15
+ isHidden,
16
+ tools
17
+ } = require('../util');
18
+ const {issues} = require('testilo/procs/score/tic');
19
+ const fs = require('fs/promises');
20
+ const path = require('path');
21
+
22
+ // FUNCTIONS
23
+
24
+ // Adds parameters to a query for the answer page.
25
+ const populateQuery = async (issueID, timeStamp, jobID, catalogIndex, pathID, query) => {
26
+ const pageDataStrings = await getPageDataStrings(timeStamp, jobID);
27
+ const {what, url, urlLink, testInfo} = pageDataStrings;
28
+ const report = await getReport(timeStamp, jobID);
29
+ const {acts, catalog} = report;
30
+ const catalogItem = catalog[catalogIndex] ?? {};
31
+ const {boxID, startTag, tagName, text} = catalogItem;
32
+ query.catalogIndex = catalogIndex;
33
+ const lines = [];
34
+ const margin = ' '.repeat(6);
35
+ if (catalogIndex && catalogItem.textLinkable) {
36
+ const href = getTextFragmentHref(text, url);
37
+ const label = `Take me to element ${catalogIndex} on the page (in a new tab)`;
38
+ const link = `<a href="${href}" target="_blank" aria-label="${label}">Take me there</a>`;
39
+ query.takeMeThere = `${margin} <p>${link}</p>`;
40
+ }
41
+ else {
42
+ query.takeMeThere = '';
43
+ }
44
+ // Add facts about the issue to the query.
45
+ query.target = what;
46
+ query.urlLink = urlLink;
47
+ query.testInfo = testInfo;
48
+ query.issue = issues[issueID].summary;
49
+ const issue = issues[issueID];
50
+ const {wcag, weight, why} = issue;
51
+ query.why = why;
52
+ query.priority = getWeightName(weight);
53
+ query.wcag = `<a href="${getWCAGLink(wcag)}">${wcag}</a>`;
54
+ query.tagName = tagName || 'HTML';
55
+ if (text && ! ['HTML', 'BODY', 'HEAD', 'SCRIPT', 'STYLE', 'NOSCRIPT'].includes(tagName)) {
56
+ const textString = text.split('\n').join(' … ');
57
+ query.text = `<q>${htmlSafe(textString)}</q>`;
58
+ }
59
+ else {
60
+ query.text = '[not applicable]';
61
+ }
62
+ query.startTag = htmlSafe(startTag) || '[not obtained]';
63
+ query.pathID = pathID || '[not obtained]';
64
+ if (boxID) {
65
+ const dims = boxID.split(':');
66
+ query.box = `x = ${dims[0]}, y = ${dims[1]}, width = ${dims[2]}, height = ${dims[3]}`;
67
+ }
68
+ else {
69
+ query.box = '[not obtained]';
70
+ }
71
+ // Initialize an array of diagnoses.
72
+ let diagnoses = [];
73
+ const testActs = acts.filter(act => act.type === 'test');
74
+ // For each test act:
75
+ testActs.forEach(act => {
76
+ const {result, which} = act;
77
+ const caseInstances = result?.standardResult?.instances?.filter(
78
+ instance => instance.issueID === issueID && instance.catalogIndex === catalogIndex
79
+ ) ?? [];
80
+ // For each standard instance that pertains to this combination of issue and violator:
81
+ caseInstances.forEach(instance => {
82
+ const {ruleID, what} = instance;
83
+ // Add lines for it to the array.
84
+ diagnoses.push({
85
+ toolID: which,
86
+ ruleID,
87
+ what
88
+ });
89
+ });
90
+ });
91
+ // For each diagnosis:
92
+ diagnoses.forEach(diagnosis => {
93
+ const {toolID, ruleID, what} = diagnosis;
94
+ // Add lines.
95
+ lines.push(`${margin}<li>${htmlSafe(what)}`);
96
+ lines.push(`${margin} <p>Tool: ${tools[toolID][0]} (${tools[toolID][1]})</p>`);
97
+ if (ruleID !== what) {
98
+ lines.push(`${margin} <p>Rule: <code>${ruleID}</code></p>`);
99
+ }
100
+ lines.push(`${margin}</li>`);
101
+ });
102
+ // Add the lines to the query.
103
+ query.diagnoses = lines.join('\n');
104
+ };
105
+ // Returns a page answering the diagnoses question.
106
+ exports.answer = async (pageArgs, search) => {
107
+ const [issueID, timeStamp, jobID, catalogIndex] = pageArgs.split('/');
108
+ const reportIsHidden = await isHidden(timeStamp, jobID);
109
+ // If the report is not available:
110
+ if (reportIsHidden) {
111
+ return {
112
+ status: 'error',
113
+ message: 'Report not available'
114
+ };
115
+ }
116
+ const params = new URLSearchParams(search);
117
+ const pathID = params.get('pathID');
118
+ const query = {};
119
+ // Create a query to replace the placeholders.
120
+ await populateQuery(issueID, timeStamp, jobID, catalogIndex, pathID, query);
121
+ // If the test specifications are valid:
122
+ if (query.testInfo) {
123
+ // Get the template.
124
+ let answerPage = await fs.readFile(path.join(__dirname, 'index.html'), 'utf8');
125
+ // Replace its placeholders.
126
+ Object.keys(query).forEach(param => {
127
+ answerPage = answerPage.replace(new RegExp(`__${param}__`, 'g'), query[param]);
128
+ });
129
+ // Return the populated page.
130
+ return {
131
+ status: 'ok',
132
+ answerPage
133
+ };
134
+ }
135
+ // Otherwise, report this.
136
+ return {
137
+ status: 'error',
138
+ error: 'Invalid report specification'
139
+ };
140
+ };
package/env.example ADDED
@@ -0,0 +1,21 @@
1
+ # For a local development/test host, ENVIRONMENT=test.
2
+ ENVIRONMENT=production
3
+ # PROTOCOL must be http if a local dev/test host or reverse-proxied server, or otherwise https.
4
+ PROTOCOL=__placeholder__
5
+ # AUTH_CODE must be 3 characters long.
6
+ AUTH_CODE=__placeholder__
7
+ TESTARO_AGENT=testaro-agent
8
+ # TESTARO_AGENT_PW must be identical to NETWATCH_AUTH in the Testaro .env.
9
+ TESTARO_AGENT_PW=__placeholder__
10
+ # Alert email configuration:
11
+ MANAGER_EMAIL=__placeholder__
12
+ ALERT_API_HOST=api.resend.com
13
+ ALERT_API_PATH=/emails
14
+ ALERT_API_KEY=__placeholder__
15
+ ALERT_FROM=__placeholder__
16
+ # Alert thresholds:
17
+ WAVE_BALANCE_THRESHOLD=30
18
+ AI_SERVICE0_BALANCE_THRESHOLD=10
19
+ # Per-million-token prices (Claude Haiku 4.5) on 2026-05-01 were $1 in, $5 out.
20
+ AI_MODEL0_INPUT_PRICE=0.000001
21
+ AI_MODEL0_OUTPUT_PRICE=0.000005
package/env.testaro ADDED
@@ -0,0 +1,17 @@
1
+ # You can get a WAVE API key at https://wave.webaim.org/api.
2
+ WAVE_KEY=__placeholder__
3
+ AGENT=testaro-agent
4
+ HEADED_BROWSER=false
5
+ DEBUG=false
6
+ WAITS=0
7
+ NODE_OPTIONS='--trace-uncaught --trace-warnings'
8
+ PUPPETEER_DISABLE_HEADLESS_WARNING=true
9
+ # Replace the next 2 placeholders with:
10
+ # http://localhost:3000 if Kilotest and Testaro are on the same host.
11
+ # the URL of the Kilotest server (e.g., https://kilotest.com) if they are on different hosts.
12
+ NETWATCH_JOB=__placeholder__/api/testaro-agent/job
13
+ NETWATCH_REPORT=__placeholder__/api/testaro-agent/report
14
+ # NETWATCH_AUTH must be identical to TESTARO_AGENT_PW in the Kilotest .env.
15
+ NETWATCH_AUTH=__placeholder__
16
+ TIMEOUT_MULTIPLIER=1
17
+ TMPDIRNAME=scratch
package/error.html ADDED
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="publisher" content="Jonathan Robert Pool">
7
+ <meta name="creator" content="Jonathan Robert Pool">
8
+ <meta name="keywords" content="report,accessibility,a11y">
9
+ <title>Error | Kilotest</title>
10
+ <link rel="stylesheet" href="/style.css">
11
+ </head>
12
+ <body>
13
+ <main>
14
+ <h1><a href="/">Kilotest</a>: error</h1>
15
+ <p class="error">__error__</p>
16
+ </main>
17
+ </body>
18
+ </html>
@@ -0,0 +1,53 @@
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import json from "@eslint/json";
4
+ import markdown from "@eslint/markdown";
5
+ import css from "@eslint/css";
6
+ import { defineConfig } from "eslint/config";
7
+
8
+ export default defineConfig([
9
+ {
10
+ ignores: [
11
+ "DEVELOPMENT.md",
12
+ "package-lock.json"
13
+ ]
14
+ },
15
+ {
16
+ files: ["**/*.{js,mjs,cjs}"],
17
+ plugins: { js },
18
+ extends: ["js/recommended"],
19
+ languageOptions: {
20
+ globals: globals.node
21
+ }
22
+ },
23
+ {
24
+ files: ["**/*.js"],
25
+ languageOptions: {
26
+ sourceType: "commonjs"
27
+ }
28
+ },
29
+ {
30
+ files: ["**/*.json"],
31
+ plugins: {
32
+ json
33
+ },
34
+ language: "json/json",
35
+ extends: ["json/recommended"]
36
+ },
37
+ {
38
+ files: ["**/*.md"],
39
+ plugins: {
40
+ markdown
41
+ },
42
+ language: "markdown/gfm",
43
+ extends: ["markdown/recommended"]
44
+ },
45
+ {
46
+ files: ["**/*.css"],
47
+ plugins: {
48
+ css
49
+ },
50
+ language: "css/css",
51
+ extends: ["css/recommended"]
52
+ }
53
+ ]);
package/favicon.ico ADDED
Binary file
package/index.html ADDED
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="publisher" content="Jonathan Robert Pool">
7
+ <meta name="creator" content="Jonathan Robert Pool">
8
+ <meta name="keywords" content="report,accessibility,a11y">
9
+ <title>Kilotest</title>
10
+ <link rel="stylesheet" href="/style.css">
11
+ </head>
12
+ <body>
13
+ <main>
14
+ <h1>Kilotest</h1>
15
+ <p>Kilotest performs more than a thousand tests on a web page.</p>
16
+ <p><a href="/tutorial.html">New to accessibility testing? Start with the tutorial.</a></p>
17
+ <details>
18
+ <summary>More than a thousand? Really?</summary>
19
+ <h2>Ensemble testing</h2>
20
+ <p>Kilotest does <strong>ensemble testing</strong> of web pages for <a href="https://www.w3.org/mission/accessibility/">accessibility</a>, usability, and conformity to HTML standards.</p>
21
+ <p>Efficient testing <strong>first</strong> leverages <strong>automated tools</strong> and corrects the defects that they discover, <strong>then</strong> employs human testers.</p>
22
+ <p>Choosing an automated tool is hard, because they <a href="https://www.w3.org/WAI/test-evaluate/tools/list/">are numerous</a> and <a href="https://arxiv.org/pdf/2304.07591">vary in what and how they test</a>.</p>
23
+ <p><strong>Ensemble testing</strong>, instead of choosing a tool, combines multiple tools. It can discover more issues than single-tool testing, and the tools can check one another&rsquo;s quality.</p>
24
+ <h2>The tools</h2>
25
+ <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
+ <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
+ <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>
29
+ <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
+ </details>
31
+ <h2>What do you want to know?</h2>
32
+ <ul class="nav">
33
+ <li><a href="targets.html">Which web pages have been tested?</a></li>
34
+ <li><a href="issues.html">Which issues have been reported?</a></li>
35
+ <li><a href="manage.html">What can Kilotest managers do?</a></li>
36
+ </ul>
37
+ </main>
38
+ </body>
39
+ </html>