testaro 76.2.1 → 77.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/CONTAINERS.md CHANGED
@@ -14,7 +14,9 @@ Containerization is practical. The reference image runs every Testaro tool:
14
14
 
15
15
  ## Version coupling
16
16
 
17
- The tag of the base image in the `Dockerfile` (`mcr.microsoft.com/playwright:v1.60.0-noble`) must match the version of the `playwright` package in `package-lock.json`, so that the browsers baked into the image are the ones the installed Playwright expects. When upgrading Playwright, change both together.
17
+ The tag of the base image in the `Dockerfile` (such as `mcr.microsoft.com/playwright:v1.62.0-noble`) must match the version of the `playwright` package in `package-lock.json`, so that the browsers baked into the image are the ones the installed Playwright expects. When you update dependencies, check the version of the `playwright` package (with `npm list playwright --depth=0`) and incorporate that version into the `Dockerfile`.
18
+
19
+ However, to be sure that this works, check `https://mcr.microsoft.com/v2/playwright/tags/list` to determine whether an image for that version exists yet. If not and you want to run Testaro containerized, pin the version in `package.json` to the last version for which a base image exists and use that version in `Dockerfile`, too.
18
20
 
19
21
  ## The Chromium sandbox
20
22
 
package/Dockerfile CHANGED
@@ -10,7 +10,7 @@
10
10
  # The base-image tag must match the version of the playwright package in
11
11
  # package-lock.json, so that the browsers baked into the image are the ones
12
12
  # the installed Playwright expects.
13
- FROM mcr.microsoft.com/playwright:v1.61.1-noble
13
+ FROM mcr.microsoft.com/playwright:v1.62.0-noble
14
14
 
15
15
  # Install a headless Java runtime for the nuVnu test (vnu-jar); baking it in
16
16
  # prevents vnu-jar from downloading a Temurin runtime over the network on
