@jrpool/kilotest 59.1.4 → 60.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/README.md +2 -5
- package/api/awaitTest.test.ts +9 -9
- package/api/awaitTest.ts +12 -12
- package/api/getReport.test.ts +4 -4
- package/api/getReport.ts +9 -9
- package/api/listDiagnoses.test.ts +14 -14
- package/api/listDiagnoses.ts +8 -8
- package/api/listIssues.test.ts +15 -15
- package/api/listIssues.ts +18 -20
- package/api/listPages.test.ts +2 -2
- package/api/listPages.ts +4 -4
- package/api/listViolators.test.ts +10 -10
- package/api/listViolators.ts +10 -10
- package/api/orderNewTest.test.ts +1 -2
- package/api/orderNewTest.ts +3 -4
- package/api/orderRetest.test.ts +10 -10
- package/api/orderRetest.ts +13 -14
- package/api/requestRetest.test.ts +11 -11
- package/api/requestRetest.ts +11 -11
- package/api/routes.ts +9 -11
- package/api/schemas.ts +18 -28
- package/api/util.test.ts +2 -2
- package/api/util.ts +7 -7
- package/capability.md +4 -4
- package/docs/AI-TOOLS.md +41 -1
- package/docs/decisions/009-report-id.md +16 -0
- package/index.test.ts +91 -41
- package/index.ts +66 -15
- package/lhm.plugin.json +33 -119
- package/llms-full.txt +18 -24
- package/llms.txt +12 -12
- package/mcp.test.ts +11 -20
- package/mcp.ts +18 -18
- package/openapi-3.0.yaml +1847 -0
- package/openapi.yaml +38 -97
- package/package.json +1 -1
- package/routing.test.ts +1 -1
- package/scripts/generate-openapi.ts +83 -1
- package/server.json +1 -1
- package/util.test.ts +32 -33
- package/util.ts +47 -43
- package/web/hideReportForm/index.ts +5 -6
- package/web/listDiagnoses/index.ts +4 -5
- package/web/listDiagnoses/listDiagnoses.test.ts +13 -13
- package/web/listIssues/index.ts +11 -12
- package/web/listIssues/listIssues.test.ts +24 -24
- package/web/listPages/index.ts +2 -2
- package/web/listPages/listPages.test.ts +2 -2
- package/web/listTopIssues/index.ts +2 -2
- package/web/listViolators/index.ts +5 -6
- package/web/listViolators/listViolators.test.ts +12 -12
- package/web/reannotate/index.ts +3 -3
- package/web/reannotateForm/index.ts +1 -2
- package/web/reportDeletion.ts +9 -12
- package/web/requestRetest/index.ts +2 -2
- package/web/requestRetest/requestRetest.test.ts +7 -7
- package/web/requestRetestForm/index.html +2 -2
- package/web/requestRetestForm/index.ts +3 -4
- package/web/requestRetestForm/requestRetestForm.test.ts +7 -8
- package/web/unhideReportForm/index.ts +4 -6
- package/web/util.test.ts +29 -29
- package/web/util.ts +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ An ensemble testing and reporting service for front-end web quality
|
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
This application uses an ensemble of 12 rule engines to test web pages for front-end quality (i.e. accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG
|
|
13
|
+
This application uses an ensemble of 12 rule engines to test web pages for front-end quality (i.e. accessibility, usability, and web standards, including HTML, CSS, ARIA, and WCAG).
|
|
14
14
|
|
|
15
15
|
The testing paradigm employed by Kilotest is discussed in these papers:
|
|
16
16
|
|
|
@@ -55,10 +55,7 @@ See the [`SERVICE.md`](docs/SERVICE.md) file for instructions on how to make Kil
|
|
|
55
55
|
|
|
56
56
|
Kilotest is not only a web application but also an API and an MCP server. AI platforms and models can use Kilotest via those interfaces.
|
|
57
57
|
|
|
58
|
-
The URL for models using the MCP server is `https://kilotest.com/mcp`. The MCP server is
|
|
59
|
-
|
|
60
|
-
- [Smithery](https://smithery.ai/servers/pool/kilotest)
|
|
61
|
-
- [Glama](https://glama.ai/mcp/connectors/com.kilotest/kilotest)
|
|
58
|
+
The public URL for models using the MCP server at `kilotest.com` is `https://kilotest.com/mcp`. The MCP server is registered at several registries. See [AI-TOOLS.md](docs/AI-TOOLS.md#External-features) for details.
|
|
62
59
|
|
|
63
60
|
There is additional documentation at:
|
|
64
61
|
|
package/api/awaitTest.test.ts
CHANGED
|
@@ -43,24 +43,24 @@ after(() => {
|
|
|
43
43
|
|
|
44
44
|
// TESTS
|
|
45
45
|
|
|
46
|
-
test('awaitTest rejects a malformed
|
|
47
|
-
const body = await response(['999999T9999
|
|
46
|
+
test('awaitTest rejects a malformed report identifier', async () => {
|
|
47
|
+
const body = await response(['999999T9999-xyz']);
|
|
48
48
|
const details = body['response content']['details about your wait'] as any;
|
|
49
49
|
assert.ok(details.error.includes('malformed'));
|
|
50
50
|
assert.equal(body['response content']['disposition of your wait'], null);
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
test('awaitTest returns "completed" for an identifier with an already-available report', async () => {
|
|
54
|
-
const body = await response(['260101T0000
|
|
54
|
+
const body = await response(['260101T0000-mix']);
|
|
55
55
|
const details = body['response content']['details about your wait'] as any;
|
|
56
|
-
assert.
|
|
56
|
+
assert.equal(details['report identifier'], '260101T0000-mix');
|
|
57
57
|
const disposition = body['response content']['disposition of your wait'] as any;
|
|
58
58
|
assert.equal(disposition.outcome, 'completed');
|
|
59
59
|
assert.ok(disposition['how you can check for completion'].includes('listIssues'));
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test('awaitTest returns an error for an identifier naming neither a report nor a pending job', async () => {
|
|
63
|
-
const body = await response(['260101T0000
|
|
63
|
+
const body = await response(['260101T0000-zzz']);
|
|
64
64
|
const details = body['response content']['details about your wait'] as any;
|
|
65
65
|
assert.ok(details.error.includes('no report'));
|
|
66
66
|
assert.equal(body['response content']['disposition of your wait'], null);
|
|
@@ -71,7 +71,7 @@ test('awaitTest returns "failed" when the job has moved to the failed directory'
|
|
|
71
71
|
await fs.mkdir(path.dirname(failedPath), {recursive: true});
|
|
72
72
|
await fs.writeFile(failedPath, JSON.stringify({target: {what: 'Failed Page', url: 'https://example.com/failed'}}));
|
|
73
73
|
try {
|
|
74
|
-
const body = await response(['260101T4444
|
|
74
|
+
const body = await response(['260101T4444-flj']);
|
|
75
75
|
const disposition = body['response content']['disposition of your wait'] as any;
|
|
76
76
|
assert.equal(disposition.outcome, 'failed');
|
|
77
77
|
assert.equal(disposition['how you can check for completion'], 'Not applicable.');
|
|
@@ -85,7 +85,7 @@ test('awaitTest returns "timedOut" when a claimed job neither completes nor fail
|
|
|
85
85
|
const claimedPath = path.join(fixtureDBDir, 'jobs', 'claimed', '260101T5555-toj.json');
|
|
86
86
|
await fs.writeFile(claimedPath, JSON.stringify({target: {what: 'Stuck Page', url: 'https://example.com/stuck'}}));
|
|
87
87
|
try {
|
|
88
|
-
const body = await response(['260101T5555
|
|
88
|
+
const body = await response(['260101T5555-toj']);
|
|
89
89
|
const disposition = body['response content']['disposition of your wait'] as any;
|
|
90
90
|
assert.equal(disposition.outcome, 'timedOut');
|
|
91
91
|
assert.ok(disposition['how you can check for completion'].includes('listIssues'));
|
|
@@ -96,14 +96,14 @@ test('awaitTest returns "timedOut" when a claimed job neither completes nor fail
|
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
test('awaitTest response has no web UI equivalents for this request or its ancestors', async () => {
|
|
99
|
-
const body = await response(['260101T0000
|
|
99
|
+
const body = await response(['260101T0000-mix']);
|
|
100
100
|
const similarWeb = body['URLs of similar requests for web users'] as any;
|
|
101
101
|
assert.equal(similarWeb['this request'], null);
|
|
102
102
|
assert.equal(similarWeb['closest ancestor request'], null);
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
test('awaitTest reports both orderNewTest and orderRetest as possible ancestors', async () => {
|
|
106
|
-
const body = await response(['260101T0000
|
|
106
|
+
const body = await response(['260101T0000-mix']);
|
|
107
107
|
const ancestors = (body['this request'] as any)['closest ancestor request'];
|
|
108
108
|
assert.equal(ancestors.length, 2);
|
|
109
109
|
const names = ancestors.map((a: any) => a['tool name']).sort();
|
package/api/awaitTest.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import {z} from 'zod';
|
|
9
9
|
import {getResponseMetadata, getThisHost, getToolsFacts} from './util.ts';
|
|
10
|
-
import {awaitJob,
|
|
10
|
+
import {awaitJob, isReportID} from '../util.ts';
|
|
11
11
|
import {awaitTestResponseSchema} from './schemas.ts';
|
|
12
12
|
|
|
13
13
|
// TYPES
|
|
@@ -19,35 +19,35 @@ type ResponseContent = z.infer<typeof awaitTestResponseSchema>['response content
|
|
|
19
19
|
|
|
20
20
|
// Returns the response body.
|
|
21
21
|
export const response = async (args: string[]) => {
|
|
22
|
-
const [
|
|
22
|
+
const [reportID = ''] = args;
|
|
23
23
|
const thisHost = getThisHost();
|
|
24
24
|
// Initialize the response-content properties.
|
|
25
25
|
let requestDetails: ResponseContent['details about your wait'];
|
|
26
26
|
let requestDisposition: ResponseContent['disposition of your wait'] = null;
|
|
27
|
-
// If the
|
|
28
|
-
if (!
|
|
27
|
+
// If the report identifier is not syntactically valid:
|
|
28
|
+
if (!isReportID(reportID)) {
|
|
29
29
|
requestDetails = {
|
|
30
|
-
error: 'wait invalid: the
|
|
30
|
+
error: 'wait invalid: the reportID is malformed'
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
// Otherwise, i.e. if the identifier is syntactically valid:
|
|
34
34
|
else {
|
|
35
35
|
requestDetails = {
|
|
36
|
-
'report identifier':
|
|
36
|
+
'report identifier': reportID
|
|
37
37
|
};
|
|
38
38
|
// Wait for the job to complete, fail, or time out. No progress notices are sent
|
|
39
39
|
// during this wait; the caller receives only this one, final response.
|
|
40
|
-
const outcome = await awaitJob(
|
|
40
|
+
const outcome = await awaitJob(reportID);
|
|
41
41
|
if (outcome === 'notFound') {
|
|
42
42
|
requestDetails = {
|
|
43
|
-
error: `wait invalid: no report with
|
|
43
|
+
error: `wait invalid: no report with identifier "${reportID}" is completed, pending, or in progress`
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
else if (outcome === 'completed') {
|
|
47
47
|
requestDisposition = {
|
|
48
48
|
outcome: 'completed',
|
|
49
49
|
'what happened': 'The report is ready.',
|
|
50
|
-
'how you can check for completion': `Call the listIssues tool with
|
|
50
|
+
'how you can check for completion': `Call the listIssues tool with reportID "${reportID}" to retrieve the report.`
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
else if (outcome === 'failed') {
|
|
@@ -61,7 +61,7 @@ export const response = async (args: string[]) => {
|
|
|
61
61
|
requestDisposition = {
|
|
62
62
|
outcome: 'timedOut',
|
|
63
63
|
'what happened': 'The job was not completed within the maximum wait time, but it may still be running.',
|
|
64
|
-
'how you can check for completion': `Call the listIssues tool with
|
|
64
|
+
'how you can check for completion': `Call the listIssues tool with reportID "${reportID}" after a few minutes to check whether the report is ready.`
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -75,9 +75,9 @@ export const response = async (args: string[]) => {
|
|
|
75
75
|
'tool collection': getToolsFacts(),
|
|
76
76
|
'tool name': 'awaitTest',
|
|
77
77
|
'this request': {
|
|
78
|
-
description: 'Wait for a job ordered via the orderNewTest or orderRetest tool to complete. The
|
|
78
|
+
description: 'Wait for a job ordered via the orderNewTest or orderRetest tool to complete. The reportID parameter is the report identifier that orderNewTest or orderRetest returned. This call blocks until the report is ready, the job fails, or a maximum wait time elapses.',
|
|
79
79
|
method: 'POST',
|
|
80
|
-
URL: `${thisHost}/api/awaitTest/${
|
|
80
|
+
URL: `${thisHost}/api/awaitTest/${reportID}`,
|
|
81
81
|
// awaitTest's own parameters do not say whether the job was ordered via orderNewTest
|
|
82
82
|
// or orderRetest, so both are reported as possible ancestors, rather than
|
|
83
83
|
// arbitrarily naming just one.
|
package/api/getReport.test.ts
CHANGED
|
@@ -29,7 +29,7 @@ after(() => {
|
|
|
29
29
|
// TESTS
|
|
30
30
|
|
|
31
31
|
test('getReport returns the full report for a valid report', async () => {
|
|
32
|
-
const body = await response(['260101T0000
|
|
32
|
+
const body = await response(['260101T0000-mix']);
|
|
33
33
|
const content = body['response content'];
|
|
34
34
|
assert.equal(typeof content['size of the report in bytes'], 'number');
|
|
35
35
|
assert.ok((content['size of the report in bytes'] as number) > 0);
|
|
@@ -41,21 +41,21 @@ test('getReport returns the full report for a valid report', async () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
test('getReport returns an error for a nonexistent report', async () => {
|
|
44
|
-
const body = await response(['999999T9999
|
|
44
|
+
const body = await response(['999999T9999-xyz']);
|
|
45
45
|
const content = body['response content'];
|
|
46
46
|
assert.equal(content['full report'], null);
|
|
47
47
|
assert.equal(typeof content['size of the report in bytes'], 'string');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test('getReport returns an error for the hidden report', async () => {
|
|
51
|
-
const body = await response(['260101T0007
|
|
51
|
+
const body = await response(['260101T0007-hid']);
|
|
52
52
|
const content = body['response content'];
|
|
53
53
|
assert.equal(content['full report'], null);
|
|
54
54
|
assert.equal(typeof content['size of the report in bytes'], 'string');
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
test('getReport includes tool name and metadata', async () => {
|
|
58
|
-
const body = await response(['260101T0000
|
|
58
|
+
const body = await response(['260101T0000-mix']);
|
|
59
59
|
assert.equal(body['tool name'], 'getReport');
|
|
60
60
|
assert.ok(body['response metadata'].identifier);
|
|
61
61
|
});
|
package/api/getReport.ts
CHANGED
|
@@ -23,18 +23,18 @@ type ResponseContent = z.infer<typeof getReportResponseSchema>['response content
|
|
|
23
23
|
|
|
24
24
|
// Returns the response body.
|
|
25
25
|
export const response = async (args: string[]) => {
|
|
26
|
-
const [
|
|
26
|
+
const [reportID = ''] = args;
|
|
27
27
|
const thisHost = getThisHost();
|
|
28
|
-
// Get the report size. A malformed
|
|
28
|
+
// Get the report size. A malformed report identifier resolves to a nonexistent file
|
|
29
29
|
// path, which getReportStats already reports as unavailable, so no separate syntactic
|
|
30
30
|
// check is needed here.
|
|
31
|
-
const reportStats = await getReportStats(
|
|
31
|
+
const reportStats = await getReportStats(reportID);
|
|
32
32
|
// Create the response content.
|
|
33
33
|
const responseContent: ResponseContent = reportStats
|
|
34
34
|
? {
|
|
35
35
|
'size of the report in bytes': reportStats.reportSize,
|
|
36
36
|
// Get the report (which may be only an error message).
|
|
37
|
-
'full report': await getReport(
|
|
37
|
+
'full report': await getReport(reportID)
|
|
38
38
|
}
|
|
39
39
|
: {
|
|
40
40
|
'size of the report in bytes': 'Error: The report could not be accessed for an unknown reason.',
|
|
@@ -45,19 +45,19 @@ export const response = async (args: string[]) => {
|
|
|
45
45
|
'tool collection': getToolsFacts(),
|
|
46
46
|
'tool name': 'getReport',
|
|
47
47
|
'this request': {
|
|
48
|
-
description: 'Provide one full report in JSON format. The
|
|
48
|
+
description: 'Provide one full report in JSON format. The reportID parameter identifies the report that I want details about. It was in the response to my earlier listIssues request.',
|
|
49
49
|
method: 'GET',
|
|
50
|
-
URL: `${thisHost}/api/getReport/${
|
|
50
|
+
URL: `${thisHost}/api/getReport/${reportID}`,
|
|
51
51
|
'closest ancestor request': {
|
|
52
52
|
'tool name': 'listIssues',
|
|
53
53
|
description: 'Provide details about one report, including basics about the issues reported in it.',
|
|
54
54
|
method: 'GET',
|
|
55
|
-
URL: `${thisHost}/api/listIssues/${
|
|
55
|
+
URL: `${thisHost}/api/listIssues/${reportID}`
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
58
|
'URLs of similar requests for web users': {
|
|
59
|
-
'this request': `${thisHost}/fullReport.json/${
|
|
60
|
-
'closest ancestor request': `${thisHost}/listIssues.html/${
|
|
59
|
+
'this request': `${thisHost}/fullReport.json/${reportID}`,
|
|
60
|
+
'closest ancestor request': `${thisHost}/listIssues.html/${reportID}`
|
|
61
61
|
},
|
|
62
62
|
'response metadata': getResponseMetadata(),
|
|
63
63
|
'response content': responseContent
|
|
@@ -29,7 +29,7 @@ after(() => {
|
|
|
29
29
|
// TESTS
|
|
30
30
|
|
|
31
31
|
test('listDiagnoses returns 2 diagnoses for catalogIndex 0, linkNoText, in the mixed report (axe and alfa, excluding cantTell)', async () => {
|
|
32
|
-
const body = await response(['0', 'linkNoText', '260101T0000
|
|
32
|
+
const body = await response(['0', 'linkNoText', '260101T0000-mix']);
|
|
33
33
|
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
34
34
|
assert.equal(diagnoses.length, 2);
|
|
35
35
|
const rules = diagnoses.map((d: any) => d['identifier of the violated rule']).sort();
|
|
@@ -39,13 +39,13 @@ test('listDiagnoses returns 2 diagnoses for catalogIndex 0, linkNoText, in the m
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
test('listDiagnoses returns 0 diagnoses when all instances are cantTell', async () => {
|
|
42
|
-
const body = await response(['0', 'focusIndicationBad', '260101T0001
|
|
42
|
+
const body = await response(['0', 'focusIndicationBad', '260101T0001-ct']);
|
|
43
43
|
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
44
44
|
assert.equal(diagnoses.length, 0);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('listDiagnoses treats missing outcome as a violation', async () => {
|
|
48
|
-
const body = await response(['0', 'linkNoText', '260101T0002
|
|
48
|
+
const body = await response(['0', 'linkNoText', '260101T0002-no']);
|
|
49
49
|
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
50
50
|
assert.equal(diagnoses.length, 1);
|
|
51
51
|
assert.equal(diagnoses[0]['description of the violation'], 'Button has no accessible name');
|
|
@@ -53,7 +53,7 @@ test('listDiagnoses treats missing outcome as a violation', async () => {
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test('listDiagnoses returns element basics from the catalog', async () => {
|
|
56
|
-
const body = await response(['0', 'linkNoText', '260101T0000
|
|
56
|
+
const body = await response(['0', 'linkNoText', '260101T0000-mix']);
|
|
57
57
|
const elementBasics = body['response content']['basics about the element'] as any;
|
|
58
58
|
assert.equal(elementBasics.identifier, '0');
|
|
59
59
|
assert.equal(elementBasics['tag name'], 'A');
|
|
@@ -61,7 +61,7 @@ test('listDiagnoses returns element basics from the catalog', async () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
test('listDiagnoses returns element details from the catalog', async () => {
|
|
64
|
-
const body = await response(['0', 'linkNoText', '260101T0000
|
|
64
|
+
const body = await response(['0', 'linkNoText', '260101T0000-mix']);
|
|
65
65
|
const elementDetails = body['response content']['details about the element'] as any;
|
|
66
66
|
assert.equal(elementDetails['start tag'], '<a>');
|
|
67
67
|
assert.equal(elementDetails['XPath'], '/html/body/a[1]');
|
|
@@ -69,39 +69,39 @@ test('listDiagnoses returns element details from the catalog', async () => {
|
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
test('listDiagnoses returns an error for an unknown issue ID', async () => {
|
|
72
|
-
const body = await response(['0', 'nonexistentIssue', '260101T0000
|
|
72
|
+
const body = await response(['0', 'nonexistentIssue', '260101T0000-mix']);
|
|
73
73
|
const basics = body['response content']['basics about the issue'] as any;
|
|
74
74
|
assert.ok(basics.error);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test('listDiagnoses returns an error for a nonexistent catalog index', async () => {
|
|
78
|
-
const body = await response(['999', 'linkNoText', '260101T0000
|
|
78
|
+
const body = await response(['999', 'linkNoText', '260101T0000-mix']);
|
|
79
79
|
const basics = body['response content']['basics about the element'] as any;
|
|
80
80
|
assert.ok(basics.error);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
test('listDiagnoses returns an error for a nonexistent report', async () => {
|
|
84
|
-
const body = await response(['0', 'linkNoText', '999999T9999
|
|
84
|
+
const body = await response(['0', 'linkNoText', '999999T9999-xyz']);
|
|
85
85
|
const basics = body['response content']['basics about the report'] as any;
|
|
86
86
|
assert.ok(basics.error);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test('listDiagnoses returns guideline layer for an issue with a short WCAG code', async () => {
|
|
90
|
-
const body = await response(['0', 'duplicateID', '260101T0009
|
|
90
|
+
const body = await response(['0', 'duplicateID', '260101T0009-brd']);
|
|
91
91
|
const issueBasics = body['response content']['basics about the issue'] as any;
|
|
92
92
|
assert.equal(issueBasics['related WCAG standard'].layer, 'guideline');
|
|
93
93
|
assert.equal(issueBasics['related WCAG standard'].identifier, '4.1');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
96
|
test('listDiagnoses returns null tag name and inner text for a catalog item missing them', async () => {
|
|
97
|
-
const body = await response(['1', 'duplicateID', '260101T0009
|
|
97
|
+
const body = await response(['1', 'duplicateID', '260101T0009-brd']);
|
|
98
98
|
const elementBasics = body['response content']['basics about the element'] as any;
|
|
99
99
|
assert.equal(elementBasics['tag name'], null);
|
|
100
100
|
assert.equal(elementBasics['inner text'], null);
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
test('listDiagnoses returns null rule ID when ruleID equals what', async () => {
|
|
104
|
-
const body = await response(['0', 'duplicateID', '260101T0009
|
|
104
|
+
const body = await response(['0', 'duplicateID', '260101T0009-brd']);
|
|
105
105
|
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
106
106
|
const axeDiagnosis = diagnoses.find((d: any) => d['description of the violation'] === 'r99');
|
|
107
107
|
assert.ok(axeDiagnosis);
|
|
@@ -109,7 +109,7 @@ test('listDiagnoses returns null rule ID when ruleID equals what', async () => {
|
|
|
109
109
|
});
|
|
110
110
|
|
|
111
111
|
test('listDiagnoses defaults count to 1 when count is missing', async () => {
|
|
112
|
-
const body = await response(['1', 'duplicateID', '260101T0009
|
|
112
|
+
const body = await response(['1', 'duplicateID', '260101T0009-brd']);
|
|
113
113
|
const diagnoses = body['response content']['diagnoses of how the element exhibited the issue'] as any;
|
|
114
114
|
const ibmDiagnosis = diagnoses.find((d: any) => d['description of the violation'] === 'Element has no role');
|
|
115
115
|
assert.ok(ibmDiagnosis);
|
|
@@ -117,7 +117,7 @@ test('listDiagnoses defaults count to 1 when count is missing', async () => {
|
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
test('listDiagnoses returns null for missing start tag, XPath, and bounding box', async () => {
|
|
120
|
-
const body = await response(['2', 'duplicateID', '260101T0009
|
|
120
|
+
const body = await response(['2', 'duplicateID', '260101T0009-brd']);
|
|
121
121
|
const elementDetails = body['response content']['details about the element'] as any;
|
|
122
122
|
assert.equal(elementDetails['start tag'], null);
|
|
123
123
|
assert.equal(elementDetails['XPath'], null);
|
|
@@ -125,7 +125,7 @@ test('listDiagnoses returns null for missing start tag, XPath, and bounding box'
|
|
|
125
125
|
});
|
|
126
126
|
|
|
127
127
|
test('listDiagnoses returns issue error for a falsy issue ID', async () => {
|
|
128
|
-
const body = await response(['0', '', '260101T0009
|
|
128
|
+
const body = await response(['0', '', '260101T0009-brd']);
|
|
129
129
|
const issueBasics = body['response content']['basics about the issue'] as any;
|
|
130
130
|
assert.ok(issueBasics.error);
|
|
131
131
|
});
|
package/api/listDiagnoses.ts
CHANGED
|
@@ -27,15 +27,15 @@ type Diagnoses = Extract<ResponseContent['diagnoses of how the element exhibited
|
|
|
27
27
|
|
|
28
28
|
// Returns the response body.
|
|
29
29
|
export const response = async (args: string[]) => {
|
|
30
|
-
const [catalogIndex = '', issueID = '',
|
|
30
|
+
const [catalogIndex = '', issueID = '', reportID = ''] = args;
|
|
31
31
|
const thisHost = getThisHost();
|
|
32
32
|
// Get the report.
|
|
33
|
-
const report = await getReport(
|
|
33
|
+
const report = await getReport(reportID);
|
|
34
34
|
// Get the specification of the issue.
|
|
35
35
|
const issueSpec = issueID ? getIssueSpec(issueID) : null;
|
|
36
36
|
// Initialize the response content.
|
|
37
37
|
const responseContent: ResponseContent = {
|
|
38
|
-
'basics about the report': isReportError(report) ? report : await getReportBasics(
|
|
38
|
+
'basics about the report': isReportError(report) ? report : await getReportBasics(reportID),
|
|
39
39
|
'basics about the issue': {
|
|
40
40
|
'error': 'No information about the specified issue is available'
|
|
41
41
|
},
|
|
@@ -115,19 +115,19 @@ export const response = async (args: string[]) => {
|
|
|
115
115
|
'tool collection': getToolsFacts(),
|
|
116
116
|
'tool name': 'listDiagnoses',
|
|
117
117
|
'this request': {
|
|
118
|
-
description: 'Provide details about one element reported as exhibiting one issue in one report, including the diagnoses provided by rule engines about how the element exhibited the issue. The catalogIndex, issueID,
|
|
118
|
+
description: 'Provide details about one element reported as exhibiting one issue in one report, including the diagnoses provided by rule engines about how the element exhibited the issue. The catalogIndex, issueID, and reportID parameters identify the element, issue, and report that I want details about. Those parameters were in the response to my earlier listViolators request.',
|
|
119
119
|
method: 'GET',
|
|
120
|
-
URL: `${thisHost}/api/listDiagnoses/${catalogIndex}/${issueID}/${
|
|
120
|
+
URL: `${thisHost}/api/listDiagnoses/${catalogIndex}/${issueID}/${reportID}`,
|
|
121
121
|
'closest ancestor request': {
|
|
122
122
|
'tool name': 'listViolators',
|
|
123
123
|
description: 'Provide details about one issue in one report, including basics about the elements of the tested page that were reported as exhibiting the issue.',
|
|
124
124
|
method: 'GET',
|
|
125
|
-
URL: `${thisHost}/api/listViolators/${issueID}/${
|
|
125
|
+
URL: `${thisHost}/api/listViolators/${issueID}/${reportID}`
|
|
126
126
|
}
|
|
127
127
|
},
|
|
128
128
|
'URLs of similar requests for web users': {
|
|
129
|
-
'this request': `${thisHost}/listDiagnoses.html/${issueID}/${
|
|
130
|
-
'closest ancestor request': `${thisHost}/listViolators.html/${issueID}/${
|
|
129
|
+
'this request': `${thisHost}/listDiagnoses.html/${issueID}/${reportID}/${catalogIndex}`,
|
|
130
|
+
'closest ancestor request': `${thisHost}/listViolators.html/${issueID}/${reportID}`
|
|
131
131
|
},
|
|
132
132
|
'response metadata': getResponseMetadata(),
|
|
133
133
|
'response content': responseContent
|
package/api/listIssues.test.ts
CHANGED
|
@@ -29,7 +29,7 @@ after(() => {
|
|
|
29
29
|
// TESTS
|
|
30
30
|
|
|
31
31
|
test('listIssues returns 2 issues for the mixed-outcomes report, excluding cantTell', async () => {
|
|
32
|
-
const body = await response(['260101T0000
|
|
32
|
+
const body = await response(['260101T0000-mix']);
|
|
33
33
|
const issues = body['response content']['basics about all issues reported in the report'];
|
|
34
34
|
assert.ok(issues);
|
|
35
35
|
assert.equal(issues.length, 2);
|
|
@@ -38,14 +38,14 @@ test('listIssues returns 2 issues for the mixed-outcomes report, excluding cantT
|
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
test('listIssues returns 0 issues for the all-cantTell report', async () => {
|
|
41
|
-
const body = await response(['260101T0001
|
|
41
|
+
const body = await response(['260101T0001-ct']);
|
|
42
42
|
const issues = body['response content']['basics about all issues reported in the report'];
|
|
43
43
|
assert.ok(issues);
|
|
44
44
|
assert.equal(issues.length, 0);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
test('listIssues treats missing outcome as a violation, not cantTell', async () => {
|
|
48
|
-
const body = await response(['260101T0002
|
|
48
|
+
const body = await response(['260101T0002-no']);
|
|
49
49
|
const issues = body['response content']['basics about all issues reported in the report'];
|
|
50
50
|
assert.ok(issues);
|
|
51
51
|
assert.equal(issues.length, 1);
|
|
@@ -53,14 +53,14 @@ test('listIssues treats missing outcome as a violation, not cantTell', async ()
|
|
|
53
53
|
});
|
|
54
54
|
|
|
55
55
|
test('listIssues returns 0 issues for the empty report', async () => {
|
|
56
|
-
const body = await response(['260101T0005
|
|
56
|
+
const body = await response(['260101T0005-emp']);
|
|
57
57
|
const issues = body['response content']['basics about all issues reported in the report'];
|
|
58
58
|
assert.ok(issues);
|
|
59
59
|
assert.equal(issues.length, 0);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test('listIssues reports prevented rule engines for the prevented report', async () => {
|
|
63
|
-
const body = await response(['260101T0006
|
|
63
|
+
const body = await response(['260101T0006-prv']);
|
|
64
64
|
const details = body['response content']['details about the report'] as any;
|
|
65
65
|
const preventions = details['test results']['rule engines that could not test the page'];
|
|
66
66
|
assert.equal(preventions.length, 1);
|
|
@@ -69,7 +69,7 @@ test('listIssues reports prevented rule engines for the prevented report', async
|
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
test('listIssues includes reporter names for each issue in the mixed report', async () => {
|
|
72
|
-
const body = await response(['260101T0000
|
|
72
|
+
const body = await response(['260101T0000-mix']);
|
|
73
73
|
const issues = body['response content']['basics about all issues reported in the report'];
|
|
74
74
|
assert.ok(issues);
|
|
75
75
|
const linkIssue = issues.find(i => i.identifier === 'linkNoText');
|
|
@@ -81,7 +81,7 @@ test('listIssues includes reporter names for each issue in the mixed report', as
|
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
test('listIssues counts issues by priority correctly for the mixed report', async () => {
|
|
84
|
-
const body = await response(['260101T0000
|
|
84
|
+
const body = await response(['260101T0000-mix']);
|
|
85
85
|
const details = body['response content']['details about the report'] as any;
|
|
86
86
|
const counts = details['test results']['counts of issues by priority'];
|
|
87
87
|
// linkNoText weight 4 (highest), allCaps weight 1 (lowest).
|
|
@@ -92,19 +92,19 @@ test('listIssues counts issues by priority correctly for the mixed report', asyn
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
test('listIssues reports the superseded status for the mixed report', async () => {
|
|
95
|
-
const body = await response(['260101T0000
|
|
95
|
+
const body = await response(['260101T0000-mix']);
|
|
96
96
|
const basics = body['response content']['basics about the report'] as any;
|
|
97
97
|
assert.equal(basics['whether a later report about the same page exists'], true);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
100
|
test('listIssues returns an error for a nonexistent report', async () => {
|
|
101
|
-
const body = await response(['999999T9999
|
|
101
|
+
const body = await response(['999999T9999-xyz']);
|
|
102
102
|
const basics = body['response content']['basics about the report'] as any;
|
|
103
103
|
assert.ok(basics.error);
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
test('listIssues handles instances with missing issueID and null instances without error', async () => {
|
|
107
|
-
const body = await response(['260101T0009
|
|
107
|
+
const body = await response(['260101T0009-brd']);
|
|
108
108
|
const issues = body['response content']['basics about all issues reported in the report'];
|
|
109
109
|
// The brd fixture has one instance with duplicateID (a valid issue) and one with no issueID (skipped).
|
|
110
110
|
// The alfa act has no instances array, so it should be handled as empty.
|
|
@@ -115,7 +115,7 @@ test('listIssues handles instances with missing issueID and null instances witho
|
|
|
115
115
|
});
|
|
116
116
|
|
|
117
117
|
test('listIssues lists the history of reports about the page, from oldest to latest', async () => {
|
|
118
|
-
const body = await response(['260101T0000
|
|
118
|
+
const body = await response(['260101T0000-mix']);
|
|
119
119
|
const history = body['response content']['history of reports about the page']!;
|
|
120
120
|
assert.deepEqual(history.map(entry => entry.identifier), ['260101T0000-mix', '260202T0000-new']);
|
|
121
121
|
assert.deepEqual(
|
|
@@ -128,12 +128,12 @@ test('listIssues lists the history of reports about the page, from oldest to lat
|
|
|
128
128
|
);
|
|
129
129
|
assert.equal(history[1]!['completion date and time'], '2026-02-02T00:10:00.000Z');
|
|
130
130
|
assert.equal(typeof history[1]!['days since the report was completed'], 'number');
|
|
131
|
-
assert.ok(history[1]!['how to get details about the report'].URL.endsWith('/api/listIssues/260202T0000
|
|
132
|
-
assert.ok(history[1]!['web users can get details about the report at'].endsWith('/listIssues.html/260202T0000
|
|
131
|
+
assert.ok(history[1]!['how to get details about the report'].URL.endsWith('/api/listIssues/260202T0000-new'));
|
|
132
|
+
assert.ok(history[1]!['web users can get details about the report at'].endsWith('/listIssues.html/260202T0000-new'));
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
test('listIssues gives a one-report history for the only report about a page', async () => {
|
|
136
|
-
const body = await response(['260101T0001
|
|
136
|
+
const body = await response(['260101T0001-ct']);
|
|
137
137
|
const history = body['response content']['history of reports about the page']!;
|
|
138
138
|
assert.equal(history.length, 1);
|
|
139
139
|
assert.equal(history[0]!['whether it is the oldest report about the page'], true);
|
|
@@ -142,6 +142,6 @@ test('listIssues gives a one-report history for the only report about a page', a
|
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
test('listIssues gives no history for a nonexistent report', async () => {
|
|
145
|
-
const body = await response(['999999T9999
|
|
145
|
+
const body = await response(['999999T9999-xyz']);
|
|
146
146
|
assert.equal(body['response content']['history of reports about the page'], null);
|
|
147
147
|
});
|