@validate.qa/runner 1.0.101 → 1.0.103
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/README.md +3 -0
- package/dist/{chunk-SN3PN4G4.mjs → chunk-36AWDKPL.mjs} +6 -2
- package/dist/{chunk-CYWHPXNZ.mjs → chunk-7SH3COMQ.mjs} +1 -1
- package/dist/{chunk-BBTK6OX6.mjs → chunk-BECCGMU4.mjs} +383 -11
- package/dist/{chunk-4SZC5ZX3.mjs → chunk-CG75I3ZZ.mjs} +25477 -5374
- package/dist/{chunk-TPU42VH2.mjs → chunk-CMM4DOXC.mjs} +10 -5
- package/dist/{chunk-M3VZGHZQ.mjs → chunk-NNJYKR3O.mjs} +22 -11
- package/dist/{chunk-IGXGSDE3.mjs → chunk-OTKJZIOD.mjs} +1298 -384
- package/dist/{chunk-CO5KHIVX.mjs → chunk-TK5KEGNW.mjs} +1 -1
- package/dist/{chunk-FQUH2PU2.mjs → chunk-WVLBQDFP.mjs} +140 -7
- package/dist/{chunk-2OKZY3WN.mjs → chunk-YI3ZC2XI.mjs} +2 -2
- package/dist/{chunk-XZUUBMP3.mjs → chunk-YIM746WO.mjs} +1 -1
- package/dist/cli.js +140692 -118903
- package/dist/cli.mjs +362 -108
- package/dist/credential-mcp-server.js +1402 -350
- package/dist/credential-mcp-server.mjs +3 -3
- package/dist/discovery-mcp-server.js +1330 -401
- package/dist/discovery-mcp-server.mjs +4 -4
- package/dist/dist-2G7YTNH5.mjs +17 -0
- package/dist/inbox-mcp-server.js +1676 -629
- package/dist/inbox-mcp-server.mjs +3 -3
- package/dist/route-seed-worker.js +1244 -330
- package/dist/route-seed-worker.mjs +4 -4
- package/dist/run-write-guard-preload.js +1622 -332
- package/dist/run-write-guard-preload.mjs +4 -4
- package/dist/run-write-guard.js +1622 -332
- package/dist/run-write-guard.mjs +3 -3
- package/dist/spec-driver.js +1 -1
- package/dist/spec-driver.mjs +1 -1
- package/package.json +3 -2
- package/dist/dist-VTPBY2C4.mjs +0 -17
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
init_public_address,
|
|
4
|
+
public_address_exports,
|
|
3
5
|
require_ai_queue,
|
|
4
6
|
require_api_auth_headers,
|
|
5
7
|
require_api_call_redaction,
|
|
@@ -31,22 +33,23 @@ import {
|
|
|
31
33
|
require_resource_governor,
|
|
32
34
|
require_result_api_call_bodies,
|
|
33
35
|
require_result_shed
|
|
34
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-CG75I3ZZ.mjs";
|
|
35
37
|
import {
|
|
36
38
|
require_tar_isolated
|
|
37
39
|
} from "./chunk-AAUHXGDS.mjs";
|
|
38
40
|
import "./chunk-EXRFHATN.mjs";
|
|
39
41
|
import {
|
|
40
|
-
require_browser_config
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
import "./chunk-
|
|
44
|
-
import "./chunk-
|
|
45
|
-
import "./chunk-
|
|
46
|
-
import "./chunk-
|
|
47
|
-
import "./chunk-
|
|
48
|
-
import "./chunk-
|
|
49
|
-
import "./chunk-
|
|
42
|
+
require_browser_config,
|
|
43
|
+
require_cloud_runner
|
|
44
|
+
} from "./chunk-CMM4DOXC.mjs";
|
|
45
|
+
import "./chunk-NNJYKR3O.mjs";
|
|
46
|
+
import "./chunk-YIM746WO.mjs";
|
|
47
|
+
import "./chunk-WVLBQDFP.mjs";
|
|
48
|
+
import "./chunk-7SH3COMQ.mjs";
|
|
49
|
+
import "./chunk-TK5KEGNW.mjs";
|
|
50
|
+
import "./chunk-YI3ZC2XI.mjs";
|
|
51
|
+
import "./chunk-BECCGMU4.mjs";
|
|
52
|
+
import "./chunk-36AWDKPL.mjs";
|
|
50
53
|
import {
|
|
51
54
|
LIVE_FRAME_IDLE_PUSH_INTERVAL_MS,
|
|
52
55
|
MOBILE_COLD_BOOT_SETTLE_MS,
|
|
@@ -81,18 +84,175 @@ import {
|
|
|
81
84
|
stepAppliesToPlatform,
|
|
82
85
|
stepsMissingPlatformLocators,
|
|
83
86
|
stepsNeedingReviewForPlatform
|
|
84
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-OTKJZIOD.mjs";
|
|
85
88
|
import {
|
|
86
89
|
__commonJS,
|
|
90
|
+
__require,
|
|
91
|
+
__toCommonJS,
|
|
87
92
|
__toESM
|
|
88
93
|
} from "./chunk-WQXBGQU4.mjs";
|
|
89
94
|
|
|
95
|
+
// ../runner-core/dist/utils/playwright-signal-handlers.js
|
|
96
|
+
var require_playwright_signal_handlers = __commonJS({
|
|
97
|
+
"../runner-core/dist/utils/playwright-signal-handlers.js"(exports) {
|
|
98
|
+
"use strict";
|
|
99
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
100
|
+
exports.patchBrowserTypeSignalDefaults = patchBrowserTypeSignalDefaults;
|
|
101
|
+
exports.disablePlaywrightSignalHandlers = disablePlaywrightSignalHandlers2;
|
|
102
|
+
var playwright_1 = __require("playwright");
|
|
103
|
+
var PATCHED = /* @__PURE__ */ Symbol.for("validateqa.playwrightSignalDefaults");
|
|
104
|
+
var LAUNCH_METHODS = { launch: 0, launchPersistentContext: 1 };
|
|
105
|
+
function patchBrowserTypeSignalDefaults(browserType) {
|
|
106
|
+
const target = browserType;
|
|
107
|
+
if (target[PATCHED])
|
|
108
|
+
return;
|
|
109
|
+
for (const [method, optionsIndex] of Object.entries(LAUNCH_METHODS)) {
|
|
110
|
+
const original = target[method];
|
|
111
|
+
if (typeof original !== "function")
|
|
112
|
+
continue;
|
|
113
|
+
target[method] = function(...args) {
|
|
114
|
+
const given = args[optionsIndex];
|
|
115
|
+
args[optionsIndex] = { handleSIGINT: false, handleSIGTERM: false, ...given && typeof given === "object" ? given : {} };
|
|
116
|
+
return original.apply(this, args);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
target[PATCHED] = true;
|
|
120
|
+
}
|
|
121
|
+
function disablePlaywrightSignalHandlers2() {
|
|
122
|
+
patchBrowserTypeSignalDefaults(Object.getPrototypeOf(playwright_1.chromium));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ../runner-core/dist/utils/tls-cert-probe.js
|
|
128
|
+
var require_tls_cert_probe = __commonJS({
|
|
129
|
+
"../runner-core/dist/utils/tls-cert-probe.js"(exports) {
|
|
130
|
+
"use strict";
|
|
131
|
+
var __importDefault = exports && exports.__importDefault || function(mod) {
|
|
132
|
+
return mod && mod.__esModule ? mod : { "default": mod };
|
|
133
|
+
};
|
|
134
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
135
|
+
exports.RUN_NOTE_WAIT_MS = void 0;
|
|
136
|
+
exports.probeTargetCertificate = probeTargetCertificate;
|
|
137
|
+
exports.tlsCertFindingLine = tlsCertFindingLine;
|
|
138
|
+
exports.targetCertificateNote = targetCertificateNote2;
|
|
139
|
+
exports.noteWithinCap = noteWithinCap2;
|
|
140
|
+
exports.withRunNote = withRunNote2;
|
|
141
|
+
var node_dns_1 = __require("dns");
|
|
142
|
+
var node_net_1 = __importDefault(__require("net"));
|
|
143
|
+
var node_tls_1 = __importDefault(__require("tls"));
|
|
144
|
+
var public_address_1 = (init_public_address(), __toCommonJS(public_address_exports));
|
|
145
|
+
var cloud_runner_js_1 = require_cloud_runner();
|
|
146
|
+
var PROBE_TIMEOUT_MS = 5e3;
|
|
147
|
+
var CERT_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
148
|
+
"CERT_HAS_EXPIRED",
|
|
149
|
+
"CERT_NOT_YET_VALID",
|
|
150
|
+
"CERT_REVOKED",
|
|
151
|
+
"CERT_REJECTED",
|
|
152
|
+
"CERT_UNTRUSTED",
|
|
153
|
+
"CERT_SIGNATURE_FAILURE",
|
|
154
|
+
"CERT_CHAIN_TOO_LONG",
|
|
155
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
156
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
157
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
158
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
159
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
160
|
+
"UNABLE_TO_DECRYPT_CERT_SIGNATURE",
|
|
161
|
+
"UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
|
|
162
|
+
"ERROR_IN_CERT_NOT_BEFORE_FIELD",
|
|
163
|
+
"ERROR_IN_CERT_NOT_AFTER_FIELD",
|
|
164
|
+
"INVALID_CA",
|
|
165
|
+
"INVALID_PURPOSE",
|
|
166
|
+
"PATH_LENGTH_EXCEEDED",
|
|
167
|
+
"HOSTNAME_MISMATCH",
|
|
168
|
+
"ERR_TLS_CERT_ALTNAME_INVALID"
|
|
169
|
+
]);
|
|
170
|
+
async function probeTargetCertificate(baseUrl, opts = {}) {
|
|
171
|
+
let url;
|
|
172
|
+
try {
|
|
173
|
+
url = new URL(baseUrl);
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
if (url.protocol !== "https:" || !url.hostname)
|
|
178
|
+
return null;
|
|
179
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
180
|
+
const port = url.port ? Number(url.port) : 443;
|
|
181
|
+
const shared = opts.shared ?? (0, cloud_runner_js_1.isSharedCloudRunner)();
|
|
182
|
+
const ipLiteral = node_net_1.default.isIP(hostname) !== 0;
|
|
183
|
+
if (shared && ipLiteral && (0, public_address_1.isPrivateOrLocalIpAddress)(hostname))
|
|
184
|
+
return null;
|
|
185
|
+
return new Promise((resolve3) => {
|
|
186
|
+
let settled = false;
|
|
187
|
+
let socket = null;
|
|
188
|
+
const finish = (finding) => {
|
|
189
|
+
if (settled)
|
|
190
|
+
return;
|
|
191
|
+
settled = true;
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
socket?.destroy();
|
|
194
|
+
resolve3(finding);
|
|
195
|
+
};
|
|
196
|
+
const timer = setTimeout(() => finish(null), opts.timeoutMs ?? PROBE_TIMEOUT_MS);
|
|
197
|
+
try {
|
|
198
|
+
socket = node_tls_1.default.connect({
|
|
199
|
+
host: hostname,
|
|
200
|
+
port,
|
|
201
|
+
...ipLiteral ? {} : { servername: hostname },
|
|
202
|
+
rejectUnauthorized: true,
|
|
203
|
+
...opts.ca ? { ca: opts.ca } : {},
|
|
204
|
+
...shared ? { lookup: (0, public_address_1.createPublicOnlyLookup)((name, o, cb) => (0, node_dns_1.lookup)(name, o, cb)) } : {},
|
|
205
|
+
ALPNProtocols: ["http/1.1"]
|
|
206
|
+
});
|
|
207
|
+
} catch {
|
|
208
|
+
finish(null);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
socket.once("secureConnect", () => finish(null));
|
|
212
|
+
socket.once("error", (err) => {
|
|
213
|
+
const code = typeof err.code === "string" ? err.code : "";
|
|
214
|
+
finish(CERT_ERROR_CODES.has(code) ? { host: url.host, code, detail: err.message.replace(/\s+/g, " ").trim().slice(0, 300) } : null);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function tlsCertFindingLine(finding) {
|
|
219
|
+
return `\u26A0\uFE0F TLS certificate: ${finding.host} presented a certificate that strict TLS rejects (${finding.code}: ${finding.detail}). The run went ahead because test browsers ignore HTTPS errors, so this is not a failure. A visitor's browser may show a certificate warning: this check does not fetch a missing intermediate certificate and does not use the operating system's trust store (a private CA the visitor's machine trusts), so a browser can accept a certificate it rejects.`;
|
|
220
|
+
}
|
|
221
|
+
async function targetCertificateNote2(baseUrl, opts = {}) {
|
|
222
|
+
try {
|
|
223
|
+
const finding = await probeTargetCertificate(baseUrl, opts);
|
|
224
|
+
return finding ? tlsCertFindingLine(finding) : null;
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
exports.RUN_NOTE_WAIT_MS = 250;
|
|
230
|
+
async function noteWithinCap2(note, capMs = exports.RUN_NOTE_WAIT_MS) {
|
|
231
|
+
let timer;
|
|
232
|
+
const cap = new Promise((resolve3) => {
|
|
233
|
+
timer = setTimeout(() => resolve3(null), capMs);
|
|
234
|
+
});
|
|
235
|
+
try {
|
|
236
|
+
return await Promise.race([note.catch(() => null), cap]);
|
|
237
|
+
} finally {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function withRunNote2(result, note) {
|
|
242
|
+
if (!note)
|
|
243
|
+
return result;
|
|
244
|
+
return { ...result, logs: result.logs ? `${note}
|
|
245
|
+
${result.logs}` : note };
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
90
250
|
// package.json
|
|
91
251
|
var require_package = __commonJS({
|
|
92
252
|
"package.json"(exports, module) {
|
|
93
253
|
module.exports = {
|
|
94
254
|
name: "@validate.qa/runner",
|
|
95
|
-
version: "1.0.
|
|
255
|
+
version: "1.0.103",
|
|
96
256
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
97
257
|
bin: {
|
|
98
258
|
"validate-runner": "dist/cli.js"
|
|
@@ -100,7 +260,8 @@ var require_package = __commonJS({
|
|
|
100
260
|
scripts: {
|
|
101
261
|
build: "tsup",
|
|
102
262
|
dev: "tsx src/cli.ts",
|
|
103
|
-
|
|
263
|
+
lint: "eslint .",
|
|
264
|
+
test: "node --import tsx --test src/supervisor-child-argv.test.ts src/audit-scrub.test.ts src/retry-on-429.test.ts src/version-check.test.ts src/poll-interval.test.ts src/node-version-notice.test.ts src/services/auth-state.test.ts src/run-no-longer-active.test.ts src/mode-abort-no-verdict.test.ts src/discovery-render-type.test.ts src/batch-heal-no-start.test.ts src/heartbeat-active-runs.test.ts src/result-post-structure-413.test.ts src/result-shed-routes-413.test.ts src/result-post-retry.test.ts src/result-media-stale-epoch.test.ts src/run-write-guard-bundle.test.ts src/run-write-guard-bundle-skip.test.ts src/spec-driver-bundle.test.ts src/mcp-servers-bundle.test.ts src/codex-files-mcp-server-bundle.test.ts src/claim-nonce-not-logged.test.ts src/sign-in-capture.test.ts src/run-status-verdicts.test.ts src/sign-in-failure.test.ts src/mobile-ui-path-succeeded.test.ts src/supervisor-policy.test.ts src/login-check.test.ts src/heal-post-lost-ack.test.ts src/ui-heal-result-video.test.ts src/heal-log-stream-chunks.test.ts src/shutdown-claim-window.test.ts src/result-post-redeploy-budget.test.ts src/shutdown-signals.test.ts",
|
|
104
265
|
"test:mobile": "node --import tsx --test --test-concurrency=2 src/runner.mobile-ai-env.test.ts src/runner.mobile-heal-audit-wiring.test.ts src/mobile-device-queue.test.ts src/device-lease.test.ts src/runner-watchdog.test.ts src/result-payload-budget.test.ts src/result-media-upload.test.ts src/services/appium-*.test.ts src/services/*mobile*.test.ts src/services/mobile/*.test.ts",
|
|
105
266
|
"smoke:installed": "node scripts/installed-run-smoke.mjs",
|
|
106
267
|
"check:packed-mcp": "node scripts/packed-install-check.mjs",
|
|
@@ -320,6 +481,7 @@ var import_grok_repo_enrichment_runner = __toESM(require_grok_repo_enrichment_ru
|
|
|
320
481
|
var import_repo_brief_execution = __toESM(require_repo_brief_execution());
|
|
321
482
|
var import_repo_read_log = __toESM(require_repo_read_log());
|
|
322
483
|
var import_cli_session_failure = __toESM(require_cli_session_failure());
|
|
484
|
+
var import_playwright_signal_handlers = __toESM(require_playwright_signal_handlers());
|
|
323
485
|
var import_cli_limit = __toESM(require_cli_limit());
|
|
324
486
|
var import_fetch_cli_subscription = __toESM(require_fetch_cli_subscription());
|
|
325
487
|
var import_codex_cli = __toESM(require_codex_cli());
|
|
@@ -381,6 +543,7 @@ var EXIT_TERMINAL = 64;
|
|
|
381
543
|
var SUPERVISOR_WINDOW_MS = 36e5;
|
|
382
544
|
var SUPERVISOR_HEALTHY_MS = 10 * 6e4;
|
|
383
545
|
function nextSupervisorAction(input, state2, limits) {
|
|
546
|
+
if (input.signalled) return { action: "stop", exitCode: 0, reason: "clean" };
|
|
384
547
|
if (input.code === 0) return { action: "stop", exitCode: 0, reason: "clean" };
|
|
385
548
|
if (input.code === EXIT_TERMINAL) return { action: "stop", exitCode: EXIT_TERMINAL, reason: "terminal" };
|
|
386
549
|
const restartTimes = [...state2.restartTimes.filter((t) => input.now - t < SUPERVISOR_WINDOW_MS), input.now];
|
|
@@ -532,6 +695,24 @@ function upgradeAction(activeRuns) {
|
|
|
532
695
|
return activeRuns > 0 ? "drain" : "exit";
|
|
533
696
|
}
|
|
534
697
|
|
|
698
|
+
// src/shutdown-signals.ts
|
|
699
|
+
var REPEAT_SIGNAL_WINDOW_MS = 1e3;
|
|
700
|
+
function installShutdownSignals(target, onShutdown, opts) {
|
|
701
|
+
const now = opts.now ?? Date.now;
|
|
702
|
+
const windowMs = opts.repeatWindowMs ?? REPEAT_SIGNAL_WINDOW_MS;
|
|
703
|
+
let firstAt = null;
|
|
704
|
+
const handle = () => {
|
|
705
|
+
if (firstAt === null) {
|
|
706
|
+
firstAt = now();
|
|
707
|
+
onShutdown();
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (now() - firstAt > windowMs) opts.forceExit();
|
|
711
|
+
};
|
|
712
|
+
target.on("SIGINT", handle);
|
|
713
|
+
target.on("SIGTERM", handle);
|
|
714
|
+
}
|
|
715
|
+
|
|
535
716
|
// src/runner.ts
|
|
536
717
|
init_dist();
|
|
537
718
|
|
|
@@ -1627,7 +1808,8 @@ async function clearField(sessionPath, bounds) {
|
|
|
1627
1808
|
} catch (err) {
|
|
1628
1809
|
if (isAppiumDriverDeadError(err)) throw err;
|
|
1629
1810
|
throw new Error(
|
|
1630
|
-
`No focused element available for CLEAR. Tap an input field first.${err instanceof Error ? ` (${err.message})` : ""}
|
|
1811
|
+
`No focused element available for CLEAR. Tap an input field first.${err instanceof Error ? ` (${err.message})` : ""}`,
|
|
1812
|
+
{ cause: err }
|
|
1631
1813
|
);
|
|
1632
1814
|
}
|
|
1633
1815
|
}
|
|
@@ -3857,7 +4039,7 @@ async function getWindowSize2(sessionId, retries = 3) {
|
|
|
3857
4039
|
} catch (err) {
|
|
3858
4040
|
if (attempt === retries - 1) {
|
|
3859
4041
|
if (isTransportError(err)) throw err;
|
|
3860
|
-
throw new Error(`Failed to get window size after ${retries} attempts: ${err instanceof Error ? err.message : String(err)}
|
|
4042
|
+
throw new Error(`Failed to get window size after ${retries} attempts: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
3861
4043
|
}
|
|
3862
4044
|
await new Promise((resolve3) => setTimeout(resolve3, Math.pow(2, attempt) * 100));
|
|
3863
4045
|
}
|
|
@@ -5224,7 +5406,7 @@ async function executeSwipe(sessionId, step) {
|
|
|
5224
5406
|
screenH = windowSize.height;
|
|
5225
5407
|
} catch (err) {
|
|
5226
5408
|
if (isTransportError(err)) throw err;
|
|
5227
|
-
throw new Error(`Swipe action failed: could not determine screen dimensions. ${err instanceof Error ? err.message : String(err)}
|
|
5409
|
+
throw new Error(`Swipe action failed: could not determine screen dimensions. ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
5228
5410
|
}
|
|
5229
5411
|
const { startX, startY, endX, endY } = computeMobileSwipePointers(screenW, screenH, direction, distance);
|
|
5230
5412
|
await wdRequest(`/session/${sessionId}/actions`, {
|
|
@@ -5543,12 +5725,12 @@ async function executeMobileTest(options) {
|
|
|
5543
5725
|
const base = err instanceof Error ? err.message : String(err);
|
|
5544
5726
|
if (selectedDevice.isPhysical) {
|
|
5545
5727
|
const guidance = platformFilter === "IOS" ? `Real iOS devices require a signed WebDriverAgent (configure signing in Xcode, or pass appium:xcodeOrgId / appium:xcodeSigningId) and the app (${options.target.appId}) must be installed and trusted on the device.` : `Ensure ${selectedDevice.name} is authorized for USB debugging and ${options.target.appId} is installed.`;
|
|
5546
|
-
throw new Error(`${base} \u2014 could not start an Appium session on the physical device ${selectedDevice.name}. ${guidance}
|
|
5728
|
+
throw new Error(`${base} \u2014 could not start an Appium session on the physical device ${selectedDevice.name}. ${guidance}`, { cause: err });
|
|
5547
5729
|
}
|
|
5548
5730
|
const appListUnknown = !selectedDevice.installedApps || selectedDevice.installedApps.length === 0;
|
|
5549
5731
|
if (appListUnknown) {
|
|
5550
5732
|
const installHint = platformFilter === "IOS" ? `xcrun simctl install ${selectedDevice.id} <app-path>` : `adb -s ${selectedDevice.id} install <apk-path>`;
|
|
5551
|
-
throw new Error(`${base} \u2014 could not confirm ${options.target.appId} is installed on ${selectedDevice.name}; if it is missing, install it first: ${installHint}
|
|
5733
|
+
throw new Error(`${base} \u2014 could not confirm ${options.target.appId} is installed on ${selectedDevice.name}; if it is missing, install it first: ${installHint}`, { cause: err });
|
|
5552
5734
|
}
|
|
5553
5735
|
throw err;
|
|
5554
5736
|
}
|
|
@@ -5588,7 +5770,7 @@ async function executeMobileTest(options) {
|
|
|
5588
5770
|
detail,
|
|
5589
5771
|
recordedFromClearedState
|
|
5590
5772
|
);
|
|
5591
|
-
if (requirementError) throw new Error(requirementError);
|
|
5773
|
+
if (requirementError) throw new Error(requirementError, { cause: err });
|
|
5592
5774
|
log2(`App-data clear failed (AUTO continues with plain fresh launch): ${detail}`);
|
|
5593
5775
|
}
|
|
5594
5776
|
} else {
|
|
@@ -5623,7 +5805,8 @@ async function executeMobileTest(options) {
|
|
|
5623
5805
|
await openDeepLink(sessionId, options.target.platform, options.target.deeplink, options.target.appId);
|
|
5624
5806
|
} catch (err) {
|
|
5625
5807
|
throw new Error(
|
|
5626
|
-
`Deep-link launch failed; refusing to replay from the wrong entry screen: ${err instanceof Error ? err.message : String(err)}
|
|
5808
|
+
`Deep-link launch failed; refusing to replay from the wrong entry screen: ${err instanceof Error ? err.message : String(err)}`,
|
|
5809
|
+
{ cause: err }
|
|
5627
5810
|
);
|
|
5628
5811
|
}
|
|
5629
5812
|
}
|
|
@@ -5904,7 +6087,8 @@ async function createMobileDiscoverySession(target, options) {
|
|
|
5904
6087
|
});
|
|
5905
6088
|
releaseControlPortSlot(discoveryPortSlotDeviceId);
|
|
5906
6089
|
throw new Error(
|
|
5907
|
-
`Deep-link launch failed for mobile discovery: ${err instanceof Error ? err.message : String(err)}
|
|
6090
|
+
`Deep-link launch failed for mobile discovery: ${err instanceof Error ? err.message : String(err)}`,
|
|
6091
|
+
{ cause: err }
|
|
5908
6092
|
);
|
|
5909
6093
|
}
|
|
5910
6094
|
}
|
|
@@ -6960,7 +7144,8 @@ async function startSession(validated, res) {
|
|
|
6960
7144
|
} catch (err) {
|
|
6961
7145
|
const detail = err instanceof Error ? err.message : String(err);
|
|
6962
7146
|
throw new Error(
|
|
6963
|
-
`Failed to open the configured deep link before recording (${detail}). The Appium session was closed; verify the URL and app deep-link configuration, then try again
|
|
7147
|
+
`Failed to open the configured deep link before recording (${detail}). The Appium session was closed; verify the URL and app deep-link configuration, then try again.`,
|
|
7148
|
+
{ cause: err }
|
|
6964
7149
|
);
|
|
6965
7150
|
}
|
|
6966
7151
|
}
|
|
@@ -8888,23 +9073,42 @@ function withMediaReferences(result, refs) {
|
|
|
8888
9073
|
// src/result-post-retry.ts
|
|
8889
9074
|
var RESULT_POST_MAX_ATTEMPTS = 3;
|
|
8890
9075
|
var RESULT_POST_BACKOFF_BASE_MS = 1e3;
|
|
9076
|
+
var RESULT_POST_BACKOFF_CAP_MS = 15e3;
|
|
9077
|
+
var RESULT_POST_RETRY_BUDGET_DEFAULT_MS = 15e4;
|
|
9078
|
+
function resultPostRetryBudgetMs(env = process.env) {
|
|
9079
|
+
return Math.max(1e4, Number.parseInt(env.RUNNER_RESULT_RETRY_BUDGET_MS ?? "", 10) || RESULT_POST_RETRY_BUDGET_DEFAULT_MS);
|
|
9080
|
+
}
|
|
9081
|
+
function defaultResultPostBudgetMs(backoffBaseMs) {
|
|
9082
|
+
return backoffBaseMs > 0 ? resultPostRetryBudgetMs() : 0;
|
|
9083
|
+
}
|
|
9084
|
+
function nextResultPostDelay(attempt, opts) {
|
|
9085
|
+
const delay = Math.min(RESULT_POST_BACKOFF_CAP_MS, Math.pow(2, attempt) * opts.backoffBaseMs);
|
|
9086
|
+
if (attempt < opts.maxAttempts) return delay;
|
|
9087
|
+
const left = opts.startedAt + opts.budgetMs - (opts.now ?? Date.now());
|
|
9088
|
+
if (left <= 0) return null;
|
|
9089
|
+
return Math.min(delay, left);
|
|
9090
|
+
}
|
|
8891
9091
|
function isTransientResultPostStatus(status) {
|
|
8892
9092
|
return status === 408 || status === 429 || status >= 500;
|
|
8893
9093
|
}
|
|
8894
|
-
async function postRetryingTransient(send, label, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS) {
|
|
9094
|
+
async function postRetryingTransient(send, label, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS, budgetMs = defaultResultPostBudgetMs(backoffBaseMs)) {
|
|
9095
|
+
const startedAt = Date.now();
|
|
8895
9096
|
for (let attempt = 1; ; attempt++) {
|
|
8896
9097
|
let failure;
|
|
9098
|
+
let delay;
|
|
8897
9099
|
try {
|
|
8898
9100
|
const res = await send();
|
|
8899
|
-
if (!isTransientResultPostStatus(res.status)
|
|
9101
|
+
if (!isTransientResultPostStatus(res.status)) return res;
|
|
9102
|
+
delay = nextResultPostDelay(attempt, { maxAttempts: RESULT_POST_MAX_ATTEMPTS, startedAt, budgetMs, backoffBaseMs });
|
|
9103
|
+
if (delay === null) return res;
|
|
8900
9104
|
await res.body?.cancel().catch(() => {
|
|
8901
9105
|
});
|
|
8902
9106
|
failure = `HTTP ${res.status}`;
|
|
8903
9107
|
} catch (err) {
|
|
8904
|
-
|
|
9108
|
+
delay = nextResultPostDelay(attempt, { maxAttempts: RESULT_POST_MAX_ATTEMPTS, startedAt, budgetMs, backoffBaseMs });
|
|
9109
|
+
if (delay === null) throw err;
|
|
8905
9110
|
failure = err instanceof Error ? err.message : String(err);
|
|
8906
9111
|
}
|
|
8907
|
-
const delay = Math.pow(2, attempt) * backoffBaseMs;
|
|
8908
9112
|
console.warn(`[runner] ${label} post attempt ${attempt} failed (${failure}), retrying in ${delay}ms...`);
|
|
8909
9113
|
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
8910
9114
|
}
|
|
@@ -8937,6 +9141,7 @@ function clampPollInterval(value) {
|
|
|
8937
9141
|
init_dist();
|
|
8938
9142
|
var import_ai_queue = __toESM(require_ai_queue());
|
|
8939
9143
|
var import_base_url_check = __toESM(require_base_url_check());
|
|
9144
|
+
var import_tls_cert_probe = __toESM(require_tls_cert_probe());
|
|
8940
9145
|
var import_rate_limit_detector = __toESM(require_rate_limit_detector());
|
|
8941
9146
|
var import_resource_governor = __toESM(require_resource_governor());
|
|
8942
9147
|
var import_process_inventory = __toESM(require_process_inventory());
|
|
@@ -9151,7 +9356,7 @@ async function claimRun(serverUrl, headers, runId) {
|
|
|
9151
9356
|
if (typeof body.claimNonce === "string" && body.claimNonce) claimNonces.set(runId, body.claimNonce);
|
|
9152
9357
|
return body.claimEpoch;
|
|
9153
9358
|
}
|
|
9154
|
-
async function postResult(serverUrl, headers, runId, result, claimEpoch, maxAttemptsArg = 3, backoffBaseMs = 1e3) {
|
|
9359
|
+
async function postResult(serverUrl, headers, runId, result, claimEpoch, maxAttemptsArg = 3, backoffBaseMs = 1e3, budgetMs = defaultResultPostBudgetMs(backoffBaseMs)) {
|
|
9155
9360
|
let maxAttempts = maxAttemptsArg;
|
|
9156
9361
|
let verdictOnlyApplied = false;
|
|
9157
9362
|
let lastError;
|
|
@@ -9170,8 +9375,10 @@ async function postResult(serverUrl, headers, runId, result, claimEpoch, maxAtte
|
|
|
9170
9375
|
}
|
|
9171
9376
|
let payload = budgeted.payload;
|
|
9172
9377
|
let apiCallBodiesStripped = false;
|
|
9378
|
+
const startedAt = Date.now();
|
|
9173
9379
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
9174
9380
|
attemptsMade = attempt;
|
|
9381
|
+
let transient = false;
|
|
9175
9382
|
try {
|
|
9176
9383
|
const res = await fetchRetrying429(`${serverUrl}/api/runner/runs/${runId}/result`, () => ({
|
|
9177
9384
|
method: "POST",
|
|
@@ -9223,15 +9430,17 @@ async function postResult(serverUrl, headers, runId, result, claimEpoch, maxAtte
|
|
|
9223
9430
|
throw new RunNoLongerActiveError(`Run ${runId} is no longer active on the server (HTTP ${res.status})`);
|
|
9224
9431
|
}
|
|
9225
9432
|
lastError = new Error(`HTTP ${res.status}`);
|
|
9433
|
+
transient = isTransientResultPostStatus(res.status);
|
|
9226
9434
|
} catch (err) {
|
|
9227
9435
|
if (err instanceof RunNoLongerActiveError) throw err;
|
|
9228
9436
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
9437
|
+
transient = true;
|
|
9229
9438
|
}
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9439
|
+
const delay = nextResultPostDelay(attempt, { maxAttempts, startedAt, budgetMs: transient ? budgetMs : 0, backoffBaseMs });
|
|
9440
|
+
if (delay === null) break;
|
|
9441
|
+
if (attempt >= maxAttempts) maxAttempts++;
|
|
9442
|
+
console.warn(`[runner] postResult attempt ${attempt} failed (${lastError.message}), retrying in ${delay}ms...`);
|
|
9443
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
9235
9444
|
}
|
|
9236
9445
|
throw new Error(`Failed to post result after ${attemptsMade} attempts: ${lastError?.message}`);
|
|
9237
9446
|
}
|
|
@@ -9288,12 +9497,13 @@ async function postMobileHealResult(serverUrl, headers, runId, body, claimEpoch)
|
|
|
9288
9497
|
function narrowMobileLaunchMode(mode) {
|
|
9289
9498
|
return mode === "LAUNCH_APP" || mode === "DEEP_LINK" || mode === "RESUME" ? mode : void 0;
|
|
9290
9499
|
}
|
|
9291
|
-
async function postBatchHealResult(serverUrl, headers, runId, body, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS) {
|
|
9500
|
+
async function postBatchHealResult(serverUrl, headers, runId, body, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS, claimEpoch) {
|
|
9292
9501
|
const progress = { body, fromRung: 0 };
|
|
9293
9502
|
const res = await postRetryingTransient(() => postShedOn413((payload) => fetchRetrying429(`${serverUrl}/api/runner/runs/${runId}/batch-heal-result`, () => ({
|
|
9294
9503
|
method: "POST",
|
|
9295
9504
|
headers,
|
|
9296
|
-
body:
|
|
9505
|
+
// At send time, not in the body: the verdict-only rung keeps a fixed list of fields and would drop it.
|
|
9506
|
+
body: JSON.stringify(withClaimEpoch(payload, claimEpoch)),
|
|
9297
9507
|
signal: AbortSignal.timeout(12e4)
|
|
9298
9508
|
})), progress.body, "batch heal result", import_result_shed.BATCH_HEAL_SHED_LADDER, progress), "batch heal result", backoffBaseMs);
|
|
9299
9509
|
throwUnlessResultLanded(res, runId, "batch heal result");
|
|
@@ -9305,11 +9515,11 @@ function throwUnlessResultLanded(res, runId, label) {
|
|
|
9305
9515
|
}
|
|
9306
9516
|
throw new Error(`Failed to post ${label}: ${res.status}`);
|
|
9307
9517
|
}
|
|
9308
|
-
async function postBatchHealPartial(serverUrl, headers, runId, result) {
|
|
9518
|
+
async function postBatchHealPartial(serverUrl, headers, runId, result, claimEpoch) {
|
|
9309
9519
|
const res = await postShedOn413((payload) => fetchRetrying429(`${serverUrl}/api/runner/runs/${runId}/batch-heal-partial`, () => ({
|
|
9310
9520
|
method: "POST",
|
|
9311
9521
|
headers,
|
|
9312
|
-
body: JSON.stringify(payload),
|
|
9522
|
+
body: JSON.stringify(withClaimEpoch(payload, claimEpoch)),
|
|
9313
9523
|
// Fires per verdict: a slow server must not hold up the next test.
|
|
9314
9524
|
signal: AbortSignal.timeout(3e4)
|
|
9315
9525
|
})), { result }, "batch heal partial", import_result_shed.BATCH_HEAL_PARTIAL_SHED_LADDER);
|
|
@@ -9330,11 +9540,11 @@ async function fetchBatchHealAppliedIds(serverUrl, headers, runId, projectId) {
|
|
|
9330
9540
|
return /* @__PURE__ */ new Set();
|
|
9331
9541
|
}
|
|
9332
9542
|
}
|
|
9333
|
-
function batchHealPartialHooks(serverUrl, headers, runId, projectId, warn = (msg) => log.warn(msg)) {
|
|
9543
|
+
function batchHealPartialHooks(serverUrl, headers, runId, projectId, warn = (msg) => log.warn(msg), claimEpoch) {
|
|
9334
9544
|
return {
|
|
9335
9545
|
onPartialResult: async (result) => {
|
|
9336
9546
|
try {
|
|
9337
|
-
await postBatchHealPartial(serverUrl, headers, runId, result);
|
|
9547
|
+
await postBatchHealPartial(serverUrl, headers, runId, result, claimEpoch);
|
|
9338
9548
|
} catch (err) {
|
|
9339
9549
|
warn(`batch-heal partial post failed for ${result.testCaseId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
9340
9550
|
}
|
|
@@ -9430,18 +9640,25 @@ function createHealLogStreamer(serverUrl, headers, runId) {
|
|
|
9430
9640
|
};
|
|
9431
9641
|
return { onLog, flushNow };
|
|
9432
9642
|
}
|
|
9433
|
-
async function postShedOn413(send, body, label, ladder = import_result_shed.CAPTURE_SHED_LADDER, progress) {
|
|
9643
|
+
async function postShedOn413(send, body, label, ladder = import_result_shed.CAPTURE_SHED_LADDER, progress, onPlain413) {
|
|
9434
9644
|
let current = progress?.body ?? body;
|
|
9435
9645
|
let fromRung = progress?.fromRung ?? 0;
|
|
9436
9646
|
let res = await send(current);
|
|
9437
|
-
while (res.status === 413
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9647
|
+
while (res.status === 413) {
|
|
9648
|
+
if (await (0, import_result_api_call_bodies2.isResultStructureTooCostlyResponse)(res)) {
|
|
9649
|
+
const next = (0, import_result_shed.nextShedPayload)(current, ladder, fromRung);
|
|
9650
|
+
if (!next) break;
|
|
9651
|
+
console.warn(
|
|
9652
|
+
`[runner] Server refused ${label} as too costly to parse (413 ${import_result_api_call_bodies2.RESULT_STRUCTURE_TOO_COSTLY}); retrying a lighter form (shed level ${next.level}, ${next.shed} item${next.shed === 1 ? "" : "s"} removed).`
|
|
9653
|
+
);
|
|
9654
|
+
current = next.payload;
|
|
9655
|
+
fromRung = next.nextRung;
|
|
9656
|
+
} else {
|
|
9657
|
+
const lighter = onPlain413?.(current) ?? null;
|
|
9658
|
+
if (!lighter) break;
|
|
9659
|
+
console.warn(`[runner] Server refused ${label} as too large (413); retrying it without its media.`);
|
|
9660
|
+
current = lighter;
|
|
9661
|
+
}
|
|
9445
9662
|
if (progress) {
|
|
9446
9663
|
progress.body = current;
|
|
9447
9664
|
progress.fromRung = fromRung;
|
|
@@ -9460,22 +9677,24 @@ async function postCaptureResult(serverUrl, headers, runId, result, backoffBaseM
|
|
|
9460
9677
|
})), progress.body, "capture result", import_result_shed.CAPTURE_SHED_LADDER, progress), "capture result", backoffBaseMs);
|
|
9461
9678
|
throwUnlessResultLanded(res, runId, "capture result");
|
|
9462
9679
|
}
|
|
9463
|
-
async function postRegressionResult(serverUrl, headers, runId, body, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS) {
|
|
9464
|
-
const
|
|
9680
|
+
async function postRegressionResult(serverUrl, headers, runId, body, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS, claimEpoch) {
|
|
9681
|
+
const fit = (0, import_result_shed.fitRegressionScreenshots)(withBrowsersPathHint(body));
|
|
9682
|
+
if (fit.dropped > 0) console.warn(`[runner] Regression result over the route's body limit: ${fit.dropped} failing spec(s)' screenshot left out.`);
|
|
9683
|
+
const progress = { body: fit.payload, fromRung: 0 };
|
|
9465
9684
|
const res = await postRetryingTransient(() => postShedOn413((payload) => fetchRetrying429(`${serverUrl}/api/runner/runs/${runId}/regression-result`, () => ({
|
|
9466
9685
|
method: "POST",
|
|
9467
9686
|
headers,
|
|
9468
|
-
body: JSON.stringify(payload),
|
|
9687
|
+
body: JSON.stringify(withClaimEpoch(payload, claimEpoch)),
|
|
9469
9688
|
signal: AbortSignal.timeout(6e4)
|
|
9470
|
-
})), progress.body, "regression result", import_result_shed.REGRESSION_SHED_LADDER, progress), "regression result", backoffBaseMs);
|
|
9689
|
+
})), progress.body, "regression result", import_result_shed.REGRESSION_SHED_LADDER, progress, import_result_shed.withoutRegressionScreenshots), "regression result", backoffBaseMs);
|
|
9471
9690
|
throwUnlessResultLanded(res, runId, "regression result");
|
|
9472
9691
|
}
|
|
9473
|
-
function postLiveSuiteResult(serverUrl, headers, runId, body, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS) {
|
|
9692
|
+
function postLiveSuiteResult(serverUrl, headers, runId, body, backoffBaseMs = RESULT_POST_BACKOFF_BASE_MS, claimEpoch) {
|
|
9474
9693
|
const progress = { body, fromRung: 0 };
|
|
9475
9694
|
return postRetryingTransient(() => postShedOn413((payload) => fetchRetrying429(`${serverUrl}/api/runner/runs/${runId}/live-suite-result`, () => ({
|
|
9476
9695
|
method: "POST",
|
|
9477
9696
|
headers,
|
|
9478
|
-
body: JSON.stringify(payload),
|
|
9697
|
+
body: JSON.stringify(withClaimEpoch(payload, claimEpoch)),
|
|
9479
9698
|
signal: AbortSignal.timeout(6e4)
|
|
9480
9699
|
})), progress.body, "live suite result", import_result_shed.LIVE_SUITE_SHED_LADDER, progress), "live suite result", backoffBaseMs);
|
|
9481
9700
|
}
|
|
@@ -9881,7 +10100,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
9881
10100
|
const runLabel = run2.testCaseId ?? run2.testName ?? (runMode === "run" ? "auth-setup" : runMode);
|
|
9882
10101
|
const modeLabel = runMode === "run" ? RUNNER_MODE : runMode;
|
|
9883
10102
|
log.info(`Running test: ${runLabel} (run ${run2.runId}) [mode: ${modeLabel}]`);
|
|
9884
|
-
|
|
10103
|
+
let tlsCertNote = Promise.resolve(null);
|
|
10104
|
+
const postRunResult = async (result) => postResult(opts.serverUrl, headers, run2.runId, (0, import_tls_cert_probe.withRunNote)(result, await (0, import_tls_cert_probe.noteWithinCap)(tlsCertNote)), run2.claimEpoch);
|
|
9885
10105
|
const browserlessRepositoryRead = (0, import_live_browser_registry.isBrowserlessRepositoryRead)(run2.discoveryContext?.frameworkImport?.kind);
|
|
9886
10106
|
const liveBrowserHandle = browserlessRepositoryRead ? import_live_browser_registry.NO_LIVE_BROWSER : import_live_browser_registry.liveBrowserRegistry.register({
|
|
9887
10107
|
sessionId: `${run2.runId}`,
|
|
@@ -9929,6 +10149,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
9929
10149
|
authStandingMode: authPlan.standingHeadersMode,
|
|
9930
10150
|
storageStatePath: wrapperStorageStatePath,
|
|
9931
10151
|
apiBaseUrl: run2.apiBaseUrl,
|
|
10152
|
+
// The run's own @api tag: an imported API spec's code may not say so (see executeNativeOnce's apiTest).
|
|
10153
|
+
apiTest: Array.isArray(run2.tags) && run2.tags.includes("@api"),
|
|
9932
10154
|
...nativeOptions,
|
|
9933
10155
|
liveBrowserHandle,
|
|
9934
10156
|
onLiveBrowserSnapshot: requestLiveBrowserHeartbeat,
|
|
@@ -10003,6 +10225,10 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
10003
10225
|
} else if (preflight.note) {
|
|
10004
10226
|
log.info(preflight.note);
|
|
10005
10227
|
}
|
|
10228
|
+
tlsCertNote = (0, import_tls_cert_probe.targetCertificateNote)(run2.baseUrl);
|
|
10229
|
+
void tlsCertNote.then((note) => {
|
|
10230
|
+
if (note) log.warn(note);
|
|
10231
|
+
});
|
|
10006
10232
|
}
|
|
10007
10233
|
if (runMode === "auth-setup") {
|
|
10008
10234
|
if (!run2.playwrightCode) {
|
|
@@ -10083,7 +10309,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
10083
10309
|
log.info(`Heal engine: GROK BATCH (${tests.length} tests, healerModel=${run2.healerModel ?? "null"})`);
|
|
10084
10310
|
const noStart = batchHealNoStart(tests, run2.baseUrl);
|
|
10085
10311
|
if (noStart) {
|
|
10086
|
-
await postBatchHealResult(opts.serverUrl, headers, run2.runId, noStart.body);
|
|
10312
|
+
await postBatchHealResult(opts.serverUrl, headers, run2.runId, noStart.body, RESULT_POST_BACKOFF_BASE_MS, run2.claimEpoch);
|
|
10087
10313
|
log[noStart.level](noStart.logLine);
|
|
10088
10314
|
return;
|
|
10089
10315
|
}
|
|
@@ -10120,7 +10346,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
10120
10346
|
// Returns HEAL_TIMEOUT: for the tests not reached instead of dying at the runner's batch-heal deadline.
|
|
10121
10347
|
deadlineAt: (0, import_runner_core7.batchHealDeadlineAt)(startTime),
|
|
10122
10348
|
onLog: healLogStream.onLog,
|
|
10123
|
-
...batchHealPartialHooks(opts.serverUrl, headers, run2.runId, run2.projectId || void 0)
|
|
10349
|
+
...batchHealPartialHooks(opts.serverUrl, headers, run2.runId, run2.projectId || void 0, void 0, run2.claimEpoch)
|
|
10124
10350
|
});
|
|
10125
10351
|
await healLogStream.flushNow();
|
|
10126
10352
|
try {
|
|
@@ -10135,7 +10361,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
10135
10361
|
systemicDiagnosis: batchResult.systemicDiagnosis,
|
|
10136
10362
|
logs: batchResult.logs,
|
|
10137
10363
|
durationMs: batchResult.totalDurationMs
|
|
10138
|
-
});
|
|
10364
|
+
}, RESULT_POST_BACKOFF_BASE_MS, run2.claimEpoch);
|
|
10139
10365
|
log.ok((0, import_runner_core9.batchHealCompleteLine)(batchResult.perTest, tests.length, batchResult.totalDurationMs));
|
|
10140
10366
|
} catch (err) {
|
|
10141
10367
|
log.fail(`Failed to post batch heal result: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -10289,7 +10515,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10289
10515
|
const legacyCold = (0, import_runner_core8.apiHealColdSessionRelabel)("legacy", {
|
|
10290
10516
|
tags: run2.tags ?? [],
|
|
10291
10517
|
authStateExpected: apiHealAuthPlan.needsCookieState,
|
|
10292
|
-
authStateInjected:
|
|
10518
|
+
authStateInjected: (0, import_runner_core8.storageStateDelivered)(apiHealStorageStatePath, finalVerifyResult),
|
|
10293
10519
|
needsStandingHeaders: apiHealAuthPlan.needsStandingHeaders,
|
|
10294
10520
|
standingHeadersInjected: !!finalVerifyResult?.authDelivery?.standingHeadersInjected,
|
|
10295
10521
|
error: result3.bugDescription ?? null,
|
|
@@ -10312,14 +10538,15 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10312
10538
|
}
|
|
10313
10539
|
if (result3.verdict === "unfixable") {
|
|
10314
10540
|
log.fail(` Unfixable: ${result3.reason}`);
|
|
10315
|
-
finalUnfixableReason = result3.reason;
|
|
10541
|
+
finalUnfixableReason = result3.reason ?? "AI could not fix the test";
|
|
10316
10542
|
break;
|
|
10317
10543
|
}
|
|
10318
10544
|
lastHealedCode = result3.playwrightCode ?? result3.healedCode;
|
|
10319
10545
|
lastChangeDescription = result3.changeDescription;
|
|
10320
10546
|
if (!lastHealedCode) {
|
|
10321
|
-
log.
|
|
10322
|
-
|
|
10547
|
+
log.warn(` Attempt ${attempt}: AI returned fix verdict but no code \u2014 retrying with prompt`);
|
|
10548
|
+
currentError = `Attempt ${attempt} failed: You described a fix but did not include the \`playwrightCode\` field. Please provide the complete updated test file as \`playwrightCode\`.`;
|
|
10549
|
+
continue;
|
|
10323
10550
|
}
|
|
10324
10551
|
finalVerifyResult = await runNativeWithLiveBrowser({
|
|
10325
10552
|
playwrightCode: lastHealedCode,
|
|
@@ -10361,10 +10588,14 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10361
10588
|
const allAppBug = stepCount > 0 && enrichedSteps.every((s) => s.verdict === "app_bug");
|
|
10362
10589
|
const hasFixFailed = enrichedSteps.some((s) => s.verdict === "fix_failed");
|
|
10363
10590
|
const hasPass = enrichedSteps.some((s) => s.verdict === "pass");
|
|
10364
|
-
const
|
|
10591
|
+
const verifyPassed = stepCount > 0 ? finalVerifyResult?.passed === true && hasPass && !hasFixFailed && !allAppBug : finalVerifyResult?.passed === true;
|
|
10592
|
+
const partialSkipError = verifyPassed ? (0, import_runner_core7.apiHealPartialSkipError)(enrichedSteps) : void 0;
|
|
10593
|
+
const passed = verifyPassed && partialSkipError === void 0;
|
|
10365
10594
|
let composedError;
|
|
10366
10595
|
if (!passed) {
|
|
10367
|
-
if (
|
|
10596
|
+
if (partialSkipError !== void 0) {
|
|
10597
|
+
composedError = partialSkipError;
|
|
10598
|
+
} else if (finalUnfixableReason) {
|
|
10368
10599
|
composedError = `Unfixable after ${attemptsUsed} attempt(s): ${finalUnfixableReason}`;
|
|
10369
10600
|
} else if (allAppBug || enrichedSteps.some((s) => s.verdict === "app_bug") && !hasFixFailed) {
|
|
10370
10601
|
const bugs = enrichedSteps.filter((s) => s.verdict === "app_bug");
|
|
@@ -10376,7 +10607,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10376
10607
|
let apiHealStatus = passed ? "PASSED" : "FAILED";
|
|
10377
10608
|
let apiHealError = composedError;
|
|
10378
10609
|
if (!passed) {
|
|
10379
|
-
if (finalUnfixableReason?.startsWith(import_runner_core7.HEAL_AI_NO_ANSWER_ERROR_PREFIX)) {
|
|
10610
|
+
if (finalUnfixableReason?.startsWith(import_runner_core7.HEAL_AI_NO_ANSWER_ERROR_PREFIX) || (0, import_runner_core7.isHealAiQuotaError)(finalUnfixableReason)) {
|
|
10380
10611
|
apiHealStatus = "ERROR";
|
|
10381
10612
|
apiHealError = finalUnfixableReason;
|
|
10382
10613
|
} else if (isConfigLikeReason(classifyErrorMessage(composedError ?? null))) {
|
|
@@ -10417,7 +10648,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10417
10648
|
const cold = (0, import_runner_core8.apiHealColdSessionRelabel)("verified", {
|
|
10418
10649
|
tags: run2.tags ?? [],
|
|
10419
10650
|
authStateExpected: apiHealAuthPlan.needsCookieState,
|
|
10420
|
-
authStateInjected:
|
|
10651
|
+
authStateInjected: (0, import_runner_core8.storageStateDelivered)(apiHealStorageStatePath, finalVerifyResult),
|
|
10421
10652
|
needsStandingHeaders: apiHealAuthPlan.needsStandingHeaders,
|
|
10422
10653
|
standingHeadersInjected: !!finalVerifyResult?.authDelivery?.standingHeadersInjected,
|
|
10423
10654
|
error: apiHealError ?? finalVerifyResult?.error,
|
|
@@ -10563,23 +10794,24 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10563
10794
|
[audit] ${drainErr instanceof Error ? drainErr.message : String(drainErr)}`;
|
|
10564
10795
|
console.warn(`[runner]${auditDrainError}`);
|
|
10565
10796
|
}
|
|
10797
|
+
const relabelSteps = result2.relabelEvidenceSteps ?? result2.stepResults;
|
|
10566
10798
|
const isConfigIssue = !result2.passed && isConfigLikeReason(classifyErrorMessage(result2.error ?? null));
|
|
10567
10799
|
let healStatus = (0, import_runner_core7.healRunStatus)({ passed: result2.passed, error: result2.error, isConfigIssue });
|
|
10568
10800
|
let healError = result2.error;
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10801
|
+
const rateLimitRelabel = (0, import_runner_core8.uiHealRateLimitRelabel)({
|
|
10802
|
+
passed: result2.passed,
|
|
10803
|
+
isConfigIssue,
|
|
10804
|
+
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
10805
|
+
tags: run2.tags ?? [],
|
|
10806
|
+
error: result2.error,
|
|
10807
|
+
logs: result2.logs,
|
|
10808
|
+
stepResults: relabelSteps,
|
|
10809
|
+
apiCalls: result2.apiCalls
|
|
10810
|
+
});
|
|
10811
|
+
if (rateLimitRelabel) {
|
|
10812
|
+
healStatus = rateLimitRelabel.status;
|
|
10813
|
+
healError = rateLimitRelabel.error;
|
|
10814
|
+
log.warn(rateLimitRelabel.logLine);
|
|
10583
10815
|
}
|
|
10584
10816
|
const loginRelabel = (0, import_runner_core8.uiHealLoginRelabel)({
|
|
10585
10817
|
status: healStatus,
|
|
@@ -10587,7 +10819,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10587
10819
|
tags: run2.tags ?? [],
|
|
10588
10820
|
error: healError,
|
|
10589
10821
|
logs: result2.logs,
|
|
10590
|
-
stepResults:
|
|
10822
|
+
stepResults: relabelSteps
|
|
10591
10823
|
});
|
|
10592
10824
|
if (loginRelabel) {
|
|
10593
10825
|
healStatus = loginRelabel.status;
|
|
@@ -10599,6 +10831,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10599
10831
|
status: healStatus,
|
|
10600
10832
|
stepResults: result2.stepResults,
|
|
10601
10833
|
screenshots: result2.screenshots,
|
|
10834
|
+
video: result2.video,
|
|
10602
10835
|
apiCalls: result2.apiCalls,
|
|
10603
10836
|
logs: (result2.logs ?? "") + auditDrainError,
|
|
10604
10837
|
error: healError,
|
|
@@ -10663,7 +10896,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10663
10896
|
error: result2.error,
|
|
10664
10897
|
testResults: result2.testResults,
|
|
10665
10898
|
skippedSpecs: result2.skippedSpecs
|
|
10666
|
-
});
|
|
10899
|
+
}, RESULT_POST_BACKOFF_BASE_MS, run2.claimEpoch);
|
|
10667
10900
|
log[result2.passed ? "ok" : "fail"](
|
|
10668
10901
|
`Regression ${result2.passed ? "PASSED" : "FAILED"} (${result2.duration}ms) - ${result2.testResults.length} specs`
|
|
10669
10902
|
);
|
|
@@ -10699,8 +10932,18 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10699
10932
|
body: JSON.stringify({ error: String(failedTest.error ?? "").slice(0, 5e5), testCaseId: failedTest.testCaseId }),
|
|
10700
10933
|
signal: AbortSignal.timeout(6e4)
|
|
10701
10934
|
});
|
|
10702
|
-
|
|
10703
|
-
|
|
10935
|
+
const healBody = healRes.ok ? await healRes.json() : null;
|
|
10936
|
+
if (healBody?.verdict === "unfixable" && (0, import_runner_core7.isHealAiQuotaError)(healBody.reason)) {
|
|
10937
|
+
results.push({
|
|
10938
|
+
testCaseId: failedTest.testCaseId,
|
|
10939
|
+
status: "unfixable",
|
|
10940
|
+
error: healBody.reason,
|
|
10941
|
+
diagnosis: healBody.reason,
|
|
10942
|
+
duration: Date.now() - testStart
|
|
10943
|
+
});
|
|
10944
|
+
log.warn(` Not healed (AI provider quota): ${failedTest.name}`);
|
|
10945
|
+
} else if (healBody) {
|
|
10946
|
+
const { healedCode } = healBody;
|
|
10704
10947
|
const failedTestPlan = authPlanFor(failedTest.tags, healedCode);
|
|
10705
10948
|
const verifyResult = await runNativeWithLiveBrowser({
|
|
10706
10949
|
playwrightCode: healedCode,
|
|
@@ -10710,15 +10953,16 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10710
10953
|
authStandingMode: failedTestPlan.standingHeadersMode,
|
|
10711
10954
|
validateContext: callbackContext
|
|
10712
10955
|
});
|
|
10956
|
+
const verdict = (0, import_runner_core7.apiHealVerifyVerdict)(verifyResult);
|
|
10713
10957
|
results.push({
|
|
10714
10958
|
testCaseId: failedTest.testCaseId,
|
|
10715
|
-
status:
|
|
10716
|
-
error:
|
|
10959
|
+
status: verdict.fixed ? "fixed" : "unfixable",
|
|
10960
|
+
error: verdict.error,
|
|
10717
10961
|
diagnosis: "API test healed via server-side AI",
|
|
10718
|
-
healedCode:
|
|
10962
|
+
healedCode: verdict.fixed ? healedCode : void 0,
|
|
10719
10963
|
duration: Date.now() - testStart
|
|
10720
10964
|
});
|
|
10721
|
-
if (
|
|
10965
|
+
if (verdict.fixed) {
|
|
10722
10966
|
log.ok(` Fixed: ${failedTest.name}`);
|
|
10723
10967
|
} else {
|
|
10724
10968
|
log.fail(` Could not fix: ${failedTest.name}`);
|
|
@@ -10828,10 +11072,11 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10828
11072
|
log.ok(` Fixed: ${failedTest.name}`);
|
|
10829
11073
|
} else {
|
|
10830
11074
|
const quotaStopped = (0, import_runner_core7.isHealAiQuotaError)(healResult.error);
|
|
10831
|
-
const
|
|
11075
|
+
const timeoutStopped = (0, import_runner_core7.isHealTimeoutError)(healResult.error);
|
|
11076
|
+
const isAppBug = !quotaStopped && !timeoutStopped && (0, import_runner_core7.isAppBugHealError)(healResult.error);
|
|
10832
11077
|
const isConfigIssue = isConfigLikeReason(classifyErrorMessage(healResult.error ?? null));
|
|
10833
11078
|
const status = isAppBug ? "app-bug" : isConfigIssue ? "config-issue" : "unfixable";
|
|
10834
|
-
const diagnosisMsg = isAppBug ? "Application error detected" : isConfigIssue ? "Configuration issue detected" : quotaStopped && healResult.error ? healResult.error : "Could not heal after AI attempts";
|
|
11079
|
+
const diagnosisMsg = isAppBug ? "Application error detected" : isConfigIssue ? "Configuration issue detected" : (quotaStopped || timeoutStopped) && healResult.error ? healResult.error : "Could not heal after AI attempts";
|
|
10835
11080
|
results.push({
|
|
10836
11081
|
testCaseId: failedTest.testCaseId,
|
|
10837
11082
|
status,
|
|
@@ -10839,7 +11084,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10839
11084
|
diagnosis: diagnosisMsg,
|
|
10840
11085
|
duration: Date.now() - testStart
|
|
10841
11086
|
});
|
|
10842
|
-
log[isAppBug || isConfigIssue || quotaStopped ? "warn" : "fail"](` ${isAppBug ? "App bug" : isConfigIssue ? "Config issue" : quotaStopped ? "Not healed (AI provider quota)" : "Could not fix"}: ${failedTest.name}`);
|
|
11087
|
+
log[isAppBug || isConfigIssue || quotaStopped || timeoutStopped ? "warn" : "fail"](` ${isAppBug ? "App bug" : isConfigIssue ? "Config issue" : quotaStopped ? "Not healed (AI provider quota)" : timeoutStopped ? "Not healed (heal time budget)" : "Could not fix"}: ${failedTest.name}`);
|
|
10843
11088
|
}
|
|
10844
11089
|
} catch (err) {
|
|
10845
11090
|
results.push({
|
|
@@ -10890,7 +11135,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
10890
11135
|
`Live suite heal: ${fixedCount} fixed, ${appBugCount} app bugs, ${configIssueCount} config issues, ${unfixableCount} unfixable`,
|
|
10891
11136
|
auditDrainWarning
|
|
10892
11137
|
].filter(Boolean).join("\n")
|
|
10893
|
-
});
|
|
11138
|
+
}, RESULT_POST_BACKOFF_BASE_MS, run2.claimEpoch);
|
|
10894
11139
|
if (!liveRes.ok) {
|
|
10895
11140
|
log.warn(`Failed to post live-suite result: ${liveRes.status}`);
|
|
10896
11141
|
}
|
|
@@ -11847,7 +12092,7 @@ ${line}`)
|
|
|
11847
12092
|
log.warn("[runner] discovery-har POST failed - final /discovery-result will carry HAR fallback");
|
|
11848
12093
|
}
|
|
11849
12094
|
const finalBody = buildFinalBody(discoveryResult, void 0, discoveryAuditWarning || void 0, auditDroppedCount);
|
|
11850
|
-
const postOk = await postDiscoveryFinalResult(opts.serverUrl, headers, run2.runId, finalBody);
|
|
12095
|
+
const postOk = await postDiscoveryFinalResult(opts.serverUrl, headers, run2.runId, withClaimEpoch(finalBody, run2.claimEpoch));
|
|
11851
12096
|
if (!postOk) throw new Error("Failed to post discovery result (3 attempts, or refused with 413)");
|
|
11852
12097
|
const discoveryError = discoveryResult.error ?? (discoveryResult.summary?.startsWith("Fatal error:") ? discoveryResult.summary : void 0);
|
|
11853
12098
|
if (discoveryError) {
|
|
@@ -11894,7 +12139,7 @@ ${line}`)
|
|
|
11894
12139
|
headers: { ...headers, "Content-Type": "application/json" },
|
|
11895
12140
|
body: JSON.stringify(body),
|
|
11896
12141
|
signal: AbortSignal.timeout(12e4)
|
|
11897
|
-
})), errorBody, "discovery error result", import_result_shed.DISCOVERY_SHED_LADDER).catch(() => {
|
|
12142
|
+
})), withClaimEpoch(errorBody, run2.claimEpoch), "discovery error result", import_result_shed.DISCOVERY_SHED_LADDER).catch(() => {
|
|
11898
12143
|
});
|
|
11899
12144
|
} finally {
|
|
11900
12145
|
if (poolBrowser) browserPool.release(poolBrowser);
|
|
@@ -12027,7 +12272,7 @@ ${line}`)
|
|
|
12027
12272
|
if (mobileSteps.length === 0) {
|
|
12028
12273
|
await postRunResult({
|
|
12029
12274
|
status: "ERROR",
|
|
12030
|
-
error: "No mobile test steps found."
|
|
12275
|
+
error: "CONFIG_ISSUE: No mobile test steps found."
|
|
12031
12276
|
});
|
|
12032
12277
|
log.fail("ERROR - No mobile steps");
|
|
12033
12278
|
return;
|
|
@@ -12555,7 +12800,7 @@ ${result2.logs}`);
|
|
|
12555
12800
|
stepResults: result.stepResults,
|
|
12556
12801
|
apiCalls: result.apiCalls,
|
|
12557
12802
|
authStateExpected,
|
|
12558
|
-
authStateInjected:
|
|
12803
|
+
authStateInjected: (0, import_runner_core8.storageStateDelivered)(storageStatePath, result),
|
|
12559
12804
|
needsStandingHeaders: normalRunAuthPlan.needsStandingHeaders,
|
|
12560
12805
|
standingHeadersInjected: !!result.authDelivery?.standingHeadersInjected
|
|
12561
12806
|
});
|
|
@@ -12627,6 +12872,7 @@ ${result2.logs}`);
|
|
|
12627
12872
|
}
|
|
12628
12873
|
}
|
|
12629
12874
|
async function startRunner(opts) {
|
|
12875
|
+
(0, import_playwright_signal_handlers.disablePlaywrightSignalHandlers)();
|
|
12630
12876
|
const { serverUrl, authToken, projectId, enableMobile = false, enableMobileTunnel = false } = opts;
|
|
12631
12877
|
const pollIntervalMs = clampPollInterval(opts.pollIntervalMs);
|
|
12632
12878
|
process.env.SERVER_URL = serverUrl;
|
|
@@ -13151,11 +13397,13 @@ async function startRunner(opts) {
|
|
|
13151
13397
|
log.info("Runner stopped.");
|
|
13152
13398
|
process.exit(0);
|
|
13153
13399
|
};
|
|
13154
|
-
process
|
|
13155
|
-
void cleanup();
|
|
13156
|
-
});
|
|
13157
|
-
process.once("SIGTERM", () => {
|
|
13400
|
+
installShutdownSignals(process, () => {
|
|
13158
13401
|
void cleanup();
|
|
13402
|
+
}, {
|
|
13403
|
+
forceExit: () => {
|
|
13404
|
+
console.error("Second interrupt \u2014 forcing exit");
|
|
13405
|
+
process.exit(0);
|
|
13406
|
+
}
|
|
13159
13407
|
});
|
|
13160
13408
|
const PIDS_WATCHDOG_INTERVAL_MS = Math.max(5e3, envInt("RUNNER_PIDS_WATCHDOG_INTERVAL_MS", 3e4));
|
|
13161
13409
|
const PIDS_SOFT_WARN_PCT = Math.max(0, Math.min(100, envInt("RUNNER_PIDS_SOFT_WARN_PCT", 70)));
|
|
@@ -13266,6 +13514,10 @@ async function startRunner(opts) {
|
|
|
13266
13514
|
for (const run2 of pending) {
|
|
13267
13515
|
if (shuttingDown) break;
|
|
13268
13516
|
if (activeRuns >= MAX_CONCURRENT) break;
|
|
13517
|
+
if (activeWebRuns.has(run2.runId)) {
|
|
13518
|
+
logDeferredOnce(run2.runId, ` Deferred ${run2.runId} (an earlier claim of it is still settling on this runner)`);
|
|
13519
|
+
continue;
|
|
13520
|
+
}
|
|
13269
13521
|
const runMode = run2.mode ?? "run";
|
|
13270
13522
|
const isMobileOp = enableMobile && isMobileDeviceOp(run2);
|
|
13271
13523
|
const discoveryKind = isMobileOp ? discoveryKindOf(run2.discoveryContext) : null;
|
|
@@ -13854,7 +14106,9 @@ async function superviseRunnerStart(argv, authTokenForChild) {
|
|
|
13854
14106
|
stdio: "inherit",
|
|
13855
14107
|
env: childEnv
|
|
13856
14108
|
});
|
|
14109
|
+
let signalled = false;
|
|
13857
14110
|
const onSignal = (sig) => {
|
|
14111
|
+
signalled = true;
|
|
13858
14112
|
try {
|
|
13859
14113
|
child.kill(sig);
|
|
13860
14114
|
} catch {
|
|
@@ -13866,19 +14120,19 @@ async function superviseRunnerStart(argv, authTokenForChild) {
|
|
|
13866
14120
|
process.removeListener("SIGINT", onSignal);
|
|
13867
14121
|
process.removeListener("SIGTERM", onSignal);
|
|
13868
14122
|
if (signal === "SIGINT" || signal === "SIGTERM") {
|
|
13869
|
-
resolve3(0);
|
|
14123
|
+
resolve3({ code: 0, signalled: true });
|
|
13870
14124
|
return;
|
|
13871
14125
|
}
|
|
13872
|
-
resolve3(code ?? 1);
|
|
14126
|
+
resolve3({ code: code ?? 1, signalled });
|
|
13873
14127
|
});
|
|
13874
14128
|
});
|
|
13875
14129
|
console.log("\u{1F6E1} Runner supervisor enabled \u2014 will auto-restart on crash/stuck exit");
|
|
13876
14130
|
console.log(" (disable with --no-supervise or VALIDATE_RUNNER_SUPERVISE=0)");
|
|
13877
14131
|
for (; ; ) {
|
|
13878
14132
|
const startedAt = Date.now();
|
|
13879
|
-
const code = await runOnce();
|
|
14133
|
+
const { code, signalled } = await runOnce();
|
|
13880
14134
|
const next = nextSupervisorAction(
|
|
13881
|
-
{ code, ranMs: Date.now() - startedAt, now: Date.now() },
|
|
14135
|
+
{ code, ranMs: Date.now() - startedAt, now: Date.now(), signalled },
|
|
13882
14136
|
state2,
|
|
13883
14137
|
{ maxRestartsPerHour, minBackoffMs, maxBackoffMs }
|
|
13884
14138
|
);
|
|
@@ -13959,7 +14213,7 @@ program.command("start").description("Start the local test runner").option("--to
|
|
|
13959
14213
|
console.warn("\u26A0 --tunnel has no effect without --mobile (the mobile bridge it exposes only starts with --mobile). Ignoring --tunnel.");
|
|
13960
14214
|
}
|
|
13961
14215
|
{
|
|
13962
|
-
const { hasLocalGoogleLogin, GOOGLE_LOGIN_COMMAND } = await import("./dist-
|
|
14216
|
+
const { hasLocalGoogleLogin, GOOGLE_LOGIN_COMMAND } = await import("./dist-2G7YTNH5.mjs");
|
|
13963
14217
|
if (process.stdout.isTTY && !await hasLocalGoogleLogin()) {
|
|
13964
14218
|
console.log(`Tip: for apps with "Continue with Google", sign in to Google once on this machine: ${GOOGLE_LOGIN_COMMAND}`);
|
|
13965
14219
|
}
|
|
@@ -13989,7 +14243,7 @@ async function askLine(question) {
|
|
|
13989
14243
|
}
|
|
13990
14244
|
}
|
|
13991
14245
|
program.command("google-login").description('Sign in to a TEST Google account once on this machine, for tests of apps with "Continue with Google"').option("--site <url...>", "The app address(es) this sign-in may be used for, e.g. https://app.example.com").option("--allow-file-storage", "Keep the sign-in in a plain file (only you can read it) when the system keychain is not available").option("--yes", "Confirm without asking that this is a dedicated test Google account").action(async (opts) => {
|
|
13992
|
-
const { signInLocalGoogle, LOCAL_GOOGLE_WARNING, grantedSite, defaultStoreKind } = await import("./dist-
|
|
14246
|
+
const { signInLocalGoogle, LOCAL_GOOGLE_WARNING, grantedSite, defaultStoreKind } = await import("./dist-2G7YTNH5.mjs");
|
|
13993
14247
|
console.log(LOCAL_GOOGLE_WARNING);
|
|
13994
14248
|
if (!opts.yes) {
|
|
13995
14249
|
const answer = await askLine('Is this a dedicated test Google account? Type "yes" to continue: ');
|
|
@@ -14024,7 +14278,7 @@ program.command("google-login").description('Sign in to a TEST Google account on
|
|
|
14024
14278
|
}
|
|
14025
14279
|
});
|
|
14026
14280
|
program.command("google-allow-site <url>").description("Let the Google sign-in on this machine be used for one more app address").action(async (url) => {
|
|
14027
|
-
const { allowLocalGoogleSites, grantedSite } = await import("./dist-
|
|
14281
|
+
const { allowLocalGoogleSites, grantedSite } = await import("./dist-2G7YTNH5.mjs");
|
|
14028
14282
|
const site = grantedSite(url);
|
|
14029
14283
|
if (!site) {
|
|
14030
14284
|
console.error("\u2717 Not a web address: give it like https://app.example.com");
|
|
@@ -14038,7 +14292,7 @@ program.command("google-allow-site <url>").description("Let the Google sign-in o
|
|
|
14038
14292
|
console.log(`\u2713 Tests on this machine may now use the Google sign-in for: ${sites.join(", ")}`);
|
|
14039
14293
|
});
|
|
14040
14294
|
program.command("google-logout").description("Forget the Google sign-in saved on this machine").action(async () => {
|
|
14041
|
-
const { deleteLocalGoogleLogin } = await import("./dist-
|
|
14295
|
+
const { deleteLocalGoogleLogin } = await import("./dist-2G7YTNH5.mjs");
|
|
14042
14296
|
await deleteLocalGoogleLogin();
|
|
14043
14297
|
console.log("\u2713 Google sign-in removed from this machine");
|
|
14044
14298
|
});
|