testaro 17.0.0 → 18.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.
Files changed (34) hide show
  1. package/README.md +7 -51
  2. package/actSpecs.js +0 -14
  3. package/package.json +1 -1
  4. package/run.js +1 -120
  5. package/standardize.js +0 -34
  6. package/testaro/hovInd.js +100 -74
  7. package/tests/testaro.js +0 -1
  8. package/validation/tests/jobs/allHidden.json +0 -2
  9. package/validation/tests/jobs/attVal.json +0 -3
  10. package/validation/tests/jobs/bulk.json +0 -2
  11. package/validation/tests/jobs/docType.json +0 -2
  12. package/validation/tests/jobs/dupAtt.json +0 -3
  13. package/validation/tests/jobs/elements.json +0 -3
  14. package/validation/tests/jobs/embAc.json +0 -3
  15. package/validation/tests/jobs/filter.json +0 -3
  16. package/validation/tests/jobs/focAll.json +0 -3
  17. package/validation/tests/jobs/focInd.json +0 -2
  18. package/validation/tests/jobs/focOp.json +2 -5
  19. package/validation/tests/jobs/focVis.json +0 -1
  20. package/validation/tests/jobs/labClash.json +0 -2
  21. package/validation/tests/jobs/linkTo.json +0 -2
  22. package/validation/tests/jobs/linkUl.json +0 -3
  23. package/validation/tests/jobs/miniText.json +0 -2
  24. package/validation/tests/jobs/motion.json +0 -1
  25. package/validation/tests/jobs/nonTable.json +0 -1
  26. package/validation/tests/jobs/radioSet.json +0 -2
  27. package/validation/tests/jobs/role.json +0 -2
  28. package/validation/tests/jobs/styleDiff.json +0 -2
  29. package/validation/tests/jobs/tabNav.json +0 -3
  30. package/validation/tests/jobs/textNodes.json +0 -5
  31. package/validation/tests/jobs/title.json +0 -2
  32. package/validation/tests/jobs/titledEl.json +0 -2
  33. package/validation/tests/jobs/zIndex.json +0 -3
  34. package/tests/tenon.js +0 -144
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Ensemble testing for web accessibility
4
4
 
5
+ ## Notice: tool change
6
+
7
+ As of this version 18.0.0, the 10 tools integrated by Testaro have been changed to 9 tools. One tool, Tenon, was withdrawn by its owner on 2023-08-06, so it was removed from Testaro.
8
+
5
9
  ## Introduction
6
10
 
7
11
  Testaro is an application for automated web accessibility testing.
