testaro 74.2.3 → 75.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 (63) hide show
  1. package/LICENSE +2 -0
  2. package/README.md +52 -59
  3. package/actSpecs-doc.md +25 -1
  4. package/actSpecs.js +6 -4
  5. package/package.json +1 -1
  6. package/procs/catalog.js +0 -1
  7. package/procs/dateTime.js +4 -2
  8. package/procs/doActs.js +15 -8
  9. package/procs/launch.js +0 -1
  10. package/procs/shoot.js +48 -43
  11. package/testaro/adbID.js +2 -2
  12. package/testaro/allCapStyle.js +2 -2
  13. package/testaro/allCaps.js +3 -3
  14. package/testaro/allHidden.js +2 -2
  15. package/testaro/allSlanted.js +2 -2
  16. package/testaro/altScheme.js +2 -2
  17. package/testaro/attVal.js +2 -2
  18. package/testaro/autocomplete.js +3 -2
  19. package/testaro/bulk.js +2 -2
  20. package/testaro/buttonMenu.js +3 -3
  21. package/testaro/captionLoc.js +2 -2
  22. package/testaro/datalistRef.js +2 -2
  23. package/testaro/distortion.js +2 -2
  24. package/testaro/docType.js +1 -1
  25. package/testaro/dupAtt.js +2 -2
  26. package/testaro/embAc.js +2 -2
  27. package/testaro/focAll.js +2 -2
  28. package/testaro/focAndOp.js +2 -2
  29. package/testaro/focInd.js +2 -2
  30. package/testaro/focVis.js +2 -2
  31. package/testaro/headEl.js +2 -2
  32. package/testaro/headingAmb.js +2 -2
  33. package/testaro/hovInd.js +2 -2
  34. package/testaro/hover.js +2 -2
  35. package/testaro/hr.js +2 -2
  36. package/testaro/imageLink.js +2 -2
  37. package/testaro/labClash.js +2 -2
  38. package/testaro/legendLoc.js +2 -2
  39. package/testaro/lineHeight.js +2 -2
  40. package/testaro/linkAmb.js +2 -2
  41. package/testaro/linkExt.js +2 -2
  42. package/testaro/linkOldAtt.js +2 -2
  43. package/testaro/linkTo.js +2 -2
  44. package/testaro/linkUl.js +2 -2
  45. package/testaro/miniText.js +2 -2
  46. package/testaro/motion.js +6 -6
  47. package/testaro/nonTable.js +2 -2
  48. package/testaro/optRoleSel.js +2 -2
  49. package/testaro/phOnly.js +2 -2
  50. package/testaro/pseudoP.js +2 -2
  51. package/testaro/radioSet.js +2 -2
  52. package/testaro/role.js +2 -2
  53. package/testaro/secHeading.js +2 -2
  54. package/testaro/shoot0.js +11 -2
  55. package/testaro/shoot1.js +12 -4
  56. package/testaro/styleDiff.js +2 -2
  57. package/testaro/tabNav.js +2 -2
  58. package/testaro/targetsNear.js +2 -2
  59. package/testaro/textNodes.js +1 -1
  60. package/testaro/textSem.js +2 -2
  61. package/testaro/titledEl.js +2 -2
  62. package/testaro/zIndex.js +2 -2
  63. package/tests/testaro.js +2 -10
package/LICENSE CHANGED
@@ -2,6 +2,8 @@ MIT License
2
2
 
3
3
  © 2021–2025 CVS Health and/or one of its affiliates. All rights reserved.
4
4
  © 2025–2026 Jonathan Robert Pool.
5
+ © 2025 Juan S. Casado.
6
+ © 2026 Jeff Witt.
5
7
 
6
8
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
7
9
 
package/README.md CHANGED
@@ -2,17 +2,15 @@
2
2
 
3
3
  Ensemble testing for web accessibility
4
4
 
5
- ## Breaking change notice
5
+ ## Breaking change notices
6
6
 
7
- Version 68.0.0 introduced major breaking changes.
7
+ Version 75.0.0 introduced a breaking change in the methods for making screenshots of web pages.
8
8
 
9
- Any application that has successfully relied on version 67.1.0 is likely to fail if it updates the `testaro` dependency to version 68.0.0 or later. To prevent such failures, pin `testaro` to version 67.1.0 in your `package.json` file.
10
-
11
- Revision of this `README` document to reflect the latest version is in progress but is incomplete.
9
+ Version 68.0.0 introduced a breaking change in the format of reports.
12
10
 
13
11
  ## Purposes
14
12
 
15
- Testaro is an application that performs ensemble testing of web pages, primarily for accessibility.
13
+ Testaro is an application that performs ensemble testing of web pages for accessibility, usability, and conformity to HTML and CSS specifications.
16
14
 
17
15
  The purposes of Testaro are to:
18
16
 
@@ -28,20 +26,20 @@ Testaro is described in two papers:
28
26
 
29
27
  ## Functionality
30
28
 
