testaro 60.4.0 → 60.4.1

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/UPGRADES.md CHANGED
@@ -2802,3 +2802,113 @@ This gives you:
2802
2802
  ✅ No git conflicts
2803
2803
 
2804
2804
  Claude Sonnet 4.5 • 1x
2805
+
2806
+ ## Cleanup synchronization
2807
+
2808
+ Claude Sonnet 4.5 recommendation to handle already-closed error messages on server (but not the local host) after end of a job:
2809
+
2810
+ The issue is that forceKillBrowsers() is killing the browser processes immediately, but the async browserClose() calls in the signal handlers are still trying to close already-dead browsers, causing repeated errors.
2811
+
2812
+ Solution: Coordinate Async and Sync Cleanup
2813
+ Replace the cleanup handlers in run.js:
2814
+
2815
+ ```
2816
+ // CLEANUP HANDLERS
2817
+
2818
+ // Track cleanup state.
2819
+ let cleanupInProgress = false;
2820
+ let forceKillScheduled = false;
2821
+
2822
+ // Force kills all Playwright browser processes synchronously.
2823
+ const forceKillBrowsers = () => {
2824
+ if (cleanupInProgress) {
2825
+ return;
2826
+ }
2827
+ cleanupInProgress = true;
2828
+
2829
+ const {execSync} = require('child_process');
2830
+ try {
2831
+ // Kill Chromium headless shell processes.
2832
+ execSync('pkill -9 -f "chromium_headless_shell.*headless_shell"', {stdio: 'ignore'});
2833
+ }
2834
+ catch(error) {
2835
+ // Ignore errors - processes may not exist.
2836
+ }
2837
+ };
2838
+
2839
+ // Graceful cleanup with timeout fallback.
2840
+ const gracefulCleanup = async (timeoutMs = 2000) => {
2841
+ if (forceKillScheduled) {
2842
+ return;
2843
+ }
2844
+ forceKillScheduled = true;
2845
+
2846
+ // Try graceful close with timeout.
2847
+ const closePromise = browserClose();
2848
+ const timeoutPromise = new Promise(resolve => setTimeout(resolve, timeoutMs));
2849
+
2850
+ await Promise.race([closePromise, timeoutPromise]);
2851
+
2852
+ // Force kill any remaining processes.
2853
+ forceKillBrowsers();
2854
+ };
2855
+
2856
+ // Synchronous cleanup on process exit (last resort).
2857
+ process.on('exit', () => {
2858
+ if (!cleanupInProgress) {
2859
+ forceKillBrowsers();
2860
+ }
2861
+ });
2862
+
2863
+ // Graceful cleanup on SIGINT (Ctrl+C).
2864
+ process.on('SIGINT', async () => {
2865
+ console.log('\nReceived SIGINT, cleaning up...');
2866
+ await gracefulCleanup();
2867
+ process.exit(130);
2868
+ });
2869
+
2870
+ // Graceful cleanup on SIGTERM.
2871
+ process.on('SIGTERM', async () => {
2872
+ console.log('Received SIGTERM, cleaning up...');
2873
+ await gracefulCleanup();
2874
+ process.exit(143);
2875
+ });
2876
+
2877
+ // Cleanup on uncaught exceptions.
2878
+ process.on('uncaughtException', async error => {
2879
+ console.error('Uncaught exception:', error);
2880
+ await gracefulCleanup(1000);
2881
+ process.exit(1);
2882
+ });
2883
+ ```
2884
+
2885
+ And in `doActs`, replace the cleanup:
2886
+
2887
+ ```
2888
+ console.log('Acts completed');
2889
+ // If standardization is required:
2890
+ if (['also', 'only'].includes(standard)) {
2891
+ // ... existing standardization code ...
2892
+ }
2893
+ // Close the browser gracefully.
2894
+ await gracefulCleanup();
2895
+ // Delete the temporary report file.
2896
+ await fs.rm(reportPath, {force: true});
2897
+ return report;
2898
+ ```
2899
+
2900
+ Key changes:
2901
+
2902
+ gracefulCleanup(): Attempts async browserClose() but races it against a timeout, then force-kills
2903
+ Guard flags: Prevent multiple simultaneous cleanup attempts that cause the error messages
2904
+ Timeout: Gives browserClose 2 seconds to complete before forcing kill
2905
+ No redundant calls: Only gracefulCleanup() calls both browserClose() and forceKillBrowsers()
2906
+ process.on('exit') as fallback: Only runs if other cleanup didn't complete
2907
+ This approach:
2908
+
2909
+ Tries graceful closure first (avoiding the errors)
2910
+ Falls back to force-kill after timeout
2911
+ Prevents the async cleanup from running after force-kill
2912
+ Works consistently on both macOS and Ubuntu
2913
+
2914
+ The above recommendation seems complex. Meanwhile the error message has been suppressed on the basis that context closure is not necessarily an error.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testaro",
3
- "version": "60.4.0",
3
+ "version": "60.4.1",
4
4
  "description": "Run 1000 web accessibility tests from 11 tools and get a standardized report",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -35,7 +35,7 @@ const agent = process.env.AGENT;
