@validate.qa/runner 1.0.42 → 1.0.43
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/dist/cli.js +914 -131
- package/dist/cli.mjs +914 -131
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1756,7 +1756,18 @@ var init_constants = __esm({
|
|
|
1756
1756
|
"MINIMAX_API_KEY",
|
|
1757
1757
|
"ANTHROPIC_API_KEY",
|
|
1758
1758
|
"NVIDIA_API_KEY",
|
|
1759
|
-
"DEEPSEEK_API_KEY"
|
|
1759
|
+
"DEEPSEEK_API_KEY",
|
|
1760
|
+
// Run-scoped callback capabilities. The parent runner injects these after
|
|
1761
|
+
// sanitization; customer variables must never impersonate the control plane.
|
|
1762
|
+
"AUTH_FILE",
|
|
1763
|
+
"SHARED_AUTH_INJECT",
|
|
1764
|
+
"VALIDATE_RUN_ID",
|
|
1765
|
+
"VALIDATE_CLAIM_EPOCH",
|
|
1766
|
+
"VALIDATE_AUTH_CONFIG_HASH",
|
|
1767
|
+
"VALIDATE_TASK_ARN",
|
|
1768
|
+
"VALIDATE_RUNNER_AUTH",
|
|
1769
|
+
"VALIDATE_SERVER_URL",
|
|
1770
|
+
"VALIDATE_LIVE_BROWSER_STATE_PATH"
|
|
1760
1771
|
]);
|
|
1761
1772
|
SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
1762
1773
|
// Runtime essentials - Node.js and Playwright need these to function
|
|
@@ -5024,6 +5035,16 @@ var init_voice_config = __esm({
|
|
|
5024
5035
|
}
|
|
5025
5036
|
});
|
|
5026
5037
|
|
|
5038
|
+
// ../shared/dist/feature-flags.js
|
|
5039
|
+
function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
|
|
5040
|
+
return value?.trim().toLowerCase() !== "false";
|
|
5041
|
+
}
|
|
5042
|
+
var init_feature_flags = __esm({
|
|
5043
|
+
"../shared/dist/feature-flags.js"() {
|
|
5044
|
+
"use strict";
|
|
5045
|
+
}
|
|
5046
|
+
});
|
|
5047
|
+
|
|
5027
5048
|
// ../shared/dist/index.js
|
|
5028
5049
|
var dist_exports = {};
|
|
5029
5050
|
__export(dist_exports, {
|
|
@@ -5147,6 +5168,7 @@ __export(dist_exports, {
|
|
|
5147
5168
|
isRunnableStep: () => isRunnableStep,
|
|
5148
5169
|
isSafeDeepLink: () => isSafeDeepLink,
|
|
5149
5170
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
5171
|
+
isSharedAuthInjectEnabled: () => isSharedAuthInjectEnabled,
|
|
5150
5172
|
isSttModelForProvider: () => isSttModelForProvider,
|
|
5151
5173
|
isTestReadyForPlatform: () => isTestReadyForPlatform,
|
|
5152
5174
|
isTrialExpired: () => isTrialExpired,
|
|
@@ -5213,6 +5235,7 @@ var init_dist = __esm({
|
|
|
5213
5235
|
init_test_counting();
|
|
5214
5236
|
init_origin_domain();
|
|
5215
5237
|
init_voice_config();
|
|
5238
|
+
init_feature_flags();
|
|
5216
5239
|
}
|
|
5217
5240
|
});
|
|
5218
5241
|
|
|
@@ -10676,7 +10699,7 @@ var require_transport = __commonJS({
|
|
|
10676
10699
|
"../../node_modules/pino/lib/transport.js"(exports2, module2) {
|
|
10677
10700
|
"use strict";
|
|
10678
10701
|
var { createRequire } = require("module");
|
|
10679
|
-
var { existsSync:
|
|
10702
|
+
var { existsSync: existsSync3 } = require("fs");
|
|
10680
10703
|
var getCallers = require_caller();
|
|
10681
10704
|
var { join: join7, isAbsolute, sep } = require("path");
|
|
10682
10705
|
var { fileURLToPath } = require("url");
|
|
@@ -10750,7 +10773,7 @@ var require_transport = __commonJS({
|
|
|
10750
10773
|
return false;
|
|
10751
10774
|
}
|
|
10752
10775
|
}
|
|
10753
|
-
return isAbsolute(path) && !
|
|
10776
|
+
return isAbsolute(path) && !existsSync3(path);
|
|
10754
10777
|
}
|
|
10755
10778
|
function stripQuotes(value) {
|
|
10756
10779
|
const first = value[0];
|
|
@@ -76165,6 +76188,126 @@ export const test = base.extend({
|
|
|
76165
76188
|
}
|
|
76166
76189
|
});
|
|
76167
76190
|
|
|
76191
|
+
// ../runner-core/dist/services/run-callback-proxy.js
|
|
76192
|
+
var require_run_callback_proxy = __commonJS({
|
|
76193
|
+
"../runner-core/dist/services/run-callback-proxy.js"(exports2) {
|
|
76194
|
+
"use strict";
|
|
76195
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
76196
|
+
exports2.startRunCallbackProxy = startRunCallbackProxy;
|
|
76197
|
+
var node_crypto_1 = require("crypto");
|
|
76198
|
+
var node_http_1 = require("http");
|
|
76199
|
+
var MAX_CALLBACK_BODY_BYTES = 5 * 1024 * 1024;
|
|
76200
|
+
var CALLBACK_TIMEOUT_MS = 3e4;
|
|
76201
|
+
function sendJson(res, status, body) {
|
|
76202
|
+
const payload = JSON.stringify(body);
|
|
76203
|
+
res.writeHead(status, {
|
|
76204
|
+
"cache-control": "no-store",
|
|
76205
|
+
"content-type": "application/json",
|
|
76206
|
+
"content-length": Buffer.byteLength(payload)
|
|
76207
|
+
});
|
|
76208
|
+
res.end(payload);
|
|
76209
|
+
}
|
|
76210
|
+
async function readRequestBody(req) {
|
|
76211
|
+
const chunks = [];
|
|
76212
|
+
let total = 0;
|
|
76213
|
+
for await (const chunk of req) {
|
|
76214
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
76215
|
+
total += buffer.length;
|
|
76216
|
+
if (total > MAX_CALLBACK_BODY_BYTES)
|
|
76217
|
+
throw new Error("callback body too large");
|
|
76218
|
+
chunks.push(buffer);
|
|
76219
|
+
}
|
|
76220
|
+
return Buffer.concat(chunks);
|
|
76221
|
+
}
|
|
76222
|
+
async function startRunCallbackProxy(input) {
|
|
76223
|
+
const upstream = new URL(input.serverUrl);
|
|
76224
|
+
if (upstream.protocol !== "http:" && upstream.protocol !== "https:") {
|
|
76225
|
+
throw new Error("callback server URL must use http or https");
|
|
76226
|
+
}
|
|
76227
|
+
const runPath = `/api/runner/runs/${encodeURIComponent(input.runId)}`;
|
|
76228
|
+
const allowedPaths = /* @__PURE__ */ new Set([`${runPath}/credential`, `${runPath}/auth-state`]);
|
|
76229
|
+
const token = (0, node_crypto_1.randomBytes)(32).toString("base64url");
|
|
76230
|
+
const authHeader = `Bearer ${token}`;
|
|
76231
|
+
const server3 = (0, node_http_1.createServer)(async (req, res) => {
|
|
76232
|
+
try {
|
|
76233
|
+
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
76234
|
+
if (req.method !== "POST" || !allowedPaths.has(requestUrl.pathname) || requestUrl.search) {
|
|
76235
|
+
sendJson(res, 404, { error: "Callback route not available" });
|
|
76236
|
+
return;
|
|
76237
|
+
}
|
|
76238
|
+
if (req.headers.authorization !== authHeader) {
|
|
76239
|
+
sendJson(res, 401, { error: "Invalid callback capability" });
|
|
76240
|
+
return;
|
|
76241
|
+
}
|
|
76242
|
+
const body = await readRequestBody(req);
|
|
76243
|
+
let parsedBody;
|
|
76244
|
+
try {
|
|
76245
|
+
const parsed = JSON.parse(body.toString("utf8"));
|
|
76246
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
76247
|
+
throw new Error("invalid body");
|
|
76248
|
+
parsedBody = parsed;
|
|
76249
|
+
} catch {
|
|
76250
|
+
sendJson(res, 400, { error: "Callback body must be a JSON object" });
|
|
76251
|
+
return;
|
|
76252
|
+
}
|
|
76253
|
+
const upstreamBody = JSON.stringify({
|
|
76254
|
+
...parsedBody,
|
|
76255
|
+
...input.taskArn ? { taskArn: input.taskArn } : {},
|
|
76256
|
+
...input.claimEpoch !== void 0 ? { claimEpoch: input.claimEpoch } : {},
|
|
76257
|
+
...input.authConfigHash ? { authConfigHash: input.authConfigHash } : {}
|
|
76258
|
+
});
|
|
76259
|
+
const destination = new URL(requestUrl.pathname, upstream);
|
|
76260
|
+
const response = await fetch(destination, {
|
|
76261
|
+
method: "POST",
|
|
76262
|
+
headers: {
|
|
76263
|
+
Authorization: input.upstreamAuthHeader,
|
|
76264
|
+
"Content-Type": "application/json"
|
|
76265
|
+
},
|
|
76266
|
+
body: upstreamBody,
|
|
76267
|
+
redirect: "error",
|
|
76268
|
+
signal: AbortSignal.timeout(CALLBACK_TIMEOUT_MS)
|
|
76269
|
+
});
|
|
76270
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
76271
|
+
res.writeHead(response.status, {
|
|
76272
|
+
"cache-control": "no-store",
|
|
76273
|
+
"content-type": response.headers.get("content-type") ?? "application/json",
|
|
76274
|
+
"content-length": responseBody.length
|
|
76275
|
+
});
|
|
76276
|
+
res.end(responseBody);
|
|
76277
|
+
} catch (error2) {
|
|
76278
|
+
const message = error2 instanceof Error && error2.message === "callback body too large" ? error2.message : "Callback forwarding failed";
|
|
76279
|
+
sendJson(res, message === "callback body too large" ? 413 : 502, { error: message });
|
|
76280
|
+
}
|
|
76281
|
+
});
|
|
76282
|
+
server3.on("clientError", (_error, socket) => socket.destroy());
|
|
76283
|
+
await new Promise((resolve2, reject) => {
|
|
76284
|
+
const onError = (error2) => reject(error2);
|
|
76285
|
+
server3.once("error", onError);
|
|
76286
|
+
server3.listen(0, "127.0.0.1", () => {
|
|
76287
|
+
server3.off("error", onError);
|
|
76288
|
+
resolve2();
|
|
76289
|
+
});
|
|
76290
|
+
});
|
|
76291
|
+
const address = server3.address();
|
|
76292
|
+
if (!address) {
|
|
76293
|
+
server3.close();
|
|
76294
|
+
throw new Error("callback proxy failed to bind");
|
|
76295
|
+
}
|
|
76296
|
+
let closed = false;
|
|
76297
|
+
return {
|
|
76298
|
+
serverUrl: `http://127.0.0.1:${address.port}`,
|
|
76299
|
+
authHeader,
|
|
76300
|
+
close: async () => {
|
|
76301
|
+
if (closed)
|
|
76302
|
+
return;
|
|
76303
|
+
closed = true;
|
|
76304
|
+
await new Promise((resolve2) => server3.close(() => resolve2()));
|
|
76305
|
+
}
|
|
76306
|
+
};
|
|
76307
|
+
}
|
|
76308
|
+
}
|
|
76309
|
+
});
|
|
76310
|
+
|
|
76168
76311
|
// ../runner-core/dist/services/playwright-native.js
|
|
76169
76312
|
var require_playwright_native = __commonJS({
|
|
76170
76313
|
"../runner-core/dist/services/playwright-native.js"(exports2) {
|
|
@@ -76209,6 +76352,7 @@ var require_playwright_native = __commonJS({
|
|
|
76209
76352
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
76210
76353
|
exports2.scrubHarRequestBody = exports2.buildOriginScopedApiFixtures = exports2.isAppOriginRequest = exports2.resolveApiAuthHeaders = exports2.parseApiAuthHeaders = void 0;
|
|
76211
76354
|
exports2.executeTestViaNativePlaywright = executeTestViaNativePlaywright2;
|
|
76355
|
+
exports2.buildNativePlaywrightChildEnv = buildNativePlaywrightChildEnv;
|
|
76212
76356
|
exports2.parseJsonResults = parseJsonResults2;
|
|
76213
76357
|
exports2.parseHarToApiSummary = parseHarToApiSummary;
|
|
76214
76358
|
var promises_1 = require("fs/promises");
|
|
@@ -76220,6 +76364,7 @@ var require_playwright_native = __commonJS({
|
|
|
76220
76364
|
var api_call_redaction_js_1 = require_api_call_redaction();
|
|
76221
76365
|
var har_stream_js_1 = require_har_stream();
|
|
76222
76366
|
var api_auth_headers_js_1 = require_api_auth_headers();
|
|
76367
|
+
var run_callback_proxy_js_1 = require_run_callback_proxy();
|
|
76223
76368
|
var api_auth_headers_js_2 = require_api_auth_headers();
|
|
76224
76369
|
Object.defineProperty(exports2, "parseApiAuthHeaders", { enumerable: true, get: function() {
|
|
76225
76370
|
return api_auth_headers_js_2.parseApiAuthHeaders;
|
|
@@ -76242,7 +76387,7 @@ var require_playwright_native = __commonJS({
|
|
|
76242
76387
|
var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
|
|
76243
76388
|
var PW_CLI = require.resolve("@playwright/test/cli");
|
|
76244
76389
|
async function executeTestViaNativePlaywright2(options) {
|
|
76245
|
-
const { playwrightCode, baseUrl, testName, testVariables, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76390
|
+
const { playwrightCode, baseUrl, testName, testVariables, authStateCapturePath, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76246
76391
|
const logs2 = [];
|
|
76247
76392
|
const screenshots = [];
|
|
76248
76393
|
logs2.push(`\u{1F3AD} Native Playwright starting`);
|
|
@@ -76252,6 +76397,7 @@ var require_playwright_native = __commonJS({
|
|
|
76252
76397
|
logs2.push("\u26A0\uFE0F WARNING: baseUrl is empty - test will likely fail with navigation errors");
|
|
76253
76398
|
}
|
|
76254
76399
|
let runDir = null;
|
|
76400
|
+
let callbackProxy;
|
|
76255
76401
|
try {
|
|
76256
76402
|
runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
|
|
76257
76403
|
await (0, promises_1.mkdir)(runDir, { recursive: true });
|
|
@@ -76511,35 +76657,51 @@ module.exports = {
|
|
|
76511
76657
|
const configPath = (0, path_1.join)(runDir, "playwright.config.js");
|
|
76512
76658
|
await (0, promises_1.writeFile)(configPath, config);
|
|
76513
76659
|
const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
|
|
76514
|
-
const
|
|
76515
|
-
|
|
76660
|
+
const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
|
|
76661
|
+
const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
|
|
76662
|
+
if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
|
|
76663
|
+
try {
|
|
76664
|
+
callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
|
|
76665
|
+
runId: validateContext.runId,
|
|
76666
|
+
serverUrl: validateContext.serverUrl,
|
|
76667
|
+
upstreamAuthHeader: validateContext.authHeader,
|
|
76668
|
+
taskArn: process.env.VALIDATE_TASK_ARN,
|
|
76669
|
+
claimEpoch: callbackClaimEpoch,
|
|
76670
|
+
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
|
|
76671
|
+
});
|
|
76672
|
+
} catch (error3) {
|
|
76673
|
+
logs2.push(`Callback proxy unavailable: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
76674
|
+
}
|
|
76675
|
+
}
|
|
76676
|
+
const childTestVariables = { ...testVariables ?? {} };
|
|
76677
|
+
const trustedRuntimeEnv = {
|
|
76516
76678
|
...isolation.env,
|
|
76517
|
-
//
|
|
76518
|
-
//
|
|
76519
|
-
|
|
76520
|
-
|
|
76521
|
-
|
|
76522
|
-
|
|
76523
|
-
|
|
76679
|
+
// Generated callbacks receive only a random loopback capability. The global
|
|
76680
|
+
// runner bearer token and real control-plane URL stay in the parent process.
|
|
76681
|
+
...callbackProxy && validateContext?.runId ? {
|
|
76682
|
+
VALIDATE_RUN_ID: validateContext.runId,
|
|
76683
|
+
...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
|
|
76684
|
+
VALIDATE_SERVER_URL: callbackProxy.serverUrl,
|
|
76685
|
+
VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
|
|
76686
|
+
} : {},
|
|
76524
76687
|
// Owning Fargate taskArn (a per-task constant set once at task boot in
|
|
76525
76688
|
// execute-one.ts; absent on the local/Railway runner). Read from
|
|
76526
76689
|
// process.env rather than validateContext since it's process-wide, not
|
|
76527
76690
|
// per-run. Lets persistCredential() prove ownership to /credential.
|
|
76528
76691
|
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
|
|
76692
|
+
...authStateCapturePath ? { AUTH_FILE: authStateCapturePath } : {},
|
|
76529
76693
|
...liveBrowserStatePath ? { VALIDATE_LIVE_BROWSER_STATE_PATH: liveBrowserStatePath } : {},
|
|
76530
|
-
//
|
|
76531
|
-
//
|
|
76532
|
-
//
|
|
76533
|
-
// this forward the flag reads as undefined in the test and the early publish is dead
|
|
76534
|
-
// code. Forwarded (not allowlisted) so it stays a per-run toggle; absent → inert.
|
|
76694
|
+
// Preserve an explicit shared-auth kill-switch in the child. Unset defaults on in
|
|
76695
|
+
// both parent and generated __vqaLogin code; "false" must still cross this process
|
|
76696
|
+
// boundary to disable the early publish POST. It remains a per-run toggle.
|
|
76535
76697
|
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
|
|
76536
76698
|
};
|
|
76537
|
-
delete
|
|
76699
|
+
delete childTestVariables.API_AUTH_HEADERS;
|
|
76538
76700
|
logs2.push("\u{1F680} Spawning playwright test...");
|
|
76539
76701
|
const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
|
|
76540
76702
|
let playResult;
|
|
76541
76703
|
try {
|
|
76542
|
-
playResult = await runPlaywright(configPath,
|
|
76704
|
+
playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv);
|
|
76543
76705
|
} finally {
|
|
76544
76706
|
await stopLiveBrowserPolling?.();
|
|
76545
76707
|
}
|
|
@@ -76623,6 +76785,8 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76623
76785
|
error: msg
|
|
76624
76786
|
};
|
|
76625
76787
|
} finally {
|
|
76788
|
+
await callbackProxy?.close().catch(() => {
|
|
76789
|
+
});
|
|
76626
76790
|
if (runDir) {
|
|
76627
76791
|
await (0, promises_1.rm)(runDir, { recursive: true, force: true }).catch(() => {
|
|
76628
76792
|
});
|
|
@@ -76674,9 +76838,9 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76674
76838
|
stopped = true;
|
|
76675
76839
|
};
|
|
76676
76840
|
}
|
|
76677
|
-
function runPlaywright(configPath,
|
|
76841
|
+
function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
|
|
76678
76842
|
return new Promise((resolvePromise) => {
|
|
76679
|
-
const sandboxedEnv = (
|
|
76843
|
+
const sandboxedEnv = buildNativePlaywrightChildEnv(process.env, testVariables, trustedRuntimeEnv);
|
|
76680
76844
|
const proc = (0, spawn_with_tree_kill_js_1.spawnDetachedGroup)(process.execPath, [PW_CLI, "test", "--config", configPath], {
|
|
76681
76845
|
cwd: PACKAGE_DIR,
|
|
76682
76846
|
env: sandboxedEnv,
|
|
@@ -76719,6 +76883,12 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76719
76883
|
});
|
|
76720
76884
|
});
|
|
76721
76885
|
}
|
|
76886
|
+
function buildNativePlaywrightChildEnv(processEnv, testVariables, trustedRuntimeEnv) {
|
|
76887
|
+
return {
|
|
76888
|
+
...(0, shared_1.buildSandboxedEnv)(processEnv, testVariables),
|
|
76889
|
+
...trustedRuntimeEnv ?? {}
|
|
76890
|
+
};
|
|
76891
|
+
}
|
|
76722
76892
|
async function collectScreenshots(dir, screenshots) {
|
|
76723
76893
|
let entries;
|
|
76724
76894
|
try {
|
|
@@ -80440,10 +80610,12 @@ var require_auth_recipe = __commonJS({
|
|
|
80440
80610
|
exports2.upsertAuthBlock = upsertAuthBlock;
|
|
80441
80611
|
exports2.buildHealAuthStamp = buildHealAuthStamp;
|
|
80442
80612
|
var crypto_1 = require("crypto");
|
|
80613
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
80443
80614
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
80444
|
-
exports2.AUTH_MARKER_VERSION = "
|
|
80615
|
+
exports2.AUTH_MARKER_VERSION = "v3";
|
|
80445
80616
|
var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports2.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
|
|
80446
80617
|
var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports2.AUTH_MARKER_VERSION} <<<`;
|
|
80618
|
+
var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
|
|
80447
80619
|
var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
|
|
80448
80620
|
function computeRecipeHash(input) {
|
|
80449
80621
|
const canonical = JSON.stringify({
|
|
@@ -80521,16 +80693,18 @@ var require_auth_recipe = __commonJS({
|
|
|
80521
80693
|
if (opts?.warmGuard) {
|
|
80522
80694
|
const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
|
|
80523
80695
|
const loginRoute = recipe.route ?? recipe.preNavRoute;
|
|
80524
|
-
lines.push(`
|
|
80696
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
|
|
80697
|
+
lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
|
|
80525
80698
|
if (recipe.authProof.kind === "url") {
|
|
80526
80699
|
const pred = urlProofPredicateExpr(recipe.authProof.route);
|
|
80527
|
-
lines.push(`
|
|
80528
|
-
lines.push(`
|
|
80700
|
+
lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
|
|
80701
|
+
lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
|
|
80529
80702
|
} else if (loginRoute) {
|
|
80530
|
-
lines.push(`
|
|
80703
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
|
|
80531
80704
|
} else {
|
|
80532
|
-
lines.push(`
|
|
80705
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
|
|
80533
80706
|
}
|
|
80707
|
+
lines.push(` }`);
|
|
80534
80708
|
}
|
|
80535
80709
|
lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
|
|
80536
80710
|
for (const cmd of recipe.preNavCmds)
|
|
@@ -80543,11 +80717,11 @@ var require_auth_recipe = __commonJS({
|
|
|
80543
80717
|
lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
|
|
80544
80718
|
lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
|
|
80545
80719
|
if (opts?.warmGuard) {
|
|
80546
|
-
lines.push(` if (process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
80547
|
-
lines.push(` if (process.env.
|
|
80720
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
80721
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.VALIDATE_TASK_ARN && process.env.VALIDATE_CLAIM_EPOCH && process.env.VALIDATE_SERVER_URL && process.env.VALIDATE_RUN_ID) {`);
|
|
80548
80722
|
lines.push(` try {`);
|
|
80549
80723
|
lines.push(` const __vqaState = await page.context().storageState();`);
|
|
80550
|
-
lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN }, timeout: 10000 });`);
|
|
80724
|
+
lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN, claimEpoch: Number(process.env.VALIDATE_CLAIM_EPOCH) }, timeout: 10000 });`);
|
|
80551
80725
|
lines.push(` } catch {}`);
|
|
80552
80726
|
lines.push(` }`);
|
|
80553
80727
|
}
|
|
@@ -80559,6 +80733,8 @@ var require_auth_recipe = __commonJS({
|
|
|
80559
80733
|
"AUTH_FILE",
|
|
80560
80734
|
"SHARED_AUTH_INJECT",
|
|
80561
80735
|
"VALIDATE_RUN_ID",
|
|
80736
|
+
"VALIDATE_CLAIM_EPOCH",
|
|
80737
|
+
"VALIDATE_AUTH_CONFIG_HASH",
|
|
80562
80738
|
"VALIDATE_TASK_ARN",
|
|
80563
80739
|
"VALIDATE_RUNNER_AUTH",
|
|
80564
80740
|
"VALIDATE_SERVER_URL"
|
|
@@ -80731,7 +80907,7 @@ ${tail}`;
|
|
|
80731
80907
|
let result = stripAuthRegions(code);
|
|
80732
80908
|
result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
|
|
80733
80909
|
result = ensureExpectImported(result);
|
|
80734
|
-
const warmGuard =
|
|
80910
|
+
const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
|
|
80735
80911
|
result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
|
|
80736
80912
|
result = ensureLoginCall(result);
|
|
80737
80913
|
const n = countAuthRegions(result);
|
|
@@ -85861,9 +86037,9 @@ ${testOpen}`;
|
|
|
85861
86037
|
basePath: options?.basePath
|
|
85862
86038
|
});
|
|
85863
86039
|
const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
|
|
85864
|
-
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process
|
|
85865
|
-
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]);
|
|
85866
|
-
const tags = authGuardStamped
|
|
86040
|
+
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && (0, shared_1.isSharedAuthInjectEnabled)() && /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
86041
|
+
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]).filter((tag) => tag !== "@auth-guarded");
|
|
86042
|
+
const tags = authGuardStamped ? [...baseTags, "@auth-guarded"] : baseTags;
|
|
85867
86043
|
return {
|
|
85868
86044
|
name: String(test.name || "Unnamed test").slice(0, 200),
|
|
85869
86045
|
description: String(test.description || test.name || "").slice(0, 500),
|
|
@@ -99678,7 +99854,7 @@ ${lanes.join("\n")}
|
|
|
99678
99854
|
return process.memoryUsage().heapUsed;
|
|
99679
99855
|
},
|
|
99680
99856
|
getFileSize(path) {
|
|
99681
|
-
const stat =
|
|
99857
|
+
const stat = statSync(path);
|
|
99682
99858
|
if (stat == null ? void 0 : stat.isFile()) {
|
|
99683
99859
|
return stat.size;
|
|
99684
99860
|
}
|
|
@@ -99722,7 +99898,7 @@ ${lanes.join("\n")}
|
|
|
99722
99898
|
}
|
|
99723
99899
|
};
|
|
99724
99900
|
return nodeSystem;
|
|
99725
|
-
function
|
|
99901
|
+
function statSync(path) {
|
|
99726
99902
|
try {
|
|
99727
99903
|
return _fs.statSync(path, statSyncOptions);
|
|
99728
99904
|
} catch {
|
|
@@ -99781,7 +99957,7 @@ ${lanes.join("\n")}
|
|
|
99781
99957
|
activeSession.post("Profiler.stop", (err, { profile }) => {
|
|
99782
99958
|
var _a;
|
|
99783
99959
|
if (!err) {
|
|
99784
|
-
if ((_a =
|
|
99960
|
+
if ((_a = statSync(profilePath)) == null ? void 0 : _a.isDirectory()) {
|
|
99785
99961
|
profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
|
|
99786
99962
|
}
|
|
99787
99963
|
try {
|
|
@@ -99901,7 +100077,7 @@ ${lanes.join("\n")}
|
|
|
99901
100077
|
let stat;
|
|
99902
100078
|
if (typeof dirent === "string" || dirent.isSymbolicLink()) {
|
|
99903
100079
|
const name = combinePaths(path, entry);
|
|
99904
|
-
stat =
|
|
100080
|
+
stat = statSync(name);
|
|
99905
100081
|
if (!stat) {
|
|
99906
100082
|
continue;
|
|
99907
100083
|
}
|
|
@@ -99925,7 +100101,7 @@ ${lanes.join("\n")}
|
|
|
99925
100101
|
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
|
|
99926
100102
|
}
|
|
99927
100103
|
function fileSystemEntryExists(path, entryKind) {
|
|
99928
|
-
const stat =
|
|
100104
|
+
const stat = statSync(path);
|
|
99929
100105
|
if (!stat) {
|
|
99930
100106
|
return false;
|
|
99931
100107
|
}
|
|
@@ -99967,7 +100143,7 @@ ${lanes.join("\n")}
|
|
|
99967
100143
|
}
|
|
99968
100144
|
function getModifiedTime3(path) {
|
|
99969
100145
|
var _a;
|
|
99970
|
-
return (_a =
|
|
100146
|
+
return (_a = statSync(path)) == null ? void 0 : _a.mtime;
|
|
99971
100147
|
}
|
|
99972
100148
|
function setModifiedTime(path, time) {
|
|
99973
100149
|
try {
|
|
@@ -308478,6 +308654,7 @@ var require_regression_runner = __commonJS({
|
|
|
308478
308654
|
var chromium_isolation_js_1 = require_chromium_isolation();
|
|
308479
308655
|
var browser_config_js_1 = require_browser_config();
|
|
308480
308656
|
var spawn_with_tree_kill_js_1 = require_spawn_with_tree_kill();
|
|
308657
|
+
var run_callback_proxy_js_1 = require_run_callback_proxy();
|
|
308481
308658
|
var SUITE_SPAWN_TIMEOUT_MS = 85 * 60 * 1e3;
|
|
308482
308659
|
var PACKAGE_DIR = (0, path_1.resolve)(__dirname, "..", "..");
|
|
308483
308660
|
var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
|
|
@@ -308492,6 +308669,7 @@ var require_regression_runner = __commonJS({
|
|
|
308492
308669
|
const tarPath = (0, path_1.join)(RUNS_DIR, `regression-${runId}.tar.gz`);
|
|
308493
308670
|
const startTime = Date.now();
|
|
308494
308671
|
let logs2 = "";
|
|
308672
|
+
let callbackProxy;
|
|
308495
308673
|
try {
|
|
308496
308674
|
await (0, promises_1.mkdir)(extractDir, { recursive: true });
|
|
308497
308675
|
const tarballUrl = `https://api.github.com/repos/${repoOwner}/${repoName}/tarball/${repoBranch}`;
|
|
@@ -308521,19 +308699,40 @@ var require_regression_runner = __commonJS({
|
|
|
308521
308699
|
await (0, promises_1.writeFile)((0, path_1.join)(extractDir, ".env"), envContent, "utf-8");
|
|
308522
308700
|
const jsonResultsPath = (0, path_1.join)(extractDir, "test-results", "results.json");
|
|
308523
308701
|
const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(extractDir);
|
|
308702
|
+
const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
|
|
308703
|
+
const callbackClaimEpoch = Number.isSafeInteger(opts.claimEpoch) ? opts.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
|
|
308704
|
+
if (opts.validateServerUrl && opts.validateRunnerAuthHeader) {
|
|
308705
|
+
try {
|
|
308706
|
+
callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
|
|
308707
|
+
runId,
|
|
308708
|
+
serverUrl: opts.validateServerUrl,
|
|
308709
|
+
upstreamAuthHeader: opts.validateRunnerAuthHeader,
|
|
308710
|
+
taskArn: process.env.VALIDATE_TASK_ARN,
|
|
308711
|
+
claimEpoch: callbackClaimEpoch,
|
|
308712
|
+
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
|
|
308713
|
+
});
|
|
308714
|
+
} catch (error2) {
|
|
308715
|
+
logs2 += `Callback proxy unavailable: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
308716
|
+
`;
|
|
308717
|
+
}
|
|
308718
|
+
}
|
|
308524
308719
|
const runEnv = {
|
|
308525
308720
|
...(0, shared_1.buildSandboxedEnv)(process.env, testVariables),
|
|
308526
308721
|
...isolation.env,
|
|
308527
308722
|
CI: "true",
|
|
308528
308723
|
PLAYWRIGHT_JSON_OUTPUT_NAME: jsonResultsPath,
|
|
308529
|
-
//
|
|
308530
|
-
//
|
|
308531
|
-
|
|
308532
|
-
|
|
308533
|
-
|
|
308724
|
+
// The suite receives a random run-scoped loopback callback, never the
|
|
308725
|
+
// global runner bearer credential.
|
|
308726
|
+
...callbackProxy ? {
|
|
308727
|
+
VALIDATE_RUN_ID: runId,
|
|
308728
|
+
...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
|
|
308729
|
+
VALIDATE_SERVER_URL: callbackProxy.serverUrl,
|
|
308730
|
+
VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
|
|
308731
|
+
} : {},
|
|
308534
308732
|
// Owning Fargate taskArn (per-task constant; unset off-Fargate). Forwarded
|
|
308535
308733
|
// so persistCredential() can prove ownership to the /credential fence.
|
|
308536
|
-
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {}
|
|
308734
|
+
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
|
|
308735
|
+
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
|
|
308537
308736
|
};
|
|
308538
308737
|
logs2 += `Running playwright --project=${opts.suiteProject ?? "suite"}...
|
|
308539
308738
|
`;
|
|
@@ -308562,6 +308761,8 @@ var require_regression_runner = __commonJS({
|
|
|
308562
308761
|
error: String(err)
|
|
308563
308762
|
};
|
|
308564
308763
|
} finally {
|
|
308764
|
+
await callbackProxy?.close().catch(() => {
|
|
308765
|
+
});
|
|
308565
308766
|
(0, promises_1.rm)(extractDir, { recursive: true, force: true }).catch(() => {
|
|
308566
308767
|
});
|
|
308567
308768
|
await (0, browser_config_js_1.cleanupPlaywrightTempArtifacts)().catch(() => {
|
|
@@ -324805,6 +325006,455 @@ var require_resource_governor = __commonJS({
|
|
|
324805
325006
|
}
|
|
324806
325007
|
});
|
|
324807
325008
|
|
|
325009
|
+
// ../runner-core/dist/services/local-auth-state.js
|
|
325010
|
+
var require_local_auth_state = __commonJS({
|
|
325011
|
+
"../runner-core/dist/services/local-auth-state.js"(exports2) {
|
|
325012
|
+
"use strict";
|
|
325013
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
325014
|
+
exports2.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = exports2.LOCAL_AUTH_STATE_LOCK_STALE_MS = exports2.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = exports2.LOCAL_AUTH_STATE_WAIT_MS = exports2.LOCAL_AUTH_STATE_MAX_AGE_MS = void 0;
|
|
325015
|
+
exports2.shouldSkipWarmAuthSetup = shouldSkipWarmAuthSetup2;
|
|
325016
|
+
exports2.shouldSkipDisabledAutomaticAuthSetup = shouldSkipDisabledAutomaticAuthSetup2;
|
|
325017
|
+
exports2.localAuthStateFingerprint = localAuthStateFingerprint;
|
|
325018
|
+
exports2.ensurePrivateDirectory = ensurePrivateDirectory2;
|
|
325019
|
+
exports2.isValidStorageState = isValidStorageState;
|
|
325020
|
+
exports2.isValidStorageStateJson = isValidStorageStateJson;
|
|
325021
|
+
exports2.isValidLocalAuthStateFile = isValidLocalAuthStateFile;
|
|
325022
|
+
exports2.cleanupLocalAuthStateFiles = cleanupLocalAuthStateFiles;
|
|
325023
|
+
exports2.cleanupExpiredLocalAuthStateFiles = cleanupExpiredLocalAuthStateFiles2;
|
|
325024
|
+
exports2.localAuthStatePaths = localAuthStatePaths2;
|
|
325025
|
+
exports2.getValidLocalAuthState = getValidLocalAuthState2;
|
|
325026
|
+
exports2.waitForValidLocalAuthState = waitForValidLocalAuthState;
|
|
325027
|
+
exports2.waitForLocalAuthStateProducer = waitForLocalAuthStateProducer2;
|
|
325028
|
+
exports2.promoteLocalAuthStateCapture = promoteLocalAuthStateCapture2;
|
|
325029
|
+
exports2.discardLocalAuthStateCapture = discardLocalAuthStateCapture2;
|
|
325030
|
+
exports2.acquireLocalAuthStateLock = acquireLocalAuthStateLock2;
|
|
325031
|
+
exports2.localAuthStateFilename = localAuthStateFilename;
|
|
325032
|
+
var node_fs_1 = require("fs");
|
|
325033
|
+
var node_crypto_1 = require("crypto");
|
|
325034
|
+
var node_path_1 = require("path");
|
|
325035
|
+
var auth_recipe_js_1 = require_auth_recipe();
|
|
325036
|
+
exports2.LOCAL_AUTH_STATE_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
325037
|
+
exports2.LOCAL_AUTH_STATE_WAIT_MS = 3 * 60 * 1e3;
|
|
325038
|
+
exports2.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = 15 * 60 * 1e3;
|
|
325039
|
+
exports2.LOCAL_AUTH_STATE_LOCK_STALE_MS = 2 * 60 * 1e3;
|
|
325040
|
+
exports2.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = 30 * 60 * 1e3;
|
|
325041
|
+
function shouldSkipWarmAuthSetup2(authForceRun, sharedAuthEnabled, cacheWarm) {
|
|
325042
|
+
return sharedAuthEnabled && authForceRun !== true && cacheWarm;
|
|
325043
|
+
}
|
|
325044
|
+
function shouldSkipDisabledAutomaticAuthSetup2(authForceRun, authVerify, sharedAuthEnabled) {
|
|
325045
|
+
return !sharedAuthEnabled && authForceRun !== true && authVerify !== true;
|
|
325046
|
+
}
|
|
325047
|
+
function normalizeOrigin(value) {
|
|
325048
|
+
try {
|
|
325049
|
+
return new URL(value).origin.toLowerCase();
|
|
325050
|
+
} catch {
|
|
325051
|
+
return value.trim().toLowerCase();
|
|
325052
|
+
}
|
|
325053
|
+
}
|
|
325054
|
+
function normalizeAppUrl(value) {
|
|
325055
|
+
try {
|
|
325056
|
+
const url = new URL(value);
|
|
325057
|
+
url.hash = "";
|
|
325058
|
+
return `${url.origin.toLowerCase()}${url.pathname}${url.search}`;
|
|
325059
|
+
} catch {
|
|
325060
|
+
return value.trim();
|
|
325061
|
+
}
|
|
325062
|
+
}
|
|
325063
|
+
function safeSegment(value, fallback) {
|
|
325064
|
+
const safe = value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 120);
|
|
325065
|
+
return safe || fallback;
|
|
325066
|
+
}
|
|
325067
|
+
function stableVariables(testVariables) {
|
|
325068
|
+
return Object.entries(testVariables).filter(([key]) => !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(key)).sort(([a], [b]) => a.localeCompare(b));
|
|
325069
|
+
}
|
|
325070
|
+
function localAuthStateFingerprint(input) {
|
|
325071
|
+
const payload = JSON.stringify({
|
|
325072
|
+
version: 1,
|
|
325073
|
+
projectId: input.projectId,
|
|
325074
|
+
serverOrigin: normalizeOrigin(input.serverUrl),
|
|
325075
|
+
appUrl: normalizeAppUrl(input.baseUrl),
|
|
325076
|
+
testVariables: stableVariables(input.testVariables)
|
|
325077
|
+
});
|
|
325078
|
+
const key = input.authToken || `validate-local-auth:${input.projectId}`;
|
|
325079
|
+
return (0, node_crypto_1.createHmac)("sha256", key).update(payload).digest("hex").slice(0, 24);
|
|
325080
|
+
}
|
|
325081
|
+
function ensurePrivateDirectory2(dir) {
|
|
325082
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
325083
|
+
try {
|
|
325084
|
+
(0, node_fs_1.chmodSync)(dir, 448);
|
|
325085
|
+
} catch {
|
|
325086
|
+
}
|
|
325087
|
+
}
|
|
325088
|
+
function removeFile(path) {
|
|
325089
|
+
try {
|
|
325090
|
+
(0, node_fs_1.unlinkSync)(path);
|
|
325091
|
+
} catch {
|
|
325092
|
+
}
|
|
325093
|
+
}
|
|
325094
|
+
function readLockOwner(path) {
|
|
325095
|
+
try {
|
|
325096
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
325097
|
+
return typeof parsed.token === "string" && parsed.token.length > 0 && Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0 ? { token: parsed.token, pid: parsed.pid } : void 0;
|
|
325098
|
+
} catch {
|
|
325099
|
+
return void 0;
|
|
325100
|
+
}
|
|
325101
|
+
}
|
|
325102
|
+
function processIsAlive(pid) {
|
|
325103
|
+
try {
|
|
325104
|
+
process.kill(pid, 0);
|
|
325105
|
+
return true;
|
|
325106
|
+
} catch (error2) {
|
|
325107
|
+
return error2.code === "EPERM";
|
|
325108
|
+
}
|
|
325109
|
+
}
|
|
325110
|
+
function staleLockCanBeReaped(path, now, staleMs) {
|
|
325111
|
+
try {
|
|
325112
|
+
const ageMs = now - (0, node_fs_1.statSync)(path).mtimeMs;
|
|
325113
|
+
if (ageMs < staleMs)
|
|
325114
|
+
return false;
|
|
325115
|
+
const owner = readLockOwner(path);
|
|
325116
|
+
if (owner) {
|
|
325117
|
+
if (!processIsAlive(owner.pid))
|
|
325118
|
+
return true;
|
|
325119
|
+
return ageMs >= Math.max(staleMs * 5, exports2.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS);
|
|
325120
|
+
}
|
|
325121
|
+
return ageMs >= staleMs * 5;
|
|
325122
|
+
} catch {
|
|
325123
|
+
return false;
|
|
325124
|
+
}
|
|
325125
|
+
}
|
|
325126
|
+
function isRecord(value) {
|
|
325127
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
325128
|
+
}
|
|
325129
|
+
function isValidStorageState(value) {
|
|
325130
|
+
if (!isRecord(value))
|
|
325131
|
+
return false;
|
|
325132
|
+
const cookies = value.cookies;
|
|
325133
|
+
const origins = value.origins;
|
|
325134
|
+
if (!Array.isArray(cookies) || !Array.isArray(origins))
|
|
325135
|
+
return false;
|
|
325136
|
+
const cookiesValid = cookies.every((cookie) => {
|
|
325137
|
+
if (!isRecord(cookie))
|
|
325138
|
+
return false;
|
|
325139
|
+
return typeof cookie.name === "string" && typeof cookie.value === "string" && typeof cookie.domain === "string" && typeof cookie.path === "string" && typeof cookie.expires === "number" && Number.isFinite(cookie.expires) && typeof cookie.httpOnly === "boolean" && typeof cookie.secure === "boolean" && (cookie.sameSite === "Strict" || cookie.sameSite === "Lax" || cookie.sameSite === "None");
|
|
325140
|
+
});
|
|
325141
|
+
if (!cookiesValid)
|
|
325142
|
+
return false;
|
|
325143
|
+
const originsValid = origins.every((origin) => {
|
|
325144
|
+
if (!isRecord(origin) || typeof origin.origin !== "string" || !Array.isArray(origin.localStorage))
|
|
325145
|
+
return false;
|
|
325146
|
+
return origin.localStorage.every((entry) => isRecord(entry) && typeof entry.name === "string" && typeof entry.value === "string");
|
|
325147
|
+
});
|
|
325148
|
+
if (!originsValid)
|
|
325149
|
+
return false;
|
|
325150
|
+
return cookies.length > 0 || origins.some((origin) => origin.localStorage.length > 0);
|
|
325151
|
+
}
|
|
325152
|
+
function isValidStorageStateJson(value) {
|
|
325153
|
+
try {
|
|
325154
|
+
return isValidStorageState(JSON.parse(value));
|
|
325155
|
+
} catch {
|
|
325156
|
+
return false;
|
|
325157
|
+
}
|
|
325158
|
+
}
|
|
325159
|
+
function isStorageStateShape(value) {
|
|
325160
|
+
if (!value || typeof value !== "object")
|
|
325161
|
+
return false;
|
|
325162
|
+
return isValidStorageState(value);
|
|
325163
|
+
}
|
|
325164
|
+
function isValidLocalAuthStateFile(path) {
|
|
325165
|
+
try {
|
|
325166
|
+
return isStorageStateShape(JSON.parse((0, node_fs_1.readFileSync)(path, "utf8")));
|
|
325167
|
+
} catch {
|
|
325168
|
+
return false;
|
|
325169
|
+
}
|
|
325170
|
+
}
|
|
325171
|
+
function cleanupLocalAuthStateFiles(runsDir, projectId, now = Date.now(), maxAgeMs = exports2.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325172
|
+
ensurePrivateDirectory2(runsDir);
|
|
325173
|
+
const projectSegment = safeSegment(projectId, "default");
|
|
325174
|
+
const legacyName = `auth-state-${projectSegment}.json`;
|
|
325175
|
+
const prefix = `auth-state-${projectSegment}-`;
|
|
325176
|
+
for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
|
|
325177
|
+
if (name === legacyName) {
|
|
325178
|
+
removeFile((0, node_path_1.join)(runsDir, name));
|
|
325179
|
+
continue;
|
|
325180
|
+
}
|
|
325181
|
+
if (!name.startsWith(prefix))
|
|
325182
|
+
continue;
|
|
325183
|
+
const path = (0, node_path_1.join)(runsDir, name);
|
|
325184
|
+
try {
|
|
325185
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
|
|
325186
|
+
if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
|
|
325187
|
+
removeFile(path);
|
|
325188
|
+
}
|
|
325189
|
+
} catch {
|
|
325190
|
+
}
|
|
325191
|
+
}
|
|
325192
|
+
}
|
|
325193
|
+
function cleanupExpiredLocalAuthStateFiles2(runsDir, now = Date.now(), maxAgeMs = exports2.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325194
|
+
ensurePrivateDirectory2(runsDir);
|
|
325195
|
+
for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
|
|
325196
|
+
if (!name.startsWith("auth-state-"))
|
|
325197
|
+
continue;
|
|
325198
|
+
const path = (0, node_path_1.join)(runsDir, name);
|
|
325199
|
+
try {
|
|
325200
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
|
|
325201
|
+
if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
|
|
325202
|
+
removeFile(path);
|
|
325203
|
+
}
|
|
325204
|
+
} catch {
|
|
325205
|
+
}
|
|
325206
|
+
}
|
|
325207
|
+
}
|
|
325208
|
+
function localAuthStatePaths2(runsDir, input, runId) {
|
|
325209
|
+
cleanupLocalAuthStateFiles(runsDir, input.projectId);
|
|
325210
|
+
const projectSegment = safeSegment(input.projectId, "default");
|
|
325211
|
+
const runSegment = safeSegment(runId, `${process.pid}`);
|
|
325212
|
+
const fingerprint = localAuthStateFingerprint(input);
|
|
325213
|
+
const cachePath = (0, node_path_1.join)(runsDir, `auth-state-${projectSegment}-${fingerprint}.json`);
|
|
325214
|
+
return {
|
|
325215
|
+
cachePath,
|
|
325216
|
+
capturePath: `${cachePath}.capture-${runSegment}-${process.pid}.tmp`,
|
|
325217
|
+
fingerprint,
|
|
325218
|
+
lockKey: `${normalizeOrigin(input.serverUrl)}:${projectSegment}:${fingerprint}`,
|
|
325219
|
+
lockPath: `${cachePath}.lock`
|
|
325220
|
+
};
|
|
325221
|
+
}
|
|
325222
|
+
function getValidLocalAuthState2(path, now = Date.now(), maxAgeMs = exports2.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325223
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
325224
|
+
return void 0;
|
|
325225
|
+
try {
|
|
325226
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs || !isValidLocalAuthStateFile(path)) {
|
|
325227
|
+
removeFile(path);
|
|
325228
|
+
return void 0;
|
|
325229
|
+
}
|
|
325230
|
+
return path;
|
|
325231
|
+
} catch {
|
|
325232
|
+
return void 0;
|
|
325233
|
+
}
|
|
325234
|
+
}
|
|
325235
|
+
async function waitForValidLocalAuthState(path, timeoutMs = exports2.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100) {
|
|
325236
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
325237
|
+
do {
|
|
325238
|
+
const valid = getValidLocalAuthState2(path);
|
|
325239
|
+
if (valid)
|
|
325240
|
+
return valid;
|
|
325241
|
+
const remaining = deadline - Date.now();
|
|
325242
|
+
if (remaining <= 0)
|
|
325243
|
+
return void 0;
|
|
325244
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(Math.max(10, pollMs), remaining)));
|
|
325245
|
+
} while (true);
|
|
325246
|
+
}
|
|
325247
|
+
async function waitForLocalAuthStateProducer2(path, lockKey, startupGraceMs = 2e3, maxWaitMs = exports2.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100, lockPath) {
|
|
325248
|
+
const startedAt = Date.now();
|
|
325249
|
+
const producerIsActive = () => lockTails.has(lockKey) || !!lockPath && (0, node_fs_1.existsSync)(lockPath);
|
|
325250
|
+
let sawProducer = producerIsActive();
|
|
325251
|
+
do {
|
|
325252
|
+
const valid = getValidLocalAuthState2(path);
|
|
325253
|
+
if (valid)
|
|
325254
|
+
return valid;
|
|
325255
|
+
const producerActive = producerIsActive();
|
|
325256
|
+
if (producerActive)
|
|
325257
|
+
sawProducer = true;
|
|
325258
|
+
const elapsed = Date.now() - startedAt;
|
|
325259
|
+
if (sawProducer && !producerActive)
|
|
325260
|
+
return void 0;
|
|
325261
|
+
if (!sawProducer && elapsed >= Math.max(0, startupGraceMs))
|
|
325262
|
+
return void 0;
|
|
325263
|
+
if (elapsed >= Math.max(0, maxWaitMs))
|
|
325264
|
+
return void 0;
|
|
325265
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.max(10, pollMs)));
|
|
325266
|
+
} while (true);
|
|
325267
|
+
}
|
|
325268
|
+
function promoteLocalAuthStateCapture2(capturePath, cachePath, replaceExisting = false, lease) {
|
|
325269
|
+
if (lease && !lease.isOwner()) {
|
|
325270
|
+
removeFile(capturePath);
|
|
325271
|
+
return false;
|
|
325272
|
+
}
|
|
325273
|
+
if (!(0, node_fs_1.existsSync)(capturePath) || !isValidLocalAuthStateFile(capturePath)) {
|
|
325274
|
+
removeFile(capturePath);
|
|
325275
|
+
return false;
|
|
325276
|
+
}
|
|
325277
|
+
try {
|
|
325278
|
+
(0, node_fs_1.chmodSync)(capturePath, 384);
|
|
325279
|
+
} catch {
|
|
325280
|
+
}
|
|
325281
|
+
try {
|
|
325282
|
+
if (lease && !lease.isOwner()) {
|
|
325283
|
+
removeFile(capturePath);
|
|
325284
|
+
return false;
|
|
325285
|
+
}
|
|
325286
|
+
if ((0, node_fs_1.existsSync)(cachePath)) {
|
|
325287
|
+
if (!replaceExisting && isValidLocalAuthStateFile(cachePath)) {
|
|
325288
|
+
removeFile(capturePath);
|
|
325289
|
+
return true;
|
|
325290
|
+
}
|
|
325291
|
+
removeFile(cachePath);
|
|
325292
|
+
}
|
|
325293
|
+
if (lease && !lease.isOwner()) {
|
|
325294
|
+
removeFile(capturePath);
|
|
325295
|
+
return false;
|
|
325296
|
+
}
|
|
325297
|
+
(0, node_fs_1.renameSync)(capturePath, cachePath);
|
|
325298
|
+
try {
|
|
325299
|
+
(0, node_fs_1.chmodSync)(cachePath, 384);
|
|
325300
|
+
} catch {
|
|
325301
|
+
}
|
|
325302
|
+
return true;
|
|
325303
|
+
} catch {
|
|
325304
|
+
const winnerIsValid = (0, node_fs_1.existsSync)(cachePath) && isValidLocalAuthStateFile(cachePath);
|
|
325305
|
+
removeFile(capturePath);
|
|
325306
|
+
return winnerIsValid;
|
|
325307
|
+
}
|
|
325308
|
+
}
|
|
325309
|
+
function discardLocalAuthStateCapture2(capturePath) {
|
|
325310
|
+
removeFile(capturePath);
|
|
325311
|
+
}
|
|
325312
|
+
var lockTails = /* @__PURE__ */ new Map();
|
|
325313
|
+
function publishCompleteLockOwner(lockPath, owner, linkFile) {
|
|
325314
|
+
const metadata = JSON.stringify(owner);
|
|
325315
|
+
const tempPath = `${lockPath}.${owner.pid}.${owner.token}.tmp`;
|
|
325316
|
+
let createdLock = false;
|
|
325317
|
+
try {
|
|
325318
|
+
(0, node_fs_1.writeFileSync)(tempPath, metadata, { flag: "wx", mode: 384 });
|
|
325319
|
+
try {
|
|
325320
|
+
linkFile(tempPath, lockPath);
|
|
325321
|
+
createdLock = true;
|
|
325322
|
+
return (0, node_fs_1.openSync)(lockPath, "r+");
|
|
325323
|
+
} catch (error2) {
|
|
325324
|
+
const code = error2.code;
|
|
325325
|
+
if (!["EPERM", "EACCES", "EXDEV", "ENOSYS", "ENOTSUP", "EOPNOTSUPP"].includes(code ?? "")) {
|
|
325326
|
+
throw error2;
|
|
325327
|
+
}
|
|
325328
|
+
let fd;
|
|
325329
|
+
try {
|
|
325330
|
+
fd = (0, node_fs_1.openSync)(lockPath, "wx+", 384);
|
|
325331
|
+
createdLock = true;
|
|
325332
|
+
(0, node_fs_1.writeFileSync)(fd, metadata, { encoding: "utf8" });
|
|
325333
|
+
(0, node_fs_1.fsyncSync)(fd);
|
|
325334
|
+
return fd;
|
|
325335
|
+
} catch (fallbackError) {
|
|
325336
|
+
if (fd !== void 0) {
|
|
325337
|
+
try {
|
|
325338
|
+
(0, node_fs_1.closeSync)(fd);
|
|
325339
|
+
} catch {
|
|
325340
|
+
}
|
|
325341
|
+
}
|
|
325342
|
+
if (createdLock)
|
|
325343
|
+
removeFile(lockPath);
|
|
325344
|
+
throw fallbackError;
|
|
325345
|
+
}
|
|
325346
|
+
}
|
|
325347
|
+
} catch (error2) {
|
|
325348
|
+
if (createdLock && readLockOwner(lockPath)?.token === owner.token)
|
|
325349
|
+
removeFile(lockPath);
|
|
325350
|
+
throw error2;
|
|
325351
|
+
} finally {
|
|
325352
|
+
removeFile(tempPath);
|
|
325353
|
+
}
|
|
325354
|
+
}
|
|
325355
|
+
async function acquireExclusiveFileLock(lockPath, linkFile = node_fs_1.linkSync) {
|
|
325356
|
+
ensurePrivateDirectory2((0, node_path_1.dirname)(lockPath));
|
|
325357
|
+
const startedAt = Date.now();
|
|
325358
|
+
while (true) {
|
|
325359
|
+
try {
|
|
325360
|
+
const ownerToken = (0, node_crypto_1.randomBytes)(24).toString("base64url");
|
|
325361
|
+
const fd = publishCompleteLockOwner(lockPath, { token: ownerToken, pid: process.pid }, linkFile);
|
|
325362
|
+
const ownsPath = () => {
|
|
325363
|
+
try {
|
|
325364
|
+
const owner = readLockOwner(lockPath);
|
|
325365
|
+
return owner?.token === ownerToken && owner.pid === process.pid;
|
|
325366
|
+
} catch {
|
|
325367
|
+
return false;
|
|
325368
|
+
}
|
|
325369
|
+
};
|
|
325370
|
+
const heartbeat = setInterval(() => {
|
|
325371
|
+
try {
|
|
325372
|
+
if (!ownsPath()) {
|
|
325373
|
+
clearInterval(heartbeat);
|
|
325374
|
+
return;
|
|
325375
|
+
}
|
|
325376
|
+
const now = /* @__PURE__ */ new Date();
|
|
325377
|
+
(0, node_fs_1.futimesSync)(fd, now, now);
|
|
325378
|
+
} catch {
|
|
325379
|
+
clearInterval(heartbeat);
|
|
325380
|
+
}
|
|
325381
|
+
}, Math.max(1e3, Math.floor(exports2.LOCAL_AUTH_STATE_LOCK_STALE_MS / 3)));
|
|
325382
|
+
heartbeat.unref();
|
|
325383
|
+
let released = false;
|
|
325384
|
+
const release = (() => {
|
|
325385
|
+
if (released)
|
|
325386
|
+
return;
|
|
325387
|
+
released = true;
|
|
325388
|
+
clearInterval(heartbeat);
|
|
325389
|
+
try {
|
|
325390
|
+
if (ownsPath())
|
|
325391
|
+
removeFile(lockPath);
|
|
325392
|
+
} catch {
|
|
325393
|
+
}
|
|
325394
|
+
try {
|
|
325395
|
+
(0, node_fs_1.closeSync)(fd);
|
|
325396
|
+
} catch {
|
|
325397
|
+
}
|
|
325398
|
+
});
|
|
325399
|
+
release.isOwner = () => !released && ownsPath();
|
|
325400
|
+
return release;
|
|
325401
|
+
} catch (error2) {
|
|
325402
|
+
const code = error2.code;
|
|
325403
|
+
if (code !== "EEXIST")
|
|
325404
|
+
throw error2;
|
|
325405
|
+
try {
|
|
325406
|
+
if (staleLockCanBeReaped(lockPath, Date.now(), exports2.LOCAL_AUTH_STATE_LOCK_STALE_MS)) {
|
|
325407
|
+
removeFile(lockPath);
|
|
325408
|
+
continue;
|
|
325409
|
+
}
|
|
325410
|
+
} catch {
|
|
325411
|
+
continue;
|
|
325412
|
+
}
|
|
325413
|
+
if (Date.now() - startedAt >= exports2.LOCAL_AUTH_STATE_WAIT_MS) {
|
|
325414
|
+
throw new Error(`Timed out waiting for auth-state lock: ${(0, node_path_1.basename)(lockPath)}`);
|
|
325415
|
+
}
|
|
325416
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
325417
|
+
}
|
|
325418
|
+
}
|
|
325419
|
+
}
|
|
325420
|
+
async function acquireLocalAuthStateLock2(key, lockPath, linkFile = node_fs_1.linkSync) {
|
|
325421
|
+
const previous = lockTails.get(key) ?? Promise.resolve();
|
|
325422
|
+
let unlockCurrent;
|
|
325423
|
+
const current = new Promise((resolve2) => {
|
|
325424
|
+
unlockCurrent = resolve2;
|
|
325425
|
+
});
|
|
325426
|
+
const tail = previous.then(() => current);
|
|
325427
|
+
lockTails.set(key, tail);
|
|
325428
|
+
await previous;
|
|
325429
|
+
let releaseFileLock;
|
|
325430
|
+
try {
|
|
325431
|
+
if (lockPath)
|
|
325432
|
+
releaseFileLock = await acquireExclusiveFileLock(lockPath, linkFile);
|
|
325433
|
+
} catch (error2) {
|
|
325434
|
+
unlockCurrent();
|
|
325435
|
+
if (lockTails.get(key) === tail)
|
|
325436
|
+
lockTails.delete(key);
|
|
325437
|
+
throw error2;
|
|
325438
|
+
}
|
|
325439
|
+
let released = false;
|
|
325440
|
+
const release = (() => {
|
|
325441
|
+
if (released)
|
|
325442
|
+
return;
|
|
325443
|
+
released = true;
|
|
325444
|
+
releaseFileLock?.();
|
|
325445
|
+
unlockCurrent();
|
|
325446
|
+
if (lockTails.get(key) === tail)
|
|
325447
|
+
lockTails.delete(key);
|
|
325448
|
+
});
|
|
325449
|
+
release.isOwner = () => !released && (releaseFileLock?.isOwner() ?? true);
|
|
325450
|
+
return release;
|
|
325451
|
+
}
|
|
325452
|
+
function localAuthStateFilename(path) {
|
|
325453
|
+
return (0, node_path_1.basename)(path);
|
|
325454
|
+
}
|
|
325455
|
+
}
|
|
325456
|
+
});
|
|
325457
|
+
|
|
324808
325458
|
// ../../node_modules/adm-zip/util/constants.js
|
|
324809
325459
|
var require_constants5 = __commonJS({
|
|
324810
325460
|
"../../node_modules/adm-zip/util/constants.js"(exports2, module2) {
|
|
@@ -325118,7 +325768,7 @@ var require_utils2 = __commonJS({
|
|
|
325118
325768
|
module2.exports = Utils;
|
|
325119
325769
|
Utils.prototype.makeDir = function(folder) {
|
|
325120
325770
|
const self = this;
|
|
325121
|
-
function
|
|
325771
|
+
function mkdirSync4(fpath) {
|
|
325122
325772
|
let resolvedPath = fpath.split(self.sep)[0];
|
|
325123
325773
|
fpath.split(self.sep).forEach(function(name) {
|
|
325124
325774
|
if (!name || name.substr(-1, 1) === ":") return;
|
|
@@ -325136,7 +325786,7 @@ var require_utils2 = __commonJS({
|
|
|
325136
325786
|
if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
|
|
325137
325787
|
});
|
|
325138
325788
|
}
|
|
325139
|
-
|
|
325789
|
+
mkdirSync4(folder);
|
|
325140
325790
|
};
|
|
325141
325791
|
Utils.prototype.writeFileTo = function(path, content, overwrite, attr) {
|
|
325142
325792
|
const self = this;
|
|
@@ -337060,6 +337710,8 @@ var require_dist2 = __commonJS({
|
|
|
337060
337710
|
__exportStar(require_discover_agent(), exports2);
|
|
337061
337711
|
__exportStar(require_browser_pool(), exports2);
|
|
337062
337712
|
__exportStar(require_resource_governor(), exports2);
|
|
337713
|
+
__exportStar(require_local_auth_state(), exports2);
|
|
337714
|
+
__exportStar(require_run_callback_proxy(), exports2);
|
|
337063
337715
|
__exportStar(require_discover_explorer(), exports2);
|
|
337064
337716
|
__exportStar(require_discover_feature_explorer(), exports2);
|
|
337065
337717
|
__exportStar(require_discover_planner(), exports2);
|
|
@@ -337336,7 +337988,7 @@ var require_package4 = __commonJS({
|
|
|
337336
337988
|
"package.json"(exports2, module2) {
|
|
337337
337989
|
module2.exports = {
|
|
337338
337990
|
name: "@validate.qa/runner",
|
|
337339
|
-
version: "1.0.
|
|
337991
|
+
version: "1.0.43",
|
|
337340
337992
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
337341
337993
|
bin: {
|
|
337342
337994
|
"validate-runner": "dist/cli.js"
|
|
@@ -337344,7 +337996,7 @@ var require_package4 = __commonJS({
|
|
|
337344
337996
|
scripts: {
|
|
337345
337997
|
build: "tsup",
|
|
337346
337998
|
dev: "tsx src/cli.ts",
|
|
337347
|
-
test: "node --import tsx --test src/version-check.test.ts",
|
|
337999
|
+
test: "node --import tsx --test src/version-check.test.ts src/services/auth-state.test.ts",
|
|
337348
338000
|
"test:mobile": "node --import tsx --test --test-concurrency=2 src/runner.mobile-ai-env.test.ts src/mobile-device-queue.test.ts src/device-lease.test.ts src/result-payload-budget.test.ts src/services/appium-*.test.ts src/services/*mobile*.test.ts src/services/mobile/*.test.ts",
|
|
337349
338001
|
prepublishOnly: "npm run build"
|
|
337350
338002
|
},
|
|
@@ -337423,7 +338075,6 @@ function getConfigDir() {
|
|
|
337423
338075
|
}
|
|
337424
338076
|
|
|
337425
338077
|
// src/runner.ts
|
|
337426
|
-
var import_fs5 = require("fs");
|
|
337427
338078
|
var import_path4 = require("path");
|
|
337428
338079
|
var import_os6 = require("os");
|
|
337429
338080
|
var import_node_crypto2 = require("crypto");
|
|
@@ -337451,23 +338102,33 @@ var import_runner_core6 = __toESM(require_dist2());
|
|
|
337451
338102
|
|
|
337452
338103
|
// src/runner.ts
|
|
337453
338104
|
var import_runner_core11 = __toESM(require_dist2());
|
|
338105
|
+
var import_runner_core12 = __toESM(require_dist2());
|
|
337454
338106
|
|
|
337455
338107
|
// src/services/auth-state.ts
|
|
338108
|
+
init_dist();
|
|
337456
338109
|
var INLINE_LOGIN_TAG = "@inline-login";
|
|
337457
338110
|
var GUARDED_LOGIN_TAG = "@auth-guarded";
|
|
337458
338111
|
function sharedAuthInjectEnabled() {
|
|
337459
|
-
return
|
|
338112
|
+
return isSharedAuthInjectEnabled();
|
|
338113
|
+
}
|
|
338114
|
+
function hasGeneratedAuthGuard(playwrightCode) {
|
|
338115
|
+
if (!playwrightCode) return false;
|
|
338116
|
+
return /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
338117
|
+
}
|
|
338118
|
+
function shouldCaptureAuthState(tags, playwrightCode) {
|
|
338119
|
+
return sharedAuthInjectEnabled() && Array.isArray(tags) && tags.includes("@needs-auth") && tags.includes(GUARDED_LOGIN_TAG) && !tags.includes("@auth") && hasGeneratedAuthGuard(playwrightCode);
|
|
337460
338120
|
}
|
|
337461
338121
|
function hasInlineLoginSteps(playwrightCode) {
|
|
337462
338122
|
if (!playwrightCode) return false;
|
|
337463
338123
|
return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
|
|
337464
338124
|
}
|
|
337465
338125
|
function shouldUseAuthState(tags, playwrightCode) {
|
|
338126
|
+
if (!sharedAuthInjectEnabled()) return false;
|
|
337466
338127
|
if (!Array.isArray(tags)) return false;
|
|
337467
338128
|
if (tags.includes("@auth")) return false;
|
|
337468
338129
|
if (tags.includes("@api")) return true;
|
|
337469
338130
|
if (!tags.includes("@needs-auth")) return false;
|
|
337470
|
-
if (
|
|
338131
|
+
if (tags.includes(GUARDED_LOGIN_TAG) && hasGeneratedAuthGuard(playwrightCode)) return true;
|
|
337471
338132
|
const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
|
|
337472
338133
|
const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(playwrightCode);
|
|
337473
338134
|
return !tags.includes(INLINE_LOGIN_TAG) && !hasLegacyInlineLogin;
|
|
@@ -344177,8 +344838,8 @@ function withMediaReferences(result, refs) {
|
|
|
344177
344838
|
|
|
344178
344839
|
// src/runner.ts
|
|
344179
344840
|
init_dist();
|
|
344180
|
-
var import_runner_core12 = __toESM(require_dist2());
|
|
344181
344841
|
var import_runner_core13 = __toESM(require_dist2());
|
|
344842
|
+
var import_runner_core14 = __toESM(require_dist2());
|
|
344182
344843
|
function fingerprintTest(test) {
|
|
344183
344844
|
const code = test.framework === "APPIUM" ? [test.appiumCode ?? "", JSON.stringify(test.steps ?? [])].join("\n--steps--\n") : test.playwrightCode ?? "";
|
|
344184
344845
|
return (0, import_node_crypto2.createHash)("sha256").update(test.name).update("\n--suite--\n").update(test.suiteName ?? "").update("\n--type--\n").update(test.testType ?? "").update("\n--framework--\n").update(test.framework ?? "").update("\n--code--\n").update(code).digest("hex").slice(0, 24);
|
|
@@ -344302,21 +344963,11 @@ var log = {
|
|
|
344302
344963
|
warn: (msg) => console.log(`${C2.yellow}\u26A0${C2.reset} ${msg}`),
|
|
344303
344964
|
dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
|
|
344304
344965
|
};
|
|
344305
|
-
var AUTH_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
344306
344966
|
function getRunsDir() {
|
|
344307
344967
|
const dir = process.env.RUNS_DIR || (0, import_path4.join)((0, import_os6.homedir)(), ".validate.qa", "runs");
|
|
344308
|
-
|
|
344968
|
+
(0, import_runner_core12.ensurePrivateDirectory)(dir);
|
|
344309
344969
|
return dir;
|
|
344310
344970
|
}
|
|
344311
|
-
function authStatePath(projectId) {
|
|
344312
|
-
return (0, import_path4.join)(getRunsDir(), `auth-state-${projectId || "default"}.json`);
|
|
344313
|
-
}
|
|
344314
|
-
function getValidAuthState(projectId) {
|
|
344315
|
-
const path = authStatePath(projectId);
|
|
344316
|
-
if (!(0, import_fs5.existsSync)(path)) return void 0;
|
|
344317
|
-
const age = Date.now() - (0, import_fs5.statSync)(path).mtimeMs;
|
|
344318
|
-
return age < AUTH_MAX_AGE_MS ? path : void 0;
|
|
344319
|
-
}
|
|
344320
344971
|
function buildHeaders(authToken, projectId) {
|
|
344321
344972
|
return {
|
|
344322
344973
|
"Content-Type": "application/json",
|
|
@@ -344365,7 +345016,13 @@ async function claimRun(serverUrl, headers, runId) {
|
|
|
344365
345016
|
if (res.status === 429) return false;
|
|
344366
345017
|
if (!res.ok) throw new Error(`Claim failed: ${res.status}`);
|
|
344367
345018
|
const body = await res.json();
|
|
344368
|
-
|
|
345019
|
+
if (!body.claimed) {
|
|
345020
|
+
throw new Error("Claim response did not confirm ownership");
|
|
345021
|
+
}
|
|
345022
|
+
if (typeof body.claimEpoch !== "number" || !Number.isSafeInteger(body.claimEpoch) || body.claimEpoch < 0) {
|
|
345023
|
+
throw new Error("Claim response did not include a valid ownership epoch");
|
|
345024
|
+
}
|
|
345025
|
+
return body.claimEpoch;
|
|
344369
345026
|
}
|
|
344370
345027
|
async function postResult(serverUrl, headers, runId, result) {
|
|
344371
345028
|
const MAX_ATTEMPTS = 3;
|
|
@@ -344533,12 +345190,12 @@ async function postCaptureResult(serverUrl, headers, runId, result) {
|
|
|
344533
345190
|
});
|
|
344534
345191
|
if (!res.ok) throw new Error(`Failed to post capture result: ${res.status}`);
|
|
344535
345192
|
}
|
|
344536
|
-
async function postCredentialRotation(serverUrl, headers, runId, body) {
|
|
345193
|
+
async function postCredentialRotation(serverUrl, headers, runId, body, claimEpoch) {
|
|
344537
345194
|
try {
|
|
344538
345195
|
const res = await fetch(`${serverUrl}/api/runner/runs/${runId}/credential`, {
|
|
344539
345196
|
method: "POST",
|
|
344540
345197
|
headers,
|
|
344541
|
-
body: JSON.stringify(body),
|
|
345198
|
+
body: JSON.stringify({ ...body, ...claimEpoch !== void 0 ? { claimEpoch } : {} }),
|
|
344542
345199
|
signal: AbortSignal.timeout(3e4)
|
|
344543
345200
|
});
|
|
344544
345201
|
if (!res.ok) {
|
|
@@ -344875,6 +345532,12 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344875
345532
|
liveBrowserHandle,
|
|
344876
345533
|
onLiveBrowserSnapshot: requestLiveBrowserHeartbeat
|
|
344877
345534
|
});
|
|
345535
|
+
const callbackContext = {
|
|
345536
|
+
runId: run2.runId,
|
|
345537
|
+
serverUrl: opts.serverUrl,
|
|
345538
|
+
authHeader: headers.Authorization,
|
|
345539
|
+
claimEpoch: run2.claimEpoch
|
|
345540
|
+
};
|
|
344878
345541
|
const startTime = Date.now();
|
|
344879
345542
|
try {
|
|
344880
345543
|
if (mobileLeaseError) {
|
|
@@ -344904,7 +345567,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344904
345567
|
log.fail(`PRE-FLIGHT FAILED - ${preflightError}`);
|
|
344905
345568
|
return;
|
|
344906
345569
|
}
|
|
344907
|
-
const preflight = await (0,
|
|
345570
|
+
const preflight = await (0, import_runner_core13.resolveBaseUrlPreflight)(run2.baseUrl);
|
|
344908
345571
|
if (preflight.error) {
|
|
344909
345572
|
await postResult(opts.serverUrl, headers, run2.runId, { status: "ERROR", error: preflight.error });
|
|
344910
345573
|
log.fail(`PRE-FLIGHT FAILED - ${preflight.error}`);
|
|
@@ -344921,31 +345584,72 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344921
345584
|
log.fail("ERROR - No auth setup code");
|
|
344922
345585
|
return;
|
|
344923
345586
|
}
|
|
344924
|
-
const
|
|
344925
|
-
|
|
344926
|
-
|
|
344927
|
-
|
|
345587
|
+
const sharedAuthEnabled = sharedAuthInjectEnabled();
|
|
345588
|
+
if ((0, import_runner_core12.shouldSkipDisabledAutomaticAuthSetup)(run2.authForceRun, run2.authVerify, sharedAuthEnabled)) {
|
|
345589
|
+
const duration2 = Date.now() - startTime;
|
|
345590
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345591
|
+
status: "PASSED",
|
|
345592
|
+
logs: "Automatic auth prewarm skipped: shared session reuse is disabled.",
|
|
345593
|
+
duration: duration2
|
|
345594
|
+
});
|
|
345595
|
+
log.info(`Auth prewarm skipped (${duration2}ms) - SHARED_AUTH_INJECT=false`);
|
|
345596
|
+
return;
|
|
345597
|
+
}
|
|
345598
|
+
const authPaths2 = sharedAuthEnabled ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
345599
|
+
projectId: run2.projectId || opts.projectId || "default",
|
|
345600
|
+
serverUrl: opts.serverUrl,
|
|
344928
345601
|
baseUrl: run2.baseUrl,
|
|
344929
|
-
|
|
344930
|
-
|
|
344931
|
-
|
|
344932
|
-
|
|
344933
|
-
|
|
344934
|
-
|
|
345602
|
+
testVariables: run2.testVariables,
|
|
345603
|
+
authToken: opts.authToken
|
|
345604
|
+
}, run2.runId) : void 0;
|
|
345605
|
+
const releaseAuthLock2 = authPaths2 ? await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths2.lockKey, authPaths2.lockPath) : void 0;
|
|
345606
|
+
try {
|
|
345607
|
+
if (authPaths2 && (0, import_runner_core12.shouldSkipWarmAuthSetup)(
|
|
345608
|
+
run2.authForceRun,
|
|
345609
|
+
sharedAuthEnabled,
|
|
345610
|
+
!!(0, import_runner_core12.getValidLocalAuthState)(authPaths2.cachePath)
|
|
345611
|
+
)) {
|
|
345612
|
+
const duration3 = Date.now() - startTime;
|
|
345613
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345614
|
+
status: "PASSED",
|
|
345615
|
+
logs: "Auth setup skipped: the credential-bound local session is already warm.",
|
|
345616
|
+
duration: duration3
|
|
345617
|
+
});
|
|
345618
|
+
log.ok(`Auth setup skipped (${duration3}ms) - local session already warm`);
|
|
345619
|
+
return;
|
|
344935
345620
|
}
|
|
344936
|
-
|
|
344937
|
-
|
|
344938
|
-
|
|
344939
|
-
|
|
344940
|
-
|
|
344941
|
-
|
|
344942
|
-
|
|
344943
|
-
|
|
344944
|
-
|
|
344945
|
-
|
|
344946
|
-
|
|
344947
|
-
|
|
344948
|
-
|
|
345621
|
+
log.info(run2.authVerify ? "Auth verification: replaying the current login recipe" : sharedAuthEnabled ? "Auth setup: capturing a credential-bound local session" : "Auth setup: running login with shared session reuse disabled");
|
|
345622
|
+
const capturePath = sharedAuthEnabled && !run2.authVerify ? authPaths2?.capturePath : void 0;
|
|
345623
|
+
const result2 = await runNativeWithLiveBrowser({
|
|
345624
|
+
playwrightCode: run2.playwrightCode,
|
|
345625
|
+
baseUrl: run2.baseUrl,
|
|
345626
|
+
testName: "auth-setup",
|
|
345627
|
+
testVariables: run2.testVariables,
|
|
345628
|
+
authStateCapturePath: capturePath,
|
|
345629
|
+
validateContext: callbackContext
|
|
345630
|
+
});
|
|
345631
|
+
const captured = !!capturePath && !!authPaths2 && result2.passed && !result2.suspiciousPass && (0, import_runner_core12.promoteLocalAuthStateCapture)(
|
|
345632
|
+
capturePath,
|
|
345633
|
+
authPaths2.cachePath,
|
|
345634
|
+
run2.authForceRun === true,
|
|
345635
|
+
releaseAuthLock2
|
|
345636
|
+
);
|
|
345637
|
+
const duration2 = Date.now() - startTime;
|
|
345638
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345639
|
+
status: result2.passed ? "PASSED" : "FAILED",
|
|
345640
|
+
apiCalls: result2.apiCalls,
|
|
345641
|
+
logs: result2.logs,
|
|
345642
|
+
error: result2.error,
|
|
345643
|
+
duration: duration2
|
|
345644
|
+
});
|
|
345645
|
+
log[result2.passed ? "ok" : "fail"](
|
|
345646
|
+
`Auth setup ${result2.passed ? "PASSED" : "FAILED"} (${duration2}ms)${captured ? " - session cached" : ""}${result2.error ? ` - ${result2.error}` : ""}`
|
|
345647
|
+
);
|
|
345648
|
+
return;
|
|
345649
|
+
} finally {
|
|
345650
|
+
if (authPaths2?.capturePath) (0, import_runner_core12.discardLocalAuthStateCapture)(authPaths2.capturePath);
|
|
345651
|
+
releaseAuthLock2?.();
|
|
345652
|
+
}
|
|
344949
345653
|
}
|
|
344950
345654
|
if (runMode === "batch-heal") {
|
|
344951
345655
|
const tests = run2.batchHealTests ?? [];
|
|
@@ -344964,7 +345668,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344964
345668
|
const batchResult = await (0, import_runner_core10.executeGrokPerBatchHeal)(tests, run2.baseUrl, {
|
|
344965
345669
|
testVariables: run2.testVariables,
|
|
344966
345670
|
runNativeTest: runNativeWithLiveBrowser,
|
|
344967
|
-
validateContext:
|
|
345671
|
+
validateContext: callbackContext,
|
|
344968
345672
|
onLog: healLogStream.onLog
|
|
344969
345673
|
});
|
|
344970
345674
|
await healLogStream.flushNow();
|
|
@@ -345000,6 +345704,15 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
345000
345704
|
);
|
|
345001
345705
|
const describeKind = isSerialDescribe ? "serial" : "non-serial";
|
|
345002
345706
|
log.info(`API test heal - ${subTestCount} sub-test(s) [${describeKind}], ${failingSubTestCount} budgeted, ${MAX_API_HEAL_ATTEMPTS} attempts`);
|
|
345707
|
+
const apiHealAuthPaths = shouldUseAuthState(run2.tags, run2.playwrightCode) ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
345708
|
+
projectId: run2.projectId || opts.projectId,
|
|
345709
|
+
serverUrl: opts.serverUrl,
|
|
345710
|
+
baseUrl: run2.baseUrl,
|
|
345711
|
+
testVariables: run2.testVariables,
|
|
345712
|
+
authToken: opts.authToken
|
|
345713
|
+
}, run2.runId) : void 0;
|
|
345714
|
+
const apiHealStorageStatePath = apiHealAuthPaths ? (0, import_runner_core12.getValidLocalAuthState)(apiHealAuthPaths.cachePath) : void 0;
|
|
345715
|
+
if (apiHealStorageStatePath) log.info("API heal verification is using saved auth state");
|
|
345003
345716
|
const stickyAppBugs = /* @__PURE__ */ new Map();
|
|
345004
345717
|
const lastChangeByTitle = /* @__PURE__ */ new Map();
|
|
345005
345718
|
let currentError = run2.lastFailureError ?? "Test failed";
|
|
@@ -345065,7 +345778,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
345065
345778
|
playwrightCode: lastHealedCode,
|
|
345066
345779
|
baseUrl: run2.baseUrl,
|
|
345067
345780
|
testVariables: run2.testVariables,
|
|
345068
|
-
|
|
345781
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345782
|
+
validateContext: callbackContext
|
|
345069
345783
|
});
|
|
345070
345784
|
if (finalVerifyResult.passed) {
|
|
345071
345785
|
log.ok(` Attempt ${attempt} terminal verification PASSED (${bugCount} app_bug, ${passCount} pass)`);
|
|
@@ -345085,7 +345799,9 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345085
345799
|
finalVerifyResult = await runNativeWithLiveBrowser({
|
|
345086
345800
|
playwrightCode: lastHealedCode,
|
|
345087
345801
|
baseUrl: run2.baseUrl,
|
|
345088
|
-
testVariables: run2.testVariables
|
|
345802
|
+
testVariables: run2.testVariables,
|
|
345803
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345804
|
+
validateContext: callbackContext
|
|
345089
345805
|
});
|
|
345090
345806
|
const stepResults = finalVerifyResult.stepResults ?? [];
|
|
345091
345807
|
const nonAppBugFails = stepResults.filter(
|
|
@@ -345135,7 +345851,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345135
345851
|
playwrightCode: lastHealedCode,
|
|
345136
345852
|
baseUrl: run2.baseUrl,
|
|
345137
345853
|
testVariables: run2.testVariables,
|
|
345138
|
-
|
|
345854
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345855
|
+
validateContext: callbackContext
|
|
345139
345856
|
});
|
|
345140
345857
|
if (finalVerifyResult.passed) {
|
|
345141
345858
|
log.ok(` Attempt ${attempt} verification PASSED`);
|
|
@@ -345188,7 +345905,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345188
345905
|
if (classifyErrorMessage(composedError ?? null) === "config_issue") {
|
|
345189
345906
|
apiHealStatus = "ERROR";
|
|
345190
345907
|
} else {
|
|
345191
|
-
const rlDetection = (0,
|
|
345908
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
345192
345909
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
345193
345910
|
tags: run2.tags ?? [],
|
|
345194
345911
|
error: composedError ?? finalVerifyResult?.error ?? null,
|
|
@@ -345285,8 +346002,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345285
346002
|
lastHealAttempt: run2.lastHealAttempt ?? null,
|
|
345286
346003
|
lastHealedCode: run2.lastHealedCode ?? null,
|
|
345287
346004
|
failureScreenshot: run2.failureScreenshot ?? null,
|
|
345288
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
|
|
345289
|
-
validateContext:
|
|
346005
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
|
|
346006
|
+
validateContext: callbackContext,
|
|
345290
346007
|
liveBrowserHandle,
|
|
345291
346008
|
runNativeTest: runNativeWithLiveBrowser
|
|
345292
346009
|
};
|
|
@@ -345328,7 +346045,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345328
346045
|
let healStatus = result2.passed ? "PASSED" : isConfigIssue ? "ERROR" : "FAILED";
|
|
345329
346046
|
let healError = result2.error;
|
|
345330
346047
|
if (!result2.passed && !isConfigIssue) {
|
|
345331
|
-
const rlDetection = (0,
|
|
346048
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
345332
346049
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
345333
346050
|
tags: run2.tags ?? [],
|
|
345334
346051
|
error: result2.error,
|
|
@@ -345399,7 +346116,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345399
346116
|
// Allow generated `persistCredential(...)` calls inside the suite to
|
|
345400
346117
|
// POST back to the server with the runner's bearer token.
|
|
345401
346118
|
validateServerUrl: opts.serverUrl,
|
|
345402
|
-
validateRunnerAuthHeader: headers.Authorization
|
|
346119
|
+
validateRunnerAuthHeader: headers.Authorization,
|
|
346120
|
+
claimEpoch: run2.claimEpoch
|
|
345403
346121
|
});
|
|
345404
346122
|
const overall = result2.error && result2.testResults.length === 0 ? "ERROR" : result2.passed ? "PASSED" : "FAILED";
|
|
345405
346123
|
const rr = await fetch(`${opts.serverUrl}/api/runner/runs/${run2.runId}/regression-result`, {
|
|
@@ -345454,7 +346172,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345454
346172
|
playwrightCode: healedCode,
|
|
345455
346173
|
baseUrl: run2.baseUrl,
|
|
345456
346174
|
testVariables: run2.testVariables,
|
|
345457
|
-
validateContext:
|
|
346175
|
+
validateContext: callbackContext
|
|
345458
346176
|
});
|
|
345459
346177
|
results.push({
|
|
345460
346178
|
testCaseId: failedTest.testCaseId,
|
|
@@ -345517,8 +346235,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345517
346235
|
onAICall: healAudit,
|
|
345518
346236
|
modelOverride: run2.healerModel,
|
|
345519
346237
|
consecutiveFails: failedTest.consecutiveFails,
|
|
345520
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
|
|
345521
|
-
validateContext:
|
|
346238
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
|
|
346239
|
+
validateContext: callbackContext,
|
|
345522
346240
|
liveBrowserHandle,
|
|
345523
346241
|
runNativeTest: runNativeWithLiveBrowser
|
|
345524
346242
|
};
|
|
@@ -345723,6 +346441,13 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345723
346441
|
}
|
|
345724
346442
|
return;
|
|
345725
346443
|
}
|
|
346444
|
+
ctx.onCredentialUpdate = async (input) => postCredentialRotation(
|
|
346445
|
+
opts.serverUrl,
|
|
346446
|
+
headers,
|
|
346447
|
+
run2.runId,
|
|
346448
|
+
{ ...input, actor: "ai-explorer" },
|
|
346449
|
+
run2.claimEpoch
|
|
346450
|
+
);
|
|
345726
346451
|
log.info(`Discovery (${ctx.mode}) on ${ctx.baseUrl}`);
|
|
345727
346452
|
const { push: pushAudit, drain: drainAudit } = createAuditFlusher(
|
|
345728
346453
|
`${opts.serverUrl}/api/runner/runs/${run2.runId}/ai-audit-batch`,
|
|
@@ -346425,7 +347150,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346425
347150
|
featureDescription: ctx.featureDescription,
|
|
346426
347151
|
featurePages: ctx.featurePages,
|
|
346427
347152
|
appBrief: ctx.appBrief,
|
|
346428
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" })
|
|
347153
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" }, run2.claimEpoch)
|
|
346429
347154
|
});
|
|
346430
347155
|
if (logBuffer.length > 0) {
|
|
346431
347156
|
postCaptureLog(opts.serverUrl, headers, run2.runId, ctx.jobId, truncateTextTail(logBuffer.join("\n"), DISCOVERY_LOG_POST_MAX_CHARS, "capture log"));
|
|
@@ -346670,7 +347395,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346670
347395
|
}
|
|
346671
347396
|
return verify;
|
|
346672
347397
|
};
|
|
346673
|
-
return await (0,
|
|
347398
|
+
return await (0, import_runner_core14.executeMobileLiveHeal)({
|
|
346674
347399
|
steps: a.steps,
|
|
346675
347400
|
target: a.target,
|
|
346676
347401
|
testVariables: a.testVariables,
|
|
@@ -346687,7 +347412,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346687
347412
|
}
|
|
346688
347413
|
};
|
|
346689
347414
|
log.info("\u{1FA7A} Mobile AI heal \u2014 Phase 1 (script) \u2192 Phase 2 (live device, lazy)...");
|
|
346690
|
-
const heal = await (0,
|
|
347415
|
+
const heal = await (0, import_runner_core14.executeMobileTestWithHealing)(
|
|
346691
347416
|
mobileSteps,
|
|
346692
347417
|
healTarget,
|
|
346693
347418
|
{
|
|
@@ -346700,7 +347425,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346700
347425
|
healerOptions
|
|
346701
347426
|
);
|
|
346702
347427
|
const finalRes = nativeVerify.last;
|
|
346703
|
-
const outcome = (0,
|
|
347428
|
+
const outcome = (0, import_runner_core14.computeMobileHealOutcome)({
|
|
346704
347429
|
heal,
|
|
346705
347430
|
finalError: finalRes?.error ?? null,
|
|
346706
347431
|
reproduceError,
|
|
@@ -346846,26 +347571,77 @@ ${result2.logs}`);
|
|
|
346846
347571
|
}
|
|
346847
347572
|
return;
|
|
346848
347573
|
}
|
|
346849
|
-
const
|
|
346850
|
-
|
|
346851
|
-
|
|
346852
|
-
|
|
346853
|
-
|
|
346854
|
-
|
|
347574
|
+
const projectId = run2.projectId || opts.projectId;
|
|
347575
|
+
const authStateExpected = shouldUseAuthState(run2.tags, run2.playwrightCode);
|
|
347576
|
+
const captureAuth = shouldCaptureAuthState(run2.tags, run2.playwrightCode);
|
|
347577
|
+
const authPaths = authStateExpected || captureAuth ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
347578
|
+
projectId,
|
|
347579
|
+
serverUrl: opts.serverUrl,
|
|
346855
347580
|
baseUrl: run2.baseUrl,
|
|
346856
347581
|
testVariables: run2.testVariables,
|
|
346857
|
-
|
|
346858
|
-
|
|
346859
|
-
|
|
346860
|
-
|
|
346861
|
-
|
|
347582
|
+
authToken: opts.authToken
|
|
347583
|
+
}, run2.runId) : void 0;
|
|
347584
|
+
let releaseAuthLock;
|
|
347585
|
+
let storageStatePath;
|
|
347586
|
+
if (authPaths && authStateExpected) {
|
|
347587
|
+
releaseAuthLock = await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
|
|
347588
|
+
storageStatePath = (0, import_runner_core12.getValidLocalAuthState)(authPaths.cachePath);
|
|
347589
|
+
if (storageStatePath || !captureAuth) {
|
|
347590
|
+
releaseAuthLock();
|
|
347591
|
+
releaseAuthLock = void 0;
|
|
347592
|
+
}
|
|
347593
|
+
if (!storageStatePath && !captureAuth && sharedAuthInjectEnabled()) {
|
|
347594
|
+
log.info(`Waiting for the auth producer to warm project ${projectId}`);
|
|
347595
|
+
storageStatePath = await (0, import_runner_core12.waitForLocalAuthStateProducer)(
|
|
347596
|
+
authPaths.cachePath,
|
|
347597
|
+
authPaths.lockKey,
|
|
347598
|
+
2e3,
|
|
347599
|
+
void 0,
|
|
347600
|
+
100,
|
|
347601
|
+
authPaths.lockPath
|
|
347602
|
+
);
|
|
346862
347603
|
}
|
|
346863
|
-
}
|
|
347604
|
+
}
|
|
347605
|
+
if (storageStatePath) {
|
|
347606
|
+
log.info(`Using auth state for project ${projectId}`);
|
|
347607
|
+
}
|
|
347608
|
+
const runAuthPath = captureAuth ? authPaths?.capturePath : void 0;
|
|
347609
|
+
const result = await (async () => {
|
|
347610
|
+
try {
|
|
347611
|
+
return await runNativeWithLiveBrowser({
|
|
347612
|
+
playwrightCode: run2.playwrightCode,
|
|
347613
|
+
baseUrl: run2.baseUrl,
|
|
347614
|
+
testVariables: run2.testVariables,
|
|
347615
|
+
authStateCapturePath: runAuthPath,
|
|
347616
|
+
storageStatePath,
|
|
347617
|
+
validateContext: callbackContext
|
|
347618
|
+
});
|
|
347619
|
+
} finally {
|
|
347620
|
+
if (runAuthPath && authPaths) {
|
|
347621
|
+
if ((0, import_runner_core12.getValidLocalAuthState)(runAuthPath)) {
|
|
347622
|
+
const releasePromotionLock = releaseAuthLock ? void 0 : await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
|
|
347623
|
+
try {
|
|
347624
|
+
const captured = (0, import_runner_core12.promoteLocalAuthStateCapture)(
|
|
347625
|
+
runAuthPath,
|
|
347626
|
+
authPaths.cachePath,
|
|
347627
|
+
!!storageStatePath,
|
|
347628
|
+
releaseAuthLock ?? releasePromotionLock
|
|
347629
|
+
);
|
|
347630
|
+
if (captured) log.info(`Captured auth state for project ${projectId}`);
|
|
347631
|
+
} finally {
|
|
347632
|
+
releasePromotionLock?.();
|
|
347633
|
+
}
|
|
347634
|
+
}
|
|
347635
|
+
(0, import_runner_core12.discardLocalAuthStateCapture)(runAuthPath);
|
|
347636
|
+
}
|
|
347637
|
+
releaseAuthLock?.();
|
|
347638
|
+
}
|
|
347639
|
+
})();
|
|
346864
347640
|
const duration = Date.now() - startTime;
|
|
346865
347641
|
let runStatus = result.passed ? "PASSED" : "FAILED";
|
|
346866
347642
|
let runError = result.error;
|
|
346867
347643
|
if (!result.passed) {
|
|
346868
|
-
const rlDetection = (0,
|
|
347644
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
346869
347645
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
346870
347646
|
tags: run2.tags ?? [],
|
|
346871
347647
|
error: result.error,
|
|
@@ -346926,6 +347702,12 @@ async function startRunner(opts) {
|
|
|
346926
347702
|
process.env.AUTH_TOKEN = authToken;
|
|
346927
347703
|
process.env.RUNNER_TOKEN = authToken;
|
|
346928
347704
|
if (projectId) process.env.PROJECT_ID = projectId;
|
|
347705
|
+
(0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir());
|
|
347706
|
+
const authCleanupTimer = setInterval(
|
|
347707
|
+
() => (0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir()),
|
|
347708
|
+
import_runner_core12.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS
|
|
347709
|
+
);
|
|
347710
|
+
authCleanupTimer.unref?.();
|
|
346929
347711
|
const headers = buildHeaders(authToken, projectId);
|
|
346930
347712
|
console.log("");
|
|
346931
347713
|
console.log(`${C2.bold}${C2.cyan}validate.qa Local Runner${C2.reset}`);
|
|
@@ -347034,7 +347816,7 @@ async function startRunner(opts) {
|
|
|
347034
347816
|
capture: envInt("MAX_CAPTURE", DEFAULT_TYPE_LIMITS.capture),
|
|
347035
347817
|
run: envInt("MAX_RUN", DEFAULT_TYPE_LIMITS.run)
|
|
347036
347818
|
};
|
|
347037
|
-
const RESOURCE_GOVERNOR_OPTIONS = (0,
|
|
347819
|
+
const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core13.createResourceGovernorOptions)({
|
|
347038
347820
|
isCloud: IS_CLOUD,
|
|
347039
347821
|
maxConcurrent: MAX_CONCURRENT,
|
|
347040
347822
|
typeLimits: TYPE_LIMITS
|
|
@@ -347202,8 +347984,8 @@ async function startRunner(opts) {
|
|
|
347202
347984
|
android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
|
|
347203
347985
|
} : { web: true, ios: false, android: false };
|
|
347204
347986
|
const bridge = enableMobile ? getBridgeState() : void 0;
|
|
347205
|
-
const resourceStatus = (0,
|
|
347206
|
-
const processInventory = await (0,
|
|
347987
|
+
const resourceStatus = (0, import_runner_core13.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
|
|
347988
|
+
const processInventory = await (0, import_runner_core13.getBrowserProcessInventory)();
|
|
347207
347989
|
lastInventoryPids = new Set(processInventory.processes.map((p) => p.pid));
|
|
347208
347990
|
lastInventoryKinds.clear();
|
|
347209
347991
|
for (const p of processInventory.processes) lastInventoryKinds.set(p.pid, p.kind);
|
|
@@ -347233,7 +348015,7 @@ async function startRunner(opts) {
|
|
|
347233
348015
|
resourceSnapshot: resourceStatus.snapshot,
|
|
347234
348016
|
resourceGovernor: resourceStatus.governor,
|
|
347235
348017
|
processInventory,
|
|
347236
|
-
aiQueue: (0,
|
|
348018
|
+
aiQueue: (0, import_runner_core13.getRunnerAIQueueStats)(),
|
|
347237
348019
|
liveBrowsers: import_runner_core11.liveBrowserRegistry.snapshotForHeartbeat(),
|
|
347238
348020
|
// Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
|
|
347239
348021
|
// Cached & cheap — see getCachedDoctor above.
|
|
@@ -347344,7 +348126,7 @@ async function startRunner(opts) {
|
|
|
347344
348126
|
let pidsWatchdogWarned = false;
|
|
347345
348127
|
const pidsWatchdog = setInterval(() => {
|
|
347346
348128
|
if (shuttingDown) return;
|
|
347347
|
-
const snapshot = (0,
|
|
348129
|
+
const snapshot = (0, import_runner_core13.getResourceSnapshot)();
|
|
347348
348130
|
const pct = snapshot.pidsPct;
|
|
347349
348131
|
if (pct == null || !Number.isFinite(pct)) return;
|
|
347350
348132
|
if (pct >= PIDS_RECYCLE_PCT) {
|
|
@@ -347384,7 +348166,7 @@ async function startRunner(opts) {
|
|
|
347384
348166
|
log.dim(` Deferred ${run2.runId} (project ${runProject} at capacity ${getActiveForProject(runProject)}/${projectLimit})`);
|
|
347385
348167
|
continue;
|
|
347386
348168
|
}
|
|
347387
|
-
const admission = (0,
|
|
348169
|
+
const admission = (0, import_runner_core13.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
|
|
347388
348170
|
mode: runMode,
|
|
347389
348171
|
activeRuns,
|
|
347390
348172
|
activeByType: Object.fromEntries(activeByType),
|
|
@@ -347462,9 +348244,9 @@ async function startRunner(opts) {
|
|
|
347462
348244
|
leasedDeviceId = freeDeviceId;
|
|
347463
348245
|
leasedDeviceIds.set(freeDeviceId, run2.runId);
|
|
347464
348246
|
}
|
|
347465
|
-
let
|
|
348247
|
+
let claimEpoch = false;
|
|
347466
348248
|
try {
|
|
347467
|
-
|
|
348249
|
+
claimEpoch = await claimRun(serverUrl, headers, run2.runId);
|
|
347468
348250
|
} catch (err) {
|
|
347469
348251
|
if (leasedDeviceId) {
|
|
347470
348252
|
leasedDeviceIds.delete(leasedDeviceId);
|
|
@@ -347472,7 +348254,7 @@ async function startRunner(opts) {
|
|
|
347472
348254
|
}
|
|
347473
348255
|
throw err;
|
|
347474
348256
|
}
|
|
347475
|
-
if (
|
|
348257
|
+
if (claimEpoch === false) {
|
|
347476
348258
|
log.dim(` Skipped ${run2.runId} (already claimed)`);
|
|
347477
348259
|
if (leasedDeviceId) {
|
|
347478
348260
|
leasedDeviceIds.delete(leasedDeviceId);
|
|
@@ -347480,6 +348262,7 @@ async function startRunner(opts) {
|
|
|
347480
348262
|
}
|
|
347481
348263
|
continue;
|
|
347482
348264
|
}
|
|
348265
|
+
run2.claimEpoch = claimEpoch;
|
|
347483
348266
|
activeRuns++;
|
|
347484
348267
|
incrementType(runMode);
|
|
347485
348268
|
incrementProject(runProject);
|