@jrpool/kilotest 57.2.0 → 57.3.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.
@@ -14,6 +14,7 @@ import {parse} from 'node-html-parser';
14
14
  // Mock the util functions called by index.ts before requiring it, so that
15
15
  // it imports the mocked versions. Other exports delegate to the real module.
16
16
  import * as realUtil from '../../util.ts';
17
+ import * as realWebUtil from '../util.ts';
17
18
  let getReportCallCount = 0;
18
19
  let failGetReportOnCall = -1;
19
20
  let pageDataStringsOverride: any = null;
@@ -26,12 +27,17 @@ mock.module('../../util.ts', {
26
27
  return {error: 'Report is invalid (test override)'};
27
28
  }
28
29
  return (realUtil.getReport as any)(...args);
29
- },
30
+ }
31
+ }
32
+ } as any);
33
+ mock.module('../util.ts', {
34
+ exports: {
35
+ ...realWebUtil,
30
36
  getPageDataStrings: async (...args: any[]) => {
31
37
  if (pageDataStringsOverride !== null) {
32
38
  return pageDataStringsOverride;
33
39
  }
34
- return (realUtil.getPageDataStrings as any)(...args);
40
+ return (realWebUtil.getPageDataStrings as any)(...args);
35
41
  }
36
42
  }
37
43
  } as any);
@@ -6,19 +6,21 @@
6
6
  // IMPORTS
7
7
 
8
8
  import {
9
- getPageData,
10
- getPageDataStrings,
11
9
  getReport,
12
- getEngineNamesString,
13
10
  getTestActInstances,
14
- getWCAGLink,
15
- getWeightName,
16
11
  htmlSafe,
17
12
  isUsableReport,
18
13
  objectSort,
19
14
  populateTemplate,
20
15
  ruleEngines
21
16
  } from '../../util.ts';
17
+ import {
18
+ getPageData,
19
+ getPageDataStrings,
20
+ getEngineNamesString,
21
+ getWCAGLink,
22
+ getWeightName
23
+ } from '../util.ts';
22
24
  import {issues as issueSpecs} from 'testaro-issues';
23
25
 
24
26
  // FUNCTIONS
@@ -13,15 +13,16 @@ import {parse} from 'node-html-parser';
13
13
  // Mock the util functions called by index.ts before requiring it, so that
14
14
  // it imports the mocked versions. Other exports delegate to the real module.
15
15
  import * as realUtil from '../../util.ts';
16
+ import * as realWebUtil from '../util.ts';
16
17
  let pageDataStringsOverride: any = null;
