@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.mjs
CHANGED
|
@@ -1761,7 +1761,18 @@ var init_constants = __esm({
|
|
|
1761
1761
|
"MINIMAX_API_KEY",
|
|
1762
1762
|
"ANTHROPIC_API_KEY",
|
|
1763
1763
|
"NVIDIA_API_KEY",
|
|
1764
|
-
"DEEPSEEK_API_KEY"
|
|
1764
|
+
"DEEPSEEK_API_KEY",
|
|
1765
|
+
// Run-scoped callback capabilities. The parent runner injects these after
|
|
1766
|
+
// sanitization; customer variables must never impersonate the control plane.
|
|
1767
|
+
"AUTH_FILE",
|
|
1768
|
+
"SHARED_AUTH_INJECT",
|
|
1769
|
+
"VALIDATE_RUN_ID",
|
|
1770
|
+
"VALIDATE_CLAIM_EPOCH",
|
|
1771
|
+
"VALIDATE_AUTH_CONFIG_HASH",
|
|
1772
|
+
"VALIDATE_TASK_ARN",
|
|
1773
|
+
"VALIDATE_RUNNER_AUTH",
|
|
1774
|
+
"VALIDATE_SERVER_URL",
|
|
1775
|
+
"VALIDATE_LIVE_BROWSER_STATE_PATH"
|
|
1765
1776
|
]);
|
|
1766
1777
|
SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
1767
1778
|
// Runtime essentials - Node.js and Playwright need these to function
|
|
@@ -5029,6 +5040,16 @@ var init_voice_config = __esm({
|
|
|
5029
5040
|
}
|
|
5030
5041
|
});
|
|
5031
5042
|
|
|
5043
|
+
// ../shared/dist/feature-flags.js
|
|
5044
|
+
function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
|
|
5045
|
+
return value?.trim().toLowerCase() !== "false";
|
|
5046
|
+
}
|
|
5047
|
+
var init_feature_flags = __esm({
|
|
5048
|
+
"../shared/dist/feature-flags.js"() {
|
|
5049
|
+
"use strict";
|
|
5050
|
+
}
|
|
5051
|
+
});
|
|
5052
|
+
|
|
5032
5053
|
// ../shared/dist/index.js
|
|
5033
5054
|
var dist_exports = {};
|
|
5034
5055
|
__export(dist_exports, {
|
|
@@ -5152,6 +5173,7 @@ __export(dist_exports, {
|
|
|
5152
5173
|
isRunnableStep: () => isRunnableStep,
|
|
5153
5174
|
isSafeDeepLink: () => isSafeDeepLink,
|
|
5154
5175
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
5176
|
+
isSharedAuthInjectEnabled: () => isSharedAuthInjectEnabled,
|
|
5155
5177
|
isSttModelForProvider: () => isSttModelForProvider,
|
|
5156
5178
|
isTestReadyForPlatform: () => isTestReadyForPlatform,
|
|
5157
5179
|
isTrialExpired: () => isTrialExpired,
|
|
@@ -5218,6 +5240,7 @@ var init_dist = __esm({
|
|
|
5218
5240
|
init_test_counting();
|
|
5219
5241
|
init_origin_domain();
|
|
5220
5242
|
init_voice_config();
|
|
5243
|
+
init_feature_flags();
|
|
5221
5244
|
}
|
|
5222
5245
|
});
|
|
5223
5246
|
|
|
@@ -10681,7 +10704,7 @@ var require_transport = __commonJS({
|
|
|
10681
10704
|
"../../node_modules/pino/lib/transport.js"(exports, module) {
|
|
10682
10705
|
"use strict";
|
|
10683
10706
|
var { createRequire } = __require("module");
|
|
10684
|
-
var { existsSync:
|
|
10707
|
+
var { existsSync: existsSync3 } = __require("fs");
|
|
10685
10708
|
var getCallers = require_caller();
|
|
10686
10709
|
var { join: join7, isAbsolute, sep } = __require("path");
|
|
10687
10710
|
var { fileURLToPath } = __require("url");
|
|
@@ -10755,7 +10778,7 @@ var require_transport = __commonJS({
|
|
|
10755
10778
|
return false;
|
|
10756
10779
|
}
|
|
10757
10780
|
}
|
|
10758
|
-
return isAbsolute(path) && !
|
|
10781
|
+
return isAbsolute(path) && !existsSync3(path);
|
|
10759
10782
|
}
|
|
10760
10783
|
function stripQuotes(value) {
|
|
10761
10784
|
const first = value[0];
|
|
@@ -76170,6 +76193,126 @@ export const test = base.extend({
|
|
|
76170
76193
|
}
|
|
76171
76194
|
});
|
|
76172
76195
|
|
|
76196
|
+
// ../runner-core/dist/services/run-callback-proxy.js
|
|
76197
|
+
var require_run_callback_proxy = __commonJS({
|
|
76198
|
+
"../runner-core/dist/services/run-callback-proxy.js"(exports) {
|
|
76199
|
+
"use strict";
|
|
76200
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76201
|
+
exports.startRunCallbackProxy = startRunCallbackProxy;
|
|
76202
|
+
var node_crypto_1 = __require("crypto");
|
|
76203
|
+
var node_http_1 = __require("http");
|
|
76204
|
+
var MAX_CALLBACK_BODY_BYTES = 5 * 1024 * 1024;
|
|
76205
|
+
var CALLBACK_TIMEOUT_MS = 3e4;
|
|
76206
|
+
function sendJson(res, status, body) {
|
|
76207
|
+
const payload = JSON.stringify(body);
|
|
76208
|
+
res.writeHead(status, {
|
|
76209
|
+
"cache-control": "no-store",
|
|
76210
|
+
"content-type": "application/json",
|
|
76211
|
+
"content-length": Buffer.byteLength(payload)
|
|
76212
|
+
});
|
|
76213
|
+
res.end(payload);
|
|
76214
|
+
}
|
|
76215
|
+
async function readRequestBody(req) {
|
|
76216
|
+
const chunks = [];
|
|
76217
|
+
let total = 0;
|
|
76218
|
+
for await (const chunk of req) {
|
|
76219
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
76220
|
+
total += buffer.length;
|
|
76221
|
+
if (total > MAX_CALLBACK_BODY_BYTES)
|
|
76222
|
+
throw new Error("callback body too large");
|
|
76223
|
+
chunks.push(buffer);
|
|
76224
|
+
}
|
|
76225
|
+
return Buffer.concat(chunks);
|
|
76226
|
+
}
|
|
76227
|
+
async function startRunCallbackProxy(input) {
|
|
76228
|
+
const upstream = new URL(input.serverUrl);
|
|
76229
|
+
if (upstream.protocol !== "http:" && upstream.protocol !== "https:") {
|
|
76230
|
+
throw new Error("callback server URL must use http or https");
|
|
76231
|
+
}
|
|
76232
|
+
const runPath = `/api/runner/runs/${encodeURIComponent(input.runId)}`;
|
|
76233
|
+
const allowedPaths = /* @__PURE__ */ new Set([`${runPath}/credential`, `${runPath}/auth-state`]);
|
|
76234
|
+
const token = (0, node_crypto_1.randomBytes)(32).toString("base64url");
|
|
76235
|
+
const authHeader = `Bearer ${token}`;
|
|
76236
|
+
const server3 = (0, node_http_1.createServer)(async (req, res) => {
|
|
76237
|
+
try {
|
|
76238
|
+
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
76239
|
+
if (req.method !== "POST" || !allowedPaths.has(requestUrl.pathname) || requestUrl.search) {
|
|
76240
|
+
sendJson(res, 404, { error: "Callback route not available" });
|
|
76241
|
+
return;
|
|
76242
|
+
}
|
|
76243
|
+
if (req.headers.authorization !== authHeader) {
|
|
76244
|
+
sendJson(res, 401, { error: "Invalid callback capability" });
|
|
76245
|
+
return;
|
|
76246
|
+
}
|
|
76247
|
+
const body = await readRequestBody(req);
|
|
76248
|
+
let parsedBody;
|
|
76249
|
+
try {
|
|
76250
|
+
const parsed = JSON.parse(body.toString("utf8"));
|
|
76251
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
76252
|
+
throw new Error("invalid body");
|
|
76253
|
+
parsedBody = parsed;
|
|
76254
|
+
} catch {
|
|
76255
|
+
sendJson(res, 400, { error: "Callback body must be a JSON object" });
|
|
76256
|
+
return;
|
|
76257
|
+
}
|
|
76258
|
+
const upstreamBody = JSON.stringify({
|
|
76259
|
+
...parsedBody,
|
|
76260
|
+
...input.taskArn ? { taskArn: input.taskArn } : {},
|
|
76261
|
+
...input.claimEpoch !== void 0 ? { claimEpoch: input.claimEpoch } : {},
|
|
76262
|
+
...input.authConfigHash ? { authConfigHash: input.authConfigHash } : {}
|
|
76263
|
+
});
|
|
76264
|
+
const destination = new URL(requestUrl.pathname, upstream);
|
|
76265
|
+
const response = await fetch(destination, {
|
|
76266
|
+
method: "POST",
|
|
76267
|
+
headers: {
|
|
76268
|
+
Authorization: input.upstreamAuthHeader,
|
|
76269
|
+
"Content-Type": "application/json"
|
|
76270
|
+
},
|
|
76271
|
+
body: upstreamBody,
|
|
76272
|
+
redirect: "error",
|
|
76273
|
+
signal: AbortSignal.timeout(CALLBACK_TIMEOUT_MS)
|
|
76274
|
+
});
|
|
76275
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
76276
|
+
res.writeHead(response.status, {
|
|
76277
|
+
"cache-control": "no-store",
|
|
76278
|
+
"content-type": response.headers.get("content-type") ?? "application/json",
|
|
76279
|
+
"content-length": responseBody.length
|
|
76280
|
+
});
|
|
76281
|
+
res.end(responseBody);
|
|
76282
|
+
} catch (error2) {
|
|
76283
|
+
const message = error2 instanceof Error && error2.message === "callback body too large" ? error2.message : "Callback forwarding failed";
|
|
76284
|
+
sendJson(res, message === "callback body too large" ? 413 : 502, { error: message });
|
|
76285
|
+
}
|
|
76286
|
+
});
|
|
76287
|
+
server3.on("clientError", (_error, socket) => socket.destroy());
|
|
76288
|
+
await new Promise((resolve2, reject) => {
|
|
76289
|
+
const onError = (error2) => reject(error2);
|
|
76290
|
+
server3.once("error", onError);
|
|
76291
|
+
server3.listen(0, "127.0.0.1", () => {
|
|
76292
|
+
server3.off("error", onError);
|
|
76293
|
+
resolve2();
|
|
76294
|
+
});
|
|
76295
|
+
});
|
|
76296
|
+
const address = server3.address();
|
|
76297
|
+
if (!address) {
|
|
76298
|
+
server3.close();
|
|
76299
|
+
throw new Error("callback proxy failed to bind");
|
|
76300
|
+
}
|
|
76301
|
+
let closed = false;
|
|
76302
|
+
return {
|
|
76303
|
+
serverUrl: `http://127.0.0.1:${address.port}`,
|
|
76304
|
+
authHeader,
|
|
76305
|
+
close: async () => {
|
|
76306
|
+
if (closed)
|
|
76307
|
+
return;
|
|
76308
|
+
closed = true;
|
|
76309
|
+
await new Promise((resolve2) => server3.close(() => resolve2()));
|
|
76310
|
+
}
|
|
76311
|
+
};
|
|
76312
|
+
}
|
|
76313
|
+
}
|
|
76314
|
+
});
|
|
76315
|
+
|
|
76173
76316
|
// ../runner-core/dist/services/playwright-native.js
|
|
76174
76317
|
var require_playwright_native = __commonJS({
|
|
76175
76318
|
"../runner-core/dist/services/playwright-native.js"(exports) {
|
|
@@ -76214,6 +76357,7 @@ var require_playwright_native = __commonJS({
|
|
|
76214
76357
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76215
76358
|
exports.scrubHarRequestBody = exports.buildOriginScopedApiFixtures = exports.isAppOriginRequest = exports.resolveApiAuthHeaders = exports.parseApiAuthHeaders = void 0;
|
|
76216
76359
|
exports.executeTestViaNativePlaywright = executeTestViaNativePlaywright2;
|
|
76360
|
+
exports.buildNativePlaywrightChildEnv = buildNativePlaywrightChildEnv;
|
|
76217
76361
|
exports.parseJsonResults = parseJsonResults2;
|
|
76218
76362
|
exports.parseHarToApiSummary = parseHarToApiSummary;
|
|
76219
76363
|
var promises_1 = __require("fs/promises");
|
|
@@ -76225,6 +76369,7 @@ var require_playwright_native = __commonJS({
|
|
|
76225
76369
|
var api_call_redaction_js_1 = require_api_call_redaction();
|
|
76226
76370
|
var har_stream_js_1 = require_har_stream();
|
|
76227
76371
|
var api_auth_headers_js_1 = require_api_auth_headers();
|
|
76372
|
+
var run_callback_proxy_js_1 = require_run_callback_proxy();
|
|
76228
76373
|
var api_auth_headers_js_2 = require_api_auth_headers();
|
|
76229
76374
|
Object.defineProperty(exports, "parseApiAuthHeaders", { enumerable: true, get: function() {
|
|
76230
76375
|
return api_auth_headers_js_2.parseApiAuthHeaders;
|
|
@@ -76247,7 +76392,7 @@ var require_playwright_native = __commonJS({
|
|
|
76247
76392
|
var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
|
|
76248
76393
|
var PW_CLI = __require.resolve("@playwright/test/cli");
|
|
76249
76394
|
async function executeTestViaNativePlaywright2(options) {
|
|
76250
|
-
const { playwrightCode, baseUrl, testName, testVariables, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76395
|
+
const { playwrightCode, baseUrl, testName, testVariables, authStateCapturePath, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76251
76396
|
const logs2 = [];
|
|
76252
76397
|
const screenshots = [];
|
|
76253
76398
|
logs2.push(`\u{1F3AD} Native Playwright starting`);
|
|
@@ -76257,6 +76402,7 @@ var require_playwright_native = __commonJS({
|
|
|
76257
76402
|
logs2.push("\u26A0\uFE0F WARNING: baseUrl is empty - test will likely fail with navigation errors");
|
|
76258
76403
|
}
|
|
76259
76404
|
let runDir = null;
|
|
76405
|
+
let callbackProxy;
|
|
76260
76406
|
try {
|
|
76261
76407
|
runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
|
|
76262
76408
|
await (0, promises_1.mkdir)(runDir, { recursive: true });
|
|
@@ -76516,35 +76662,51 @@ module.exports = {
|
|
|
76516
76662
|
const configPath = (0, path_1.join)(runDir, "playwright.config.js");
|
|
76517
76663
|
await (0, promises_1.writeFile)(configPath, config);
|
|
76518
76664
|
const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
|
|
76519
|
-
const
|
|
76520
|
-
|
|
76665
|
+
const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
|
|
76666
|
+
const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
|
|
76667
|
+
if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
|
|
76668
|
+
try {
|
|
76669
|
+
callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
|
|
76670
|
+
runId: validateContext.runId,
|
|
76671
|
+
serverUrl: validateContext.serverUrl,
|
|
76672
|
+
upstreamAuthHeader: validateContext.authHeader,
|
|
76673
|
+
taskArn: process.env.VALIDATE_TASK_ARN,
|
|
76674
|
+
claimEpoch: callbackClaimEpoch,
|
|
76675
|
+
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
|
|
76676
|
+
});
|
|
76677
|
+
} catch (error3) {
|
|
76678
|
+
logs2.push(`Callback proxy unavailable: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
76679
|
+
}
|
|
76680
|
+
}
|
|
76681
|
+
const childTestVariables = { ...testVariables ?? {} };
|
|
76682
|
+
const trustedRuntimeEnv = {
|
|
76521
76683
|
...isolation.env,
|
|
76522
|
-
//
|
|
76523
|
-
//
|
|
76524
|
-
|
|
76525
|
-
|
|
76526
|
-
|
|
76527
|
-
|
|
76528
|
-
|
|
76684
|
+
// Generated callbacks receive only a random loopback capability. The global
|
|
76685
|
+
// runner bearer token and real control-plane URL stay in the parent process.
|
|
76686
|
+
...callbackProxy && validateContext?.runId ? {
|
|
76687
|
+
VALIDATE_RUN_ID: validateContext.runId,
|
|
76688
|
+
...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
|
|
76689
|
+
VALIDATE_SERVER_URL: callbackProxy.serverUrl,
|
|
76690
|
+
VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
|
|
76691
|
+
} : {},
|
|
76529
76692
|
// Owning Fargate taskArn (a per-task constant set once at task boot in
|
|
76530
76693
|
// execute-one.ts; absent on the local/Railway runner). Read from
|
|
76531
76694
|
// process.env rather than validateContext since it's process-wide, not
|
|
76532
76695
|
// per-run. Lets persistCredential() prove ownership to /credential.
|
|
76533
76696
|
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
|
|
76697
|
+
...authStateCapturePath ? { AUTH_FILE: authStateCapturePath } : {},
|
|
76534
76698
|
...liveBrowserStatePath ? { VALIDATE_LIVE_BROWSER_STATE_PATH: liveBrowserStatePath } : {},
|
|
76535
|
-
//
|
|
76536
|
-
//
|
|
76537
|
-
//
|
|
76538
|
-
// this forward the flag reads as undefined in the test and the early publish is dead
|
|
76539
|
-
// code. Forwarded (not allowlisted) so it stays a per-run toggle; absent → inert.
|
|
76699
|
+
// Preserve an explicit shared-auth kill-switch in the child. Unset defaults on in
|
|
76700
|
+
// both parent and generated __vqaLogin code; "false" must still cross this process
|
|
76701
|
+
// boundary to disable the early publish POST. It remains a per-run toggle.
|
|
76540
76702
|
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
|
|
76541
76703
|
};
|
|
76542
|
-
delete
|
|
76704
|
+
delete childTestVariables.API_AUTH_HEADERS;
|
|
76543
76705
|
logs2.push("\u{1F680} Spawning playwright test...");
|
|
76544
76706
|
const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
|
|
76545
76707
|
let playResult;
|
|
76546
76708
|
try {
|
|
76547
|
-
playResult = await runPlaywright(configPath,
|
|
76709
|
+
playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv);
|
|
76548
76710
|
} finally {
|
|
76549
76711
|
await stopLiveBrowserPolling?.();
|
|
76550
76712
|
}
|
|
@@ -76628,6 +76790,8 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76628
76790
|
error: msg
|
|
76629
76791
|
};
|
|
76630
76792
|
} finally {
|
|
76793
|
+
await callbackProxy?.close().catch(() => {
|
|
76794
|
+
});
|
|
76631
76795
|
if (runDir) {
|
|
76632
76796
|
await (0, promises_1.rm)(runDir, { recursive: true, force: true }).catch(() => {
|
|
76633
76797
|
});
|
|
@@ -76679,9 +76843,9 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76679
76843
|
stopped = true;
|
|
76680
76844
|
};
|
|
76681
76845
|
}
|
|
76682
|
-
function runPlaywright(configPath,
|
|
76846
|
+
function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
|
|
76683
76847
|
return new Promise((resolvePromise) => {
|
|
76684
|
-
const sandboxedEnv = (
|
|
76848
|
+
const sandboxedEnv = buildNativePlaywrightChildEnv(process.env, testVariables, trustedRuntimeEnv);
|
|
76685
76849
|
const proc = (0, spawn_with_tree_kill_js_1.spawnDetachedGroup)(process.execPath, [PW_CLI, "test", "--config", configPath], {
|
|
76686
76850
|
cwd: PACKAGE_DIR,
|
|
76687
76851
|
env: sandboxedEnv,
|
|
@@ -76724,6 +76888,12 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76724
76888
|
});
|
|
76725
76889
|
});
|
|
76726
76890
|
}
|
|
76891
|
+
function buildNativePlaywrightChildEnv(processEnv, testVariables, trustedRuntimeEnv) {
|
|
76892
|
+
return {
|
|
76893
|
+
...(0, shared_1.buildSandboxedEnv)(processEnv, testVariables),
|
|
76894
|
+
...trustedRuntimeEnv ?? {}
|
|
76895
|
+
};
|
|
76896
|
+
}
|
|
76727
76897
|
async function collectScreenshots(dir, screenshots) {
|
|
76728
76898
|
let entries;
|
|
76729
76899
|
try {
|
|
@@ -80445,10 +80615,12 @@ var require_auth_recipe = __commonJS({
|
|
|
80445
80615
|
exports.upsertAuthBlock = upsertAuthBlock;
|
|
80446
80616
|
exports.buildHealAuthStamp = buildHealAuthStamp;
|
|
80447
80617
|
var crypto_1 = __require("crypto");
|
|
80618
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
80448
80619
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
80449
|
-
exports.AUTH_MARKER_VERSION = "
|
|
80620
|
+
exports.AUTH_MARKER_VERSION = "v3";
|
|
80450
80621
|
var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
|
|
80451
80622
|
var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} <<<`;
|
|
80623
|
+
var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
|
|
80452
80624
|
var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
|
|
80453
80625
|
function computeRecipeHash(input) {
|
|
80454
80626
|
const canonical = JSON.stringify({
|
|
@@ -80526,16 +80698,18 @@ var require_auth_recipe = __commonJS({
|
|
|
80526
80698
|
if (opts?.warmGuard) {
|
|
80527
80699
|
const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
|
|
80528
80700
|
const loginRoute = recipe.route ?? recipe.preNavRoute;
|
|
80529
|
-
lines.push(`
|
|
80701
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
|
|
80702
|
+
lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
|
|
80530
80703
|
if (recipe.authProof.kind === "url") {
|
|
80531
80704
|
const pred = urlProofPredicateExpr(recipe.authProof.route);
|
|
80532
|
-
lines.push(`
|
|
80533
|
-
lines.push(`
|
|
80705
|
+
lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
|
|
80706
|
+
lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
|
|
80534
80707
|
} else if (loginRoute) {
|
|
80535
|
-
lines.push(`
|
|
80708
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
|
|
80536
80709
|
} else {
|
|
80537
|
-
lines.push(`
|
|
80710
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
|
|
80538
80711
|
}
|
|
80712
|
+
lines.push(` }`);
|
|
80539
80713
|
}
|
|
80540
80714
|
lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
|
|
80541
80715
|
for (const cmd of recipe.preNavCmds)
|
|
@@ -80548,11 +80722,11 @@ var require_auth_recipe = __commonJS({
|
|
|
80548
80722
|
lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
|
|
80549
80723
|
lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
|
|
80550
80724
|
if (opts?.warmGuard) {
|
|
80551
|
-
lines.push(` if (process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
80552
|
-
lines.push(` if (process.env.
|
|
80725
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
80726
|
+
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) {`);
|
|
80553
80727
|
lines.push(` try {`);
|
|
80554
80728
|
lines.push(` const __vqaState = await page.context().storageState();`);
|
|
80555
|
-
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 });`);
|
|
80729
|
+
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 });`);
|
|
80556
80730
|
lines.push(` } catch {}`);
|
|
80557
80731
|
lines.push(` }`);
|
|
80558
80732
|
}
|
|
@@ -80564,6 +80738,8 @@ var require_auth_recipe = __commonJS({
|
|
|
80564
80738
|
"AUTH_FILE",
|
|
80565
80739
|
"SHARED_AUTH_INJECT",
|
|
80566
80740
|
"VALIDATE_RUN_ID",
|
|
80741
|
+
"VALIDATE_CLAIM_EPOCH",
|
|
80742
|
+
"VALIDATE_AUTH_CONFIG_HASH",
|
|
80567
80743
|
"VALIDATE_TASK_ARN",
|
|
80568
80744
|
"VALIDATE_RUNNER_AUTH",
|
|
80569
80745
|
"VALIDATE_SERVER_URL"
|
|
@@ -80736,7 +80912,7 @@ ${tail}`;
|
|
|
80736
80912
|
let result = stripAuthRegions(code);
|
|
80737
80913
|
result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
|
|
80738
80914
|
result = ensureExpectImported(result);
|
|
80739
|
-
const warmGuard =
|
|
80915
|
+
const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
|
|
80740
80916
|
result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
|
|
80741
80917
|
result = ensureLoginCall(result);
|
|
80742
80918
|
const n = countAuthRegions(result);
|
|
@@ -85866,9 +86042,9 @@ ${testOpen}`;
|
|
|
85866
86042
|
basePath: options?.basePath
|
|
85867
86043
|
});
|
|
85868
86044
|
const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
|
|
85869
|
-
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process
|
|
85870
|
-
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]);
|
|
85871
|
-
const tags = authGuardStamped
|
|
86045
|
+
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);
|
|
86046
|
+
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]).filter((tag) => tag !== "@auth-guarded");
|
|
86047
|
+
const tags = authGuardStamped ? [...baseTags, "@auth-guarded"] : baseTags;
|
|
85872
86048
|
return {
|
|
85873
86049
|
name: String(test.name || "Unnamed test").slice(0, 200),
|
|
85874
86050
|
description: String(test.description || test.name || "").slice(0, 500),
|
|
@@ -99683,7 +99859,7 @@ ${lanes.join("\n")}
|
|
|
99683
99859
|
return process.memoryUsage().heapUsed;
|
|
99684
99860
|
},
|
|
99685
99861
|
getFileSize(path) {
|
|
99686
|
-
const stat =
|
|
99862
|
+
const stat = statSync(path);
|
|
99687
99863
|
if (stat == null ? void 0 : stat.isFile()) {
|
|
99688
99864
|
return stat.size;
|
|
99689
99865
|
}
|
|
@@ -99727,7 +99903,7 @@ ${lanes.join("\n")}
|
|
|
99727
99903
|
}
|
|
99728
99904
|
};
|
|
99729
99905
|
return nodeSystem;
|
|
99730
|
-
function
|
|
99906
|
+
function statSync(path) {
|
|
99731
99907
|
try {
|
|
99732
99908
|
return _fs.statSync(path, statSyncOptions);
|
|
99733
99909
|
} catch {
|
|
@@ -99786,7 +99962,7 @@ ${lanes.join("\n")}
|
|
|
99786
99962
|
activeSession.post("Profiler.stop", (err, { profile }) => {
|
|
99787
99963
|
var _a;
|
|
99788
99964
|
if (!err) {
|
|
99789
|
-
if ((_a =
|
|
99965
|
+
if ((_a = statSync(profilePath)) == null ? void 0 : _a.isDirectory()) {
|
|
99790
99966
|
profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
|
|
99791
99967
|
}
|
|
99792
99968
|
try {
|
|
@@ -99906,7 +100082,7 @@ ${lanes.join("\n")}
|
|
|
99906
100082
|
let stat;
|
|
99907
100083
|
if (typeof dirent === "string" || dirent.isSymbolicLink()) {
|
|
99908
100084
|
const name = combinePaths(path, entry);
|
|
99909
|
-
stat =
|
|
100085
|
+
stat = statSync(name);
|
|
99910
100086
|
if (!stat) {
|
|
99911
100087
|
continue;
|
|
99912
100088
|
}
|
|
@@ -99930,7 +100106,7 @@ ${lanes.join("\n")}
|
|
|
99930
100106
|
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
|
|
99931
100107
|
}
|
|
99932
100108
|
function fileSystemEntryExists(path, entryKind) {
|
|
99933
|
-
const stat =
|
|
100109
|
+
const stat = statSync(path);
|
|
99934
100110
|
if (!stat) {
|
|
99935
100111
|
return false;
|
|
99936
100112
|
}
|
|
@@ -99972,7 +100148,7 @@ ${lanes.join("\n")}
|
|
|
99972
100148
|
}
|
|
99973
100149
|
function getModifiedTime3(path) {
|
|
99974
100150
|
var _a;
|
|
99975
|
-
return (_a =
|
|
100151
|
+
return (_a = statSync(path)) == null ? void 0 : _a.mtime;
|
|
99976
100152
|
}
|
|
99977
100153
|
function setModifiedTime(path, time) {
|
|
99978
100154
|
try {
|
|
@@ -308483,6 +308659,7 @@ var require_regression_runner = __commonJS({
|
|
|
308483
308659
|
var chromium_isolation_js_1 = require_chromium_isolation();
|
|
308484
308660
|
var browser_config_js_1 = require_browser_config();
|
|
308485
308661
|
var spawn_with_tree_kill_js_1 = require_spawn_with_tree_kill();
|
|
308662
|
+
var run_callback_proxy_js_1 = require_run_callback_proxy();
|
|
308486
308663
|
var SUITE_SPAWN_TIMEOUT_MS = 85 * 60 * 1e3;
|
|
308487
308664
|
var PACKAGE_DIR = (0, path_1.resolve)(__dirname, "..", "..");
|
|
308488
308665
|
var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
|
|
@@ -308497,6 +308674,7 @@ var require_regression_runner = __commonJS({
|
|
|
308497
308674
|
const tarPath = (0, path_1.join)(RUNS_DIR, `regression-${runId}.tar.gz`);
|
|
308498
308675
|
const startTime = Date.now();
|
|
308499
308676
|
let logs2 = "";
|
|
308677
|
+
let callbackProxy;
|
|
308500
308678
|
try {
|
|
308501
308679
|
await (0, promises_1.mkdir)(extractDir, { recursive: true });
|
|
308502
308680
|
const tarballUrl = `https://api.github.com/repos/${repoOwner}/${repoName}/tarball/${repoBranch}`;
|
|
@@ -308526,19 +308704,40 @@ var require_regression_runner = __commonJS({
|
|
|
308526
308704
|
await (0, promises_1.writeFile)((0, path_1.join)(extractDir, ".env"), envContent, "utf-8");
|
|
308527
308705
|
const jsonResultsPath = (0, path_1.join)(extractDir, "test-results", "results.json");
|
|
308528
308706
|
const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(extractDir);
|
|
308707
|
+
const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
|
|
308708
|
+
const callbackClaimEpoch = Number.isSafeInteger(opts.claimEpoch) ? opts.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
|
|
308709
|
+
if (opts.validateServerUrl && opts.validateRunnerAuthHeader) {
|
|
308710
|
+
try {
|
|
308711
|
+
callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
|
|
308712
|
+
runId,
|
|
308713
|
+
serverUrl: opts.validateServerUrl,
|
|
308714
|
+
upstreamAuthHeader: opts.validateRunnerAuthHeader,
|
|
308715
|
+
taskArn: process.env.VALIDATE_TASK_ARN,
|
|
308716
|
+
claimEpoch: callbackClaimEpoch,
|
|
308717
|
+
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
|
|
308718
|
+
});
|
|
308719
|
+
} catch (error2) {
|
|
308720
|
+
logs2 += `Callback proxy unavailable: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
308721
|
+
`;
|
|
308722
|
+
}
|
|
308723
|
+
}
|
|
308529
308724
|
const runEnv = {
|
|
308530
308725
|
...(0, shared_1.buildSandboxedEnv)(process.env, testVariables),
|
|
308531
308726
|
...isolation.env,
|
|
308532
308727
|
CI: "true",
|
|
308533
308728
|
PLAYWRIGHT_JSON_OUTPUT_NAME: jsonResultsPath,
|
|
308534
|
-
//
|
|
308535
|
-
//
|
|
308536
|
-
|
|
308537
|
-
|
|
308538
|
-
|
|
308729
|
+
// The suite receives a random run-scoped loopback callback, never the
|
|
308730
|
+
// global runner bearer credential.
|
|
308731
|
+
...callbackProxy ? {
|
|
308732
|
+
VALIDATE_RUN_ID: runId,
|
|
308733
|
+
...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
|
|
308734
|
+
VALIDATE_SERVER_URL: callbackProxy.serverUrl,
|
|
308735
|
+
VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
|
|
308736
|
+
} : {},
|
|
308539
308737
|
// Owning Fargate taskArn (per-task constant; unset off-Fargate). Forwarded
|
|
308540
308738
|
// so persistCredential() can prove ownership to the /credential fence.
|
|
308541
|
-
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {}
|
|
308739
|
+
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
|
|
308740
|
+
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
|
|
308542
308741
|
};
|
|
308543
308742
|
logs2 += `Running playwright --project=${opts.suiteProject ?? "suite"}...
|
|
308544
308743
|
`;
|
|
@@ -308567,6 +308766,8 @@ var require_regression_runner = __commonJS({
|
|
|
308567
308766
|
error: String(err)
|
|
308568
308767
|
};
|
|
308569
308768
|
} finally {
|
|
308769
|
+
await callbackProxy?.close().catch(() => {
|
|
308770
|
+
});
|
|
308570
308771
|
(0, promises_1.rm)(extractDir, { recursive: true, force: true }).catch(() => {
|
|
308571
308772
|
});
|
|
308572
308773
|
await (0, browser_config_js_1.cleanupPlaywrightTempArtifacts)().catch(() => {
|
|
@@ -324810,6 +325011,455 @@ var require_resource_governor = __commonJS({
|
|
|
324810
325011
|
}
|
|
324811
325012
|
});
|
|
324812
325013
|
|
|
325014
|
+
// ../runner-core/dist/services/local-auth-state.js
|
|
325015
|
+
var require_local_auth_state = __commonJS({
|
|
325016
|
+
"../runner-core/dist/services/local-auth-state.js"(exports) {
|
|
325017
|
+
"use strict";
|
|
325018
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
325019
|
+
exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = exports.LOCAL_AUTH_STATE_LOCK_STALE_MS = exports.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = exports.LOCAL_AUTH_STATE_WAIT_MS = exports.LOCAL_AUTH_STATE_MAX_AGE_MS = void 0;
|
|
325020
|
+
exports.shouldSkipWarmAuthSetup = shouldSkipWarmAuthSetup2;
|
|
325021
|
+
exports.shouldSkipDisabledAutomaticAuthSetup = shouldSkipDisabledAutomaticAuthSetup2;
|
|
325022
|
+
exports.localAuthStateFingerprint = localAuthStateFingerprint;
|
|
325023
|
+
exports.ensurePrivateDirectory = ensurePrivateDirectory2;
|
|
325024
|
+
exports.isValidStorageState = isValidStorageState;
|
|
325025
|
+
exports.isValidStorageStateJson = isValidStorageStateJson;
|
|
325026
|
+
exports.isValidLocalAuthStateFile = isValidLocalAuthStateFile;
|
|
325027
|
+
exports.cleanupLocalAuthStateFiles = cleanupLocalAuthStateFiles;
|
|
325028
|
+
exports.cleanupExpiredLocalAuthStateFiles = cleanupExpiredLocalAuthStateFiles2;
|
|
325029
|
+
exports.localAuthStatePaths = localAuthStatePaths2;
|
|
325030
|
+
exports.getValidLocalAuthState = getValidLocalAuthState2;
|
|
325031
|
+
exports.waitForValidLocalAuthState = waitForValidLocalAuthState;
|
|
325032
|
+
exports.waitForLocalAuthStateProducer = waitForLocalAuthStateProducer2;
|
|
325033
|
+
exports.promoteLocalAuthStateCapture = promoteLocalAuthStateCapture2;
|
|
325034
|
+
exports.discardLocalAuthStateCapture = discardLocalAuthStateCapture2;
|
|
325035
|
+
exports.acquireLocalAuthStateLock = acquireLocalAuthStateLock2;
|
|
325036
|
+
exports.localAuthStateFilename = localAuthStateFilename;
|
|
325037
|
+
var node_fs_1 = __require("fs");
|
|
325038
|
+
var node_crypto_1 = __require("crypto");
|
|
325039
|
+
var node_path_1 = __require("path");
|
|
325040
|
+
var auth_recipe_js_1 = require_auth_recipe();
|
|
325041
|
+
exports.LOCAL_AUTH_STATE_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
325042
|
+
exports.LOCAL_AUTH_STATE_WAIT_MS = 3 * 60 * 1e3;
|
|
325043
|
+
exports.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = 15 * 60 * 1e3;
|
|
325044
|
+
exports.LOCAL_AUTH_STATE_LOCK_STALE_MS = 2 * 60 * 1e3;
|
|
325045
|
+
exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = 30 * 60 * 1e3;
|
|
325046
|
+
function shouldSkipWarmAuthSetup2(authForceRun, sharedAuthEnabled, cacheWarm) {
|
|
325047
|
+
return sharedAuthEnabled && authForceRun !== true && cacheWarm;
|
|
325048
|
+
}
|
|
325049
|
+
function shouldSkipDisabledAutomaticAuthSetup2(authForceRun, authVerify, sharedAuthEnabled) {
|
|
325050
|
+
return !sharedAuthEnabled && authForceRun !== true && authVerify !== true;
|
|
325051
|
+
}
|
|
325052
|
+
function normalizeOrigin(value) {
|
|
325053
|
+
try {
|
|
325054
|
+
return new URL(value).origin.toLowerCase();
|
|
325055
|
+
} catch {
|
|
325056
|
+
return value.trim().toLowerCase();
|
|
325057
|
+
}
|
|
325058
|
+
}
|
|
325059
|
+
function normalizeAppUrl(value) {
|
|
325060
|
+
try {
|
|
325061
|
+
const url = new URL(value);
|
|
325062
|
+
url.hash = "";
|
|
325063
|
+
return `${url.origin.toLowerCase()}${url.pathname}${url.search}`;
|
|
325064
|
+
} catch {
|
|
325065
|
+
return value.trim();
|
|
325066
|
+
}
|
|
325067
|
+
}
|
|
325068
|
+
function safeSegment(value, fallback) {
|
|
325069
|
+
const safe = value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 120);
|
|
325070
|
+
return safe || fallback;
|
|
325071
|
+
}
|
|
325072
|
+
function stableVariables(testVariables) {
|
|
325073
|
+
return Object.entries(testVariables).filter(([key]) => !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(key)).sort(([a], [b]) => a.localeCompare(b));
|
|
325074
|
+
}
|
|
325075
|
+
function localAuthStateFingerprint(input) {
|
|
325076
|
+
const payload = JSON.stringify({
|
|
325077
|
+
version: 1,
|
|
325078
|
+
projectId: input.projectId,
|
|
325079
|
+
serverOrigin: normalizeOrigin(input.serverUrl),
|
|
325080
|
+
appUrl: normalizeAppUrl(input.baseUrl),
|
|
325081
|
+
testVariables: stableVariables(input.testVariables)
|
|
325082
|
+
});
|
|
325083
|
+
const key = input.authToken || `validate-local-auth:${input.projectId}`;
|
|
325084
|
+
return (0, node_crypto_1.createHmac)("sha256", key).update(payload).digest("hex").slice(0, 24);
|
|
325085
|
+
}
|
|
325086
|
+
function ensurePrivateDirectory2(dir) {
|
|
325087
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
325088
|
+
try {
|
|
325089
|
+
(0, node_fs_1.chmodSync)(dir, 448);
|
|
325090
|
+
} catch {
|
|
325091
|
+
}
|
|
325092
|
+
}
|
|
325093
|
+
function removeFile(path) {
|
|
325094
|
+
try {
|
|
325095
|
+
(0, node_fs_1.unlinkSync)(path);
|
|
325096
|
+
} catch {
|
|
325097
|
+
}
|
|
325098
|
+
}
|
|
325099
|
+
function readLockOwner(path) {
|
|
325100
|
+
try {
|
|
325101
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
325102
|
+
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;
|
|
325103
|
+
} catch {
|
|
325104
|
+
return void 0;
|
|
325105
|
+
}
|
|
325106
|
+
}
|
|
325107
|
+
function processIsAlive(pid) {
|
|
325108
|
+
try {
|
|
325109
|
+
process.kill(pid, 0);
|
|
325110
|
+
return true;
|
|
325111
|
+
} catch (error2) {
|
|
325112
|
+
return error2.code === "EPERM";
|
|
325113
|
+
}
|
|
325114
|
+
}
|
|
325115
|
+
function staleLockCanBeReaped(path, now, staleMs) {
|
|
325116
|
+
try {
|
|
325117
|
+
const ageMs = now - (0, node_fs_1.statSync)(path).mtimeMs;
|
|
325118
|
+
if (ageMs < staleMs)
|
|
325119
|
+
return false;
|
|
325120
|
+
const owner = readLockOwner(path);
|
|
325121
|
+
if (owner) {
|
|
325122
|
+
if (!processIsAlive(owner.pid))
|
|
325123
|
+
return true;
|
|
325124
|
+
return ageMs >= Math.max(staleMs * 5, exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS);
|
|
325125
|
+
}
|
|
325126
|
+
return ageMs >= staleMs * 5;
|
|
325127
|
+
} catch {
|
|
325128
|
+
return false;
|
|
325129
|
+
}
|
|
325130
|
+
}
|
|
325131
|
+
function isRecord(value) {
|
|
325132
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
325133
|
+
}
|
|
325134
|
+
function isValidStorageState(value) {
|
|
325135
|
+
if (!isRecord(value))
|
|
325136
|
+
return false;
|
|
325137
|
+
const cookies = value.cookies;
|
|
325138
|
+
const origins = value.origins;
|
|
325139
|
+
if (!Array.isArray(cookies) || !Array.isArray(origins))
|
|
325140
|
+
return false;
|
|
325141
|
+
const cookiesValid = cookies.every((cookie) => {
|
|
325142
|
+
if (!isRecord(cookie))
|
|
325143
|
+
return false;
|
|
325144
|
+
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");
|
|
325145
|
+
});
|
|
325146
|
+
if (!cookiesValid)
|
|
325147
|
+
return false;
|
|
325148
|
+
const originsValid = origins.every((origin) => {
|
|
325149
|
+
if (!isRecord(origin) || typeof origin.origin !== "string" || !Array.isArray(origin.localStorage))
|
|
325150
|
+
return false;
|
|
325151
|
+
return origin.localStorage.every((entry) => isRecord(entry) && typeof entry.name === "string" && typeof entry.value === "string");
|
|
325152
|
+
});
|
|
325153
|
+
if (!originsValid)
|
|
325154
|
+
return false;
|
|
325155
|
+
return cookies.length > 0 || origins.some((origin) => origin.localStorage.length > 0);
|
|
325156
|
+
}
|
|
325157
|
+
function isValidStorageStateJson(value) {
|
|
325158
|
+
try {
|
|
325159
|
+
return isValidStorageState(JSON.parse(value));
|
|
325160
|
+
} catch {
|
|
325161
|
+
return false;
|
|
325162
|
+
}
|
|
325163
|
+
}
|
|
325164
|
+
function isStorageStateShape(value) {
|
|
325165
|
+
if (!value || typeof value !== "object")
|
|
325166
|
+
return false;
|
|
325167
|
+
return isValidStorageState(value);
|
|
325168
|
+
}
|
|
325169
|
+
function isValidLocalAuthStateFile(path) {
|
|
325170
|
+
try {
|
|
325171
|
+
return isStorageStateShape(JSON.parse((0, node_fs_1.readFileSync)(path, "utf8")));
|
|
325172
|
+
} catch {
|
|
325173
|
+
return false;
|
|
325174
|
+
}
|
|
325175
|
+
}
|
|
325176
|
+
function cleanupLocalAuthStateFiles(runsDir, projectId, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325177
|
+
ensurePrivateDirectory2(runsDir);
|
|
325178
|
+
const projectSegment = safeSegment(projectId, "default");
|
|
325179
|
+
const legacyName = `auth-state-${projectSegment}.json`;
|
|
325180
|
+
const prefix = `auth-state-${projectSegment}-`;
|
|
325181
|
+
for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
|
|
325182
|
+
if (name === legacyName) {
|
|
325183
|
+
removeFile((0, node_path_1.join)(runsDir, name));
|
|
325184
|
+
continue;
|
|
325185
|
+
}
|
|
325186
|
+
if (!name.startsWith(prefix))
|
|
325187
|
+
continue;
|
|
325188
|
+
const path = (0, node_path_1.join)(runsDir, name);
|
|
325189
|
+
try {
|
|
325190
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
|
|
325191
|
+
if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
|
|
325192
|
+
removeFile(path);
|
|
325193
|
+
}
|
|
325194
|
+
} catch {
|
|
325195
|
+
}
|
|
325196
|
+
}
|
|
325197
|
+
}
|
|
325198
|
+
function cleanupExpiredLocalAuthStateFiles2(runsDir, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325199
|
+
ensurePrivateDirectory2(runsDir);
|
|
325200
|
+
for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
|
|
325201
|
+
if (!name.startsWith("auth-state-"))
|
|
325202
|
+
continue;
|
|
325203
|
+
const path = (0, node_path_1.join)(runsDir, name);
|
|
325204
|
+
try {
|
|
325205
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
|
|
325206
|
+
if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
|
|
325207
|
+
removeFile(path);
|
|
325208
|
+
}
|
|
325209
|
+
} catch {
|
|
325210
|
+
}
|
|
325211
|
+
}
|
|
325212
|
+
}
|
|
325213
|
+
function localAuthStatePaths2(runsDir, input, runId) {
|
|
325214
|
+
cleanupLocalAuthStateFiles(runsDir, input.projectId);
|
|
325215
|
+
const projectSegment = safeSegment(input.projectId, "default");
|
|
325216
|
+
const runSegment = safeSegment(runId, `${process.pid}`);
|
|
325217
|
+
const fingerprint = localAuthStateFingerprint(input);
|
|
325218
|
+
const cachePath = (0, node_path_1.join)(runsDir, `auth-state-${projectSegment}-${fingerprint}.json`);
|
|
325219
|
+
return {
|
|
325220
|
+
cachePath,
|
|
325221
|
+
capturePath: `${cachePath}.capture-${runSegment}-${process.pid}.tmp`,
|
|
325222
|
+
fingerprint,
|
|
325223
|
+
lockKey: `${normalizeOrigin(input.serverUrl)}:${projectSegment}:${fingerprint}`,
|
|
325224
|
+
lockPath: `${cachePath}.lock`
|
|
325225
|
+
};
|
|
325226
|
+
}
|
|
325227
|
+
function getValidLocalAuthState2(path, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325228
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
325229
|
+
return void 0;
|
|
325230
|
+
try {
|
|
325231
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs || !isValidLocalAuthStateFile(path)) {
|
|
325232
|
+
removeFile(path);
|
|
325233
|
+
return void 0;
|
|
325234
|
+
}
|
|
325235
|
+
return path;
|
|
325236
|
+
} catch {
|
|
325237
|
+
return void 0;
|
|
325238
|
+
}
|
|
325239
|
+
}
|
|
325240
|
+
async function waitForValidLocalAuthState(path, timeoutMs = exports.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100) {
|
|
325241
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
325242
|
+
do {
|
|
325243
|
+
const valid = getValidLocalAuthState2(path);
|
|
325244
|
+
if (valid)
|
|
325245
|
+
return valid;
|
|
325246
|
+
const remaining = deadline - Date.now();
|
|
325247
|
+
if (remaining <= 0)
|
|
325248
|
+
return void 0;
|
|
325249
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(Math.max(10, pollMs), remaining)));
|
|
325250
|
+
} while (true);
|
|
325251
|
+
}
|
|
325252
|
+
async function waitForLocalAuthStateProducer2(path, lockKey, startupGraceMs = 2e3, maxWaitMs = exports.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100, lockPath) {
|
|
325253
|
+
const startedAt = Date.now();
|
|
325254
|
+
const producerIsActive = () => lockTails.has(lockKey) || !!lockPath && (0, node_fs_1.existsSync)(lockPath);
|
|
325255
|
+
let sawProducer = producerIsActive();
|
|
325256
|
+
do {
|
|
325257
|
+
const valid = getValidLocalAuthState2(path);
|
|
325258
|
+
if (valid)
|
|
325259
|
+
return valid;
|
|
325260
|
+
const producerActive = producerIsActive();
|
|
325261
|
+
if (producerActive)
|
|
325262
|
+
sawProducer = true;
|
|
325263
|
+
const elapsed = Date.now() - startedAt;
|
|
325264
|
+
if (sawProducer && !producerActive)
|
|
325265
|
+
return void 0;
|
|
325266
|
+
if (!sawProducer && elapsed >= Math.max(0, startupGraceMs))
|
|
325267
|
+
return void 0;
|
|
325268
|
+
if (elapsed >= Math.max(0, maxWaitMs))
|
|
325269
|
+
return void 0;
|
|
325270
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.max(10, pollMs)));
|
|
325271
|
+
} while (true);
|
|
325272
|
+
}
|
|
325273
|
+
function promoteLocalAuthStateCapture2(capturePath, cachePath, replaceExisting = false, lease) {
|
|
325274
|
+
if (lease && !lease.isOwner()) {
|
|
325275
|
+
removeFile(capturePath);
|
|
325276
|
+
return false;
|
|
325277
|
+
}
|
|
325278
|
+
if (!(0, node_fs_1.existsSync)(capturePath) || !isValidLocalAuthStateFile(capturePath)) {
|
|
325279
|
+
removeFile(capturePath);
|
|
325280
|
+
return false;
|
|
325281
|
+
}
|
|
325282
|
+
try {
|
|
325283
|
+
(0, node_fs_1.chmodSync)(capturePath, 384);
|
|
325284
|
+
} catch {
|
|
325285
|
+
}
|
|
325286
|
+
try {
|
|
325287
|
+
if (lease && !lease.isOwner()) {
|
|
325288
|
+
removeFile(capturePath);
|
|
325289
|
+
return false;
|
|
325290
|
+
}
|
|
325291
|
+
if ((0, node_fs_1.existsSync)(cachePath)) {
|
|
325292
|
+
if (!replaceExisting && isValidLocalAuthStateFile(cachePath)) {
|
|
325293
|
+
removeFile(capturePath);
|
|
325294
|
+
return true;
|
|
325295
|
+
}
|
|
325296
|
+
removeFile(cachePath);
|
|
325297
|
+
}
|
|
325298
|
+
if (lease && !lease.isOwner()) {
|
|
325299
|
+
removeFile(capturePath);
|
|
325300
|
+
return false;
|
|
325301
|
+
}
|
|
325302
|
+
(0, node_fs_1.renameSync)(capturePath, cachePath);
|
|
325303
|
+
try {
|
|
325304
|
+
(0, node_fs_1.chmodSync)(cachePath, 384);
|
|
325305
|
+
} catch {
|
|
325306
|
+
}
|
|
325307
|
+
return true;
|
|
325308
|
+
} catch {
|
|
325309
|
+
const winnerIsValid = (0, node_fs_1.existsSync)(cachePath) && isValidLocalAuthStateFile(cachePath);
|
|
325310
|
+
removeFile(capturePath);
|
|
325311
|
+
return winnerIsValid;
|
|
325312
|
+
}
|
|
325313
|
+
}
|
|
325314
|
+
function discardLocalAuthStateCapture2(capturePath) {
|
|
325315
|
+
removeFile(capturePath);
|
|
325316
|
+
}
|
|
325317
|
+
var lockTails = /* @__PURE__ */ new Map();
|
|
325318
|
+
function publishCompleteLockOwner(lockPath, owner, linkFile) {
|
|
325319
|
+
const metadata = JSON.stringify(owner);
|
|
325320
|
+
const tempPath = `${lockPath}.${owner.pid}.${owner.token}.tmp`;
|
|
325321
|
+
let createdLock = false;
|
|
325322
|
+
try {
|
|
325323
|
+
(0, node_fs_1.writeFileSync)(tempPath, metadata, { flag: "wx", mode: 384 });
|
|
325324
|
+
try {
|
|
325325
|
+
linkFile(tempPath, lockPath);
|
|
325326
|
+
createdLock = true;
|
|
325327
|
+
return (0, node_fs_1.openSync)(lockPath, "r+");
|
|
325328
|
+
} catch (error2) {
|
|
325329
|
+
const code = error2.code;
|
|
325330
|
+
if (!["EPERM", "EACCES", "EXDEV", "ENOSYS", "ENOTSUP", "EOPNOTSUPP"].includes(code ?? "")) {
|
|
325331
|
+
throw error2;
|
|
325332
|
+
}
|
|
325333
|
+
let fd;
|
|
325334
|
+
try {
|
|
325335
|
+
fd = (0, node_fs_1.openSync)(lockPath, "wx+", 384);
|
|
325336
|
+
createdLock = true;
|
|
325337
|
+
(0, node_fs_1.writeFileSync)(fd, metadata, { encoding: "utf8" });
|
|
325338
|
+
(0, node_fs_1.fsyncSync)(fd);
|
|
325339
|
+
return fd;
|
|
325340
|
+
} catch (fallbackError) {
|
|
325341
|
+
if (fd !== void 0) {
|
|
325342
|
+
try {
|
|
325343
|
+
(0, node_fs_1.closeSync)(fd);
|
|
325344
|
+
} catch {
|
|
325345
|
+
}
|
|
325346
|
+
}
|
|
325347
|
+
if (createdLock)
|
|
325348
|
+
removeFile(lockPath);
|
|
325349
|
+
throw fallbackError;
|
|
325350
|
+
}
|
|
325351
|
+
}
|
|
325352
|
+
} catch (error2) {
|
|
325353
|
+
if (createdLock && readLockOwner(lockPath)?.token === owner.token)
|
|
325354
|
+
removeFile(lockPath);
|
|
325355
|
+
throw error2;
|
|
325356
|
+
} finally {
|
|
325357
|
+
removeFile(tempPath);
|
|
325358
|
+
}
|
|
325359
|
+
}
|
|
325360
|
+
async function acquireExclusiveFileLock(lockPath, linkFile = node_fs_1.linkSync) {
|
|
325361
|
+
ensurePrivateDirectory2((0, node_path_1.dirname)(lockPath));
|
|
325362
|
+
const startedAt = Date.now();
|
|
325363
|
+
while (true) {
|
|
325364
|
+
try {
|
|
325365
|
+
const ownerToken = (0, node_crypto_1.randomBytes)(24).toString("base64url");
|
|
325366
|
+
const fd = publishCompleteLockOwner(lockPath, { token: ownerToken, pid: process.pid }, linkFile);
|
|
325367
|
+
const ownsPath = () => {
|
|
325368
|
+
try {
|
|
325369
|
+
const owner = readLockOwner(lockPath);
|
|
325370
|
+
return owner?.token === ownerToken && owner.pid === process.pid;
|
|
325371
|
+
} catch {
|
|
325372
|
+
return false;
|
|
325373
|
+
}
|
|
325374
|
+
};
|
|
325375
|
+
const heartbeat = setInterval(() => {
|
|
325376
|
+
try {
|
|
325377
|
+
if (!ownsPath()) {
|
|
325378
|
+
clearInterval(heartbeat);
|
|
325379
|
+
return;
|
|
325380
|
+
}
|
|
325381
|
+
const now = /* @__PURE__ */ new Date();
|
|
325382
|
+
(0, node_fs_1.futimesSync)(fd, now, now);
|
|
325383
|
+
} catch {
|
|
325384
|
+
clearInterval(heartbeat);
|
|
325385
|
+
}
|
|
325386
|
+
}, Math.max(1e3, Math.floor(exports.LOCAL_AUTH_STATE_LOCK_STALE_MS / 3)));
|
|
325387
|
+
heartbeat.unref();
|
|
325388
|
+
let released = false;
|
|
325389
|
+
const release = (() => {
|
|
325390
|
+
if (released)
|
|
325391
|
+
return;
|
|
325392
|
+
released = true;
|
|
325393
|
+
clearInterval(heartbeat);
|
|
325394
|
+
try {
|
|
325395
|
+
if (ownsPath())
|
|
325396
|
+
removeFile(lockPath);
|
|
325397
|
+
} catch {
|
|
325398
|
+
}
|
|
325399
|
+
try {
|
|
325400
|
+
(0, node_fs_1.closeSync)(fd);
|
|
325401
|
+
} catch {
|
|
325402
|
+
}
|
|
325403
|
+
});
|
|
325404
|
+
release.isOwner = () => !released && ownsPath();
|
|
325405
|
+
return release;
|
|
325406
|
+
} catch (error2) {
|
|
325407
|
+
const code = error2.code;
|
|
325408
|
+
if (code !== "EEXIST")
|
|
325409
|
+
throw error2;
|
|
325410
|
+
try {
|
|
325411
|
+
if (staleLockCanBeReaped(lockPath, Date.now(), exports.LOCAL_AUTH_STATE_LOCK_STALE_MS)) {
|
|
325412
|
+
removeFile(lockPath);
|
|
325413
|
+
continue;
|
|
325414
|
+
}
|
|
325415
|
+
} catch {
|
|
325416
|
+
continue;
|
|
325417
|
+
}
|
|
325418
|
+
if (Date.now() - startedAt >= exports.LOCAL_AUTH_STATE_WAIT_MS) {
|
|
325419
|
+
throw new Error(`Timed out waiting for auth-state lock: ${(0, node_path_1.basename)(lockPath)}`);
|
|
325420
|
+
}
|
|
325421
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
325422
|
+
}
|
|
325423
|
+
}
|
|
325424
|
+
}
|
|
325425
|
+
async function acquireLocalAuthStateLock2(key, lockPath, linkFile = node_fs_1.linkSync) {
|
|
325426
|
+
const previous = lockTails.get(key) ?? Promise.resolve();
|
|
325427
|
+
let unlockCurrent;
|
|
325428
|
+
const current = new Promise((resolve2) => {
|
|
325429
|
+
unlockCurrent = resolve2;
|
|
325430
|
+
});
|
|
325431
|
+
const tail = previous.then(() => current);
|
|
325432
|
+
lockTails.set(key, tail);
|
|
325433
|
+
await previous;
|
|
325434
|
+
let releaseFileLock;
|
|
325435
|
+
try {
|
|
325436
|
+
if (lockPath)
|
|
325437
|
+
releaseFileLock = await acquireExclusiveFileLock(lockPath, linkFile);
|
|
325438
|
+
} catch (error2) {
|
|
325439
|
+
unlockCurrent();
|
|
325440
|
+
if (lockTails.get(key) === tail)
|
|
325441
|
+
lockTails.delete(key);
|
|
325442
|
+
throw error2;
|
|
325443
|
+
}
|
|
325444
|
+
let released = false;
|
|
325445
|
+
const release = (() => {
|
|
325446
|
+
if (released)
|
|
325447
|
+
return;
|
|
325448
|
+
released = true;
|
|
325449
|
+
releaseFileLock?.();
|
|
325450
|
+
unlockCurrent();
|
|
325451
|
+
if (lockTails.get(key) === tail)
|
|
325452
|
+
lockTails.delete(key);
|
|
325453
|
+
});
|
|
325454
|
+
release.isOwner = () => !released && (releaseFileLock?.isOwner() ?? true);
|
|
325455
|
+
return release;
|
|
325456
|
+
}
|
|
325457
|
+
function localAuthStateFilename(path) {
|
|
325458
|
+
return (0, node_path_1.basename)(path);
|
|
325459
|
+
}
|
|
325460
|
+
}
|
|
325461
|
+
});
|
|
325462
|
+
|
|
324813
325463
|
// ../../node_modules/adm-zip/util/constants.js
|
|
324814
325464
|
var require_constants5 = __commonJS({
|
|
324815
325465
|
"../../node_modules/adm-zip/util/constants.js"(exports, module) {
|
|
@@ -325123,7 +325773,7 @@ var require_utils2 = __commonJS({
|
|
|
325123
325773
|
module.exports = Utils;
|
|
325124
325774
|
Utils.prototype.makeDir = function(folder) {
|
|
325125
325775
|
const self = this;
|
|
325126
|
-
function
|
|
325776
|
+
function mkdirSync4(fpath) {
|
|
325127
325777
|
let resolvedPath = fpath.split(self.sep)[0];
|
|
325128
325778
|
fpath.split(self.sep).forEach(function(name) {
|
|
325129
325779
|
if (!name || name.substr(-1, 1) === ":") return;
|
|
@@ -325141,7 +325791,7 @@ var require_utils2 = __commonJS({
|
|
|
325141
325791
|
if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
|
|
325142
325792
|
});
|
|
325143
325793
|
}
|
|
325144
|
-
|
|
325794
|
+
mkdirSync4(folder);
|
|
325145
325795
|
};
|
|
325146
325796
|
Utils.prototype.writeFileTo = function(path, content, overwrite, attr) {
|
|
325147
325797
|
const self = this;
|
|
@@ -337065,6 +337715,8 @@ var require_dist2 = __commonJS({
|
|
|
337065
337715
|
__exportStar(require_discover_agent(), exports);
|
|
337066
337716
|
__exportStar(require_browser_pool(), exports);
|
|
337067
337717
|
__exportStar(require_resource_governor(), exports);
|
|
337718
|
+
__exportStar(require_local_auth_state(), exports);
|
|
337719
|
+
__exportStar(require_run_callback_proxy(), exports);
|
|
337068
337720
|
__exportStar(require_discover_explorer(), exports);
|
|
337069
337721
|
__exportStar(require_discover_feature_explorer(), exports);
|
|
337070
337722
|
__exportStar(require_discover_planner(), exports);
|
|
@@ -337341,7 +337993,7 @@ var require_package4 = __commonJS({
|
|
|
337341
337993
|
"package.json"(exports, module) {
|
|
337342
337994
|
module.exports = {
|
|
337343
337995
|
name: "@validate.qa/runner",
|
|
337344
|
-
version: "1.0.
|
|
337996
|
+
version: "1.0.43",
|
|
337345
337997
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
337346
337998
|
bin: {
|
|
337347
337999
|
"validate-runner": "dist/cli.js"
|
|
@@ -337349,7 +338001,7 @@ var require_package4 = __commonJS({
|
|
|
337349
338001
|
scripts: {
|
|
337350
338002
|
build: "tsup",
|
|
337351
338003
|
dev: "tsx src/cli.ts",
|
|
337352
|
-
test: "node --import tsx --test src/version-check.test.ts",
|
|
338004
|
+
test: "node --import tsx --test src/version-check.test.ts src/services/auth-state.test.ts",
|
|
337353
338005
|
"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",
|
|
337354
338006
|
prepublishOnly: "npm run build"
|
|
337355
338007
|
},
|
|
@@ -337428,7 +338080,6 @@ function getConfigDir() {
|
|
|
337428
338080
|
}
|
|
337429
338081
|
|
|
337430
338082
|
// src/runner.ts
|
|
337431
|
-
import { existsSync as existsSync3, statSync, mkdirSync as mkdirSync4 } from "fs";
|
|
337432
338083
|
import { join as join6 } from "path";
|
|
337433
338084
|
import { homedir as homedir3 } from "os";
|
|
337434
338085
|
import { createHash } from "crypto";
|
|
@@ -337456,23 +338107,33 @@ var import_runner_core6 = __toESM(require_dist2());
|
|
|
337456
338107
|
|
|
337457
338108
|
// src/runner.ts
|
|
337458
338109
|
var import_runner_core11 = __toESM(require_dist2());
|
|
338110
|
+
var import_runner_core12 = __toESM(require_dist2());
|
|
337459
338111
|
|
|
337460
338112
|
// src/services/auth-state.ts
|
|
338113
|
+
init_dist();
|
|
337461
338114
|
var INLINE_LOGIN_TAG = "@inline-login";
|
|
337462
338115
|
var GUARDED_LOGIN_TAG = "@auth-guarded";
|
|
337463
338116
|
function sharedAuthInjectEnabled() {
|
|
337464
|
-
return
|
|
338117
|
+
return isSharedAuthInjectEnabled();
|
|
338118
|
+
}
|
|
338119
|
+
function hasGeneratedAuthGuard(playwrightCode) {
|
|
338120
|
+
if (!playwrightCode) return false;
|
|
338121
|
+
return /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
338122
|
+
}
|
|
338123
|
+
function shouldCaptureAuthState(tags, playwrightCode) {
|
|
338124
|
+
return sharedAuthInjectEnabled() && Array.isArray(tags) && tags.includes("@needs-auth") && tags.includes(GUARDED_LOGIN_TAG) && !tags.includes("@auth") && hasGeneratedAuthGuard(playwrightCode);
|
|
337465
338125
|
}
|
|
337466
338126
|
function hasInlineLoginSteps(playwrightCode) {
|
|
337467
338127
|
if (!playwrightCode) return false;
|
|
337468
338128
|
return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
|
|
337469
338129
|
}
|
|
337470
338130
|
function shouldUseAuthState(tags, playwrightCode) {
|
|
338131
|
+
if (!sharedAuthInjectEnabled()) return false;
|
|
337471
338132
|
if (!Array.isArray(tags)) return false;
|
|
337472
338133
|
if (tags.includes("@auth")) return false;
|
|
337473
338134
|
if (tags.includes("@api")) return true;
|
|
337474
338135
|
if (!tags.includes("@needs-auth")) return false;
|
|
337475
|
-
if (
|
|
338136
|
+
if (tags.includes(GUARDED_LOGIN_TAG) && hasGeneratedAuthGuard(playwrightCode)) return true;
|
|
337476
338137
|
const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
|
|
337477
338138
|
const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(playwrightCode);
|
|
337478
338139
|
return !tags.includes(INLINE_LOGIN_TAG) && !hasLegacyInlineLogin;
|
|
@@ -344185,8 +344846,8 @@ function withMediaReferences(result, refs) {
|
|
|
344185
344846
|
|
|
344186
344847
|
// src/runner.ts
|
|
344187
344848
|
init_dist();
|
|
344188
|
-
var import_runner_core12 = __toESM(require_dist2());
|
|
344189
344849
|
var import_runner_core13 = __toESM(require_dist2());
|
|
344850
|
+
var import_runner_core14 = __toESM(require_dist2());
|
|
344190
344851
|
function fingerprintTest(test) {
|
|
344191
344852
|
const code = test.framework === "APPIUM" ? [test.appiumCode ?? "", JSON.stringify(test.steps ?? [])].join("\n--steps--\n") : test.playwrightCode ?? "";
|
|
344192
344853
|
return 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);
|
|
@@ -344310,21 +344971,11 @@ var log = {
|
|
|
344310
344971
|
warn: (msg) => console.log(`${C2.yellow}\u26A0${C2.reset} ${msg}`),
|
|
344311
344972
|
dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
|
|
344312
344973
|
};
|
|
344313
|
-
var AUTH_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
344314
344974
|
function getRunsDir() {
|
|
344315
344975
|
const dir = process.env.RUNS_DIR || join6(homedir3(), ".validate.qa", "runs");
|
|
344316
|
-
|
|
344976
|
+
(0, import_runner_core12.ensurePrivateDirectory)(dir);
|
|
344317
344977
|
return dir;
|
|
344318
344978
|
}
|
|
344319
|
-
function authStatePath(projectId) {
|
|
344320
|
-
return join6(getRunsDir(), `auth-state-${projectId || "default"}.json`);
|
|
344321
|
-
}
|
|
344322
|
-
function getValidAuthState(projectId) {
|
|
344323
|
-
const path = authStatePath(projectId);
|
|
344324
|
-
if (!existsSync3(path)) return void 0;
|
|
344325
|
-
const age = Date.now() - statSync(path).mtimeMs;
|
|
344326
|
-
return age < AUTH_MAX_AGE_MS ? path : void 0;
|
|
344327
|
-
}
|
|
344328
344979
|
function buildHeaders(authToken, projectId) {
|
|
344329
344980
|
return {
|
|
344330
344981
|
"Content-Type": "application/json",
|
|
@@ -344373,7 +345024,13 @@ async function claimRun(serverUrl, headers, runId) {
|
|
|
344373
345024
|
if (res.status === 429) return false;
|
|
344374
345025
|
if (!res.ok) throw new Error(`Claim failed: ${res.status}`);
|
|
344375
345026
|
const body = await res.json();
|
|
344376
|
-
|
|
345027
|
+
if (!body.claimed) {
|
|
345028
|
+
throw new Error("Claim response did not confirm ownership");
|
|
345029
|
+
}
|
|
345030
|
+
if (typeof body.claimEpoch !== "number" || !Number.isSafeInteger(body.claimEpoch) || body.claimEpoch < 0) {
|
|
345031
|
+
throw new Error("Claim response did not include a valid ownership epoch");
|
|
345032
|
+
}
|
|
345033
|
+
return body.claimEpoch;
|
|
344377
345034
|
}
|
|
344378
345035
|
async function postResult(serverUrl, headers, runId, result) {
|
|
344379
345036
|
const MAX_ATTEMPTS = 3;
|
|
@@ -344541,12 +345198,12 @@ async function postCaptureResult(serverUrl, headers, runId, result) {
|
|
|
344541
345198
|
});
|
|
344542
345199
|
if (!res.ok) throw new Error(`Failed to post capture result: ${res.status}`);
|
|
344543
345200
|
}
|
|
344544
|
-
async function postCredentialRotation(serverUrl, headers, runId, body) {
|
|
345201
|
+
async function postCredentialRotation(serverUrl, headers, runId, body, claimEpoch) {
|
|
344545
345202
|
try {
|
|
344546
345203
|
const res = await fetch(`${serverUrl}/api/runner/runs/${runId}/credential`, {
|
|
344547
345204
|
method: "POST",
|
|
344548
345205
|
headers,
|
|
344549
|
-
body: JSON.stringify(body),
|
|
345206
|
+
body: JSON.stringify({ ...body, ...claimEpoch !== void 0 ? { claimEpoch } : {} }),
|
|
344550
345207
|
signal: AbortSignal.timeout(3e4)
|
|
344551
345208
|
});
|
|
344552
345209
|
if (!res.ok) {
|
|
@@ -344883,6 +345540,12 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344883
345540
|
liveBrowserHandle,
|
|
344884
345541
|
onLiveBrowserSnapshot: requestLiveBrowserHeartbeat
|
|
344885
345542
|
});
|
|
345543
|
+
const callbackContext = {
|
|
345544
|
+
runId: run2.runId,
|
|
345545
|
+
serverUrl: opts.serverUrl,
|
|
345546
|
+
authHeader: headers.Authorization,
|
|
345547
|
+
claimEpoch: run2.claimEpoch
|
|
345548
|
+
};
|
|
344886
345549
|
const startTime = Date.now();
|
|
344887
345550
|
try {
|
|
344888
345551
|
if (mobileLeaseError) {
|
|
@@ -344912,7 +345575,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344912
345575
|
log.fail(`PRE-FLIGHT FAILED - ${preflightError}`);
|
|
344913
345576
|
return;
|
|
344914
345577
|
}
|
|
344915
|
-
const preflight = await (0,
|
|
345578
|
+
const preflight = await (0, import_runner_core13.resolveBaseUrlPreflight)(run2.baseUrl);
|
|
344916
345579
|
if (preflight.error) {
|
|
344917
345580
|
await postResult(opts.serverUrl, headers, run2.runId, { status: "ERROR", error: preflight.error });
|
|
344918
345581
|
log.fail(`PRE-FLIGHT FAILED - ${preflight.error}`);
|
|
@@ -344929,31 +345592,72 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344929
345592
|
log.fail("ERROR - No auth setup code");
|
|
344930
345593
|
return;
|
|
344931
345594
|
}
|
|
344932
|
-
const
|
|
344933
|
-
|
|
344934
|
-
|
|
344935
|
-
|
|
345595
|
+
const sharedAuthEnabled = sharedAuthInjectEnabled();
|
|
345596
|
+
if ((0, import_runner_core12.shouldSkipDisabledAutomaticAuthSetup)(run2.authForceRun, run2.authVerify, sharedAuthEnabled)) {
|
|
345597
|
+
const duration2 = Date.now() - startTime;
|
|
345598
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345599
|
+
status: "PASSED",
|
|
345600
|
+
logs: "Automatic auth prewarm skipped: shared session reuse is disabled.",
|
|
345601
|
+
duration: duration2
|
|
345602
|
+
});
|
|
345603
|
+
log.info(`Auth prewarm skipped (${duration2}ms) - SHARED_AUTH_INJECT=false`);
|
|
345604
|
+
return;
|
|
345605
|
+
}
|
|
345606
|
+
const authPaths2 = sharedAuthEnabled ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
345607
|
+
projectId: run2.projectId || opts.projectId || "default",
|
|
345608
|
+
serverUrl: opts.serverUrl,
|
|
344936
345609
|
baseUrl: run2.baseUrl,
|
|
344937
|
-
|
|
344938
|
-
|
|
344939
|
-
|
|
344940
|
-
|
|
344941
|
-
|
|
344942
|
-
|
|
345610
|
+
testVariables: run2.testVariables,
|
|
345611
|
+
authToken: opts.authToken
|
|
345612
|
+
}, run2.runId) : void 0;
|
|
345613
|
+
const releaseAuthLock2 = authPaths2 ? await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths2.lockKey, authPaths2.lockPath) : void 0;
|
|
345614
|
+
try {
|
|
345615
|
+
if (authPaths2 && (0, import_runner_core12.shouldSkipWarmAuthSetup)(
|
|
345616
|
+
run2.authForceRun,
|
|
345617
|
+
sharedAuthEnabled,
|
|
345618
|
+
!!(0, import_runner_core12.getValidLocalAuthState)(authPaths2.cachePath)
|
|
345619
|
+
)) {
|
|
345620
|
+
const duration3 = Date.now() - startTime;
|
|
345621
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345622
|
+
status: "PASSED",
|
|
345623
|
+
logs: "Auth setup skipped: the credential-bound local session is already warm.",
|
|
345624
|
+
duration: duration3
|
|
345625
|
+
});
|
|
345626
|
+
log.ok(`Auth setup skipped (${duration3}ms) - local session already warm`);
|
|
345627
|
+
return;
|
|
344943
345628
|
}
|
|
344944
|
-
|
|
344945
|
-
|
|
344946
|
-
|
|
344947
|
-
|
|
344948
|
-
|
|
344949
|
-
|
|
344950
|
-
|
|
344951
|
-
|
|
344952
|
-
|
|
344953
|
-
|
|
344954
|
-
|
|
344955
|
-
|
|
344956
|
-
|
|
345629
|
+
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");
|
|
345630
|
+
const capturePath = sharedAuthEnabled && !run2.authVerify ? authPaths2?.capturePath : void 0;
|
|
345631
|
+
const result2 = await runNativeWithLiveBrowser({
|
|
345632
|
+
playwrightCode: run2.playwrightCode,
|
|
345633
|
+
baseUrl: run2.baseUrl,
|
|
345634
|
+
testName: "auth-setup",
|
|
345635
|
+
testVariables: run2.testVariables,
|
|
345636
|
+
authStateCapturePath: capturePath,
|
|
345637
|
+
validateContext: callbackContext
|
|
345638
|
+
});
|
|
345639
|
+
const captured = !!capturePath && !!authPaths2 && result2.passed && !result2.suspiciousPass && (0, import_runner_core12.promoteLocalAuthStateCapture)(
|
|
345640
|
+
capturePath,
|
|
345641
|
+
authPaths2.cachePath,
|
|
345642
|
+
run2.authForceRun === true,
|
|
345643
|
+
releaseAuthLock2
|
|
345644
|
+
);
|
|
345645
|
+
const duration2 = Date.now() - startTime;
|
|
345646
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345647
|
+
status: result2.passed ? "PASSED" : "FAILED",
|
|
345648
|
+
apiCalls: result2.apiCalls,
|
|
345649
|
+
logs: result2.logs,
|
|
345650
|
+
error: result2.error,
|
|
345651
|
+
duration: duration2
|
|
345652
|
+
});
|
|
345653
|
+
log[result2.passed ? "ok" : "fail"](
|
|
345654
|
+
`Auth setup ${result2.passed ? "PASSED" : "FAILED"} (${duration2}ms)${captured ? " - session cached" : ""}${result2.error ? ` - ${result2.error}` : ""}`
|
|
345655
|
+
);
|
|
345656
|
+
return;
|
|
345657
|
+
} finally {
|
|
345658
|
+
if (authPaths2?.capturePath) (0, import_runner_core12.discardLocalAuthStateCapture)(authPaths2.capturePath);
|
|
345659
|
+
releaseAuthLock2?.();
|
|
345660
|
+
}
|
|
344957
345661
|
}
|
|
344958
345662
|
if (runMode === "batch-heal") {
|
|
344959
345663
|
const tests = run2.batchHealTests ?? [];
|
|
@@ -344972,7 +345676,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344972
345676
|
const batchResult = await (0, import_runner_core10.executeGrokPerBatchHeal)(tests, run2.baseUrl, {
|
|
344973
345677
|
testVariables: run2.testVariables,
|
|
344974
345678
|
runNativeTest: runNativeWithLiveBrowser,
|
|
344975
|
-
validateContext:
|
|
345679
|
+
validateContext: callbackContext,
|
|
344976
345680
|
onLog: healLogStream.onLog
|
|
344977
345681
|
});
|
|
344978
345682
|
await healLogStream.flushNow();
|
|
@@ -345008,6 +345712,15 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
345008
345712
|
);
|
|
345009
345713
|
const describeKind = isSerialDescribe ? "serial" : "non-serial";
|
|
345010
345714
|
log.info(`API test heal - ${subTestCount} sub-test(s) [${describeKind}], ${failingSubTestCount} budgeted, ${MAX_API_HEAL_ATTEMPTS} attempts`);
|
|
345715
|
+
const apiHealAuthPaths = shouldUseAuthState(run2.tags, run2.playwrightCode) ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
345716
|
+
projectId: run2.projectId || opts.projectId,
|
|
345717
|
+
serverUrl: opts.serverUrl,
|
|
345718
|
+
baseUrl: run2.baseUrl,
|
|
345719
|
+
testVariables: run2.testVariables,
|
|
345720
|
+
authToken: opts.authToken
|
|
345721
|
+
}, run2.runId) : void 0;
|
|
345722
|
+
const apiHealStorageStatePath = apiHealAuthPaths ? (0, import_runner_core12.getValidLocalAuthState)(apiHealAuthPaths.cachePath) : void 0;
|
|
345723
|
+
if (apiHealStorageStatePath) log.info("API heal verification is using saved auth state");
|
|
345011
345724
|
const stickyAppBugs = /* @__PURE__ */ new Map();
|
|
345012
345725
|
const lastChangeByTitle = /* @__PURE__ */ new Map();
|
|
345013
345726
|
let currentError = run2.lastFailureError ?? "Test failed";
|
|
@@ -345073,7 +345786,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
345073
345786
|
playwrightCode: lastHealedCode,
|
|
345074
345787
|
baseUrl: run2.baseUrl,
|
|
345075
345788
|
testVariables: run2.testVariables,
|
|
345076
|
-
|
|
345789
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345790
|
+
validateContext: callbackContext
|
|
345077
345791
|
});
|
|
345078
345792
|
if (finalVerifyResult.passed) {
|
|
345079
345793
|
log.ok(` Attempt ${attempt} terminal verification PASSED (${bugCount} app_bug, ${passCount} pass)`);
|
|
@@ -345093,7 +345807,9 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345093
345807
|
finalVerifyResult = await runNativeWithLiveBrowser({
|
|
345094
345808
|
playwrightCode: lastHealedCode,
|
|
345095
345809
|
baseUrl: run2.baseUrl,
|
|
345096
|
-
testVariables: run2.testVariables
|
|
345810
|
+
testVariables: run2.testVariables,
|
|
345811
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345812
|
+
validateContext: callbackContext
|
|
345097
345813
|
});
|
|
345098
345814
|
const stepResults = finalVerifyResult.stepResults ?? [];
|
|
345099
345815
|
const nonAppBugFails = stepResults.filter(
|
|
@@ -345143,7 +345859,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345143
345859
|
playwrightCode: lastHealedCode,
|
|
345144
345860
|
baseUrl: run2.baseUrl,
|
|
345145
345861
|
testVariables: run2.testVariables,
|
|
345146
|
-
|
|
345862
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345863
|
+
validateContext: callbackContext
|
|
345147
345864
|
});
|
|
345148
345865
|
if (finalVerifyResult.passed) {
|
|
345149
345866
|
log.ok(` Attempt ${attempt} verification PASSED`);
|
|
@@ -345196,7 +345913,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345196
345913
|
if (classifyErrorMessage(composedError ?? null) === "config_issue") {
|
|
345197
345914
|
apiHealStatus = "ERROR";
|
|
345198
345915
|
} else {
|
|
345199
|
-
const rlDetection = (0,
|
|
345916
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
345200
345917
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
345201
345918
|
tags: run2.tags ?? [],
|
|
345202
345919
|
error: composedError ?? finalVerifyResult?.error ?? null,
|
|
@@ -345293,8 +346010,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345293
346010
|
lastHealAttempt: run2.lastHealAttempt ?? null,
|
|
345294
346011
|
lastHealedCode: run2.lastHealedCode ?? null,
|
|
345295
346012
|
failureScreenshot: run2.failureScreenshot ?? null,
|
|
345296
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
|
|
345297
|
-
validateContext:
|
|
346013
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
|
|
346014
|
+
validateContext: callbackContext,
|
|
345298
346015
|
liveBrowserHandle,
|
|
345299
346016
|
runNativeTest: runNativeWithLiveBrowser
|
|
345300
346017
|
};
|
|
@@ -345336,7 +346053,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345336
346053
|
let healStatus = result2.passed ? "PASSED" : isConfigIssue ? "ERROR" : "FAILED";
|
|
345337
346054
|
let healError = result2.error;
|
|
345338
346055
|
if (!result2.passed && !isConfigIssue) {
|
|
345339
|
-
const rlDetection = (0,
|
|
346056
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
345340
346057
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
345341
346058
|
tags: run2.tags ?? [],
|
|
345342
346059
|
error: result2.error,
|
|
@@ -345407,7 +346124,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345407
346124
|
// Allow generated `persistCredential(...)` calls inside the suite to
|
|
345408
346125
|
// POST back to the server with the runner's bearer token.
|
|
345409
346126
|
validateServerUrl: opts.serverUrl,
|
|
345410
|
-
validateRunnerAuthHeader: headers.Authorization
|
|
346127
|
+
validateRunnerAuthHeader: headers.Authorization,
|
|
346128
|
+
claimEpoch: run2.claimEpoch
|
|
345411
346129
|
});
|
|
345412
346130
|
const overall = result2.error && result2.testResults.length === 0 ? "ERROR" : result2.passed ? "PASSED" : "FAILED";
|
|
345413
346131
|
const rr = await fetch(`${opts.serverUrl}/api/runner/runs/${run2.runId}/regression-result`, {
|
|
@@ -345462,7 +346180,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345462
346180
|
playwrightCode: healedCode,
|
|
345463
346181
|
baseUrl: run2.baseUrl,
|
|
345464
346182
|
testVariables: run2.testVariables,
|
|
345465
|
-
validateContext:
|
|
346183
|
+
validateContext: callbackContext
|
|
345466
346184
|
});
|
|
345467
346185
|
results.push({
|
|
345468
346186
|
testCaseId: failedTest.testCaseId,
|
|
@@ -345525,8 +346243,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345525
346243
|
onAICall: healAudit,
|
|
345526
346244
|
modelOverride: run2.healerModel,
|
|
345527
346245
|
consecutiveFails: failedTest.consecutiveFails,
|
|
345528
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
|
|
345529
|
-
validateContext:
|
|
346246
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
|
|
346247
|
+
validateContext: callbackContext,
|
|
345530
346248
|
liveBrowserHandle,
|
|
345531
346249
|
runNativeTest: runNativeWithLiveBrowser
|
|
345532
346250
|
};
|
|
@@ -345731,6 +346449,13 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345731
346449
|
}
|
|
345732
346450
|
return;
|
|
345733
346451
|
}
|
|
346452
|
+
ctx.onCredentialUpdate = async (input) => postCredentialRotation(
|
|
346453
|
+
opts.serverUrl,
|
|
346454
|
+
headers,
|
|
346455
|
+
run2.runId,
|
|
346456
|
+
{ ...input, actor: "ai-explorer" },
|
|
346457
|
+
run2.claimEpoch
|
|
346458
|
+
);
|
|
345734
346459
|
log.info(`Discovery (${ctx.mode}) on ${ctx.baseUrl}`);
|
|
345735
346460
|
const { push: pushAudit, drain: drainAudit } = createAuditFlusher(
|
|
345736
346461
|
`${opts.serverUrl}/api/runner/runs/${run2.runId}/ai-audit-batch`,
|
|
@@ -346433,7 +347158,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346433
347158
|
featureDescription: ctx.featureDescription,
|
|
346434
347159
|
featurePages: ctx.featurePages,
|
|
346435
347160
|
appBrief: ctx.appBrief,
|
|
346436
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" })
|
|
347161
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" }, run2.claimEpoch)
|
|
346437
347162
|
});
|
|
346438
347163
|
if (logBuffer.length > 0) {
|
|
346439
347164
|
postCaptureLog(opts.serverUrl, headers, run2.runId, ctx.jobId, truncateTextTail(logBuffer.join("\n"), DISCOVERY_LOG_POST_MAX_CHARS, "capture log"));
|
|
@@ -346678,7 +347403,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346678
347403
|
}
|
|
346679
347404
|
return verify;
|
|
346680
347405
|
};
|
|
346681
|
-
return await (0,
|
|
347406
|
+
return await (0, import_runner_core14.executeMobileLiveHeal)({
|
|
346682
347407
|
steps: a.steps,
|
|
346683
347408
|
target: a.target,
|
|
346684
347409
|
testVariables: a.testVariables,
|
|
@@ -346695,7 +347420,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346695
347420
|
}
|
|
346696
347421
|
};
|
|
346697
347422
|
log.info("\u{1FA7A} Mobile AI heal \u2014 Phase 1 (script) \u2192 Phase 2 (live device, lazy)...");
|
|
346698
|
-
const heal = await (0,
|
|
347423
|
+
const heal = await (0, import_runner_core14.executeMobileTestWithHealing)(
|
|
346699
347424
|
mobileSteps,
|
|
346700
347425
|
healTarget,
|
|
346701
347426
|
{
|
|
@@ -346708,7 +347433,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346708
347433
|
healerOptions
|
|
346709
347434
|
);
|
|
346710
347435
|
const finalRes = nativeVerify.last;
|
|
346711
|
-
const outcome = (0,
|
|
347436
|
+
const outcome = (0, import_runner_core14.computeMobileHealOutcome)({
|
|
346712
347437
|
heal,
|
|
346713
347438
|
finalError: finalRes?.error ?? null,
|
|
346714
347439
|
reproduceError,
|
|
@@ -346854,26 +347579,77 @@ ${result2.logs}`);
|
|
|
346854
347579
|
}
|
|
346855
347580
|
return;
|
|
346856
347581
|
}
|
|
346857
|
-
const
|
|
346858
|
-
|
|
346859
|
-
|
|
346860
|
-
|
|
346861
|
-
|
|
346862
|
-
|
|
347582
|
+
const projectId = run2.projectId || opts.projectId;
|
|
347583
|
+
const authStateExpected = shouldUseAuthState(run2.tags, run2.playwrightCode);
|
|
347584
|
+
const captureAuth = shouldCaptureAuthState(run2.tags, run2.playwrightCode);
|
|
347585
|
+
const authPaths = authStateExpected || captureAuth ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
347586
|
+
projectId,
|
|
347587
|
+
serverUrl: opts.serverUrl,
|
|
346863
347588
|
baseUrl: run2.baseUrl,
|
|
346864
347589
|
testVariables: run2.testVariables,
|
|
346865
|
-
|
|
346866
|
-
|
|
346867
|
-
|
|
346868
|
-
|
|
346869
|
-
|
|
347590
|
+
authToken: opts.authToken
|
|
347591
|
+
}, run2.runId) : void 0;
|
|
347592
|
+
let releaseAuthLock;
|
|
347593
|
+
let storageStatePath;
|
|
347594
|
+
if (authPaths && authStateExpected) {
|
|
347595
|
+
releaseAuthLock = await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
|
|
347596
|
+
storageStatePath = (0, import_runner_core12.getValidLocalAuthState)(authPaths.cachePath);
|
|
347597
|
+
if (storageStatePath || !captureAuth) {
|
|
347598
|
+
releaseAuthLock();
|
|
347599
|
+
releaseAuthLock = void 0;
|
|
347600
|
+
}
|
|
347601
|
+
if (!storageStatePath && !captureAuth && sharedAuthInjectEnabled()) {
|
|
347602
|
+
log.info(`Waiting for the auth producer to warm project ${projectId}`);
|
|
347603
|
+
storageStatePath = await (0, import_runner_core12.waitForLocalAuthStateProducer)(
|
|
347604
|
+
authPaths.cachePath,
|
|
347605
|
+
authPaths.lockKey,
|
|
347606
|
+
2e3,
|
|
347607
|
+
void 0,
|
|
347608
|
+
100,
|
|
347609
|
+
authPaths.lockPath
|
|
347610
|
+
);
|
|
346870
347611
|
}
|
|
346871
|
-
}
|
|
347612
|
+
}
|
|
347613
|
+
if (storageStatePath) {
|
|
347614
|
+
log.info(`Using auth state for project ${projectId}`);
|
|
347615
|
+
}
|
|
347616
|
+
const runAuthPath = captureAuth ? authPaths?.capturePath : void 0;
|
|
347617
|
+
const result = await (async () => {
|
|
347618
|
+
try {
|
|
347619
|
+
return await runNativeWithLiveBrowser({
|
|
347620
|
+
playwrightCode: run2.playwrightCode,
|
|
347621
|
+
baseUrl: run2.baseUrl,
|
|
347622
|
+
testVariables: run2.testVariables,
|
|
347623
|
+
authStateCapturePath: runAuthPath,
|
|
347624
|
+
storageStatePath,
|
|
347625
|
+
validateContext: callbackContext
|
|
347626
|
+
});
|
|
347627
|
+
} finally {
|
|
347628
|
+
if (runAuthPath && authPaths) {
|
|
347629
|
+
if ((0, import_runner_core12.getValidLocalAuthState)(runAuthPath)) {
|
|
347630
|
+
const releasePromotionLock = releaseAuthLock ? void 0 : await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
|
|
347631
|
+
try {
|
|
347632
|
+
const captured = (0, import_runner_core12.promoteLocalAuthStateCapture)(
|
|
347633
|
+
runAuthPath,
|
|
347634
|
+
authPaths.cachePath,
|
|
347635
|
+
!!storageStatePath,
|
|
347636
|
+
releaseAuthLock ?? releasePromotionLock
|
|
347637
|
+
);
|
|
347638
|
+
if (captured) log.info(`Captured auth state for project ${projectId}`);
|
|
347639
|
+
} finally {
|
|
347640
|
+
releasePromotionLock?.();
|
|
347641
|
+
}
|
|
347642
|
+
}
|
|
347643
|
+
(0, import_runner_core12.discardLocalAuthStateCapture)(runAuthPath);
|
|
347644
|
+
}
|
|
347645
|
+
releaseAuthLock?.();
|
|
347646
|
+
}
|
|
347647
|
+
})();
|
|
346872
347648
|
const duration = Date.now() - startTime;
|
|
346873
347649
|
let runStatus = result.passed ? "PASSED" : "FAILED";
|
|
346874
347650
|
let runError = result.error;
|
|
346875
347651
|
if (!result.passed) {
|
|
346876
|
-
const rlDetection = (0,
|
|
347652
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
346877
347653
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
346878
347654
|
tags: run2.tags ?? [],
|
|
346879
347655
|
error: result.error,
|
|
@@ -346934,6 +347710,12 @@ async function startRunner(opts) {
|
|
|
346934
347710
|
process.env.AUTH_TOKEN = authToken;
|
|
346935
347711
|
process.env.RUNNER_TOKEN = authToken;
|
|
346936
347712
|
if (projectId) process.env.PROJECT_ID = projectId;
|
|
347713
|
+
(0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir());
|
|
347714
|
+
const authCleanupTimer = setInterval(
|
|
347715
|
+
() => (0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir()),
|
|
347716
|
+
import_runner_core12.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS
|
|
347717
|
+
);
|
|
347718
|
+
authCleanupTimer.unref?.();
|
|
346937
347719
|
const headers = buildHeaders(authToken, projectId);
|
|
346938
347720
|
console.log("");
|
|
346939
347721
|
console.log(`${C2.bold}${C2.cyan}validate.qa Local Runner${C2.reset}`);
|
|
@@ -347042,7 +347824,7 @@ async function startRunner(opts) {
|
|
|
347042
347824
|
capture: envInt("MAX_CAPTURE", DEFAULT_TYPE_LIMITS.capture),
|
|
347043
347825
|
run: envInt("MAX_RUN", DEFAULT_TYPE_LIMITS.run)
|
|
347044
347826
|
};
|
|
347045
|
-
const RESOURCE_GOVERNOR_OPTIONS = (0,
|
|
347827
|
+
const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core13.createResourceGovernorOptions)({
|
|
347046
347828
|
isCloud: IS_CLOUD,
|
|
347047
347829
|
maxConcurrent: MAX_CONCURRENT,
|
|
347048
347830
|
typeLimits: TYPE_LIMITS
|
|
@@ -347210,8 +347992,8 @@ async function startRunner(opts) {
|
|
|
347210
347992
|
android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
|
|
347211
347993
|
} : { web: true, ios: false, android: false };
|
|
347212
347994
|
const bridge = enableMobile ? getBridgeState() : void 0;
|
|
347213
|
-
const resourceStatus = (0,
|
|
347214
|
-
const processInventory = await (0,
|
|
347995
|
+
const resourceStatus = (0, import_runner_core13.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
|
|
347996
|
+
const processInventory = await (0, import_runner_core13.getBrowserProcessInventory)();
|
|
347215
347997
|
lastInventoryPids = new Set(processInventory.processes.map((p) => p.pid));
|
|
347216
347998
|
lastInventoryKinds.clear();
|
|
347217
347999
|
for (const p of processInventory.processes) lastInventoryKinds.set(p.pid, p.kind);
|
|
@@ -347241,7 +348023,7 @@ async function startRunner(opts) {
|
|
|
347241
348023
|
resourceSnapshot: resourceStatus.snapshot,
|
|
347242
348024
|
resourceGovernor: resourceStatus.governor,
|
|
347243
348025
|
processInventory,
|
|
347244
|
-
aiQueue: (0,
|
|
348026
|
+
aiQueue: (0, import_runner_core13.getRunnerAIQueueStats)(),
|
|
347245
348027
|
liveBrowsers: import_runner_core11.liveBrowserRegistry.snapshotForHeartbeat(),
|
|
347246
348028
|
// Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
|
|
347247
348029
|
// Cached & cheap — see getCachedDoctor above.
|
|
@@ -347352,7 +348134,7 @@ async function startRunner(opts) {
|
|
|
347352
348134
|
let pidsWatchdogWarned = false;
|
|
347353
348135
|
const pidsWatchdog = setInterval(() => {
|
|
347354
348136
|
if (shuttingDown) return;
|
|
347355
|
-
const snapshot = (0,
|
|
348137
|
+
const snapshot = (0, import_runner_core13.getResourceSnapshot)();
|
|
347356
348138
|
const pct = snapshot.pidsPct;
|
|
347357
348139
|
if (pct == null || !Number.isFinite(pct)) return;
|
|
347358
348140
|
if (pct >= PIDS_RECYCLE_PCT) {
|
|
@@ -347392,7 +348174,7 @@ async function startRunner(opts) {
|
|
|
347392
348174
|
log.dim(` Deferred ${run2.runId} (project ${runProject} at capacity ${getActiveForProject(runProject)}/${projectLimit})`);
|
|
347393
348175
|
continue;
|
|
347394
348176
|
}
|
|
347395
|
-
const admission = (0,
|
|
348177
|
+
const admission = (0, import_runner_core13.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
|
|
347396
348178
|
mode: runMode,
|
|
347397
348179
|
activeRuns,
|
|
347398
348180
|
activeByType: Object.fromEntries(activeByType),
|
|
@@ -347470,9 +348252,9 @@ async function startRunner(opts) {
|
|
|
347470
348252
|
leasedDeviceId = freeDeviceId;
|
|
347471
348253
|
leasedDeviceIds.set(freeDeviceId, run2.runId);
|
|
347472
348254
|
}
|
|
347473
|
-
let
|
|
348255
|
+
let claimEpoch = false;
|
|
347474
348256
|
try {
|
|
347475
|
-
|
|
348257
|
+
claimEpoch = await claimRun(serverUrl, headers, run2.runId);
|
|
347476
348258
|
} catch (err) {
|
|
347477
348259
|
if (leasedDeviceId) {
|
|
347478
348260
|
leasedDeviceIds.delete(leasedDeviceId);
|
|
@@ -347480,7 +348262,7 @@ async function startRunner(opts) {
|
|
|
347480
348262
|
}
|
|
347481
348263
|
throw err;
|
|
347482
348264
|
}
|
|
347483
|
-
if (
|
|
348265
|
+
if (claimEpoch === false) {
|
|
347484
348266
|
log.dim(` Skipped ${run2.runId} (already claimed)`);
|
|
347485
348267
|
if (leasedDeviceId) {
|
|
347486
348268
|
leasedDeviceIds.delete(leasedDeviceId);
|
|
@@ -347488,6 +348270,7 @@ async function startRunner(opts) {
|
|
|
347488
348270
|
}
|
|
347489
348271
|
continue;
|
|
347490
348272
|
}
|
|
348273
|
+
run2.claimEpoch = claimEpoch;
|
|
347491
348274
|
activeRuns++;
|
|
347492
348275
|
incrementType(runMode);
|
|
347493
348276
|
incrementProject(runProject);
|