@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 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 {what, url, why} = postData;
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
- await serveError({message: 'ERROR: A report on the page is already available'}, response, false);
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
- const responseData = await require(path.join(__dirname, 'testRecForm', 'api'))
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
- - [Summarize test results for all tested pages](https://kilotest.com/targets.html)
63
- - Provide statistics about issues reported in one report: `https://kilotest.com/reportIssues.html/{timeStamp}/{jobID}`
64
- - Provide details about one issue reported in one report: `https://kilotest.com/reportIssue.html/{issueID}/{timeStamp}/{jobID}`
65
- - Provide 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
- - [Receive a recommendation to test a not-yet-tested page](`https://kilotest.com/testRecForm.html`)
67
- - Receive a recommendation to retest a previously tested page: `https://kilotest.com/retestRecForm.html/{timeStamp}/{jobID}`
68
- - [Provide statistics about frequently reported issues across all pages](https://kilotest.com/issues.html)
69
- - [List the operations that Kilotest managers are authorized to request](https://kilotest.com/manage.html)
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, including direct immediate testing. The implementation is currently in an alpha phase and offers 3 tools:
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 (a report contains the records of one session in which a web page is tested)
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 agents give thorough, accurate and truthful answers when users ask how accessible, usable, or standard conformant web pages are. Kilotest integrates ten independent rule engines to test web pages and report results with multipage, single-page, and element-level statistics and details.
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 AI agents](https://kilotest.com/llms-full.txt).
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. Before calling recommendQualityTestingOfOneWebPage and waiting for testing to occur, call this tool to check whether a report about the page is available for immediate use.',
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: Returns summary data about every non-hidden report available from Kilotest, including the name and URL of the tested web page, when the testing was performed, how many front-end quality (i.e. accessibility, usability, and standard-conformity) issues were reported, and URLs for retrieving more detailed data from the report. This is the first endpoint to call if you want data about a particular web page. The result will tell you whether a report on that page already exists. If so, you can retrieve data from it. If not, you can use the submitWebAccessibilityTestRequest endpoint to recommend the page for testing.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jrpool/kilotest",
3
- "version": "33.0.2",
3
+ "version": "34.0.0",
4
4
  "description": "An ensemble testing service with a focus on accessibility",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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 = [process.env.LOCAL_KILOTEST_HOST, process.env.DEPLOYED_KILOTEST_HOST];
24
- // Randomly chosen Kilotest host.
25
- const kilotestHost = kilotestHosts[Math.random() < 0.5 ? 0 : 1];
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 host = hostParts[1];
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
- // Submits three request to a random Kilotest host.
34
- const requestService = async () => {
35
- const client = scheme === 'https' ? httpsClient : httpClient;
36
- const getRequestOptions = (path, method = 'GET') => ({
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', async error => {
55
- // Report it.
56
- console.log(error.message);
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
- // If the response delivers data:
50
+ // Whenever the response delivers data:
59
51
  .on('data', chunk => {
60
- // Add them to the collection.
52
+ // Add them to the array.
61
53
  chunks.push(chunk);
62
54
  })
63
55
  // When the response is completed:
64
- .on('end', async () => {
65
- const content = chunks.join('');
56
+ .on('end', () => {
57
+ const contentString = chunks.join('');
66
58
  try {
67
- // Output it.
68
- const targetsObj = JSON.parse(content);
69
- console.log(JSON.stringify(targetsObj, null, 2));
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
- console.log(error.message);
200
- console.log(`Targets response content: ${content || 'No content'}`);
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
- // Finish sending the targets request.
205
- .end();
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
- requestService();
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 an agent to the Kilotest service. The agent requested data about the web pages that Kilotest had tested for accessibility, usability, and standard-conformity and, for each page, statistics about the results of the tests. Kilotest, with the help of Testaro, Testilo, and an ensemble of ten rule engines, performs tests on web pages, using a combination of rule- and machine-learning-based methods, and produces reports. Kilotest exposes API endpoints for agents and web UI URLs for humans to recommend web pages for testing and to obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of 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.`,
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: {
@@ -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 an agent to the Kilotest service. The agent recommended that Kilotest test, for the first time, the ${what} web page at ${url} for accessibility, usability, and standard-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 for agents and several web UI URLs for humans to obtain information from Kilotest reports. To learn more about Kilotest and the advangages of testing with an ensemble of rule engines, visit the deployed instance of Kilotest (${process.env.DEPLOYED_KILOTEST_HOST}), which contains an introduction on its home page and a tutorial.`,
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();