17
- mock.module('../../util.ts', {
18
+ mock.module('../util.ts', {
18
19
  exports: {
19
- ...realUtil,
20
+ ...realWebUtil,
20
21
  getPageDataStrings: async (...args: any[]) => {
21
22
  if (pageDataStringsOverride !== null) {
22
23
  return pageDataStringsOverride;
23
24
  }
24
- return (realUtil.getPageDataStrings as any)(...args);
25
+ return (realWebUtil.getPageDataStrings as any)(...args);
25
26
  }
26
27
  }
27
28
  } as any);
@@ -7,18 +7,20 @@
7
7
 
8
8
  import {
9
9
  getAgoDays,
10
- getCountString,
11
10
  getJobNames,
12
- getMultiReportWhats,
13
11
  getObject,
14
- getPageDataStrings,
15
12
  getTestRequests,
16
- getReportData,
17
13
  getReportExtracts,
18
14
  jobsPath,
19
15
  objectSort,
20
16
  populateTemplate
21
17
  } from '../../util.ts';
18
+ import {
19
+ getCountString,
20
+ getMultiReportWhats,
21
+ getPageDataStrings,
22
+ getReportData
23
+ } from '../util.ts';
22
24
  import path from 'node:path';
23
25
 
24
26
  // FUNCTIONS
@@ -117,6 +119,7 @@ const populateQuery = async (query: Record<string, any>) => {
117
119
  preventedEngineNames,
118
120
  reporterNames,
119
121
  reporterCount,
122
+ violationCount,
120
123
  violatorCount
121
124
  } = reportData;
122
125
  // Otherwise, i.e. if it succeeded, add lines about the report.
@@ -145,20 +148,22 @@ const populateQuery = async (query: Record<string, any>) => {
145
148
  const reporterNamesString = reporterNames.join(' + ');
146
149
  reporterString = `${reporterString} (${reporterNamesString})`;
147
150
  }
148
- const issueCountString = getCountString(issueCount, 'issue was', 'issues were');
149
- const violatorString = getCountString(violatorCount, 'violator was', 'violators were');
150
151
  lines.tested.push(`${margin} <li>${reporterString}</li>`);
151
- lines.tested.push(`${margin} <li>${issueCountString} reported</li>`);
152
- lines.tested.push(`${margin} <li>${violatorString} reported</li>`);
152
+ lines.tested.push(`${margin} <li>Summary of results:`);
153
+ lines.tested.push(`${margin} <ul>`);
154
+ lines.tested.push(`${margin} <li>Violations: ${violationCount}</li>`);
155
+ lines.tested.push(`${margin} <li>Violators: ${violatorCount}</li>`);
156
+ lines.tested.push(`${margin} <li>Issues: ${issueCount}</li>`);
157
+ lines.tested.push(`${margin} </ul>`);
158
+ lines.tested.push(`${margin} </li>`);
153
159
  lines.tested.push(`${margin} </ul>`);
154
160
  lines.tested.push(`${margin}<ul class="nav">`);
155
161
  // If any issues were reported:
156
162
  if (issueCount) {
157
- // Add a question link about the reported issues to the lines.
163
+ // Add a link to issue details to the lines.
158
164
  const href = `href="listIssues.html/${timeStamp}/${jobID}"`;
159
- const label = `aria-label="What ${issueCountString} reported for the ${description} page?"`;
160
- const questionString = issueCount === 1 ? 'was the issue' : 'were the issues';
161
- const link = `<a ${href} ${label}>What ${questionString}?</a>`;
165
+ const label = `aria-label="issue details for the ${description} page"`;
166
+ const link = `<a ${href} ${label}>Issue details</a>`;
162
167
  lines.tested.push(`${margin} <li>${link}</li>`);
163
168
  }
164
169
  let retestString: string = '';
@@ -216,3 +216,76 @@ test('listReports shows no-reports message when the database is empty', async ()
216
216
  await fs.rm(tmpDir, {recursive: true}).catch(() => {});
217
217
  }
218
218
  });