35
35
 
36
36
  // FUNCTIONS
37
37
 
38
- // Sends a notification to an observer.
38
+ // Sends a notice to an observer.
39
39
  exports.tellServer = (report, messageParams, logMessage) => {
40
40
  const {serverID} = report.sources;
41
41
  const observerURL = typeof serverID === 'number' ? process.env[`NETWATCH_URL_${serverID}_OBSERVE`] : '';
package/run.js CHANGED
@@ -114,6 +114,8 @@ let actCount = 0;
114
114
  // Facts about the current act.
115
115
  let actIndex = 0;
116
116
  let browser;
117
+ let cleanupInProgress = false;
118
+ let browserCloseIntentional = false;
117
119
  let browserContext;
118
120
  let page;
119
121
  let report;
@@ -269,20 +271,19 @@ const addError = (alsoLog, alsoAbort, report, actIndex, message) => {
269
271
  abortActs(report, actIndex);
270
272
  }
271
273
  };
272
- // Closes the current browser.
274
+ // Closes any current browser.
273
275
  const browserClose = async () => {
276
+ // If a browser exists:
274
277
  if (browser) {
275
278
  browserCloseIntentional = true;
279
+ // Try to close all its contexts and ignore any messages that they are already closed.
276
280
  for (const context of browser.contexts()) {
277
281
  try {
278
282
  await context.close();
279
283
  }
280
- catch(error) {
281
- console.log(
282
- `ERROR trying to close context: ${error.message.slice(0, 200).replace(/\n.+/s, '')}`
283
- );
284
- }
284
+ catch(error) {}
285
285
  }
286
+ // Close the browser.
286
287
  await browser.close();
287
288
  browserCloseIntentional = false;
288
289
  browser = null;
@@ -303,7 +304,7 @@ const launch = exports.launch = async (
303
304
  report.target.url = url;
304
305
  // Create a browser of the specified or default type.
305
306
  const browserType = playwrightBrowsers[browserID];
306
- // Close the current browser, if any.
307
+ // Close any current browser.
307
308
  await browserClose();
308
309
  // Define browser options.
309
310
  const browserOptions = {
@@ -678,6 +679,7 @@ const launchSpecs = (act, report) => [
678
679
  // Performs the acts in a report and adds the results to the report.
679
680
  const doActs = async (report, opts = {}) => {
680
681
  const {acts} = report;
682
+ // Get the granular observation options, if any.
681
683
  const {onProgress = null, signal = null} = opts;
682
684
  // Get the standardization specification.
683
685
  const standard = report.standard || 'only';
@@ -696,7 +698,7 @@ const doActs = async (report, opts = {}) => {
696
698
  if (report.observe) {
697
699
  const whichParam = which ? `&which=${which}` : '';
698
700
  const messageParams = `act=${type}${whichParam}`;
699
- // If a progress callback has been provided:
701
+ // If a progress callback has been provided by a caller on this host:
700
702
  if (onProgress) {
701
703
  // Notify the observer of the act.
702
704
  try {
@@ -712,7 +714,7 @@ const doActs = async (report, opts = {}) => {
712
714
  }
713
715
  // Otherwise, i.e. if no progress callback has been provided:
714
716
  else {
715
- // Notify the observer of the act and log it.
717
+ // Notify the remote observer of the act and log it.
716
718
  tellServer(report, messageParams, message);
717
719
  }
718
720
  }
@@ -1598,7 +1600,7 @@ exports.doJob = async (job, opts = {}) => {
1598
1600
  process.exit();
1599
1601
  }
1600
1602
  });
1601
- // Perform the acts and get a report.
1603
+ // Perform the acts with any specified same-host observation options and get a report.
1602
1604
  report = await doActs(report, opts);
1603
1605
  // Add the end time and duration to the report.
1604
1606
  const endTime = new Date();
@@ -1623,10 +1625,6 @@ exports.doJob = async (job, opts = {}) => {
1623
1625
 
1624
1626
  // CLEANUP HANDLERS
1625
1627
 
1626
- // Track whether cleanup is in progress.
1627
- let cleanupInProgress = false;
1628
- let browserCloseIntentional = false;
1629
-
1630
1628
  // Force-kills any Playwright browser processes synchronously.
1631
1629
  const forceKillBrowsers = () => {
1632
1630
  if (cleanupInProgress) {