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 +110 -0
- package/package.json +1 -1
- package/procs/tellServer.js +1 -1
- package/run.js +12 -14
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
package/procs/tellServer.js
CHANGED
|
@@ -35,7 +35,7 @@ const agent = process.env.AGENT;
|
|
|
35
35
|
|
|
36
36
|
// FUNCTIONS
|
|
37
37
|
|
|
38
|
-
// Sends a
|
|
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
|
|
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
|
|
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) {
|