219
+
220
+ // Returns the details element of the report whose summary starts with a description and whose issue link, if required, contains a fragment.
221
+ const getReportDetails = (html: any, description: string, hrefFragment?: string) => {
222
+ const matches = html.querySelectorAll('details').filter((d: any) => {
223
+ const summary = d.querySelector('summary')?.text ?? '';
224
+ if (!summary.startsWith(description)) {
225
+ return false;
226
+ }
227
+ return hrefFragment === undefined
228
+ || d.querySelectorAll('a').some((a: any) => a.getAttribute('href')?.includes(hrefFragment));
229
+ });
230
+ assert.equal(matches.length, 1, `exactly one report matches ${description} ${hrefFragment ?? ''}`);
231
+ return matches[0];
232
+ };
233
+
234
+ // Returns the rule-engine line, the prevention line, and the 3 summary amounts of a details element.
235
+ const getResultFacts = (details: any) => {
236
+ const items = details.querySelectorAll('li');
237
+ const itemTexts: string[] = items.map((li: any) => li.text.trim());
238
+ const getAmount = (label: string) => {
239
+ const text = itemTexts.find(t => t.startsWith(`${label}: `));
240
+ assert.ok(text, `${label} item exists`);
241
+ return Number(text.slice(label.length + 2));
242
+ };
243
+ // The summary item is the parent of the 3 amount items.
244
+ const summaryItem = items.find((li: any) => li.text.trim().startsWith('Summary of results:'));
245
+ assert.ok(summaryItem, 'Summary of results item exists');
246
+ const amountLabels = summaryItem.querySelectorAll('ul > li').map((li: any) => li.text.trim().split(':')[0]);
247
+ return {
248
+ reporterLine: itemTexts.find(t => t.endsWith('reported issues') || t.includes('reported issues (')),
249
+ preventionLine: itemTexts.find(t => t.startsWith('Page not testable by')),
250
+ amountLabels,
251
+ violations: getAmount('Violations'),
252
+ violators: getAmount('Violators'),
253
+ issues: getAmount('Issues')
254
+ };
255
+ };
256
+
257
+ // Expected facts, hand-computed from the fixtures.
258
+ const expectedFacts: [string, string | undefined, string, number, number, number][] = [
259
+ // Description, issue-link fragment, reporter line, violations, violators, issues.
260
+ ['Mixed Outcomes Page', '260101T0000/mix', '2 rule engines reported issues (Alfa + Axe)', 3, 2, 2],
261
+ ['Mixed Outcomes Page', '260202T0000/new', '1 rule engine reported issues (Axe)', 1, 1, 1],
262
+ ['All CantTell Page', undefined, '0 rule engines reported issues', 0, 0, 0],
263
+ ['No Outcomes Page', undefined, '1 rule engine reported issues (Accessibility Checker)', 1, 1, 1],
264
+ ['Empty Results Page', undefined, '0 rule engines reported issues', 0, 0, 0],
265
+ ['Prevented Page', undefined, '1 rule engine reported issues (Axe)', 1, 1, 1],
266
+ ['Multi Violator Page', undefined, '2 rule engines reported issues (Alfa + Axe)', 4, 3, 1],
267
+ ['Branch Coverage Page', undefined, '1 rule engine reported issues (Axe)', 4, 4, 1]
268
+ ];
269
+
270
+ for (const [description, fragment, reporterLine, violations, violators, issues] of expectedFacts) {
271
+ test(`listReports gives correct rule-engine count and names and summary amounts for ${description}${fragment ? ` (${fragment})` : ''}`, async () => {
272
+ const result: any = await answer();
273
+ const html = parse(result.answerPage);
274
+ const facts = getResultFacts(getReportDetails(html, description, fragment));
275
+ assert.equal(facts.reporterLine, reporterLine);
276
+ assert.deepEqual(facts.amountLabels, ['Violations', 'Violators', 'Issues']);
277
+ assert.equal(facts.violations, violations);
278
+ assert.equal(facts.violators, violators);
279
+ assert.equal(facts.issues, issues);
280
+ });
281
+ }
282
+
283
+ test('listReports reports the prevented rule engine only for the report with a prevention', async () => {
284
+ const result: any = await answer();
285
+ const html = parse(result.answerPage);
286
+ const prevented = getResultFacts(getReportDetails(html, 'Prevented Page'));
287
+ assert.equal(prevented.preventionLine, 'Page not testable by 1 rule engine (Alfa)');
288
+ const others = html.querySelectorAll('details').filter((d: any) => !d.text.includes('Prevented Page'));
289
+ assert.equal(others.length, 7);
290
+ assert.ok(others.every((d: any) => !d.text.includes('Page not testable by')));
291
+ });
@@ -6,11 +6,13 @@
6
6
  // IMPORTS
7
7
 
8
8
  import {
9
- getWeightName,
10
9
  htmlSafe,
11
10
  populateTemplate,
12
11
  ruleEngines
13
12
  } from '../../util.ts';
13
+ import {
14
+ getWeightName
15
+ } from '../util.ts';
14
16
  import {issues as issueSpecs, issueRules, rules as ruleSpecs} from 'testaro-issues';