@@ -48,13 +52,10 @@ Testaro performs tests of these tools:
48
52
  - [HTML CodeSniffer](https://www.npmjs.com/package/html_codesniffer) (Squiz Labs)
49
53
  - [Nu Html Checker](https://github.com/validator/validator) (World Wide Web Consortium)
50
54
  - [QualWeb core](https://www.npmjs.com/package/@qualweb/core) (University of Lisbon)
51
- - [Tenon](https://tenon.io/documentation/what-tenon-tests.php) (Tenon)
52
55
  - [Testaro](https://www.npmjs.com/package/testaro) (Testaro)
53
56
  - [WAVE API](https://wave.webaim.org/api/) (WebAIM)
54
57
 
55
- The [BBC Accessibility Standards Checker](https://github.com/bbc/bbc-a11y) is not a formal dependency, but some of the tests in the Testaro tool are adaptations of tests of that tool.
56
-
57
- Level Access has acquired Tenon and has announced that it will retire Tenon in August 2023. Thereafter one should expect that Testaro will report failures to perform Tenon tests. In anticipation of the retirement of Tenon, tests have been added to Testaro that approximate those Tenon tests that, empirically, have been found to report issues not reported by other tools.
58
+ Some of the rules tested by Testaro are based on rules of the [BBC Accessibility Standards Checker](https://github.com/bbc/bbc-a11y) and of Tenon, a service that existed until August 2023.
58
59
 
59
60
  ## Rules
60
61
 
@@ -71,7 +72,6 @@ When you ask Testaro to run tests of a tool, you may specify a subset of the rul
71
72
  These tools always perform a fixed set of tests, and Testaro disregards irrelevant results when you specify a set of rules:
72
73
  - ibm
73
74
  - nuVal
74
- - tenon
75
75
  - wave
76
76
 
77
77
  ## Job data
@@ -126,7 +126,7 @@ To run Testaro after installation, provide the environment variables described b
126
126
 
127
127
  ## Payment
128
128
 
129
- All of the tests that Testaro can perform are free of cost, except those performed by the Tenon and WAVE tools. The owner of each of those tools gives new registrants a free allowance of credits before it becomes necessary to pay for use of the API of the tool. The required environment variables for authentication and payment are described below under “Environment variables”.
129
+ All of the tests that Testaro can perform are free of cost, except those performed by the WAVE tool. The owner of that tool gives new registrants a free allowance of credits before it becomes necessary to pay for use of the API of the tool. The required environment variable for authentication and payment is described below under “Environment variables”.
130
130
 
131
131
  ## Process objects
132
132
 
@@ -402,7 +402,7 @@ That means that a test act (i.e. an act with a `type` property having the value
402
402
 
403
403
  If a particular test act either must have or may have any other properties, those properties are specified in the `tests` property in `actSpecs.js`.
404
404
 
405
- When you include a `rules` property, you limit the tests of the tool that are performed or reported. For some tools (`alfa`, `axe`, `continuum`, `htmlcs`, `qualWeb`, and `testaro`), only the specified tests are performed. Other tools (`ibm`, `nuVal`, `tenon`, and `wave`) do not allow such a limitation, so, for those tools, all tests are performed but results are reported from only the specified tests.
405
+ When you include a `rules` property, you limit the tests of the tool that are performed or reported. For some tools (`alfa`, `axe`, `continuum`, `htmlcs`, `qualWeb`, and `testaro`), only the specified tests are performed. Other tools (`ibm`, `nuVal`, and `wave`) do not allow such a limitation, so, for those tools, all tests are performed but results are reported from only the specified tests.
406
406
 
407
407
  The `nuVal` and `testaro` tools require specific formats for the `rules` property. Those formats are described below in the sections about those tools.
408
408
 
@@ -555,48 +555,6 @@ Its `rules` argument is **not** an array of rule IDs, but instead is an array of
555
555
 
556
556
  The `qualWeb` tool performs the ACT rules, WCAG Techniques, and best-practices tests of QualWeb. Only failures and warnings are included in the report. The EARL report of QualWeb is not generated, because it is equivalent to the report of the ACT rules tests.
557
557
 
558
- ###### Tenon
559
-
560
- Most tools require only one act, but the `tenon` tool requires two acts:
561
- - An act of type `tenonRequest`.
562
- - An act of type `test` with `tenon` as the value of `which`.
563
-
564
- Example:
565
-
566
- ```json
567
- {
568
- "type": "tenonRequest",
569
- "id": "a",
570
- "withNewContent": true,
571
- "what": "Tenon API version 2 test request"
572
- }
573
- ```
574
-
575
- followed by
576
-
577
- ```json
578
- {
579
- "type": "test",
580
- "which": "tenon",
581
- "id": "a",
582
- "what": "Tenon API version 2 result retrieval"
583
- }
584
- ```
585
-
586
- The reason is that the Tenon API operates asynchronously. You ask it to perform a test, and it puts your request into a queue. To learn whether Tenon has completed your test, you make a status request. You can continue making status requests until Tenon replies that your test has been completed. Then you submit a request for the test result, and Tenon replies with the result. (As of May 2022, however, status requests were observed to misreport still-running tests as completed. The `tenon` test act works around that by requesting only the result and using the response to determine whether the tests have been completed.)
587
-
588
- Tenon says that tests are typically completed in 3 to 6 seconds but that the latency can be longer, depending on demand.
589
-
590
- Therefore, you can include a `tenonRequest` act early in your job, and a `tenon` test act late in your job. Tenon will move your request through its queue while Testaro is processing your job. When Testaro reaches your `tenon` test act, Tenon will most likely have completed your test. If not, the `tenon` test will wait and then make a second request before giving up.
591
-
592
- Thus, a `tenon` test act actually does not perform any test; it merely collects the result. The page that was active when the `tenonRequest` act was performed is the one that Tenon tests.
593
-
594
- In case you want to perform the Tenon tests more than once in the same job, you can do so. Just give each pair of acts a distinct `id` property, so each `tenon` test act will request the correct result.
595
-
596
- Tenon recommends giving it a public URL rather than giving it the content of a page, if possible. So, it is best to give the `withNewContent` property of the `tenonRequest` act the value `true`, unless the page is not public.
597
-
598
- If a `tenon` test act is included in a job, environment variables named `TENON_USER` and `TENON_PASSWORD` must exist, with your Tenon username and password, respectively, as their values. These could be obtained from [Tenon](https://tenon.io/documentation/overview) until Tenon was closed to new subscribers in or about October 2022.
599
-
600
558
  ###### Testaro
601
559
 
602
560
  If you do not specify rules when using the `testaro` tool, Testaro will test for the rules listed in the `evalRules` object of the `tests/testaro.js` file.
@@ -805,8 +763,6 @@ You may store environment variables in an untracked `.env` file if you wish, and
805
763
 
806
764
  ```conf
807
765
  URL_INJECT=yes
808
- TENON_USER=you@yourdomain.tld
809
- TENON_PASSWORD=yourTenonPassword
810
766
  WAVE_KEY=yourwavekey
811
767
  PROTOCOL=https
812
768
  JOB_URL=yourserver.tld/job
package/actSpecs.js CHANGED
@@ -113,14 +113,6 @@ exports.actSpecs = {
113
113
  what: [false, 'string', 'hasLength', 'comment']
114
114
  }
115
115
  ],
116
- tenonRequest: [
117
- 'Request a Tenon test',
118
- {
119
- id: [true, 'string', 'hasLength', 'ID for this test instance'],
120
- withNewContent: [true, 'boolean', '', 'true: use a URL; false: use page content'],
121
- what: [false, 'string', 'hasLength', 'comment']
122
- }
123
- ],
124
116
  test: [
125
117
  'Perform tests of a tool',
126
118
  {
@@ -174,12 +166,6 @@ exports.actSpecs = {
174
166
  withNewContent: [true, 'boolean', '', 'whether to use a URL instead of page content']
175
167
  }
176
168
  ],
177
- tenon: [
178
- 'Perform Tenon tests',
179
- {
180
- id: [true, 'string', 'hasLength', 'ID of the requested test instance']
181
- }
182
- ],
183
169
  testaro: [
184
170
  'Perform Testaro tests',
185
171
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "17.0.0",
3
+ "version": "18.0.0",
4
4
  "description": "Automation of accessibility testing",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/run.js CHANGED
@@ -42,7 +42,6 @@ const tests = {
42
42
  ibm: 'IBM Accessibility Checker',
43
43
  nuVal: 'Nu Html Checker',
44
44
  qualWeb: 'QualWeb',
45
- tenon: 'Tenon',
46
45
  testaro: 'Testaro',
47
46
  wave: 'WAVE',
48
47
  };
@@ -54,11 +53,6 @@ const browserTypeNames = {
54
53
  };
55
54
  // Items that may be waited for.
56
55
  const waitables = ['url', 'title', 'body'];
57
- // Tenon data.
58
- const tenonData = {
59
- accessToken: '',
60
- requestIDs: {}
61
- };
62
56
  // Strings in log messages indicating errors.
63
57
  const errorWords = [
64
58
  'but not used',
@@ -864,119 +858,6 @@ const doActs = async (report, actIndex, page) => {
864
858
  success: true
865
859
  };
866
860
  }
867
- // Otherwise, if the act is a tenon request:
868
- else if (act.type === 'tenonRequest') {
869
- const {id, withNewContent} = act;
870
- const https = require('https');
871
- // If a Tenon access token has not yet been obtained:
872
- if (! tenonData.accessToken) {
873
- // Authenticate with the Tenon API.
874
- const authData = await new Promise(resolve => {
875
- const request = https.request(
876
- {
877
- host: 'tenon.io',
878
- path: '/api/v2/auth',
879
- port: 443,
880
- protocol: 'https:',
881
- method: 'POST',
882
- headers: {
883
- 'Content-Type': 'application/json',
884
- 'Cache-Control': 'no-cache'
885
- }
886
- },
887
- response => {
888
- let responseData = '';
889
- response.on('data', chunk => {
890
- responseData += chunk;
891
- });
892
- response.on('end', () => {
893
- try {
894
- const responseJSON = JSON.parse(responseData);
895
- return resolve(responseJSON);
896
- }
897
- catch(error) {
898
- return resolve({
899
- error: 'Tenon did not return JSON authentication data.',
900
- responseData
901
- });
902
- }
903
- });
904
- }
905
- );
906
- const tenonUser = process.env.TENON_USER;
907
- const tenonPassword = process.env.TENON_PASSWORD;
908
- const postData = JSON.stringify({
909
- username: tenonUser,
910
- password: tenonPassword
911
- });
912
- request.write(postData);
913
- request.end();
914
- });
915
- // If the authentication succeeded:
916
- if (authData.access_token) {
917
- // Record the access token.
918
- tenonData.accessToken = authData.access_token;
919
- }
920
- // Otherwise, i.e. if the authentication failed:
921
- else {
922
- console.log('ERROR: tenon authentication failed');
923
- }
924
- }
925
- // If a Tenon access token exists:
926
- if (tenonData.accessToken) {
927
- // Request a Tenon test of the page and get a response ID.
928
- const option = {};
929
- // If Tenon is to be given the URL and not the content of the page:
930
- if (withNewContent) {
931
- // Specify this.
932
- option.url = page.url();
933
- }
934
- // Otherwise, i.e. if Tenon is to be given the page content:
935
- else {
936
- // Specify this.
937
- option.src = await page.content();
938
- }
939
- // Request a Tenon test and get a response ID.
940
- const responseID = await new Promise(resolve => {
941
- const request = https.request(
942
- {
943
- host: 'tenon.io',
944
- path: '/api/v2/',
945
- port: 443,
946
- protocol: 'https:',
947
- method: 'POST',
948
- headers: {
949
- 'Content-Type': 'application/json',
950
- 'Cache-Control': 'no-cache',
951
- Authorization: `Bearer ${tenonData.accessToken}`
952
- }
953
- },
954
- response => {
955
- let resultJSON = '';
956
- response.on('data', chunk => {
957
- resultJSON += chunk;
958
- });
959
- // When the data arrive, return them as an object.
960
- response.on('end', () => {
961
- try {
962
- const result = JSON.parse(resultJSON);
963
- resolve(result.data.responseID || '');
964
- }
965
- catch (error) {
966
- console.log('ERROR: Tenon did not return JSON.');
967
- resolve('');
968
- }
969
- });
970
- }
971
- );
972
- const postData = JSON.stringify(option);
973
- request.write(postData);
974
- request.end();
975
- });
976
- // Record the response ID.
977
- tenonData.requestIDs[id] = responseID || '';
978
- }
979
- }
980
861
  // Otherwise, if the act performs the tests of a tool:
981
862
  else if (act.type === 'test') {
982
863
  // Add a description of the test to the act.
@@ -997,7 +878,7 @@ const doActs = async (report, actIndex, page) => {
997
878
  }
998
879
  };
999
880
  try {
1000
- const args = [act.which === 'tenon' ? tenonData : page, options];
881
+ const args = [page, options];
1001
882
  testReport = await require(`./tests/${act.which}`).reporter(... args);
1002
883
  testReport.result.success = true;
1003
884
  }
package/standardize.js CHANGED
@@ -381,40 +381,6 @@ const convert = (toolName, result, standardResult) => {
381
381
  doQualWeb(result, standardResult, 'best-practices');
382
382
  }
383
383
  }
384
- // tenon
385
- else if (toolName === 'tenon' && result.data && result.data.resultSet) {
386
- result.data.resultSet.forEach(item => {
387
- const identifiers = getIdentifiers(
388
- item.errorSnippet.replace(/&lt;/g, '<').replace(/&gt;/g, '>')
389
- );
390
- if (! identifiers[0] && item.xpath) {
391
- const tagNameArray = item.xpath.match(/^.+\/([^/[]+)/);
392
- if (tagNameArray && tagNameArray.length === 2) {
393
- identifiers[0] = tagNameArray[1].toUpperCase();
394
- }
395
- }
396
- const instance = {
397
- ruleID: item.tID ? item.tID.toString() : '',
398
- what: item.errorTitle || '',
399
- ordinalSeverity: Math.min(
400
- 3, Math.max(0, Math.round((item.certainty || 0) * (item.priority || 0) / 3333))
401
- ),
402
- tagName: identifiers[0],
403
- id: identifiers[1],
404
- location: {
405
- doc: 'dom',
406
- type: 'xpath',
407
- spec: item.xpath || ''
408
- },
409
- excerpt: cap(item.errorSnippet || '').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
410
- };
411
- standardResult.instances.push(instance);
412
- });
413
- standardResult.totals = [0, 0, 0, 0];
414
- standardResult.instances.forEach(instance => {
415
- standardResult.totals[instance.ordinalSeverity]++;
416
- });
417
- }
418
384
  // testaro
419
385
  else if (toolName === 'testaro') {
420
386
  const rules = result.rules ? Object.keys(result.rules) : [];
package/testaro/hovInd.js CHANGED
@@ -140,88 +140,114 @@ exports.reporter = async (page, withItems, sampleSize = 20) => {
140
140
  for (const loc of sample) {
141
141
  // Get its style properties.
142
142
  const preStyles = await getHoverStyles(loc);
143
- // Focus it.
144
- await loc.focus();
145
- // Get its style properties.
146
- const focStyles = await getHoverStyles(loc);
147
- // Hover over it.
148
- await loc.hover();
149
- // Get its style properties.
150
- const fhStyles = await getHoverStyles(loc);
151
- // Blur it.
152
- await loc.blur({
153
- timeout: 500
154
- });
155
- // Get its style properties.
156
- const hovStyles = await getHoverStyles(loc);
157
- // If all 4 style declarations belong to the same element:
158
- if ([focStyles, fhStyles, hovStyles].every(style => style.code === preStyles.code)) {
159
- // Get data on the element if itemization is required.
160
- const elData = withItems ? await getLocatorData(loc) : null;
161
- // If the hover cursor is nonstandard:
162
- const cursorData = getCursorData(hovStyles);
163
- if (! cursorData.ok) {
164
- // Add to the totals.
165
- totals[2] += psRatio;
166
- data.typeTotals.badCursor += psRatio;
167
- // If itemization is required:
168
- if (withItems) {
169
- // Add an instance to the result.
170
- standardInstances.push({
171
- ruleID: 'hovInd',
172
- what: `Element has a nonstandard hover cursor (${cursorData.cursor})`,
173
- ordinalSeverity: 2,
174
- tagName: elData.tagName,
175
- id: elData.id,
176
- location: elData.location,
177
- excerpt: elData.excerpt
178
- });
143
+ // Try to focus it.
144
+ try {
145
+ await loc.focus({timeout: 500});
146
+ // If focusing succeeds, get its style properties.
147
+ const focStyles = await getHoverStyles(loc);
148
+ // Try to hover over it.
149
+ try {
150
+ await loc.hover({timeout: 500});
151
+ // If hovering succeeds, get its style properties.
152
+ const fhStyles = await getHoverStyles(loc);
153
+ // Try to blur it.
154
+ try {
155
+ await loc.blur({timeout: 500});
156
+ // If blurring succeeds, get its style properties.
157
+ const hovStyles = await getHoverStyles(loc);
158
+ // If all 4 style declarations belong to the same element:
159
+ if ([focStyles, fhStyles, hovStyles].every(style => style.code === preStyles.code)) {
160
+ // Get data on the element if itemization is required.
161
+ const elData = withItems ? await getLocatorData(loc) : null;
162
+ // If the hover cursor is nonstandard:
163
+ const cursorData = getCursorData(hovStyles);
164
+ if (! cursorData.ok) {
165
+ // Add to the totals.
166
+ totals[2] += psRatio;
167
+ data.typeTotals.badCursor += psRatio;
168
+ // If itemization is required:
169
+ if (withItems) {
170
+ // Add an instance to the result.
171
+ standardInstances.push({
172
+ ruleID: 'hovInd',
173
+ what: `Element has a nonstandard hover cursor (${cursorData.cursor})`,
174
+ ordinalSeverity: 2,
175
+ tagName: elData.tagName,
176
+ id: elData.id,
177
+ location: elData.location,
178
+ excerpt: elData.excerpt
179
+ });
180
+ }
181
+ }
182
+ // If the element is a button and the hover and default states are not distinct:
183
+ if (hovStyles.tagName === 'BUTTON' && areAlike(preStyles, hovStyles)) {
184
+ // Add to the totals.
185
+ totals[1] += psRatio;
186
+ data.typeTotals.hoverLikeDefault += psRatio;
187
+ // If itemization is required:
188
+ if (withItems) {
189
+ // Add an instance to the result.
190
+ standardInstances.push({
191
+ ruleID: 'hovInd',
192
+ what: 'Element border, outline, and background color do not change when hovered over',
193
+ ordinalSeverity: 1,
194
+ tagName: elData.tagName,
195
+ id: elData.id,
196
+ location: elData.location,
197
+ excerpt: elData.excerpt
198
+ });
199
+ }
200
+ }
201
+ // If the hover and focus-hover states are indistinct but differ from the default state:
202
+ if (areAlike(hovStyles, focStyles) && ! areAlike(hovStyles, preStyles)) {
203
+ // Add to the totals.
204
+ totals[1] += psRatio;
205
+ data.typeTotals.hoverLikeFocus += psRatio;
206
+ // If itemization is required:
207
+ if (withItems) {
208
+ // Add an instance to the result.
209
+ standardInstances.push({
210
+ ruleID: 'hovInd',
211
+ what: 'Element border, outline, and background color are alike on hover and focus',
212
+ ordinalSeverity: 1,
213
+ tagName: elData.tagName,
214
+ id: elData.id,
215
+ location: elData.location,
216
+ excerpt: elData.excerpt
217
+ });
218
+ }
219
+ }
220
+ }
221
+ // Otherwise, i.e. if the style properties do not all belong to the same element:
222
+ else {
223
+ // Report this and quit.
224
+ data.prevented = true;
225
+ data.error = 'ERROR: Page changes on focus or hover prevent test';
226
+ break;
227
+ }
179
228
  }
180
- }
181
- // If the element is a button and the hover and default states are not distinct:
182
- if (hovStyles.tagName === 'BUTTON' && areAlike(preStyles, hovStyles)) {
183
- // Add to the totals.
184
- totals[1] += psRatio;
185
- data.typeTotals.hoverLikeDefault += psRatio;
186
- // If itemization is required:
187
- if (withItems) {
188
- // Add an instance to the result.
189
- standardInstances.push({
190
- ruleID: 'hovInd',
191
- what: 'Element border, outline, and background color do not change when hovered over',
192
- ordinalSeverity: 1,
193
- tagName: elData.tagName,
194
- id: elData.id,
195
- location: elData.location,
196
- excerpt: elData.excerpt
197
- });
229
+ // If blurring fails:
230
+ catch(error) {
231
+ // Report this.
232
+ data.prevented = true;
233
+ data.error = 'ERROR: Page changes on focus or hover prevent test';
234
+ break;
198
235
  }
199
236
  }
200
- // If the hover and focus-hover states are indistinct but differ from the default state:
201
- if (areAlike(hovStyles, focStyles) && ! areAlike(hovStyles, preStyles)) {
202
- // Add to the totals.
203
- totals[1] += psRatio;
204
- data.typeTotals.hoverLikeFocus += psRatio;
205
- // If itemization is required:
206
- if (withItems) {
207
- // Add an instance to the result.
208
- standardInstances.push({
209
- ruleID: 'hovInd',
210
- what: 'Element border, outline, and background color are alike on hover and focus',
211
- ordinalSeverity: 1,
212
- tagName: elData.tagName,
213
- id: elData.id,
214
- location: elData.location,
215
- excerpt: elData.excerpt
216
- });
217
- }
237
+ // If hovering fails:
238
+ catch(error) {
239
+ // Report this.
240
+ data.prevented = true;
241
+ data.error = 'ERROR: Page changes on focus or hover prevent test';
242
+ break;
218
243
  }
219
244
  }
220
- // Otherwise, i.e. if the style properties do not all belong to the same element:
221
- else {
245
+ // If focusing fails:
246
+ catch(error) {
222
247
  // Report this.
223
248
  data.prevented = true;
224
249
  data.error = 'ERROR: Page changes on focus or hover prevent test';
250
+ break;
225
251
  }
226
252
  }
227
253
  // Round the totals.
package/tests/testaro.js CHANGED
@@ -30,7 +30,6 @@ const evalRules = {
30
30
  linkTitle: 'links with title attributes repeating text content',
31
31
  linkTo: 'links without destinations',
32
32
  linkUl: 'missing underlines on inline links',
33
- menuNav: 'nonstandard keyboard navigation between focusable menu items',
34
33
  miniText: 'text smaller than 11 pixels',
35
34
  motion: 'motion without user request',
36
35
  nonTable: 'table elements used for layout',
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "entirely or mainly hidden page",
21
20
  "stopOnFail": true,
22
21
  "expect": [
23
22
  [
@@ -60,7 +59,6 @@
60
59
  {
61
60
  "type": "test",
62
61
  "which": "testaro",
63
- "what": "entirely or mainly hidden page",
64
62
  "stopOnFail": true,
65
63
  "expect": [
66
64
  [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "attribute values",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -43,7 +42,6 @@
43
42
  {
44
43
  "type": "test",
45
44
  "which": "testaro",
46
- "what": "attribute values",
47
45
  "withItems": false,
48
46
  "stopOnFail": true,
49
47
  "expect": [
@@ -149,7 +147,6 @@
149
147
  {
150
148
  "type": "test",
151
149
  "which": "testaro",
152
- "what": "attribute values",
153
150
  "withItems": false,
154
151
  "stopOnFail": true,
155
152
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "visible element count",
21
20
  "stopOnFail": true,
22
21
  "expect": [
23
22
  [
@@ -45,7 +44,6 @@
45
44
  {
46
45
  "type": "test",
47
46
  "which": "testaro",
48
- "what": "visible element count",
49
47
  "stopOnFail": true,
50
48
  "expect": [
51
49
  [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "doctype",
21
20
  "stopOnFail": true,
22
21
  "expect": [
23
22
  [
@@ -45,7 +44,6 @@
45
44
  {
46
45
  "type": "test",
47
46
  "which": "testaro",
48
- "what": "doctype",
49
47
  "stopOnFail": true,
50
48
  "expect": [
51
49
  [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "elements with duplicate attributes",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -45,7 +44,6 @@
45
44
  {
46
45
  "type": "test",
47
46
  "which": "testaro",
48
- "what": "elements with duplicate attributes",
49
47
  "withItems": true,
50
48
  "stopOnFail": true,
51
49
  "expect": [
@@ -108,7 +106,6 @@
108
106
  {
109
107
  "type": "test",
110
108
  "which": "testaro",
111
- "what": "elements with duplicate attributes",
112
109
  "withItems": false,
113
110
  "stopOnFail": true,
114
111
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "elements",
21
20
  "stopOnFail": true,
22
21
  "expect": [
23
22
  [
@@ -38,7 +37,6 @@
38
37
  {
39
38
  "type": "test",
40
39
  "which": "testaro",
41
- "what": "elements",
42
40
  "stopOnFail": true,
43
41
  "expect": [
44
42
  [
@@ -59,7 +57,6 @@
59
57
  {
60
58
  "type": "test",
61
59
  "which": "testaro",
62
- "what": "elements",
63
60
  "stopOnFail": true,
64
61
  "expect": [
65
62
  [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "embedded active elements",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -45,7 +44,6 @@
45
44
  {
46
45
  "type": "test",
47
46
  "which": "testaro",
48
- "what": "embedded active elements",
49
47
  "withItems": true,
50
48
  "stopOnFail": true,
51
49
  "expect": [
@@ -108,7 +106,6 @@
108
106
  {
109
107
  "type": "test",
110
108
  "which": "testaro",
111
- "what": "embedded active elements",
112
109
  "withItems": false,
113
110
  "stopOnFail": true,
114
111
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "filter",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -45,7 +44,6 @@
45
44
  {
46
45
  "type": "test",
47
46
  "which": "testaro",
48
- "what": "filter",
49
47
  "withItems": true,
50
48
  "stopOnFail": true,
51
49
  "expect": [
@@ -118,7 +116,6 @@
118
116
  {
119
117
  "type": "test",
120
118
  "which": "testaro",
121
- "what": "filter",
122
119
  "withItems": false,
123
120
  "stopOnFail": true,
124
121
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "Tab-focusability",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -45,7 +44,6 @@
45
44
  {
46
45
  "type": "test",
47
46
  "which": "testaro",
48
- "what": "Tab-focusability",
49
47
  "withItems": true,
50
48
  "stopOnFail": true,
51
49
  "expect": [
@@ -93,7 +91,6 @@
93
91
  {
94
92
  "type": "test",
95
93
  "which": "testaro",
96
- "what": "Tab-focusability",
97
94
  "withItems": true,
98
95
  "stopOnFail": true,
99
96
  "expect": [
@@ -18,7 +18,6 @@
18
18
  "type": "test",
19
19
  "which": "testaro",
20
20
  "withItems": true,
21
- "what": "focus indication",
22
21
  "stopOnFail": true,
23
22
  "expect": [
24
23
  [
@@ -134,7 +133,6 @@
134
133
  "type": "test",
135
134
  "which": "testaro",
136
135
  "withItems": false,
137
- "what": "focus indication",
138
136
  "stopOnFail": true,
139
137
  "expect": [
140
138
  [
@@ -6,8 +6,8 @@
6
6
  "acts": [
7
7
  {
8
8
  "type": "launch",
9
- "which": "webkit",
10
- "what": "sole focOp-compatible browser"
9
+ "which": "chromium",
10
+ "what": "usual browser"
11
11
  },
12
12
  {
13
13
  "type": "url",
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "focusability and operability",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -50,7 +49,6 @@
50
49
  {
51
50
  "type": "test",
52
51
  "which": "testaro",
53
- "what": "focusability and operability",
54
52
  "withItems": true,
55
53
  "stopOnFail": true,
56
54
  "expect": [
@@ -158,7 +156,6 @@
158
156
  {
159
157
  "type": "test",
160
158
  "which": "testaro",
161
- "what": "focusability and operability",
162
159
  "withItems": false,
163
160
  "stopOnFail": true,
164
161
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "off-display focused links",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "Labeling",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -50,7 +49,6 @@
50
49
  {
51
50
  "type": "test",
52
51
  "which": "testaro",
53
- "what": "Labeling",
54
52
  "withItems": true,
55
53
  "stopOnFail": true,
56
54
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "links without destinations",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -80,7 +79,6 @@
80
79
  {
81
80
  "type": "test",
82
81
  "which": "testaro",
83
- "what": "links without destinations",
84
82
  "withItems": false,
85
83
  "stopOnFail": true,
86
84
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "adjacent-link underlining",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -45,7 +44,6 @@
45
44
  {
46
45
  "type": "test",
47
46
  "which": "testaro",
48
- "what": "adjacent-link underlining",
49
47
  "withItems": true,
50
48
  "stopOnFail": true,
51
49
  "expect": [
@@ -78,7 +76,6 @@
78
76
  {
79
77
  "type": "test",
80
78
  "which": "testaro",
81
- "what": "inline-link underlining",
82
79
  "withItems": true,
83
80
  "stopOnFail": true,
84
81
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "small text",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -95,7 +94,6 @@
95
94
  {
96
95
  "type": "test",
97
96
  "which": "testaro",
98
- "what": "small text",
99
97
  "withItems": false,
100
98
  "stopOnFail": true,
101
99
  "expect": [
@@ -59,7 +59,6 @@
59
59
  {
60
60
  "type": "test",
61
61
  "which": "testaro",
62
- "what": "spontaneous change of content",
63
62
  "stopOnFail": true,
64
63
  "expect": [
65
64
  [
@@ -104,7 +104,6 @@
104
104
  {
105
105
  "type": "test",
106
106
  "which": "testaro",
107
- "what": "tables used for layout",
108
107
  "withItems": false,
109
108
  "stopOnFail": true,
110
109
  "expect": [
@@ -45,7 +45,6 @@
45
45
  {
46
46
  "type": "test",
47
47
  "which": "testaro",
48
- "what": "radioSet",
49
48
  "withItems": true,
50
49
  "stopOnFail": true,
51
50
  "expect": [
@@ -148,7 +147,6 @@
148
147
  {
149
148
  "type": "test",
150
149
  "which": "testaro",
151
- "what": "radioSet",
152
150
  "withItems": false,
153
151
  "stopOnFail": true,
154
152
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "role",
21
20
  "stopOnFail": true,
22
21
  "expect": [
23
22
  [
@@ -50,7 +49,6 @@
50
49
  {
51
50
  "type": "test",
52
51
  "which": "testaro",
53
- "what": "role",
54
52
  "stopOnFail": true,
55
53
  "expect": [
56
54
  [
@@ -18,7 +18,6 @@
18
18
  "type": "test",
19
19
  "which": "testaro",
20
20
  "withItems": true,
21
- "what": "styleDiff",
22
21
  "stopOnFail": true,
23
22
  "expect": [
24
23
  [
@@ -51,7 +50,6 @@
51
50
  "type": "test",
52
51
  "which": "testaro",
53
52
  "withItems": true,
54
- "what": "styleDiff",
55
53
  "stopOnFail": true,
56
54
  "expect": [
57
55
  [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "tab-list navigation",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -50,7 +49,6 @@
50
49
  {
51
50
  "type": "test",
52
51
  "which": "testaro",
53
- "what": "tab-list navigation",
54
52
  "withItems": true,
55
53
  "stopOnFail": true,
56
54
  "expect": [
@@ -138,7 +136,6 @@
138
136
  {
139
137
  "type": "test",
140
138
  "which": "testaro",
141
- "what": "tab-list navigation",
142
139
  "withItems": false,
143
140
  "stopOnFail": true,
144
141
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "text nodes",
21
20
  "stopOnFail": true,
22
21
  "expect": [
23
22
  [
@@ -38,7 +37,6 @@
38
37
  {
39
38
  "type": "test",
40
39
  "which": "testaro",
41
- "what": "text nodes",
42
40
  "stopOnFail": true,
43
41
  "expect": [
44
42
  [
@@ -59,7 +57,6 @@
59
57
  {
60
58
  "type": "test",
61
59
  "which": "testaro",
62
- "what": "text nodes",
63
60
  "stopOnFail": true,
64
61
  "expect": [
65
62
  [
@@ -85,7 +82,6 @@
85
82
  {
86
83
  "type": "test",
87
84
  "which": "testaro",
88
- "what": "text nodes",
89
85
  "stopOnFail": true,
90
86
  "expect": [
91
87
  [
@@ -170,7 +166,6 @@
170
166
  {
171
167
  "type": "test",
172
168
  "which": "testaro",
173
- "what": "text nodes",
174
169
  "stopOnFail": true,
175
170
  "expect": [
176
171
  [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "document title",
21
20
  "stopOnFail": true,
22
21
  "expect": [
23
22
  [
@@ -40,7 +39,6 @@
40
39
  {
41
40
  "type": "test",
42
41
  "which": "testaro",
43
- "what": "document title",
44
42
  "stopOnFail": true,
45
43
  "expect": [
46
44
  [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "title attributes on inappropriate elements",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -85,7 +84,6 @@
85
84
  {
86
85
  "type": "test",
87
86
  "which": "testaro",
88
- "what": "title attributes on inappropriate elements",
89
87
  "withItems": false,
90
88
  "stopOnFail": true,
91
89
  "expect": [
@@ -17,7 +17,6 @@
17
17
  {
18
18
  "type": "test",
19
19
  "which": "testaro",
20
- "what": "zIndex",
21
20
  "withItems": true,
22
21
  "stopOnFail": true,
23
22
  "expect": [
@@ -50,7 +49,6 @@
50
49
  {
51
50
  "type": "test",
52
51
  "which": "testaro",
53
- "what": "zIndex",
54
52
  "withItems": true,
55
53
  "stopOnFail": true,
56
54
  "expect": [
@@ -108,7 +106,6 @@
108
106
  {
109
107
  "type": "test",
110
108
  "which": "testaro",
111
- "what": "zIndex",
112
109
  "withItems": false,
113
110
  "stopOnFail": true,
114
111
  "expect": [
package/tests/tenon.js DELETED
@@ -1,144 +0,0 @@
1
- /*
2
- tenon
3
- This test processes a previously requested test by the Tenon API.
4
- */
5
- const https = require('https');
6
- // Wait until a time limit in seconds expires.
7
- const wait = timeLimit => new Promise(resolve => setTimeout(resolve, 1000 * timeLimit));
8
- exports.reporter = async (tenonData, options) => {
9
- const {id, rules} = options;
10
- if (tenonData && tenonData.accessToken && tenonData.requestIDs && tenonData.requestIDs[id]) {
11
- // Shared request options.
12
- const requestOptions = {
13
- host: 'tenon.io',
14
- path: `/api/v2/${tenonData.requestIDs[id]}`,
15
- port: 443,
16
- protocol: 'https:',
17
- headers: {
18
- Authorization: `Bearer ${tenonData.accessToken}`,
19
- 'Cache-Control': 'no-cache'
20
- }
21
- };
22
- // Gets the test status.
23
- const getStatus = async () => {
24
- const testStatus = await new Promise((resolve, reject) => {
25
- requestOptions.method = 'HEAD';
26
- const statusRequest = https.request(requestOptions, statusResponse => {
27
- const {statusCode} = statusResponse;
28
- resolve(statusCode);
29
- });
30
- statusRequest.on('error', error => {
31
- console.log(`ERROR getting Tenon test status (${error.message})`);
32
- reject(`ERROR getting Tenon test status (${error.message})`);
33
- });
34
- statusRequest.end();
35
- });
36
- return testStatus;
37
- };
38
- // Gets the test result.
39
- const getResult = async () => {
40
- const testResult = await new Promise(resolve => {
41
- requestOptions.method = 'GET';
42
- const resultRequest = https.request(requestOptions, resultResponse => {
43
- let resultJSON = '';
44
- resultResponse.on('data', chunk => {
45
- resultJSON += chunk;
46
- });
47
- resultResponse.on('end', () => {
48
- try {
49
- const result = JSON.parse(resultJSON);
50
- resolve(result);
51
- }
52
- catch(error) {
53
- console.log(`ERROR getting Tenon test result (${resultJSON.slice(0, 80)} …)`);
54
- resolve({
55
- error: 'ERROR getting Tenon test result',
56
- resultStart: resultJSON.slice(0, 80)
57
- });
58
- }
59
- });
60
- });
61
- resultRequest.end();
62
- });
63
- // If any rules were specified and any test results exist:
64
- if (
65
- rules
66
- && rules.length
67
- && testResult.data
68
- && testResult.data.resultSet
69
- && testResult.data.resultSet.length
70
- ) {
71
- // Delete the results of tests of rules not specified.
72
- testResult.data.resultSet = testResult.data.resultSet.filter(
73
- result => rules.includes(result.tID.toString())
74
- );
75
- }
76
- return testResult;
77
- };
78
- // Get the test status (not reliable: may say 200 instead of 202).
79
- let testStatus = await getStatus();
80
- // If the test is still in the Tenon queue:
81
- if (testStatus === 202) {
82
- // Wait 5 seconds.
83
- await wait(5);
84
- // Get the test status again.
85
- testStatus = await getStatus();
86
- }
87
- // If the test has allegedly been completed:
88
- if (testStatus === 200) {
89
- // Get the test result.
90
- let testResult = await getResult();
91
- // If the test is still in the Tenon queue:
92
- let {status} = testResult;
93
- if (status === 202) {
94
- // Wait 5 seconds.
95
- await wait(5);
96
- // Get the test result again.
97
- testResult = await getResult();
98
- // If the test is still in the Tenon queue:
99
- status = testResult.status;
100
- if (status === 202) {
101
- // Wait 15 more seconds.
102
- await wait(15);
103
- // Get the test result again.
104
- testResult = await getResult();
105
- status = testResult.status;
106
- }
107
- }
108
- // If the test has really been completed:
109
- if (status === 200) {
110
- // Return its result.
111
- return {result: testResult};
112
- }
113
- // Otherwise, i.e. if the test is still running or failed:
114
- else {
115
- return {
116
- result: {
117
- prevented: true,
118
- error: 'ERROR: Tenon test stalled or crashed',
119
- status
120
- }
121
- };
122
- }
123
- }
124
- // Otherwise, if the test is still running after a wait for its status:
125
- else {
126
- // Report the test status.
127
- return {
128
- result: {
129
- prevented: true,
130
- error: 'ERROR: test status not completed',
131
- testStatus
132
- }
133
- };
134
- }
135
- }
136
- else {
137
- return {
138
- result: {
139
- prevented: true,
140
- error: 'ERROR: tenon authorization and test data incomplete'
141
- }
142
- };
143
- }
144
- };