@jrpool/kilotest 33.0.2 → 34.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/AI-TOOL.md +27 -0
- package/index.js +31 -6
- package/llms-full.txt +20 -11
- package/llms.txt +3 -2
- package/mcp.js +23 -1
- package/openapi.yaml +32 -2
- package/package.json +1 -1
- package/reportIssues/api.js +1 -1
- package/researchAgent.js +134 -165
- package/target/api.js +115 -0
- package/targets/api.js +1 -1
- package/testRecForm/api.js +1 -1
- package/util.js +1 -1
package/AI-TOOL.md
CHANGED
|
@@ -159,3 +159,30 @@ The behavior of Claude Haiku in Extended mode was nearly perfect. The naming of
|
|
|
159
159
|
The most notable fact was that Claude Haiku converted a long workflow to a short one by ending the output with a recommendation to the user to get the test results by self-service. The instructions to models presumed that the user and the model would jointly decide to maintain their relationship until the testing is completed and the model would then interpret the results for the user. Instead, Claude Haiku had enough imagination to suggest a viable workflow not even hinted at by the instructions.
|
|
160
160
|
|
|
161
161
|
This result suggested that the instructions should be revised to describe, as a valid option, a short workflow ending with a recommendation to the user to get the test results by self-service after the model submits a testing recommendation.
|
|
162
|
+
|
|
163
|
+
#### Increment 5
|
|
164
|
+
|
|
165
|
+
In the next increment the AI platform was changed to [Perplexity Pro](https://www.perplexity.ai/) on the web. With the platform UI, two configuration steps were taken:
|
|
166
|
+
|
|
167
|
+
1. Kilotest was added as a custom connector, and the list of connectors thereafter included Kilotest.
|
|
168
|
+
1. In the input section of the chat interface, with the “Add files or tools” button (visually `+`), Kilotest was added as a connector. Thereafter, a “Kilotest” button appeared in the input section to confirm the availability of that connector.
|
|
169
|
+
|
|
170
|
+
The first model used was GPT-5.4.
|
|
171
|
+
|
|
172
|
+
The prompt was: “I want to know whether the home page of the nonprofit organization named ‘Center for Democracy and Technology’ has quality defects, such as with accessibility and usability.”
|
|
173
|
+
|
|
174
|
+
GPT-5.4 responded by correctly using two Kilotest tools, first to check for an available report and then to inspect the results of the report about the specified web page. The model then appropriately itemized and summarized findings from the report, without any substantial interpretive additions.
|
|
175
|
+
|
|
176
|
+
The model was then changed to Sonar 2, created by Perplexity itself.
|
|
177
|
+
|
|
178
|
+
The results were similar, with one exception: The answer from Sonar 2 included advice and judgments (“most urgent fixes”, “especially notable”), without any attribution, so a user might wrongly infer that these were generated by Kilotest tools.
|
|
179
|
+
|
|
180
|
+
For both models, the same exercise was also attempted with the second configuration omitted, i.e. ••without** Kilotest being a named connector in the input section of the chat interface. In both cases, the models created their own unaided assessments and failed to mention or use the Kilotest tools.
|
|
181
|
+
|
|
182
|
+
The [Perplexity documentation](https://www.perplexity.ai/help-center/en/articles/13915507-adding-custom-remote-connectors) states that as of now the installed Perplexity application for macOS does not support remote connectors.
|
|
183
|
+
|
|
184
|
+
#### Increment 6
|
|
185
|
+
|
|
186
|
+
Once the compatibility of Kilotest as a connector with AI platforms of two different vendors had been confirmed, the focus of work returned to completion of the missing endpoints of the API. The first was an endpoint that would let a model efficiently search for an available report about a particular web page.
|
|
187
|
+
|
|
188
|
+
The new `identifyReportsAboutMatchingWebPages` tool provides inputs for fragments of a description and of a URL hostname of a web page and responds with summary data about all and only the available reports of pages that match at least one of those fragments, where matching means either including or being included by the provided fragment, case-insensitively.
|
package/index.js
CHANGED
|
@@ -692,25 +692,50 @@ const requestHandler = async (request, response) => {
|
|
|
692
692
|
);
|
|
693
693
|
}
|
|
694
694
|
}
|
|
695
|
+
// Otherwise, if the first segment is the report finding service:
|
|
696
|
+
else if (segments[0] === 'target') {
|
|
697
|
+
const {what = '', hostname = ''} = postData;
|
|
698
|
+
// If the payload contains a description fragment or URL fragment:
|
|
699
|
+
if (what || hostname) {
|
|
700
|
+
// Process the request and get the response data.
|
|
701
|
+
const responseData = await require(path.join(__dirname, 'target', 'api'))
|
|
702
|
+
.response([what, hostname]);
|
|
703
|
+
// Send them.
|
|
704
|
+
setHeaders('application/json', null, 'ultra');
|
|
705
|
+
response.end(JSON.stringify(responseData));
|
|
706
|
+
}
|
|
707
|
+
// Otherwise, i.e. if it is not a valid test recommendation:
|
|
708
|
+
else {
|
|
709
|
+
// Report this.
|
|
710
|
+
await serveError({message: 'ERROR: Request has neither a description fragment nor a URL fragment'}, response, false);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
695
713
|
// Otherwise, if the first segment is the test recommendation service:
|
|
696
714
|
else if (segments[0] === 'testRecForm') {
|
|
697
|
-
const {
|
|
715
|
+
const {
|
|
716
|
+
'description of the web page': what,
|
|
717
|
+
'URL of the web page': url,
|
|
718
|
+
'reason for testing the web page': why
|
|
719
|
+
} = postData;
|
|
698
720
|
// If the payload is a valid test recommendation:
|
|
699
721
|
if (what && isURL(url) && why) {
|
|
722
|
+
let responseData;
|
|
700
723
|
// If a report on the page is already available:
|
|
701
724
|
if (await isReportAvailable(what, url)) {
|
|
702
725
|
// Report this.
|
|
703
|
-
|
|
726
|
+
responseData = {
|
|
727
|
+
warning: 'A report on the page is already available'
|
|
728
|
+
}
|
|
704
729
|
}
|
|
705
730
|
// Otherwise, i.e. if no report on the page is available:
|
|
706
731
|
else {
|
|
707
732
|
// Process the recommendation and get the response data.
|
|
708
|
-
|
|
733
|
+
responseData = await require(path.join(__dirname, 'testRecForm', 'api'))
|
|
709
734
|
.response(what, url, why);
|
|
710
|
-
// Send them.
|
|
711
|
-
setHeaders('application/json', null, 'ultra');
|
|
712
|
-
response.end(JSON.stringify(responseData));
|
|
713
735
|
}
|
|
736
|
+
// Send the response data.
|
|
737
|
+
setHeaders('application/json', null, 'ultra');
|
|
738
|
+
response.end(JSON.stringify(responseData));
|
|
714
739
|
}
|
|
715
740
|
// Otherwise, i.e. if it is not a valid test recommendation:
|
|
716
741
|
else {
|
package/llms-full.txt
CHANGED
|
@@ -59,25 +59,34 @@ In the marketplace for MCP servers that test for front-end quality, all servers
|
|
|
59
59
|
Kilotest offers a comprehensive suite of capabilities to users via its web UI:
|
|
60
60
|
|
|
61
61
|
- [Home page](https://kilotest.com/)
|
|
62
|
-
- [
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
- [
|
|
67
|
-
-
|
|
68
|
-
- [
|
|
69
|
-
- [
|
|
62
|
+
- [Get summary test results for all tested pages](https://kilotest.com/targets.html)
|
|
63
|
+
- Get statistics about issues reported in one report: `https://kilotest.com/reportIssues.html/{timeStamp}/{jobID}`
|
|
64
|
+
- Get details about one issue reported in one report: `https://kilotest.com/reportIssue.html/{issueID}/{timeStamp}/{jobID}`
|
|
65
|
+
- Get diagnoses by rule engines of rule violations for one HTML element exhibiting one issue in one report: `https://kilotest.com/diagnoses.html/{issueID}/{timeStamp}/{jobID}/{catalogIndex}`
|
|
66
|
+
- [Recommend the testing of a not-yet-tested page](`https://kilotest.com/testRecForm.html`)
|
|
67
|
+
- Recommend the retesting of a previously tested page: `https://kilotest.com/retestRecForm.html/{timeStamp}/{jobID}`
|
|
68
|
+
- [Get statistics about frequently reported issues across all pages](https://kilotest.com/issues.html)
|
|
69
|
+
- [Get a list of the operations that Kilotest managers are authorized to request](https://kilotest.com/manage.html)
|
|
70
70
|
- [Tutorial, “Accessibility testing strategies”](https://kilotest.com/tutorial.html)
|
|
71
|
-
- [Provide a list of features available for use of Kilotest managers](https://kilotest.com/manage.html)
|
|
72
71
|
|
|
73
72
|
### API
|
|
74
73
|
|
|
75
|
-
Kilotest is implementing a richer suite of capabilities optimized for language models,
|
|
74
|
+
Kilotest is implementing a richer suite of capabilities optimized for language models. In the specifications below, the term `report` refers to the records of one session in which a web page is tested.
|
|
76
75
|
|
|
76
|
+
The implementation is currently in an alpha phase and offers 4 tools:
|
|
77
|
+
|
|
78
|
+
- `summarizeQualityOfMatchingWebPages`
|
|
79
|
+
- method: `POST`
|
|
80
|
+
- purpose: summarize front-end quality test results from available reports about web pages whose descriptions or hostnames match those that you specify
|
|
81
|
+
- path: `/api/target`
|
|
82
|
+
- payload properties
|
|
83
|
+
- `what`: all or part of a description of the page
|
|
84
|
+
- `hostname`: all or part of the hostname of the page
|
|
77
85
|
- `summarizeQualityOfAllTestedWebPages`
|
|
78
86
|
- method: `GET`
|
|
79
|
-
- purpose: summarize front-end quality test results from all reports
|
|
87
|
+
- purpose: summarize front-end quality test results from all available reports
|
|
80
88
|
- path: `/api/targets`
|
|
89
|
+
- caveat: Cached results are likely to be stale and should not be used
|
|
81
90
|
- `describeQualityOfOneWebPage`
|
|
82
91
|
- method: `GET`
|
|
83
92
|
- purpose: provide statistics about front-end quality issues reported in one report
|
package/llms.txt
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
# Kilotest
|
|
2
2
|
|
|
3
|
-
Kilotest helps AI
|
|
3
|
+
Kilotest helps language models and AI platforms to give thorough, accurate and truthful answers when users ask about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages. Kilotest integrates ten independent rule engines to test web pages and report results with multipage, single-page, and element-level statistics and details.
|
|
4
4
|
|
|
5
5
|
In the API section below, all endpoints require `GET` requests except as noted.
|
|
6
6
|
|
|
7
|
-
There is a [detailed explanation of Kilotest and its capabilities for
|
|
7
|
+
There is a [detailed explanation of Kilotest and its capabilities for LLMs](https://kilotest.com/llms-full.txt).
|
|
8
8
|
|
|
9
9
|
## API
|
|
10
10
|
|
|
11
|
+
- [Identify reports about matching web pages](/api/identifyReportsAboutMatchingWebPages)
|
|
11
12
|
- [Summarize test results for all tested pages](/api/targets)
|
|
12
13
|
- Provide statistics about issues reported in one job: `/api/reportIssues/{timeStamp}/{jobID}`
|
|
13
14
|
- Recommend testing of a web page: `/api/testRecForm` (requires `POST` request)
|
package/mcp.js
CHANGED
|
@@ -9,6 +9,7 @@ const {McpServer} = require('@modelcontextprotocol/sdk/server/mcp.js');
|
|
|
9
9
|
const {StreamableHTTPServerTransport} = require('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
10
10
|
const {z} = require('zod');
|
|
11
11
|
const {isReportAvailable, isURL} = require('./util');
|
|
12
|
+
const targetAPI = require('./target/api');
|
|
12
13
|
const targetsAPI = require('./targets/api');
|
|
13
14
|
const reportIssuesAPI = require('./reportIssues/api');
|
|
14
15
|
const testRecFormAPI = require('./testRecForm/api');
|
|
@@ -18,10 +19,31 @@ const testRecFormAPI = require('./testRecForm/api');
|
|
|
18
19
|
// Creates and returns an McpServer with Kilotest tools registered.
|
|
19
20
|
const createMCPServer = () => {
|
|
20
21
|
const server = new McpServer({name: 'Kilotest', version: '1.0.0'});
|
|
22
|
+
server.registerTool(
|
|
23
|
+
'summarizeQualityOfMatchingWebPages',
|
|
24
|
+
{
|
|
25
|
+
description: 'Returns summary data from every available Kilotest report about the front-end quality (i.e. accessibility, usability, and standards conformity) of web pages that match the description or hostname fragment of a web page that you have provided. Matching is case-insensitive and succeeds if the page property either is included by or includes the specified fragmant. Before calling recommendQualityTestingOfOneWebPage and waiting for testing to occur, call this tool to check whether a report about the page is available now.',
|
|
26
|
+
inputSchema: {
|
|
27
|
+
what: z.string().describe('All or part of a description of the web page.'),
|
|
28
|
+
url: z.string().describe('All or part of the hostname of the URL of the web page.')
|
|
29
|
+
},
|
|
30
|
+
annotations: {
|
|
31
|
+
title: 'Summarize the quality of matching web pages',
|
|
32
|
+
readOnlyHint: true,
|
|
33
|
+
idempotentHint: true,
|
|
34
|
+
destructiveHint: false,
|
|
35
|
+
openWorldHint: false
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
async ({what, url}) => {
|
|
39
|
+
const result = await targetAPI.response([what, url]);
|
|
40
|
+
return {content: [{type: 'text', text: JSON.stringify(result)}]};
|
|
41
|
+
}
|
|
42
|
+
);
|
|
21
43
|
server.registerTool(
|
|
22
44
|
'summarizeQualityOfAllTestedWebPages',
|
|
23
45
|
{
|
|
24
|
-
description: 'Returns summary data from every available Kilotest report about the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page.
|
|
46
|
+
description: 'Returns summary data from every available Kilotest report about the front-end quality (i.e. accessibility, usability, and standards conformity) of a web page.',
|
|
25
47
|
inputSchema: {},
|
|
26
48
|
annotations: {
|
|
27
49
|
title: 'Summarize the quality of all tested web pages',
|
package/openapi.yaml
CHANGED
|
@@ -13,11 +13,31 @@ servers:
|
|
|
13
13
|
description: Kilotest production server
|
|
14
14
|
|
|
15
15
|
paths:
|
|
16
|
+
/api/target:
|
|
17
|
+
post:
|
|
18
|
+
operationId: summarizeQualityOfMatchingWebPages
|
|
19
|
+
summary: Summarize the quality of matching web pages
|
|
20
|
+
description: Get summary data from the available reports about web pages whose descriptions or hostnames match your specifications. Matching is case-insensitive and succeeds at least 1 specification is included by or includes the corresponding property of a tested page. The data include the name and URL of each tested web page, when the testing was performed, how many front-end quality (i.e. accessibility, usability, and standards conformity) issues were reported, and URLs for retrieving more detailed data from the report. Before calling the recommendQualityTestingOfOneWebPage tool and waiting for testing to occur, call this tool to check whether a report about the page is available now. If so, you can retrieve data from it. If not, you can use the recommendQualityTestingOfOneWebPage tool to recommend the page for testing.
|
|
21
|
+
requestBody:
|
|
22
|
+
description: Page specifications
|
|
23
|
+
required: true
|
|
24
|
+
content:
|
|
25
|
+
application/json:
|
|
26
|
+
schema:
|
|
27
|
+
$ref: '#/components/schemas/Target'
|
|
28
|
+
responses:
|
|
29
|
+
'200':
|
|
30
|
+
description: Summary of the quality of matching web pages
|
|
31
|
+
content:
|
|
32
|
+
application/json:
|
|
33
|
+
schema:
|
|
34
|
+
$ref: '#/components/schemas/TargetsResponse'
|
|
35
|
+
|
|
16
36
|
/api/targets:
|
|
17
37
|
get:
|
|
18
38
|
operationId: summarizeQualityOfAllTestedWebPages
|
|
19
|
-
summary: Summarize quality of all tested web pages
|
|
20
|
-
description:
|
|
39
|
+
summary: Summarize the quality of all tested web pages
|
|
40
|
+
description: Get 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 front-end quality (i.e. accessibility, usability, and standard-conformity) issues were reported, and URLs for retrieving more detailed data from the report.
|
|
21
41
|
responses:
|
|
22
42
|
'200':
|
|
23
43
|
description: Summaries of available reports
|
|
@@ -288,6 +308,16 @@ components:
|
|
|
288
308
|
type: string
|
|
289
309
|
format: uri
|
|
290
310
|
|
|
311
|
+
Target:
|
|
312
|
+
type: object
|
|
313
|
+
properties:
|
|
314
|
+
what:
|
|
315
|
+
type: string
|
|
316
|
+
description: All or part of a description of the web page. If the page is the home page of an organization, the description is the name of the organization (e.g., “Earthjustice”). Otherwise, the description appends minimal differentiating words to the name of the organization (e.g., “Earthjustice Donate”), without the word “page”.
|
|
317
|
+
hostname:
|
|
318
|
+
type: string
|
|
319
|
+
description: All or part of the hostname of the web page. The hostname omits the scheme and the path.
|
|
320
|
+
|
|
291
321
|
TargetsResponse:
|
|
292
322
|
allOf:
|
|
293
323
|
- $ref: '#/components/schemas/CommonResponseFields'
|
package/package.json
CHANGED
package/reportIssues/api.js
CHANGED
|
@@ -63,7 +63,7 @@ exports.response = async args => {
|
|
|
63
63
|
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
64
64
|
// Get a response.
|
|
65
65
|
const content = {
|
|
66
|
-
summary: `This document fulfills a request made by a language model to a Kilotest tool. The model requested data from a Kilotest report about the front-end quality (i.e. accessibility, usability, and standard-conformity) of a web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes several API endpoints 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 rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
|
|
66
|
+
summary: `This document fulfills a request made by a language model to a Kilotest tool. The model requested data from a Kilotest report about the front-end quality (i.e. accessibility, usability, and standard-conformity) of a web page. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes several API endpoints 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 rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
|
|
67
67
|
'tool collection name': 'Kilotest',
|
|
68
68
|
'tool name': 'describeQualityOfOneWebPage',
|
|
69
69
|
request: {
|
package/researchAgent.js
CHANGED
|
@@ -20,192 +20,161 @@ const httpsClient = require('https');
|
|
|
20
20
|
|
|
21
21
|
// CONSTANTS
|
|
22
22
|
|
|
23
|
-
const kilotestHosts = [
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const kilotestHosts = [
|
|
24
|
+
process.env.LOCAL_KILOTEST_HOST || 'http://localhost:3000',
|
|
25
|
+
process.env.DEPLOYED_KILOTEST_HOST || 'https://kilotest.com'
|
|
26
|
+
];
|
|
27
|
+
// Kilotest host specified by the argument.
|
|
28
|
+
const kilotestHost = kilotestHosts[process.argv[2] === 'pub' ? 1 : 0];
|
|
26
29
|
const hostParts = kilotestHost.split(/:\/*/);
|
|
27
|
-
const scheme = hostParts[0];
|
|
28
|
-
const
|
|
30
|
+
const scheme = hostParts[0] === 'https' ? 'https' : 'http';
|
|
31
|
+
const client = scheme === 'https' ? httpsClient : httpClient;
|
|
32
|
+
const host = hostParts[1] || 'localhost';
|
|
29
33
|
const port = hostParts[2] || (scheme === 'https' ? 443 : 80);
|
|
30
34
|
|
|
31
35
|
// FUNCTIONS
|
|
32
36
|
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
method,
|
|
38
|
-
host,
|
|
39
|
-
port,
|
|
40
|
-
path,
|
|
41
|
-
headers: {
|
|
42
|
-
'content-type': 'application/json; charset=utf-8'
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
const path = `/api/targets`;
|
|
46
|
-
console.log('======================');
|
|
47
|
-
console.log(`About to submit ${scheme} request as JSON on port ${port} to ${host}${path}`);
|
|
48
|
-
// Submit a targets request.
|
|
49
|
-
client.request(getRequestOptions(path), response => {
|
|
50
|
-
// Initialize a collection of data from the response.
|
|
37
|
+
// Gets and outputs the content or error message from a response.
|
|
38
|
+
const getContent = async response => {
|
|
39
|
+
const content = await new Promise(resolve => {
|
|
40
|
+
// Initialize an array of data from the response.
|
|
51
41
|
const chunks = [];
|
|
52
42
|
response
|
|
53
43
|
// If the response throws an error:
|
|
54
|
-
.on('error',
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
.on('error', error => {
|
|
45
|
+
const {message} = error;
|
|
46
|
+
// Report and return the error message.
|
|
47
|
+
console.log(message);
|
|
48
|
+
resolve({error: message});
|
|
57
49
|
})
|
|
58
|
-
//
|
|
50
|
+
// Whenever the response delivers data:
|
|
59
51
|
.on('data', chunk => {
|
|
60
|
-
// Add them to the
|
|
52
|
+
// Add them to the array.
|
|
61
53
|
chunks.push(chunk);
|
|
62
54
|
})
|
|
63
55
|
// When the response is completed:
|
|
64
|
-
.on('end',
|
|
65
|
-
const
|
|
56
|
+
.on('end', () => {
|
|
57
|
+
const contentString = chunks.join('');
|
|
66
58
|
try {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const reports = targetsObj['available reports'];
|
|
71
|
-
// Get the IDs of the available reports.
|
|
72
|
-
const reportIDs = reports.map(report => report.identifier);
|
|
73
|
-
// Choose one at random.
|
|
74
|
-
const [timeStamp, jobID] = reportIDs[Math.floor(Math.random() * reportIDs.length)]
|
|
75
|
-
.split('-');
|
|
76
|
-
const path = `/api/reportIssues/${timeStamp}/${jobID}`;
|
|
77
|
-
console.log('======================');
|
|
78
|
-
console.log(`About to submit ${scheme} request as JSON on port ${port} to ${host}${path}`);
|
|
79
|
-
const requestOptions = getRequestOptions(path);
|
|
80
|
-
// Submit an issues request for it.
|
|
81
|
-
client.request(requestOptions, response => {
|
|
82
|
-
// Initialize a collection of data from the response.
|
|
83
|
-
const chunks = [];
|
|
84
|
-
response
|
|
85
|
-
// If the response throws an error:
|
|
86
|
-
.on('error', async error => {
|
|
87
|
-
// Report it.
|
|
88
|
-
console.log(error.message);
|
|
89
|
-
})
|
|
90
|
-
// If the response delivers data:
|
|
91
|
-
.on('data', chunk => {
|
|
92
|
-
// Add them to the collection.
|
|
93
|
-
chunks.push(chunk);
|
|
94
|
-
})
|
|
95
|
-
// When the response is completed:
|
|
96
|
-
.on('end', async () => {
|
|
97
|
-
const content = chunks.join('');
|
|
98
|
-
try {
|
|
99
|
-
// Output it.
|
|
100
|
-
const contentObj = JSON.parse(content);
|
|
101
|
-
console.log(JSON.stringify(contentObj, null, 2));
|
|
102
|
-
const testRecGoodPath = `/api/testRecForm`;
|
|
103
|
-
console.log('======================');
|
|
104
|
-
console.log(
|
|
105
|
-
`About to submit good ${scheme} POST request as JSON on port ${port} to ${host}${testRecGoodPath}`
|
|
106
|
-
);
|
|
107
|
-
const testRecOptions = getRequestOptions(testRecGoodPath, 'POST');
|
|
108
|
-
// Submit a good test recommendation.
|
|
109
|
-
client.request(testRecOptions, response => {
|
|
110
|
-
// Initialize a collection of data from the response.
|
|
111
|
-
const chunks = [];
|
|
112
|
-
response
|
|
113
|
-
// If the response throws an error:
|
|
114
|
-
.on('error', async error => {
|
|
115
|
-
// Report it.
|
|
116
|
-
console.log(error.message);
|
|
117
|
-
})
|
|
118
|
-
// If the response delivers data:
|
|
119
|
-
.on('data', chunk => {
|
|
120
|
-
// Add them to the collection.
|
|
121
|
-
chunks.push(chunk);
|
|
122
|
-
})
|
|
123
|
-
// When the response is completed:
|
|
124
|
-
.on('end', async () => {
|
|
125
|
-
const content = chunks.join('');
|
|
126
|
-
try {
|
|
127
|
-
// Output it.
|
|
128
|
-
const contentObj = JSON.parse(content);
|
|
129
|
-
console.log(JSON.stringify(contentObj, null, 2));
|
|
130
|
-
const testRecBadPath = `/api/testRecForm`;
|
|
131
|
-
console.log('======================');
|
|
132
|
-
console.log(
|
|
133
|
-
`About to submit bad ${scheme} POST request as JSON on port ${port} to ${host}${testRecBadPath}`
|
|
134
|
-
);
|
|
135
|
-
const testRecOptions = getRequestOptions(testRecBadPath, 'POST');
|
|
136
|
-
// Submit a bad test recommendation.
|
|
137
|
-
client.request(testRecOptions, response => {
|
|
138
|
-
// Initialize a collection of data from the response.
|
|
139
|
-
const chunks = [];
|
|
140
|
-
response
|
|
141
|
-
// If the response throws an error:
|
|
142
|
-
.on('error', async error => {
|
|
143
|
-
// Report it.
|
|
144
|
-
console.log(error.message);
|
|
145
|
-
})
|
|
146
|
-
// If the response delivers data:
|
|
147
|
-
.on('data', chunk => {
|
|
148
|
-
// Add them to the collection.
|
|
149
|
-
chunks.push(chunk);
|
|
150
|
-
})
|
|
151
|
-
// When the response is completed:
|
|
152
|
-
.on('end', async () => {
|
|
153
|
-
const content = chunks.join('');
|
|
154
|
-
try {
|
|
155
|
-
// Output it.
|
|
156
|
-
const contentObj = JSON.parse(content);
|
|
157
|
-
console.log(JSON.stringify(contentObj, null, 2));
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
console.log(error.message);
|
|
161
|
-
console.log(
|
|
162
|
-
`Test recommendation response content: ${content || 'No content'}`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
})
|
|
167
|
-
// Finish sending the bad test recommendation request.
|
|
168
|
-
.end(JSON.stringify({
|
|
169
|
-
what: 'Page Wrongly Recommended',
|
|
170
|
-
url: reports[Math.floor(Math.random() * reports.length)]
|
|
171
|
-
['tested web page']
|
|
172
|
-
.URL,
|
|
173
|
-
why: 'This URL has already been tested'
|
|
174
|
-
}));
|
|
175
|
-
}
|
|
176
|
-
catch (error) {
|
|
177
|
-
console.log(error.message);
|
|
178
|
-
console.log(`Test recommendation response content: ${content || 'No content'}`);
|
|
179
|
-
}
|
|
180
|
-
})
|
|
181
|
-
})
|
|
182
|
-
// Finish sending the good test recommendation request.
|
|
183
|
-
.end(JSON.stringify({
|
|
184
|
-
what: 'Page Not Already Tested',
|
|
185
|
-
url: 'https://pagenotalreadytested.info',
|
|
186
|
-
why: 'This is only a test'
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
catch (error) {
|
|
190
|
-
console.log(error.message);
|
|
191
|
-
console.log(`Issues response content: ${content || 'No content'}`);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
})
|
|
195
|
-
// Finish sending the issues request.
|
|
196
|
-
.end();
|
|
59
|
+
const content = JSON.parse(contentString);
|
|
60
|
+
// Return the response content as an object.
|
|
61
|
+
resolve(content);
|
|
197
62
|
}
|
|
63
|
+
// If it is not JSON:
|
|
198
64
|
catch (error) {
|
|
199
|
-
|
|
200
|
-
|
|
65
|
+
// Return this.
|
|
66
|
+
resolve({error: `Response content not JSON (${contentString})`});
|
|
201
67
|
}
|
|
202
68
|
});
|
|
69
|
+
});
|
|
70
|
+
console.log(JSON.stringify(content, null, 2));
|
|
71
|
+
return content;
|
|
72
|
+
};
|
|
73
|
+
const getRequestOptions = (path, method = 'GET') => ({
|
|
74
|
+
method,
|
|
75
|
+
host,
|
|
76
|
+
port,
|
|
77
|
+
path,
|
|
78
|
+
headers: {
|
|
79
|
+
'content-type': 'application/json; charset=utf-8'
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
// Submits a request, returns the response content, and increments the results.
|
|
83
|
+
const submitRequest = async (results, path, method, body = null) => new Promise(resolve => {
|
|
84
|
+
console.log(`Making ${scheme} ${method} request on port ${port} to ${host}${path}`);
|
|
85
|
+
client.request(getRequestOptions(path, method), async response => {
|
|
86
|
+
const responseContent = await getContent(response);
|
|
87
|
+
results.push(responseContent.error ? 'bad' : 'good');
|
|
88
|
+
resolve(responseContent);
|
|
89
|
+
})
|
|
90
|
+
.on('error', error => {
|
|
91
|
+
console.log(`ERROR submitting request (${JSON.stringify(error, null, 2)})`);
|
|
92
|
+
resolve({error});
|
|
203
93
|
})
|
|
204
|
-
|
|
205
|
-
|
|
94
|
+
.end(body ? JSON.stringify(body) : '');
|
|
95
|
+
});
|
|
96
|
+
// Submits requests to a random Kilotest host.
|
|
97
|
+
const requestService = async () => {
|
|
98
|
+
let method;
|
|
99
|
+
let path;
|
|
100
|
+
let content;
|
|
101
|
+
let reports;
|
|
102
|
+
let report;
|
|
103
|
+
let timeStamp;
|
|
104
|
+
let jobID;
|
|
105
|
+
let description;
|
|
106
|
+
let url;
|
|
107
|
+
const results = [];
|
|
108
|
+
console.log('======================\nRequest 1: Summarize nonexistent reports');
|
|
109
|
+
method = 'POST';
|
|
110
|
+
path = '/api/target';
|
|
111
|
+
content = await submitRequest(results, path, method, {
|
|
112
|
+
what: 'oesntuhaesouht',
|
|
113
|
+
hostname: 'osentuhaoesuht.aoesntuh'
|
|
114
|
+
});
|
|
115
|
+
if (content.error) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.log('======================\nRequest 2: Summarize all available reports');
|
|
119
|
+
method = 'GET';
|
|
120
|
+
path = '/api/targets';
|
|
121
|
+
content = await submitRequest(results, path, method);
|
|
122
|
+
reports = content?.['available reports'] ?? [];
|
|
123
|
+
if (content.error || ! Array.isArray(reports) || ! reports.length) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
console.log('======================\nRequest 3: Summarize matching reports');
|
|
127
|
+
// Choose one available report at random.
|
|
128
|
+
report = reports[Math.floor(Math.random() * reports.length)];
|
|
129
|
+
({description, URL: url} = report?.['tested web page'] ?? ['', '']);
|
|
130
|
+
if (! (description && url)) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
method = 'POST';
|
|
134
|
+
path = '/api/target';
|
|
135
|
+
content = await submitRequest(results, path, method, {
|
|
136
|
+
what: description,
|
|
137
|
+
hostname: new URL(url).hostname ?? ''
|
|
138
|
+
});
|
|
139
|
+
if (content.error) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
console.log(
|
|
143
|
+
'======================\nRequest 4: Summarize the issues in a report'
|
|
144
|
+
);
|
|
145
|
+
[timeStamp, jobID] = report.identifier?.split('-') ?? ['', ''];
|
|
146
|
+
if (! (timeStamp && jobID)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
method = 'GET';
|
|
150
|
+
path = `/api/reportIssues/${timeStamp}/${jobID}`;
|
|
151
|
+
content = await submitRequest(results, path, method);
|
|
152
|
+
if (content.error) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
console.log('======================\nRequest 5: Make a permitted test recommendation');
|
|
156
|
+
method = 'POST';
|
|
157
|
+
path = '/api/testRecForm';
|
|
158
|
+
content = await submitRequest(results, path, method, {
|
|
159
|
+
'description of the web page': 'aoseeou',
|
|
160
|
+
'URL of the web page': 'https://oaaestuh.osneth',
|
|
161
|
+
'reason for testing the web page': 'Just testing'
|
|
162
|
+
});
|
|
163
|
+
if (content.error) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
console.log('======================\nRequest 6: Make an illicit test recommendation');
|
|
167
|
+
content = await submitRequest(results, path, method, {
|
|
168
|
+
'description of the web page': description,
|
|
169
|
+
'URL of the web page': url,
|
|
170
|
+
'reason for testing the web page': 'Just testing'
|
|
171
|
+
});
|
|
172
|
+
console.log(`======================\nResults: ${results}\n`);
|
|
206
173
|
};
|
|
207
174
|
|
|
208
175
|
// EXECUTION
|
|
209
176
|
|
|
210
177
|
// Execute the research agent.
|
|
211
|
-
|
|
178
|
+
(async () => {
|
|
179
|
+
await requestService();
|
|
180
|
+
})();
|
package/target/api.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/*
|
|
2
|
+
api.js
|
|
3
|
+
Responds to the target request.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// IMPORTS
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
getLogs,
|
|
10
|
+
getNowStamp,
|
|
11
|
+
getRandomString,
|
|
12
|
+
getReportData,
|
|
13
|
+
minifyURL
|
|
14
|
+
} = require('../util');
|
|
15
|
+
|
|
16
|
+
// CONSTANTS
|
|
17
|
+
|
|
18
|
+
const thisHost = process.env.THIS_KILOTEST_HOST;
|
|
19
|
+
|
|
20
|
+
// FUNCTIONS
|
|
21
|
+
|
|
22
|
+
// Returns a response to a target request.
|
|
23
|
+
exports.response = async args => {
|
|
24
|
+
const [what = '', hostname = ''] = args;
|
|
25
|
+
const hostLC = hostname.toLowerCase();
|
|
26
|
+
const whatLC = what.toLowerCase();
|
|
27
|
+
const matchingReports = [];
|
|
28
|
+
// Get the non-hidden logs.
|
|
29
|
+
const targetLogs = await getLogs();
|
|
30
|
+
// For each log:
|
|
31
|
+
for (const targetLog of targetLogs) {
|
|
32
|
+
const {jobName} = targetLog;
|
|
33
|
+
const [timeStamp, jobID] = jobName.split('-');
|
|
34
|
+
const {superseded, url: reportURL, what: reportWhat} = targetLog;
|
|
35
|
+
const reportHost = new URL(minifyURL(reportURL)).hostname;
|
|
36
|
+
const reportWhatLC = reportWhat.toLowerCase();
|
|
37
|
+
// If its description or hostname fragment matches the requested page:
|
|
38
|
+
if (
|
|
39
|
+
hostLC && (reportHost.includes(hostLC) || hostLC.includes(reportHost))
|
|
40
|
+
|| whatLC && (reportWhatLC.includes(whatLC) || whatLC.includes(reportWhatLC))
|
|
41
|
+
) {
|
|
42
|
+
// Get data about its report.
|
|
43
|
+
const data = await getReportData(timeStamp, jobID);
|
|
44
|
+
const {
|
|
45
|
+
creationDate,
|
|
46
|
+
daysAgo,
|
|
47
|
+
issueCount,
|
|
48
|
+
toolNames,
|
|
49
|
+
toolCount,
|
|
50
|
+
reporterNames,
|
|
51
|
+
reporterCount,
|
|
52
|
+
violatorCount,
|
|
53
|
+
preventedToolNames,
|
|
54
|
+
preventedToolCount
|
|
55
|
+
} = data;
|
|
56
|
+
matchingReports.push({
|
|
57
|
+
identifier: jobName,
|
|
58
|
+
'creation date': creationDate,
|
|
59
|
+
'days since the creation date': daysAgo,
|
|
60
|
+
'tested web page': {
|
|
61
|
+
description: reportWhat,
|
|
62
|
+
URL: reportURL
|
|
63
|
+
},
|
|
64
|
+
'whether a later report about the same page exists': !! superseded,
|
|
65
|
+
'rule engines that tried to test the page': {
|
|
66
|
+
number: toolCount,
|
|
67
|
+
names: toolNames
|
|
68
|
+
},
|
|
69
|
+
'rule engines that were unable to test the page': {
|
|
70
|
+
number: preventedToolCount,
|
|
71
|
+
names: preventedToolNames
|
|
72
|
+
},
|
|
73
|
+
'rule engines that reported issues': {
|
|
74
|
+
number: reporterCount,
|
|
75
|
+
names: reporterNames
|
|
76
|
+
},
|
|
77
|
+
'number of issues reported': issueCount,
|
|
78
|
+
'number of HTML elements reported as exhibiting issues': violatorCount,
|
|
79
|
+
'URLs for getting data about the reported issues': {
|
|
80
|
+
'for you': `${thisHost}/api/reportIssues/${timeStamp}/${jobID}`,
|
|
81
|
+
'for humans': `${thisHost}/reportIssues/${timeStamp}/${jobID}`
|
|
82
|
+
},
|
|
83
|
+
'URL for getting the full technical report as JSON': `${thisHost}/fullReport.json/${timeStamp}/${jobID}`
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Get a response.
|
|
88
|
+
const content = {
|
|
89
|
+
summary: `This document fulfills a request made by a language model to the Kilotest API. The model asked whether Kilotest had tested a specified web page for front-end quality (i.e. accessibility, usability, and standards conformity) and had made a report of the test results available. The response provides summary information about all and only the available reports of pages that match at least one of the provided fragments, where matching means either including or being included by the provided fragment, case-insensitively. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes API endpoints 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 rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), whose home page contains an introduction and a link to a tutorial.`,
|
|
90
|
+
'tool collection name': 'Kilotest',
|
|
91
|
+
'tool name': 'identifyReportsAboutMatchingWebPages',
|
|
92
|
+
request: {
|
|
93
|
+
'type of request': {
|
|
94
|
+
identifier: 'targets',
|
|
95
|
+
description: 'Identify reports about matching web pages.'
|
|
96
|
+
},
|
|
97
|
+
method: 'POST',
|
|
98
|
+
payload: {
|
|
99
|
+
'all or part of a description of the web page': what,
|
|
100
|
+
'all or part of the hostname of the URL of the web page': hostname
|
|
101
|
+
},
|
|
102
|
+
URLs: {
|
|
103
|
+
'URL of your request': `${thisHost}/api/target`,
|
|
104
|
+
'equivalent URL for humans': 'none'
|
|
105
|
+
},
|
|
106
|
+
'closest ancestor request': null
|
|
107
|
+
},
|
|
108
|
+
'response metadata': {
|
|
109
|
+
identifier: `${getNowStamp()}-${getRandomString(3)}`,
|
|
110
|
+
'date and time': new Date().toISOString(),
|
|
111
|
+
},
|
|
112
|
+
'reports, if any, that may match your page specifications': matchingReports
|
|
113
|
+
};
|
|
114
|
+
return content;
|
|
115
|
+
};
|
package/targets/api.js
CHANGED
|
@@ -74,7 +74,7 @@ exports.response = async () => {
|
|
|
74
74
|
}
|
|
75
75
|
// Get a response.
|
|
76
76
|
const content = {
|
|
77
|
-
summary: `This document fulfills a request made by
|
|
77
|
+
summary: `This document fulfills a request made by a language model to a Kilotest tool. The model requested data about the web pages that Kilotest had tested for front-end quality (i.e. accessibility, usability, and standards conformity) and, for each page, statistics about the results of the tests. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes API endpoints and web UI URLs 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 rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), whose home page contains an introduction and a link to a tutorial.`,
|
|
78
78
|
'tool collection name': 'Kilotest',
|
|
79
79
|
'tool name': 'summarizeQualityOfAllTestedWebPages',
|
|
80
80
|
request: {
|
package/testRecForm/api.js
CHANGED
|
@@ -20,7 +20,7 @@ exports.response = async (what, url, why) => {
|
|
|
20
20
|
await updateRecs(what, url, why);
|
|
21
21
|
// Get a response.
|
|
22
22
|
const content = {
|
|
23
|
-
summary: `This response acknowledges a request made by
|
|
23
|
+
summary: `This response acknowledges a request made by a language model to a Kilotest tool. The model recommended that Kilotest test, for the first time, the ${what} web page at ${url} for front-end quality (i.e. accessibility, usability, and standards conformity). A Kilotest manager usually approves a recommendation within a day. When the recommendation is approved, the testing will be performed and results will become available. You can check for the availability of the results at ${thisHost}/api/targets. Kilotest performs its testing with the help of Testaro, Testilo, and an ensemble of ten rule engines, using a combination of rule- and machine-learning-based methods. Kilotest exposes several API endpoints and several web UI URLs to obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
|
|
24
24
|
'tool collection name': 'Kilotest',
|
|
25
25
|
'tool name': 'recommendQualityTestingOfOneWebPage',
|
|
26
26
|
request: {
|
package/util.js
CHANGED
|
@@ -756,7 +756,7 @@ exports.getToolsFacts = toolIDs => {
|
|
|
756
756
|
// Returns a string describing a count.
|
|
757
757
|
exports.getCountString = (count, singular, plural) => count === 1 ? `1 ${singular}` : `${count} ${plural}`;
|
|
758
758
|
// Minifies a URL for duplicate detection.
|
|
759
|
-
const minifyURL = exports.minifyURL = url => url.replace(/www\.|\/$/g, '');
|
|
759
|
+
const minifyURL = exports.minifyURL = url => url.replace(/www\.|\/$/g, '').toLowerCase();
|
|
760
760
|
// Returns whether a report about the specified page is already available.
|
|
761
761
|
exports.isReportAvailable = async (what, url) => {
|
|
762
762
|
const logs = await getLogs();
|