15
17
 
16
18
  // FUNCTIONS
@@ -7,16 +7,18 @@
7
7
 
8
8
  import {sendAlert} from '../../alerts.ts';
9
9
  import {
10
- getEngineNamesString,
11
10
  getReportExtracts,
12
11
  getReport,
13
12
  getTestActInstances,
14
- getWCAGLink,
15
- getWeightName,
16
13
  isReportError,
17
14
  objectSort,
18
- populateTemplate,
15
+ populateTemplate
19
16
  } from '../../util.ts';
17
+ import {
18
+ getEngineNamesString,
19
+ getWCAGLink,
20
+ getWeightName
21
+ } from '../util.ts';
20
22
  import {issues as issueSpecs} from 'testaro-issues';
21
23
 
22
24
  // FUNCTIONS
@@ -5,20 +5,22 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
+ import {
9
+ getReport,
10
+ getTestActInstances,
11
+ htmlSafe,
12
+ isReportError,
13
+ populateTemplate
14
+ } from '../../util.ts';
8
15
  import {
9
16
  getPageDataStrings,
10
17
  getPathID,
11
- getReport,
12
18
  getEngineNamesString,
13
- getTestActInstances,
14
19
  getTextFragmentHref,
15
20
  getWCAGLink,
16
21
  getWeightName,
17
- htmlSafe,
18
- isReportError,
19
- makeBreakable,
20
- populateTemplate,
21
- } from '../../util.ts';
22
+ makeBreakable
23
+ } from '../util.ts';
22
24
  import {issues as issueSpecs} from 'testaro-issues';
23
25
 
24
26
  // FUNCTIONS
@@ -14,6 +14,7 @@ import {parse} from 'node-html-parser';
14
14
  // Mock the util functions called by index.ts before requiring it, so that
15
15
  // it imports the mocked versions. Other exports delegate to the real module.
16
16
  import * as realUtil from '../../util.ts';
17
+ import * as realWebUtil from '../util.ts';
17
18
  let getReportCallCount = 0;
18
19
  let failGetReportOnCall = -1;
19
20
  let pageDataStringsOverride: any = null;
@@ -26,12 +27,17 @@ mock.module('../../util.ts', {
26
27
  return {error: 'Report is invalid (test override)'};
27
28
  }
28
29
  return (realUtil.getReport as any)(...args);
29
- },
30
+ }
31
+ }
32
+ } as any);
33
+ mock.module('../util.ts', {
34
+ exports: {
35
+ ...realWebUtil,
30
36
  getPageDataStrings: async (...args: any[]) => {
31
37
  if (pageDataStringsOverride !== null) {
32
38
  return pageDataStringsOverride;
33
39
  }
34
- return (realUtil.getPageDataStrings as any)(...args);
40
+ return (realWebUtil.getPageDataStrings as any)(...args);
35
41
  }
36
42
  }
37
43
  } as any);
@@ -7,9 +7,16 @@
7
7
  // IMPORTS
8
8
 
9
9
  import {
10
- clearMetrics, getDateTimeString, getExclusionCookieValue, getMetrics, isValidAuthCode,
11
- metricsExclusionCookieName, populateTemplate
10
+ clearMetrics,
11
+ getExclusionCookieValue,
12
+ getMetrics,
13
+ isValidAuthCode,
14
+ metricsExclusionCookieName,
15
+ populateTemplate
12
16
  } from '../../util.ts';
17
+ import {
18
+ getDateTimeString
19
+ } from '../util.ts';
13
20
 
14
21
  // FUNCTIONS
15
22
 
@@ -6,9 +6,16 @@
6
6
  // IMPORTS
7
7
 
8
8
  import {
9
- errorMessage, getReportData, isValidAuthCode, objectSort, populateTemplate, readdirOrCreate,
9
+ errorMessage,
10
+ isValidAuthCode,
11
+ objectSort,
12
+ populateTemplate,
13
+ readdirOrCreate,
10
14
  reportsPath
11
15
  } from '../util.ts';