package/README.md CHANGED
@@ -209,18 +209,50 @@ The act types and their options are documented in the `etc` property of the [act
209
209
 
210
210
  ### Job as an object
211
211
 
212
+ #### Simple execution
213
+
212
214
  An application can execute a job with:
213
215
 
214
216
  ```javascript
215
217
  const {doJob} = require('testaro/run');
218
+ // Perform the job, which adds content to the job, making it a report.
216
219
  doJob(job)
217
220
  .then(report => {
218
- // Perform the job and create a report.
221
+ // Optionally, modify the report.
219
222
 
220
223
  return report;
221
224
  });
222
225
  ```
223
226
 
227
+ #### Execution with observability
228
+
229
+ If the application is capable of listening for events while a job is being executed, it is possible to do that by passing a second argument to `doJob`:
230
+
231
+ ```javascript
232
+ const {doJob} = require('testaro/run');
233
+ // Perform the job, which adds content to the job, making it a report.
234
+ doJob(job, {
235
+ onProgress: event => {
236
+ // Handle the event. For example:
237
+ console.log(event);
238
+ }
239
+ })
240
+ .then(report => {
241
+ // Optionally, modify the report.
242
+
243
+ return report;
244
+ });
245
+ ```
246
+
247
+ Testaro emits events during job execution that allow an application to monitor progress. The events are:
248
+
249
+ - `jobStart` - Emitted when a job starts.
250
+ - `catalogStart` - Emitted when catalog compilation starts.
251
+ - `catalogEnd` - Emitted when catalog compilation ends.
252
+ - `actStart` - Emitted when an act starts.
253
+ - `actEnd` - Emitted when an act ends.
254
+ - `jobEnd` - Emitted when a job ends.
255
+
224
256
  ### Job as a file
225
257
 
226
258
  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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "76.2.1",
3
+ "version": "77.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": {
@@ -29,7 +29,6 @@
29
29
  "url": "https://github.com/jrpool/testaro/issues"
30
30
  },
31
31
  "homepage": "https://github.com/jrpool/testaro#readme",
32
- "//": "playwright is temporarily pinned to version 1.61.1 because mcr.microsoft.com/playwright:v1.62.0-noble has not yet been released, so containerized deployment requires version 1.61.1.",
33
32
  "dependencies": {
34
33
  "@qualweb/core": "*",
35
34
  "@qualweb/act-rules": "*",
@@ -45,7 +44,7 @@
45
44
  "axe-playwright": "*",
46
45
  "dotenv": "*",
47
46
  "pixelmatch": "*",
48
- "playwright": "1.61.1",
47
+ "playwright": "*",
49
48
  "playwright-dompath": "*",
50
49
  "playwright-extra": "*",
51
50
  "playwright-extra-plugin-stealth": "*",
package/procs/doActs.js CHANGED
@@ -227,7 +227,20 @@ const waitError = (page, act, error, what) => {
227
227
  return false;
228
228
  };
229
229
  // Performs the acts in a report and adds the results to the report.
230
- exports.doActs = async report => {
230
+ exports.doActs = async (report, opts = {}) => {
231
+ // Fire-and-forget progress emitter (see #44), mirroring run.js: a handler
232
+ // exception is logged, never propagated, so a faulty consumer can never abort
233
+ // the job.
234
+ const emitProgress = event => {
235
+ if (opts && typeof opts.onProgress === 'function') {
236
+ try {
237
+ opts.onProgress(event);
238
+ }
239
+ catch (error) {
240
+ console.log(`ERROR in onProgress callback: ${error.message}`);
241
+ }
242
+ }
243
+ };
231
244
  // Make a temporary copy of the report. Precondition: report is valid.
232
245
  let tempReport = JSON.parse(JSON.stringify(report));
233
246
  let page = null;
@@ -247,6 +260,9 @@ exports.doActs = async report => {
247
260
  if (tempReport?.jobData && ! tempReport.jobData.aborted) {
248
261
  let act = acts[actIndex];
249
262
  const {type, which} = act;
263
+ // #44: signal act start so a consumer can heartbeat and advance a live
264
+ // per-act (per-tool, for test acts) view while the act runs.
265
+ emitProgress({event: 'actStart', index: Number(actIndex), type, which});
250
266
  const actSuffix = type === 'test' ? ` ${which}` : '';
251
267
  const message = `>>>> ${type}${actSuffix}`;
252
268
  // Log the act.
@@ -477,6 +493,19 @@ exports.doActs = async report => {
477
493
  act.expectationFailures = failureCount;
478
494
  }
479
495
  }
496
+ // #44: signal test-act completion with its outcome — timedOut is the
497
+ // most useful, since doActs already SIGKILLs a timed-out child — and its
498
+ // elapsed time, so consumers need not re-derive them from the report.
499
+ emitProgress({
500
+ event: 'actEnd',
501
+ index: Number(actIndex),
502
+ type,
503
+ which,
504
+ outcome: timedOut
505
+ ? 'timedOut'
506
+ : act.data && act.data.prevented ? 'prevented' : 'success',
507
+ elapsedMs: Date.now() - startTime
508
+ });
480
509
  }
481
510
  // Otherwise, if a current page exists:
482
511
  else if (page) {
package/run.js CHANGED
@@ -76,6 +76,20 @@ const getTmpDirPath = async jobName => {
76
76
  };
77
77
  // Runs a job and returns a report.
78
78
  exports.doJob = async (job, opts = {}) => {
79
+ // Fire-and-forget progress emitter (see #44). A handler exception is logged,
80
+ // never propagated, so a faulty consumer can never abort the job. Payloads
81
+ // carry identifiers and timing only — no report content — and are delivered
82
+ // via the existing opts param, so doJob's signature is unchanged.
83
+ const emitProgress = event => {
84
+ if (opts && typeof opts.onProgress === 'function') {
85
+ try {
86
+ opts.onProgress(event);
87
+ }
88
+ catch (error) {
89
+ console.log(`ERROR in onProgress callback: ${error.message}`);
90
+ }
91
+ }
92
+ };
79
93
  // Initialize a report as a copy of the job.
80
94
  let report = JSON.parse(JSON.stringify(job));
81
95
  report.jobData ??= {};
@@ -94,6 +108,7 @@ exports.doJob = async (job, opts = {}) => {
94
108
  else {
95
109
  // Report this.
96
110
  console.log(`Starting job ${job.id} (${job.target.what})`);
111
+ emitProgress({event: 'jobStart', id: job.id});
97
112
  const tmpDir = await getTmpDirPath(job.id);
98
113
  // Add initialized job data to the report.
99
114
  const startTime = new Date();
@@ -128,8 +143,15 @@ exports.doJob = async (job, opts = {}) => {
128
143
  if (job.browserID && job.target && job.standard !== 'no') {
129
144
  // Initialize a catalog so it precedes any page images in the report.
130
145
  report.catalog = {};
146
+ // getCatalog() navigates to the target (procs/catalog.js) — which, on a
147
+ // slow, blocked, or dead target, can retry across browser types — before
148
+ // any act runs. Bracket it with events so a consumer can heartbeat through
149
+ // this phase instead of seeing the job fall silent until the first act.
150
+ emitProgress({event: 'catalogStart'});
151
+ const catalogStartMs = Date.now();
131
152
  // Add a catalog of the target, and a page image if required, to the report.
132
153
  report.catalog = await getCatalog(report);
154
+ emitProgress({event: 'catalogEnd', elapsedMs: Date.now() - catalogStartMs});
133
155
  }
134
156
  // Perform the acts and revise the report.
135
157
  report = await doActs(report, opts);
@@ -152,6 +174,9 @@ exports.doJob = async (job, opts = {}) => {
152
174
  report.jobData.toolTimes[item[0]] = item[1];
153
175
  });
154
176
  }
177
+ // Signal job completion (also fires for an invalid job, after its abort data
178
+ // is set) so a consumer can finalize regardless of outcome.
179
+ emitProgress({event: 'jobEnd', id: job.id, aborted: !! report.jobData.aborted});
155
180
  // Return the report.
156
181
  return report;
157
182
  };