31
- Testaro performs tasks defined by a _job_. Typically, a job identifies the URL of a web page and asks Testaro to call an ensemble of tools to test the page. Testaro adds the results of the testing to the job, thereby converting the job into a _report_.
29
+ Testaro performs tasks defined by a _job_. Typically, a job identifies the URL of a web page and asks Testaro to call an ensemble of tools to test the page. Testaro adds the results of the testing to the job, thereby converting the job to a _report_.
32
30
 
33
31
  Testaro can be given a job to perform, in which case it performs the job, delivers the report, and quits.
34
32
 
35
33
  Alternatively, testaro can run as a daemon, polling a server or a directory for new jobs and performing them when they are provided or when they appear in the directory.
36
34
 
37
- A practical application that leverages Testaro will use other software to prepare jobs, schedule them, post-process the reports as needed, and manage the report files. Some utilities for such purposes can be found in the [Testilo project](https://www.npmjs.com/package/testilo). One application that leverages Testaro for a web service is [Kilotest](https://github.com/jrpool/kilotest).
35
+ A practical application that leverages Testaro will use other software to prepare jobs, schedule them, post-process the reports as needed, and manage the report files. Some utilities for such purposes can be found in the [Testilo project](https://www.npmjs.com/package/testilo). One application that leverages Testaro for a web service is [Kilotest](https://www.npmjs.com/package/@jrpool/kilotest).
38
36
 
39
37
  ## Dependencies
40
38
 
41
39
  Testaro uses:
42
40
 
43
41
  - [Playwright](https://playwright.dev/) to launch browsers, perform user actions in them, and perform tests
44
- - [playwright-extra](https://www.npmjs.com/package/playwright-extra) and [puppeteer-extra-plugin-stealth](https://www.npmjs.com/package/puppeteer-extra-plugin-stealth) to make a Playwright-controlled browser more indistinguishable from a human-operated browser and thus make their requests more likely to succeed
42
+ - [playwright-extra](https://www.npmjs.com/package/playwright-extra) and [puppeteer-extra-plugin-stealth](https://www.npmjs.com/package/puppeteer-extra-plugin-stealth) to make a Playwright-controlled browser more indistinguishable from a human-operated browser and thus make its requests more likely to succeed
45
43
  - [playwright-dompath](https://www.npmjs.com/package/playwright-dompath) to retrieve XPaths of elements
46
44
  - [BlazeDiff](https://blazediff.dev/) to measure motion
47
45
  - [dotenv](https://www.npmjs.com/package/dotenv) to load environment variables
@@ -68,16 +66,16 @@ As shown, Testaro is not only an integrator but also one of the integrated tools
68
66
  The main concepts of Testaro are:
69
67
 
70
68
  - `job`: a document that tells Testaro what to do.
71
- - `act`: one step in a job
69
+ - `act`: one step in a job.
72
70
  - `report`: a job that Testaro has added results to.
73
- - `tool`: one of the testing applications in the ensemble that Testaro has created.
71
+ - `tool`: one of the testing applications in the ensemble assembled by Testaro.
74
72
  - `rule`: a success or failure criterion defined by a tool (currently about 1300 across all tools).
75
73
  - `test`: the software that a tool uses to apply a rule.
76
74
  - `target`: a web page that a job tells Testaro to test.
77
- - `result`: the information that Testaro adds to a job to describe the test outcomes.
78
- - `native result`: the test outcomes of a tool in the native form of that tool.
79
- - `standard result`: the test outcomes of a tool in a uniform Testaro-defined form.
80
- - `catalog`: a collection of data on the HTML elements relevant to one or more tests.
75
+ - `result`: the information that Testaro adds to a job to describe the outcomes of the tests of a tool.
76
+ - `native result`: the outcomes of the tests of a tool in exactly or approximately the original form.
77
+ - `standard result`: the outcomes of the tests of a tool in a uniform Testaro-defined form.
78
+ - `catalog`: a collection of data on the HTML elements of a target relevant to one or more tests.
81
79
 
82
80
  ## System requirements
83
81
 
@@ -99,7 +97,7 @@ One way to cope with this prohibition is to configure Playwright and Puppeteer t
99
97
  - For the `qualWeb` tool, this is done in the Testaro `tests/qualweb.js` file, where the `qualWeb.start` method is called with an options argument. Its `args` array property is modified to include `'--no-sandbox'` and `'--disable-setuid-sandbox'`.
100
98
  - The `ibm` tool, too, can launch a Puppeteer `chromium` browser, if page content instead of a Playwright page is passed to the `accessibilityChecker.getCompliance` method, or if the implementation of the tool is changed in the future. For anticipation of such a case, the Testaro `aceconfig.js` file is modified. That file defines a `module.exports` object with a `puppeteerArgs` property, and, `--no-sandbox` and `--disable-setuid-sandbox` are added to its array value.
101
99
 
102
- Non-sandboxed browsers are less secure than sandboxed ones, particularly when there is no restriction on who can use Testaro and what web pages they can test with it.
100
+ Non-sandboxed browsers are less secure than sandboxed ones, particularly when there is no restriction on who can use Testaro and what targets (web pages) they can test with it.
103
101
 
104
102
  ### Option B
105
103
 
@@ -115,13 +113,11 @@ EOF
115
113
  sudo sysctl --system
116
114
  ```
117
115
 
118
- This repository implements option B.
119
-
120
- ## Installation
116
+ This application implements option B.
121
117
 
122
- ### As an independent application
118
+ ## Installation an independent application
123
119
 
124
- To install Testaro as an independent application, clone the [Testaro repository](https://github.com/jrpool/testaro). To ensure that the binary browsers of its Playwright dependency get installed, execute `(p)npx playwright install` after executing `(p)npm install`.
120
+ To install Testaro as an independent application, rather than a dependency, clone the [Testaro repository](https://github.com/jrpool/testaro). To ensure that the binary browsers of its Playwright dependency get installed, execute `(p)npx playwright install` after executing `(p)npm install`.
125
121
 
126
122
  To update Testaro when it is an independent application, execute:
127
123
 
@@ -131,19 +127,13 @@ git pull
131
127
  (p)npm run deps
132
128
  ```
133
129
 
134
- ### As a dependency
135
-
136
- You can make `testaro` a dependency in another application. As noted at the beginning of this file, the entry in `package.json` should be `"testaro": "67.1.0"` if your application has not been designed to work with version 68.0.0 or later.
137
-
138
130
  ## Environment configuration
139
131
 
140
132
  The `.env` file stores your decisions about the environment in which Testaro runs. The variables that can be defined there are documented in the `env.example` file.
141
133
 
142
134
  ## Jobs
143
135
 
144
- Jobs tell Testaro what and how to test.
145
-
146
- ### Job example
136
+ Jobs tell Testaro what to do.
147
137
 
148
138
  Here is a sample job, showing properties that you can set:
149
139
 
@@ -204,8 +194,8 @@ Here is a sample job, showing properties that you can set:
204
194
  }
205
195
  },
206
196
  which: 'qualWeb',
207
- withNewContent: false,
208
- rules: ['QW-BP25', 'QW-BP26']
197
+ withNewContent: false, // An argument required by this tool
198
+ rules: ['QW-BP25', 'QW-BP26'] // Which rules of the tool to test for
209
199
  }
210
200
  ]
211
201
  }
@@ -213,11 +203,11 @@ Here is a sample job, showing properties that you can set:
213
203
 
214
204
  The `device` property lets you choose among [about 125 devices recognized by Playwright](https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/deviceDescriptorsSource.json).
215
205
 
216
- There are 18 act types. They and their options are documented in the `etc` property of the [actSpecs.js](actSpecs.js) object. Documentation for the `actSpecs.js` file is located in the `actSpecs-doc.md` file.
206
+ The act types and their options are documented in the `etc` property of the [actSpecs.js](actSpecs.js) object and in the [actSpecs-doc.md](actSpecs-doc.md) file.
217
207
 
218
- ### Running jobs
208
+ ## Running jobs
219
209
 
220
- #### Job as an object
210
+ ### Job as an object
221
211
 
222
212
  An application can execute a job with:
223
213
 
@@ -231,35 +221,39 @@ doJob(job)
231
221
  });
232
222
  ```
233
223
 
234
- #### Job as a file
224
+ ### Job as a file
235
225
 
236
- #### Immediate execution
226
+ Jobs can be stored as JSON files in the `todo` subdirectory of a directory identified by the `JOBDIR` environment variable. After Testaro performs such a job, Testaro moves the job file to the `done` subdirectory of the same directory and saves the report in the `raw` subdirectory of the directory identified by the `REPORTDIR` environment variable.
237
227
 
238
- A user can make Testaro execute a job from a file with a command like either of:
228
+ ### Immediate performance
229
+
230
+ A user can make Testaro perform a job from a file with a command like either of:
239
231
 
240
232
  ```bash
241
233
  node call run
242
234
  node call run 250725T
243
235
  ```
244
236
 
245
- An application can watch a directory for jobs with::
237
+ Testaro will find the first file in the `todo` subdirectory, or, if the second form of the command is used, the first file there whose name begins with the specified string.
238
+
239
+ ### Directory polling
240
+
241
+ An application can poll the `todo` subdirectory for jobs with:
246
242
 
247
243
  ```javaScript
248
244
  const {dirWatch} = require('testaro/dirWatch');
249
245
  dirWatch(true, 300);
250
246
  ```
251
247
 
252
- A user can make Testaro watch a directory for jobs with::
248
+ A user can make Testaro start to poll that subdirectory with:
253
249
 
254
250
  ```javaScript
255
251
  node call dirWatch true 300
256
252
  ```
257
253
 
258
- In both cases, the first argument of `dirWatch` tells Testaro whether to continue watching after performing one job, and the second argument tells Testaro how many secods to wait after not finding a job to perform before checking again.
254
+ In both cases, the first argument of `dirWatch` tells Testaro whether to continue polling after performing one job, and the second argument tells Testaro how many seconds to wait after not finding a job to perform, before polling again.
259
255
 
260
- Testaro finds a file containing a job in the `todo` subdirectory of the `process.env.JOBDIR` directory and saves the report of that job in the `done/raw` subdirectory. In the `node call run` case, the job selected will be the first one whose file name begins with the argument of `run`, or the first one if that argument is absent.
261
-
262
- #### Job from a server
256
+ ### Server polling
263
257
 
264
258
  Testaro can poll a server for jobs to be performed. The server can act as the “controller” described in [How to run a thousand accessibility tests](https://medium.com/cvs-health-tech-blog/how-to-run-a-thousand-accessibility-tests-63692ad120c3). The server is responsible for preparing Testaro jobs, assigning them to Testaro agents, receiving reports back from those agents, and performing any further processing of the reports, including enhancement, storage, and disclosure to audiences. It can be any server reachable with a URL. That includes a server running on the same host as Testaro, with a URL such as `localhost:3000`.
265
259
 
@@ -273,7 +267,7 @@ The job request sent to the server can be a `POST` request, in which the `agentP
273
267
 
274
268
  Testaro will send the report as a `POST` request whose payload is a JSON object with two properties: `agentPW` (the password) and `report` (the report). However, if the environment does not contain a password, the payload is a JSON object containing only the report.
275
269
 
276
- An application can poll a server for jobs with:
270
+ An application can make Testaro poll a server for jobs with:
277
271
 
278
272
  ```javaScript
279
273
  const {netWatch} = require('testaro/netWatch');
@@ -286,24 +280,22 @@ A user can make Testaro poll a server for jobs with:
286
280
  node call netWatch true 300 true
287
281
  ```
288
282
 
289
- The first argument of `netWatch` tells Testaro whether to continue polling after performing the first job. The second argument tells Testaro how many seconds to wait after receiving a no-jobs response. The third argument tells Testaro whether to be certificate-tolerant, i.e. to accept SSL certificates that fail verification against a list of certificate authorities (the default is `true`).
283
+ The first argument of `netWatch` tells Testaro whether to continue polling after performing the first job. The second argument tells Testaro how many seconds to wait after receiving a no-jobs response before polling again. The third argument tells Testaro whether to be certificate-tolerant, i.e. to accept SSL certificates that fail verification against a list of certificate authorities (the default is `true`).
290
284
 
291
285
  ## Reports
292
286
 
293
287
  A report is a job with information about the results of the performance of the job inserted by Testaro into the job.
294
288
 
295
- ### Whole-job insertions
289
+ ### Whole-job data
296
290
 
297
291
  As Testaro performs a job, information about the job as a whole is inserted into the job. That information is organized into one or two properties:
298
292
 
299
293
  - `jobData`: Facts about the performance of the job
300
- - `catalog`: A collection of data about the HTML elements on the target that are relevant to any test failures
301
-
302
- Testaro inserts the `jobData` property into every job, but inserts the `catalog` property only into jobs that instruct Testaro to produce standard results.
294
+ - `catalog`: A collection of data about the HTML elements of the target that are relevant to any test failures
303
295
 
304
- #### Catalog
296
+ Testaro inserts the `jobData` property into every job.
305
297
 
306
- Whenever a job requires any testing and requires the production of standard results, Testaro inserts a _catalog_ into the report before calling any of the testing tools. The catalog is an inventory of HTML elements in the DOM of the target.
298
+ Testaro inserts the `catalog` property only into jobs that instruct Testaro to produce standard results. The catalog is an inventory of HTML elements in the DOM of the target.
307
299
 
308
300
  The `catalog` property has an object value. Here is an example:
309
301
 
@@ -330,9 +322,10 @@ The catalog is a mechanism for the integration of the tools. Most rule violation
330
322
  Testaro uses the following techniques to make the tools calculate XPaths:
331
323
 
332
324
  - `alfa` and `aslint`: They report XPaths, so Testaro needs only to normalize them.
333
- - `ed11y`: Testaro adds it and a `window.getXPath` method to the page; when the tool reports an element, Testaro computes its XPath.
325
+ - `ed11y`: Testaro adds it and a `window.getXPath` method to the page. When the tool reports an element, Testaro computes its XPath.
334
326
  - `wave`: It reports a selector for each element; Testaro finds each element in the page via its selector and executes `window.getXPath` on the element.
335
- - `axe`, `htmlcs`, `ibm`, `nuVal`, `nuVnu`, `qualWeb`: Testaro adds `data-xpath` attributes to all elements; the tools include code excerpts, with the `data-xpath` attributes, in the reported violations.
327
+ - `htmlcs`, `ibm`, `nuVal`, `nuVnu`, `qualWeb`: Testaro adds `data-xpath` attributes to all elements. The tools include code excerpts, with the `data-xpath` attributes, in the reported violations.
328
+ - `axe`: It reports a selector for each element, and Testaro adds `data-xpath` attributes to all elements. Testaro finds each element in the page via its selector and uses the `data-xpath` attribute. When this fails, Testaro uses the `data-xpath` attribute if its complete value is included in the reported `node.html` value.
336
329
  - `testaro`: Testaro designs each of its own tests to report element XPaths.
337
330
 
338
331
  By attaching a catalog entry to each reported element, Testaro allows an application that uses Testaro to tell users, for any particular HTML element, which tools ascribed violations of which rules to that element. An application could, for example, use a screenshot or a text-fragment link or could ask the user to paste the XPath into a browser developer tool.
@@ -343,14 +336,14 @@ In some cases no catalog entry can be found. The reasons may include:
343
336
  - The element is inside a `noscript` element and therefore not considered an element in the DOM.
344
337
  - The violation is not ascribed to a single element.
345
338
 
346
- ### Act insertions
339
+ ### Act data
347
340
 
348
- As Testaro performs the acts of a job, information about the results of each act is inserted into that act. For acts of type `test`, the added properties are:
341
+ As Testaro performs the acts of a job, information about the result of each act is inserted into that act. For acts of type `test`, the added properties are:
349
342
 
350
343
  - `startTime`: When Testaro began to perform the act
351
344
  - `actualURL`: The tested URL (different from the target URL if the request was redirected)
352
345
  - `data`: Data generated by the tool
353
- - `result`: Results of the testing by the tool
346
+ - `result`: Result of the testing by the tool
354
347
 
355
348
  The `result` property is an object with one or two (depending on the value of `standard`, as described above) subproperties:
356
349
 
@@ -453,7 +446,7 @@ In a previous version of the package, the tool operated on the page content when
453
446
 
454
447
  ### Nu Html Checker
455
448
 
456
- The `nuVal` and `nuVnu` tools perform the tests of the Nu Html Checker.
449
+ The `nuVal` and `nuVnu` tools perform the tests of the Nu Html Checker. The `nuVal` tool is a remote service with an API. The `nuVnu` tool is installed as a dependency. A job can choose either one, or can try `nuVal` and if it fails then invoke `nuVnu`.
457
450
 
458
451
  Its `rules` argument is **not** an array of rule IDs, but instead is an array of rule _specifications_. A rule specification for `nuVal` or `nuVnu` is a string with the format `=ruleID` or `~ruleID`. The `=` prefix indicates that the rule ID is invariable. The `~` prefix indicates that the rule ID is variable, in which case the `ruleID` part of the specification is a matching regular expression, rather than the exact text of a message. This `rules` format arises from the fact that `nuVal` and `nuVnu` generate customized messages and do not accompany them with rule identifiers.
459
452
 
@@ -482,7 +475,7 @@ Thus, when the `rules` argument is omitted, QualWeb will test for all of the rul
482
475
 
483
476
  The target can be provided to QualWeb either as HTML or as a URL. Experience indicates that the results can differ between these methods, with each method reporting some rule violations or some instances that the other method does not report. For at least some cases, more rules are reported violated when HTML is provided (`withNewItems: false`).
484
477
 
485
- QualWeb creates sandboxed Puppeteer pages to perform its tests on. Therefore, the host must permit sandboxed browsers to be launched. See the discussion above about browser security. Also see the pertinent [Kilotest documentation](https://github.com/jrpool/kilotest/blob/main/SERVICE.md#browser-privileges) for information about the configuration of an Ubuntu Linux host for this purpose.
478
+ QualWeb creates sandboxed Puppeteer pages to perform its tests on. Therefore, the host must permit sandboxed browsers to be launched. See the discussion above about browser security.
486
479
 
487
480
  ### Testaro
488
481
 
@@ -545,7 +538,7 @@ The Playwright “Receives Events” actionability check does **not** check whet
545
538
 
546
539
  ### Test prevention
547
540
 
548
- Test targets employ mechanisms to prevent scraping, multiple requests within a short time, automated form submission, and other automated actions. These mechanisms may interfere with testing. When a test act is prevented by a target, Testaro reports this prevention.
541
+ Test targets employ mechanisms to prevent scraping, multiple requests within a short time, automated form submission, and other automated actions. These mechanisms may interfere with testing. When a test act is prevented, Testaro reports this prevention.
549
542
 
550
543
  Some targets prohibit the execution of alien scripts unless the client can demonstrate that it is the requester of the page. Failure to provide that evidence results in the script being blocked and an error message being logged, saying “Refused to execute a script because its hash, its nonce, or unsafe-inline does not appear in the script-src directive of the Content Security Policy”. This mechanism affects tools that insert scripts into a target in order to test it. To comply with this requirement, Testaro obtains a _nonce_ from the response that serves the target. Then the file that runs the tool adds that nonce to the script as the value of a `nonce` attribute when it inserts its script into the target.
551
544
 
@@ -615,7 +608,7 @@ From 12 February 2024 through 30 September 2025, contributors of code to Testaro
615
608
 
616
609
  ## Future work
617
610
 
618
- Future work on this project is being considered. Strategic recommendations for such work are recorded in the `UPGRADES.md` file.
611
+ Future work contemplated for this project is described in its [issues](https://github.com/jrpool/testaro/issues) and also discussed in the [UPGRADES.md](UPGRADES.md) file.
619
612
 
620
613
  ## Etymology
621
614
 
package/actSpecs-doc.md CHANGED
@@ -30,7 +30,10 @@ link: [
30
30
  ]
31
31
  ```
32
32
 
33
- The rule is an array with two elements: a string ('Click a link') describing the act and an object containing requirements for any act of type `link`.
33
+ The rule is an array with two elements:
34
+
35
+ - a string ('Click a link' in this case) describing the act
36
+ - an object containing requirements for any act of the type identified by the key (`link` in this case).
34
37
 
35
38
  The requirement `which: [true, 'string', 'hasLength', 'substring of the link text']` specifies what is required for the `which` property of a `link`-type act. The requirement is an array.
36
39
 
@@ -52,6 +55,27 @@ The validity criterion named in item 2 may be any of these:
52
55
  - `'isWaitable'`: is `'url'`, `'title'`, or `'body'`
53
56
  - `'areStrings'`: is an array of strings
54
57
 
58
+ ## testaro tool
59
+
60
+ The `tools.testaro` object has an `args` property specifying that a `testaro` test act may include an `args` property with an object value.
61
+
62
+ If it does, the property names of the object value must be `testaro` rule IDs. Any property value must be an array of the positional arguments to be concatenated to the four default arguments (`page`, `report`, `actIndex`, and `withItems`) in the signature of the `reporter` function of each `testaro` rule.
63
+
64
+ The rules of `testaro` that accept additional arguments are `autocomplete`, `buttonMenu`, `focInd`, and `hover`.
65
+
66
+ ## Screenshots
67
+
68
+ An act of type `shoot` creates a full-page screenshot and converts it to a base64 encoding of a PNG image. With its arguments, you can decide:
69
+
70
+ - Whether to mask certain elements (using a CSS selector)
71
+ - The color type (grayscale, RGB, grayscale alpha, or RGBA)
72
+ - What action to take to dispose of the base64 string:
73
+ - return it to the `shoot` act and save it in the result of the act (`return`)
74
+ - concatenate it to an `images` array in the report and return its index in the array (`report`)
75
+ - save it as a file in the temporary directory and return the file path (`file`)
76
+
77
+ Both the `return` and the `report` actions save the base64 string in the report, but in different places. The `file` action would permit you to create a custom act that retrieves and uses the image without the image being added to the report.
78
+
55
79
  ## License
56
80
 
57
81
  © 2026 Jonathan Robert Pool.
package/actSpecs.js CHANGED
@@ -45,6 +45,7 @@ exports.actSpecs = {
45
45
  {
46
46
  target: [false, 'object', '', 'target different from target of the job'],
47
47
  browserID: [false, 'string', 'isBrowserID', 'browser type different from browserID of the job'],
48
+ shoot: [false, 'object', '', 'if screenshot to be made, exclusionSelector, colorType, and action'],
48
49
  what: [false, 'string', 'hasLength', 'comment']
49
50
  }
50
51
  ],
@@ -123,10 +124,11 @@ exports.actSpecs = {
123
124
  }
124
125
  ],
125
126
  shoot: [
126
- 'Save a full-page screenshot to <tmpdir>/testaro-shoot-<which>.png',
127
+ 'Create and dispose of a full-page screenshot as a base-64-encoded PNG',
127
128
  {
128
- which: [true, 'string', 'hasLength', 'screenshot label, used in the filename'],
129
- exclusion: [false, 'string', 'hasLength', 'CSS selector for an element to mask'],
129
+ exclusionSelector: [false, 'string', 'hasLength', 'CSS selector for an element to mask'],
130
+ colorType: [false, 'number', '', '0=grayscale, 2=RGB, 4=grayscale alpha, 6=RGBA'],
131
+ action: [true, 'string', 'hasLength', 'disposition: return, report, file'],
130
132
  what: [false, 'string', 'hasLength', 'comment']
131
133
  }
132
134
  ],
@@ -208,7 +210,7 @@ exports.actSpecs = {
208
210
  {
209
211
  withItems: [true, 'boolean', '', 'itemize'],
210
212
  stopOnFail: [true, 'boolean', '', 'whether testing is to stop after first failure'],
211
- args: [false, 'object', 'areArrays', 'extra args (object; property names are rule IDs and values are arrays of additional argument values ({autocomplete: [["first name", "forename", "given name"], ["last name", "surname", "family name"], ["email"]], buttonMenu: [], focInd: [false, 250], hover: [20]}) by default'],
213
+ args: [false, 'object', 'areArrays', 'extra arguments of rules taking any']
212
214
  }
213
215
  ],
214
216
  wave: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "74.2.3",
3
+ "version": "75.0.0",
4
4
  "description": "Run 1300 web accessibility tests from 10 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/procs/catalog.js CHANGED
@@ -25,7 +25,6 @@
25
25
 
26
26
  // Module to close and launch browsers.
27
27
  const {browserClose, launch} = require('./launch');
28
- const {getXPathCatalogIndex} = require('./xPath');
29
28
 
30
29
  // FUNCTIONS
31
30
 
package/procs/dateTime.js CHANGED
@@ -32,5 +32,7 @@ exports.dateOf = timeStamp => {
32
32
  return null;
33
33
  }
34
34
  };
35
- // Returns a string representing the date and time.
36
- exports.nowString = () => (new Date()).toISOString().slice(2, 16);
35
+ // Returns a human-friendly representation of the current date and time.
36
+ const nowString = exports.nowString = () => (new Date()).toISOString().slice(2, 16);
37
+ // Returns a compact representation of the current date and time.
38
+ exports.nowStamp = () => nowString().replace(/[:-]/g, '');
package/procs/doActs.js CHANGED
@@ -289,13 +289,14 @@ exports.doActs = async report => {
289
289
  }
290
290
  // Otherwise, if the act is a launch:
291
291
  else if (type === 'launch') {
292
- // Launch a browser, navigate to a page, and add the result to the act.
292
+ // Launch a browser, navigate, optionally make a screenshot, and add the result to the act.
293
293
  page = await launch({
294
- tempReport,
294
+ report: tempReport,
295
295
  actIndex,
296
296
  tempBrowserID: getActBrowserID(tempReport, actIndex),
297
297
  tempURL: getActTargetURL(tempReport, actIndex),
298
- xPathNeed: 'none'
298
+ xPathNeed: 'none',
299
+ shoot: act.shoot
299
300
  });
300
301
  // If this failed:
301
302
  if (! page) {
@@ -641,11 +642,17 @@ exports.doActs = async report => {
641
642
  }
642
643
  // Otherwise, if the act is a screenshot:
643
644
  else if (type === 'shoot') {
644
- const exclusion = act.exclusion ? page.locator(act.exclusion) : null;
645
- const pngPath = await shoot(page, act.which, {exclusion});
646
- act.result = pngPath
647
- ? {success: true, path: pngPath}
648
- : {success: false, prevented: true};
645
+ const {exclusionSelector, colorType, action} = act;
646
+ // Make and dispose of a full-page screenshot.
647
+ const shotInfo = await shoot(page, tempReport, {
648
+ exclusion: exclusionSelector ? page.locator(exclusionSelector) : null,
649
+ colorType: [0, 2, 4, 6].includes(colorType) ? colorType : null,
650
+ action: ['return', 'report', 'file'].includes(action) ? action : 'report'
651
+ });
652
+ // Add the PNG base-64 encoding, image index, or file path to the act result.
653
+ act.result = shotInfo !== ''
654
+ ? {success: true, shotInfo}
655
+ : {success: false, prevented: true};
649
656
  }
650
657
  // Otherwise, if the act is a move:
651
658
  else if (moves[type]) {
package/procs/launch.js CHANGED
@@ -16,7 +16,6 @@
16
16
 
17
17
  // IMPORTS
18
18
 
19
- // Module to handle errors.
20
19
  const {addError} = require('./error');
21
20
  const headedBrowser = process.env.HEADED_BROWSER === 'true';
22
21
  // Two flavors of Playwright:
package/procs/shoot.js CHANGED
@@ -6,57 +6,35 @@
6
6
 
7
7
  /*
8
8
  shoot
9
- Makes and saves as a PNG buffer file a full-page screenshot and returns the file path.
10
-
11
- Call shape:
12
- shoot(page, label, options?)
13
- label: string|number used in the saved filename. Sanitized to
14
- testaro-shoot-<safe>.png — characters outside [A-Za-z0-9._-]
15
- collapse to '_', leading/trailing dots and underscores are
16
- stripped, length is capped at 100, and an empty result becomes
17
- 'unnamed'.
18
- options: optional object:
19
- exclusion: a Playwright Locator to mask in the screenshot.
20
- dir: output directory (defaults to the OS temp dir).
9
+ Manages the production and disposition of screenshots of a page.
21
10
  */
22
11
 
23
12
  // IMPORTS
24
13
 
25
14
  // Shared configuration for timeout multiplier.
26
15
  const {applyMultiplier} = require('./config');
16
+ const {nowStamp} = require('./dateTime');
27
17
  const fs = require('fs/promises');
28
18
  const path = require('path');
29
19
  const {PNG} = require('pngjs');
30
20
 
31
21
  // FUNCTIONS
32
22
 
33
- /*
34
- Coerces a label into a filesystem-safe string. Runs of any character outside
35
- [A-Za-z0-9._-] collapse to one underscore; leading and trailing dots and
36
- underscores are stripped (no hidden files, no traversal); capped at 100
37
- characters; falls back to 'unnamed' if nothing usable remains.
38
- */
39
- const sanitizeLabel = (label) => {
40
- const raw = String(label);
41
- const cleaned = raw
42
- .replace(/[^A-Za-z0-9._-]+/g, '_')
43
- .replace(/^[._]+|[._]+$/g, '')
44
- .slice(0, 100) || 'unnamed';
45
- if (cleaned !== raw) {
46
- console.log(`>> shoot: label sanitized from "${raw}" to "${cleaned}"`);
47
- }
48
- return cleaned;
23
+ // Returns a probably unique file name.
24
+ const randomFileName = (suffixLength = 3) => {
25
+ const fileName = `${nowStamp()}-${Math.random().toString(36).slice(2, 2 + suffixLength)}`;
26
+ return fileName;
49
27
  };
50
-
51
28
  // Creates and returns a screenshot.
52
- const screenShot = async (page, exclusion = null) => {
29
+ const screenShot = async (page, exclusionLocator = null) => {
53
30
  const options = {
54
31
  fullPage: true,
55
32
  omitBackground: true,
33
+ scale: 'css',
56
34
  timeout: applyMultiplier(4000)
57
35
  };
58
- if (exclusion) {
59
- options.mask = [exclusion];
36
+ if (exclusionLocator) {
37
+ options.mask = [exclusionLocator];
60
38
  }
61
39
  // Make and return a screenshot as a buffer.
62
40
  return await page.screenshot(options)
@@ -65,28 +43,55 @@ const screenShot = async (page, exclusion = null) => {
65
43
  return '';
66
44
  });
67
45
  };
68
- exports.shoot = async (page, label, tmpDir, options = {}) => {
69
- const exclusion = options.exclusion || null;
70
- const dir = options.dir || tmpDir;
46
+ // Creates and disposes of the PNG of a screenshot.
47
+ exports.shoot = async (page, report, {
48
+ // Playwright locator of a mask.
49
+ exclusionSelector = null,
50
+ // Color fidelity: 0 (grayscale), 2 (RGB), 4 (grayscale alpha), 6 (RGBA).
51
+ colorType = 0,
52
+ // Disposition: return, report, file.
53
+ action = 'return'
54
+ } = {}) => {
71
55
  // Make and get a screenshot as a buffer.
72
- let shot = await screenShot(page, exclusion);
56
+ let shot = await screenShot(page, exclusionSelector ? page.locator(exclusionSelector) : null);
73
57
  // If it succeeded:
74
58
  if (shot.length) {
75
59
  // Get the screenshot as an object representation of a PNG image.
76
60
  let png = PNG.sync.read(shot);
77
61
  shot = null;
78
- const pngBuffer = PNG.sync.write(png);
62
+ // Convert the PNG object to a buffer, applying the specified color type.
63
+ const pngBuffer = PNG.sync.write(png, {colorType});
79
64
  png = null;
80
65
  // Force garbage collection if available and if --expose-gc was a node option.
81
66
  if (global.gc) {
82
67
  global.gc();
83
68
  }
84
- const fileName = `testaro-shoot-${sanitizeLabel(label)}.png`;
85
- const pngPath = path.join(dir, fileName);
86
- // Save the PNG buffer.
87
- await fs.writeFile(pngPath, pngBuffer);
88
- // Return the result.
89
- return pngPath;
69
+ // Convert the buffer to a base64 string.
70
+ const base64 = pngBuffer.toString('base64');
71
+ // If the string is to be returned:
72
+ if (action === 'return') {
73
+ // Return it.
74
+ return base64;
75
+ }
76
+ // Otherwise, if it is to be appended to the report:
77
+ if (action === 'report') {
78
+ report.images ??= [];
79
+ // Append it to the images array in the report.
80
+ report.images.push(base64);
81
+ // Return the index of the added image in the array.
82
+ return report.images.length - 1;
83
+ }
84
+ // Otherwise, if it is to be saved in a file:
85
+ if (action === 'file') {
86
+ const fileName = randomFileName(4);
87
+ const filePath = path.join(report.jobData.tmpDir, fileName);
88
+ // Save it in a file.
89
+ await fs.writeFile(filePath, base64);
90
+ // Return the file name.
91
+ return fileName;
92
+ }
93
+ // Otherwise, i.e. if the action is invalid, return this.
94
+ return '';
90
95
  }
91
96
  // Otherwise, i.e. if it failed:
92
97
  else {
package/testaro/adbID.js CHANGED
@@ -22,7 +22,7 @@ const {doTest} = require('../procs/testaro');
22
22
  // FUNCTIONS
23
23
 
24
24
  // Runs the test and returns the result.
25
- exports.reporter = async (page, catalog, withItems) => {
25
+ exports.reporter = async (page, report, _, withItems) => {
26
26
  const getBadWhat = element => {
27
27
  // Get the IDs in the aria-describedby attribute of the element.
28
28
  const IDs = element.getAttribute('aria-describedby').trim().split(/\s+/).filter(Boolean);
@@ -57,6 +57,6 @@ exports.reporter = async (page, catalog, withItems) => {
57
57
  };
58
58
  const whats = 'Elements have aria-describedby attributes with missing or invalid id values';
59
59
  return await doTest(
60
- page, catalog, withItems, 'adbID', 'body [aria-describedby]', whats, 3, getBadWhat.toString()
60
+ page, report.catalog, withItems, 'adbID', 'body [aria-describedby]', whats, 3, getBadWhat.toString()
61
61
  );
62
62
  };
@@ -20,7 +20,7 @@ const {doTest} = require('../procs/testaro');
20
20
  // FUNCTIONS
21
21
 
22
22
  // Runs the test and returns the result.
23
- exports.reporter = async (page, catalog, withItems) => {
23
+ exports.reporter = async (page, report, _, withItems) => {
24
24
  const getBadWhat = element => {
25
25
  // Get the style declaration of the element.
26
26
  const styleDec = window.getComputedStyle(element);
@@ -46,6 +46,6 @@ exports.reporter = async (page, catalog, withItems) => {
46
46
  const selector = 'body, body *:not(style, script, svg)';
47
47
  const whats = 'Elements have an all-capital text transformation style';
48
48
  return await doTest(
49
- page, catalog, withItems, 'allCapStyle', selector, whats, 0, getBadWhat.toString()
49
+ page, report.catalog, withItems, 'allCapStyle', selector, whats, 0, getBadWhat.toString()
50
50
  );
51
51
  };