16
+ import {
17
+ getReportData
18
+ } from './util.ts';
12
19
  import fs from 'node:fs/promises';
13
20
  import path from 'node:path';
14
21
 
@@ -5,7 +5,8 @@
5
5
 
6
6
  // IMPORTS
7
7
 
8
- import {getAgoString, getDateTimeString, getReportExtracts, populateTemplate} from '../../util.ts';
8
+ import {getReportExtracts, populateTemplate} from '../../util.ts';
9
+ import {getAgoString, getDateTimeString} from '../util.ts';
9
10
 
10
11
  // FUNCTIONS
11
12
 
@@ -17,47 +17,34 @@
17
17
  <h2>Purpose</h2>
18
18
  <p>This tutorial shows you how to make Kilotest available to AI assistants. Suppose:</p>
19
19
  <ul>
20
- <li>You use an AI platform, like Claude, ChatGPT, Perplexity, or Mistral Vibe.</li>
20
+ <li>You use an AI platform, like Claude, Mistral Vibe, or Perplexity Pro.</li>
21
21
  <li>You want to audit the quality of a web page.</li>
22
22
  </ul>
23
23
  <p>Then the models on your AI platform can use Kilotest <strong>if</strong> you make Kilotest available on the platform.</p>
24
24
  <h2>What AI platform do you use?</h2>
25
25
  <ul>
26
26
  <li><a href="#claude">Claude.ai in a browser or Claude Desktop</a></li>
27
- <li><a href="#chatgpt">ChatGPT</a></li>
28
- <li><a href="#perplexity">Perplexity</a></li>
29
27
  <li><a href="#vibe">Mistral Vibe</a></li>
28
+ <li><a href="#perplexity">Perplexity Pro in a browser</a></li>
30
29
  </ul>
31
- <h2 id="claude">Claude.ai <a href="https://claude.ai">in a browser</a> or <a href="https://downloads.claude.com">Claude Desktop</a></h2>
32
- <p>To make Kilotest available on either Claude platform:</p>
30
+ <h2 id="claude"><a href="https://claude.ai">Claude.ai in a browser</a> or <a href="https://downloads.claude.com">Claude Desktop</a></h2>
33
31
  <ol>
34
32
  <li><a href="https://claude.ai/new">Start a new task</a>.</li>
35
33
  <li>Expand the left sidebar to see the labels.</li>
36
- <li>Click <q>Customize</q>.</li>
37
- <li>Click <q>Connectors</q>.</li>
38
- <li>In the <q>Add</q> list, click <q>Add custom connector</q>.</li>
34
+ <li>Click the <q>Customize</q> button.</li>
35
+ <li>Click the <q>Connectors</q> button.</li>
36
+ <li>In the <q>Add</q> list, click the <q>Add custom connector</q> button.</li>
39
37
  <li>Enter <q>Kilotest</q> into the <q>Name</q> input.</li>
40
38
  <li>Enter <q>https://kilotest.com/mcp</q> into the <q>Remote MCP server URL</q> input.</li>
41
39
  <li>Leave the OAuth inputs empty.</li>
42
- <li>Click <q>Add</q>.</li>
40
+ <li>Click the <q>Add</q> button.</li>
43
41
  <li>For maximum autonomy, click <q>Always allow</q> for each tool.</li>
44
42
  <li>Click the <code>x</code> button or press the <code>Escape</code> key to close the dialog.</li>
45
43
  </ol>
46
- <h2 id="chatgpt">ChatGPT</h2>
47
- <p>To make Kilotest available on ChatGPT:</p>
48
- <ol>
49
- <li>Forthcoming</li>
50
- </ol>
51
- <h2 id="perplexity">Perplexity</h2>
52
- <p>To make Kilotest available on Perplexity:</p>
53
- <ol>
54
- <li>Forthcoming</li>
55
- </ol>
56
- <h2 id="Vibe">Mistral Vibe</h2>
57
- <p>To make Kilotest available on Mistral Vibe:</p>
44
+ <h2 id="vibe">Mistral Vibe</h2>
58
45
  <ol>
59
46
  <li>Choose the <a href="https://chat.mistral.ai/work">Work tab</a>.</li>
60
- <li>Click <q>Connect your apps for better answers</q></li>
47
+ <li>Click <q>Connect your apps for better answers</q>.</li>
61
48
  <li>Click the <q>+ Add connector</q> button.</li>
62
49
  <li>Click the <q>Add a custom connector</q> button.</li>
63
50
  <li>Enter <q>Kilotest</q> into the <q>Connector name</q> input.</li>
@@ -65,6 +52,22 @@
65
52
  <li>Click the <q>Add connector</q> button.</li>
66
53
  <li>Toggle the <q>Ask/Allow</q> buttons to show <q>Allow</q>.</li>
67
54
  </ol>
55
+ <h2 id="perplexity">Perplexity Pro in a browser</h2>
56
+ <ol>
57
+ <li>After signing in, click the <q>Customize</q> button in the left sidebar.</li>
58
+ <li>Click the <q>+ Custom connector</q> menu button.</li>
59
+ <li>Click the <q>Add MCP connector</q> menu item.</li>
60
+ <li>Enter <q>Kilotest</q> into the <q>Name</q> input.</li>
61
+ <li>Enter <q>https://kilotest.com/mcp</q> into the <q>MCP server URL</q> input.</li>
62
+ <li>Click the <q>Advanced</q> menu button to open the advanced settings.</li>
63
+ <li>Under <q>Authentication</q>, change the authentication type to <q>None</q>.</li>
64
+ <li>Check the <q>I understand &hellip;</q> checkbox.</li>
65
+ <li>Click the <q>Add</q> button.</li>
66
+ </ol>
67
+ <p>Once you add Kilotest, it <strong>may</strong> become available in the Perplexity app on your computer or mobile device, too.</p>
68
+ <h2>Other AI platforms</h2>
69
+ <p>You can connect Kilotest to some other AI platforms, too. The instructions above are limited to platforms that are either free or relatively inexpensive (US$20 per month or less). If you discover an AI platform that should be added to this page, please submit a comment below.</p>
70
+ <h2>Trouble?</h2>
68
71
  <p>What if this did not work? Use the comment form below to tell us about it.</p>
69
72
  <p>You can also submit an issue on the <a href="https://github.com/jrpool/kilotest/issues">Kilotest GitHub repository</a>.</p>
70
73
  <section id="suggest-improvement" class="main">
@@ -6,7 +6,8 @@
6
6
  // IMPORTS
7
7
 
8
8
  import {sendAlert} from '../../alerts.ts';
9
- import {checkCommentDuplicate, checkCommentLength, describeMax, getEnvMax, getJSON, getNowStamp} from '../../util.ts';
9
+ import {checkCommentDuplicate, describeMax, getEnvMax, getJSON, getNowStamp} from '../../util.ts';
10
+ import {checkCommentLength} from '../util.ts';
10
11
  import fs from 'node:fs/promises';
11
12
  import path from 'node:path';
12
13
 
@@ -6,7 +6,8 @@
6
6
  // IMPORTS
7
7
 
8
8
  import {sendAlert} from '../../alerts.ts';
9
- import {checkCommentDuplicate, checkCommentLength, describeMax, getEnvMax, getJSON, getNowStamp} from '../../util.ts';
9
+ import {checkCommentDuplicate, describeMax, getEnvMax, getJSON, getNowStamp} from '../../util.ts';
10
+ import {checkCommentLength} from '../util.ts';
10
11
  import fs from 'node:fs/promises';
11
12
  import path from 'node:path';
12
13