@validate.qa/runner 1.0.41 → 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.
Files changed (3) hide show
  1. package/dist/cli.js +931 -132
  2. package/dist/cli.mjs +931 -132
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1583,6 +1583,9 @@ var init_types = __esm({
1583
1583
  });
1584
1584
 
1585
1585
  // ../shared/dist/constants.js
1586
+ function isEmailFromIdentity(value) {
1587
+ return typeof value === "string" && EMAIL_FROM_IDENTITIES.includes(value);
1588
+ }
1586
1589
  function isValidMobileAppId(appId) {
1587
1590
  return appId.length > 0 && appId.length <= MOBILE_APP_ID_MAX_LENGTH && MOBILE_APP_ID_PATTERN.test(appId);
1588
1591
  }
@@ -1641,13 +1644,22 @@ function escapeEnvValue(value) {
1641
1644
  const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
1642
1645
  return `"${escaped}"`;
1643
1646
  }
1644
- var API_PREFIX, SUPPORT_EMAIL, PLATFORM_ADMIN_EMAIL, AUDIO_CHUNK_DURATION_MS, ACTION_BATCH_INTERVAL_MS, ACTION_BATCH_MAX_SIZE, MOBILE_SCREENSHOT_INTERVAL_MS, MOBILE_ACTION_BATCH_MAX_SIZE, MOBILE_BRIDGE_PORT, MOBILE_SNAPSHOT_MAX_SIZE_BYTES, MOBILE_APP_ID_MAX_LENGTH, MOBILE_APP_ID_PATTERN, MOBILE_FOCUS_SETTLE_MS, MOBILE_POST_ACTION_SETTLE_MS, MOBILE_COLD_BOOT_SETTLE_MS, MOBILE_DEFAULT_SWIPE_DISTANCE, MOBILE_MAX_SWIPE_DISTANCE, SELECTOR_PRIORITIES, MOBILE_STEP_TYPES, BLOCKED_ENV_VARS, SPAWNED_ENV_ALLOWLIST;
1647
+ var API_PREFIX, SUPPORT_EMAIL, PLATFORM_ADMIN_EMAIL, NOREPLY_EMAIL, EMAIL_FROM_IDENTITIES, ADMIN_EMAIL_LIMITS, AUDIO_CHUNK_DURATION_MS, ACTION_BATCH_INTERVAL_MS, ACTION_BATCH_MAX_SIZE, MOBILE_SCREENSHOT_INTERVAL_MS, MOBILE_ACTION_BATCH_MAX_SIZE, MOBILE_BRIDGE_PORT, MOBILE_SNAPSHOT_MAX_SIZE_BYTES, MOBILE_APP_ID_MAX_LENGTH, MOBILE_APP_ID_PATTERN, MOBILE_FOCUS_SETTLE_MS, MOBILE_POST_ACTION_SETTLE_MS, MOBILE_COLD_BOOT_SETTLE_MS, MOBILE_DEFAULT_SWIPE_DISTANCE, MOBILE_MAX_SWIPE_DISTANCE, SELECTOR_PRIORITIES, MOBILE_STEP_TYPES, BLOCKED_ENV_VARS, SPAWNED_ENV_ALLOWLIST;
1645
1648
  var init_constants = __esm({
1646
1649
  "../shared/dist/constants.js"() {
1647
1650
  "use strict";
1648
1651
  API_PREFIX = "/api";
1649
1652
  SUPPORT_EMAIL = "support@validate.qa";
1650
1653
  PLATFORM_ADMIN_EMAIL = "admin@validate.qa";
1654
+ NOREPLY_EMAIL = "noreply@validate.qa";
1655
+ EMAIL_FROM_IDENTITIES = ["noreply", "support", "admin"];
1656
+ ADMIN_EMAIL_LIMITS = {
1657
+ recipientsPerSend: 100,
1658
+ recipientNameLength: 200,
1659
+ subjectLength: 200,
1660
+ bodyLength: 5e4,
1661
+ templateNameLength: 120
1662
+ };
1651
1663
  AUDIO_CHUNK_DURATION_MS = 3e4;
1652
1664
  ACTION_BATCH_INTERVAL_MS = 2e3;
1653
1665
  ACTION_BATCH_MAX_SIZE = 10;
@@ -1744,7 +1756,18 @@ var init_constants = __esm({
1744
1756
  "MINIMAX_API_KEY",
1745
1757
  "ANTHROPIC_API_KEY",
1746
1758
  "NVIDIA_API_KEY",
1747
- "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"
1748
1771
  ]);
1749
1772
  SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
1750
1773
  // Runtime essentials - Node.js and Playwright need these to function
@@ -5012,12 +5035,23 @@ var init_voice_config = __esm({
5012
5035
  }
5013
5036
  });
5014
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
+
5015
5048
  // ../shared/dist/index.js
5016
5049
  var dist_exports = {};
5017
5050
  __export(dist_exports, {
5018
5051
  ACTION_BATCH_INTERVAL_MS: () => ACTION_BATCH_INTERVAL_MS,
5019
5052
  ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
5020
5053
  ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
5054
+ ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
5021
5055
  AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
5022
5056
  API_PREFIX: () => API_PREFIX,
5023
5057
  AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
@@ -5031,6 +5065,7 @@ __export(dist_exports, {
5031
5065
  DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
5032
5066
  DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
5033
5067
  DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
5068
+ EMAIL_FROM_IDENTITIES: () => EMAIL_FROM_IDENTITIES,
5034
5069
  ENVIRONMENTAL_ERROR_PREFIXES: () => ENVIRONMENTAL_ERROR_PREFIXES,
5035
5070
  LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
5036
5071
  MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
@@ -5049,6 +5084,7 @@ __export(dist_exports, {
5049
5084
  MOBILE_SCREENSHOT_INTERVAL_MS: () => MOBILE_SCREENSHOT_INTERVAL_MS,
5050
5085
  MOBILE_SNAPSHOT_MAX_SIZE_BYTES: () => MOBILE_SNAPSHOT_MAX_SIZE_BYTES,
5051
5086
  MOBILE_STEP_TYPES: () => MOBILE_STEP_TYPES,
5087
+ NOREPLY_EMAIL: () => NOREPLY_EMAIL,
5052
5088
  PLAN_DEFINITIONS: () => PLAN_DEFINITIONS,
5053
5089
  PLAN_ORDER: () => PLAN_ORDER,
5054
5090
  PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
@@ -5125,12 +5161,14 @@ __export(dist_exports, {
5125
5161
  isCreditPackAmountCents: () => isCreditPackAmountCents,
5126
5162
  isDbPricingPlan: () => isDbPricingPlan,
5127
5163
  isEditableMobileClassName: () => isEditableMobileClassName,
5164
+ isEmailFromIdentity: () => isEmailFromIdentity,
5128
5165
  isEnvironmentalError: () => isEnvironmentalError,
5129
5166
  isMobilePlatform: () => isMobilePlatform,
5130
5167
  isPricingPlan: () => isPricingPlan,
5131
5168
  isRunnableStep: () => isRunnableStep,
5132
5169
  isSafeDeepLink: () => isSafeDeepLink,
5133
5170
  isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
5171
+ isSharedAuthInjectEnabled: () => isSharedAuthInjectEnabled,
5134
5172
  isSttModelForProvider: () => isSttModelForProvider,
5135
5173
  isTestReadyForPlatform: () => isTestReadyForPlatform,
5136
5174
  isTrialExpired: () => isTrialExpired,
@@ -5197,6 +5235,7 @@ var init_dist = __esm({
5197
5235
  init_test_counting();
5198
5236
  init_origin_domain();
5199
5237
  init_voice_config();
5238
+ init_feature_flags();
5200
5239
  }
5201
5240
  });
5202
5241
 
@@ -10660,7 +10699,7 @@ var require_transport = __commonJS({
10660
10699
  "../../node_modules/pino/lib/transport.js"(exports2, module2) {
10661
10700
  "use strict";
10662
10701
  var { createRequire } = require("module");
10663
- var { existsSync: existsSync4 } = require("fs");
10702
+ var { existsSync: existsSync3 } = require("fs");
10664
10703
  var getCallers = require_caller();
10665
10704
  var { join: join7, isAbsolute, sep } = require("path");
10666
10705
  var { fileURLToPath } = require("url");
@@ -10734,7 +10773,7 @@ var require_transport = __commonJS({
10734
10773
  return false;
10735
10774
  }
10736
10775
  }
10737
- return isAbsolute(path) && !existsSync4(path);
10776
+ return isAbsolute(path) && !existsSync3(path);
10738
10777
  }
10739
10778
  function stripQuotes(value) {
10740
10779
  const first = value[0];
@@ -76149,6 +76188,126 @@ export const test = base.extend({
76149
76188
  }
76150
76189
  });
76151
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
+
76152
76311
  // ../runner-core/dist/services/playwright-native.js
76153
76312
  var require_playwright_native = __commonJS({
76154
76313
  "../runner-core/dist/services/playwright-native.js"(exports2) {
@@ -76193,6 +76352,7 @@ var require_playwright_native = __commonJS({
76193
76352
  Object.defineProperty(exports2, "__esModule", { value: true });
76194
76353
  exports2.scrubHarRequestBody = exports2.buildOriginScopedApiFixtures = exports2.isAppOriginRequest = exports2.resolveApiAuthHeaders = exports2.parseApiAuthHeaders = void 0;
76195
76354
  exports2.executeTestViaNativePlaywright = executeTestViaNativePlaywright2;
76355
+ exports2.buildNativePlaywrightChildEnv = buildNativePlaywrightChildEnv;
76196
76356
  exports2.parseJsonResults = parseJsonResults2;
76197
76357
  exports2.parseHarToApiSummary = parseHarToApiSummary;
76198
76358
  var promises_1 = require("fs/promises");
@@ -76204,6 +76364,7 @@ var require_playwright_native = __commonJS({
76204
76364
  var api_call_redaction_js_1 = require_api_call_redaction();
76205
76365
  var har_stream_js_1 = require_har_stream();
76206
76366
  var api_auth_headers_js_1 = require_api_auth_headers();
76367
+ var run_callback_proxy_js_1 = require_run_callback_proxy();
76207
76368
  var api_auth_headers_js_2 = require_api_auth_headers();
76208
76369
  Object.defineProperty(exports2, "parseApiAuthHeaders", { enumerable: true, get: function() {
76209
76370
  return api_auth_headers_js_2.parseApiAuthHeaders;
@@ -76226,7 +76387,7 @@ var require_playwright_native = __commonJS({
76226
76387
  var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
76227
76388
  var PW_CLI = require.resolve("@playwright/test/cli");
76228
76389
  async function executeTestViaNativePlaywright2(options) {
76229
- const { playwrightCode, baseUrl, testName, testVariables, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
76390
+ const { playwrightCode, baseUrl, testName, testVariables, authStateCapturePath, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
76230
76391
  const logs2 = [];
76231
76392
  const screenshots = [];
76232
76393
  logs2.push(`\u{1F3AD} Native Playwright starting`);
@@ -76236,6 +76397,7 @@ var require_playwright_native = __commonJS({
76236
76397
  logs2.push("\u26A0\uFE0F WARNING: baseUrl is empty - test will likely fail with navigation errors");
76237
76398
  }
76238
76399
  let runDir = null;
76400
+ let callbackProxy;
76239
76401
  try {
76240
76402
  runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
76241
76403
  await (0, promises_1.mkdir)(runDir, { recursive: true });
@@ -76495,35 +76657,51 @@ module.exports = {
76495
76657
  const configPath = (0, path_1.join)(runDir, "playwright.config.js");
76496
76658
  await (0, promises_1.writeFile)(configPath, config);
76497
76659
  const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
76498
- const envWithIsolation = {
76499
- ...testVariables ?? {},
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 = {
76500
76678
  ...isolation.env,
76501
- // persistCredential() callback envs — set only when the runner supplied
76502
- // validateContext. Without these the helper inside the test no-ops with
76503
- // a warning, which is the correct behavior when the test is being run
76504
- // manually (outside the validate.qa harness).
76505
- ...validateContext?.runId ? { VALIDATE_RUN_ID: validateContext.runId } : {},
76506
- ...validateContext?.serverUrl ? { VALIDATE_SERVER_URL: validateContext.serverUrl } : {},
76507
- ...validateContext?.authHeader ? { VALIDATE_RUNNER_AUTH: validateContext.authHeader } : {},
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
+ } : {},
76508
76687
  // Owning Fargate taskArn (a per-task constant set once at task boot in
76509
76688
  // execute-one.ts; absent on the local/Railway runner). Read from
76510
76689
  // process.env rather than validateContext since it's process-wide, not
76511
76690
  // per-run. Lets persistCredential() prove ownership to /credential.
76512
76691
  ...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
76692
+ ...authStateCapturePath ? { AUTH_FILE: authStateCapturePath } : {},
76513
76693
  ...liveBrowserStatePath ? { VALIDATE_LIVE_BROWSER_STATE_PATH: liveBrowserStatePath } : {},
76514
- // Shared-auth kill-switch (SHARED_AUTH_INJECT) must reach the child: the stamped
76515
- // __vqaLogin's early-publish POST is gated on it at RUNTIME. buildSandboxedEnv only
76516
- // copies an allowlist from process.env (SHARED_AUTH_INJECT is not on it), so without
76517
- // this forward the flag reads as undefined in the test and the early publish is dead
76518
- // 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.
76519
76697
  ...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
76520
76698
  };
76521
- delete envWithIsolation.API_AUTH_HEADERS;
76699
+ delete childTestVariables.API_AUTH_HEADERS;
76522
76700
  logs2.push("\u{1F680} Spawning playwright test...");
76523
76701
  const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
76524
76702
  let playResult;
76525
76703
  try {
76526
- playResult = await runPlaywright(configPath, envWithIsolation);
76704
+ playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv);
76527
76705
  } finally {
76528
76706
  await stopLiveBrowserPolling?.();
76529
76707
  }
@@ -76607,6 +76785,8 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
76607
76785
  error: msg
76608
76786
  };
76609
76787
  } finally {
76788
+ await callbackProxy?.close().catch(() => {
76789
+ });
76610
76790
  if (runDir) {
76611
76791
  await (0, promises_1.rm)(runDir, { recursive: true, force: true }).catch(() => {
76612
76792
  });
@@ -76658,9 +76838,9 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
76658
76838
  stopped = true;
76659
76839
  };
76660
76840
  }
76661
- function runPlaywright(configPath, extraEnv) {
76841
+ function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
76662
76842
  return new Promise((resolvePromise) => {
76663
- const sandboxedEnv = (0, shared_1.buildSandboxedEnv)(process.env, extraEnv);
76843
+ const sandboxedEnv = buildNativePlaywrightChildEnv(process.env, testVariables, trustedRuntimeEnv);
76664
76844
  const proc = (0, spawn_with_tree_kill_js_1.spawnDetachedGroup)(process.execPath, [PW_CLI, "test", "--config", configPath], {
76665
76845
  cwd: PACKAGE_DIR,
76666
76846
  env: sandboxedEnv,
@@ -76703,6 +76883,12 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
76703
76883
  });
76704
76884
  });
76705
76885
  }
76886
+ function buildNativePlaywrightChildEnv(processEnv, testVariables, trustedRuntimeEnv) {
76887
+ return {
76888
+ ...(0, shared_1.buildSandboxedEnv)(processEnv, testVariables),
76889
+ ...trustedRuntimeEnv ?? {}
76890
+ };
76891
+ }
76706
76892
  async function collectScreenshots(dir, screenshots) {
76707
76893
  let entries;
76708
76894
  try {
@@ -80424,10 +80610,12 @@ var require_auth_recipe = __commonJS({
80424
80610
  exports2.upsertAuthBlock = upsertAuthBlock;
80425
80611
  exports2.buildHealAuthStamp = buildHealAuthStamp;
80426
80612
  var crypto_1 = require("crypto");
80613
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
80427
80614
  var discovery_lint_flags_js_1 = require_discovery_lint_flags();
80428
- exports2.AUTH_MARKER_VERSION = "v1";
80615
+ exports2.AUTH_MARKER_VERSION = "v3";
80429
80616
  var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports2.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
80430
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())`;
80431
80619
  var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
80432
80620
  function computeRecipeHash(input) {
80433
80621
  const canonical = JSON.stringify({
@@ -80505,16 +80693,18 @@ var require_auth_recipe = __commonJS({
80505
80693
  if (opts?.warmGuard) {
80506
80694
  const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
80507
80695
  const loginRoute = recipe.route ?? recipe.preNavRoute;
80508
- lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
80696
+ lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
80697
+ lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
80509
80698
  if (recipe.authProof.kind === "url") {
80510
80699
  const pred = urlProofPredicateExpr(recipe.authProof.route);
80511
- lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
80512
- lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
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;`);
80513
80702
  } else if (loginRoute) {
80514
- lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
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;`);
80515
80704
  } else {
80516
- lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
80705
+ lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
80517
80706
  }
80707
+ lines.push(` }`);
80518
80708
  }
80519
80709
  lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
80520
80710
  for (const cmd of recipe.preNavCmds)
@@ -80527,11 +80717,11 @@ var require_auth_recipe = __commonJS({
80527
80717
  lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
80528
80718
  lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
80529
80719
  if (opts?.warmGuard) {
80530
- lines.push(` if (process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
80531
- lines.push(` if (process.env.SHARED_AUTH_INJECT === 'true' && process.env.VALIDATE_TASK_ARN && process.env.VALIDATE_SERVER_URL && process.env.VALIDATE_RUN_ID) {`);
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) {`);
80532
80722
  lines.push(` try {`);
80533
80723
  lines.push(` const __vqaState = await page.context().storageState();`);
80534
- 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 });`);
80535
80725
  lines.push(` } catch {}`);
80536
80726
  lines.push(` }`);
80537
80727
  }
@@ -80543,6 +80733,8 @@ var require_auth_recipe = __commonJS({
80543
80733
  "AUTH_FILE",
80544
80734
  "SHARED_AUTH_INJECT",
80545
80735
  "VALIDATE_RUN_ID",
80736
+ "VALIDATE_CLAIM_EPOCH",
80737
+ "VALIDATE_AUTH_CONFIG_HASH",
80546
80738
  "VALIDATE_TASK_ARN",
80547
80739
  "VALIDATE_RUNNER_AUTH",
80548
80740
  "VALIDATE_SERVER_URL"
@@ -80715,7 +80907,7 @@ ${tail}`;
80715
80907
  let result = stripAuthRegions(code);
80716
80908
  result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
80717
80909
  result = ensureExpectImported(result);
80718
- const warmGuard = process.env.SHARED_AUTH_INJECT === "true";
80910
+ const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
80719
80911
  result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
80720
80912
  result = ensureLoginCall(result);
80721
80913
  const n = countAuthRegions(result);
@@ -85845,9 +86037,9 @@ ${testOpen}`;
85845
86037
  basePath: options?.basePath
85846
86038
  });
85847
86039
  const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
85848
- const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process.env.SHARED_AUTH_INJECT === "true";
85849
- const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]);
85850
- const tags = authGuardStamped && !baseTags.includes("@auth-guarded") ? [...baseTags, "@auth-guarded"] : baseTags;
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;
85851
86043
  return {
85852
86044
  name: String(test.name || "Unnamed test").slice(0, 200),
85853
86045
  description: String(test.description || test.name || "").slice(0, 500),
@@ -99662,7 +99854,7 @@ ${lanes.join("\n")}
99662
99854
  return process.memoryUsage().heapUsed;
99663
99855
  },
99664
99856
  getFileSize(path) {
99665
- const stat = statSync2(path);
99857
+ const stat = statSync(path);
99666
99858
  if (stat == null ? void 0 : stat.isFile()) {
99667
99859
  return stat.size;
99668
99860
  }
@@ -99706,7 +99898,7 @@ ${lanes.join("\n")}
99706
99898
  }
99707
99899
  };
99708
99900
  return nodeSystem;
99709
- function statSync2(path) {
99901
+ function statSync(path) {
99710
99902
  try {
99711
99903
  return _fs.statSync(path, statSyncOptions);
99712
99904
  } catch {
@@ -99765,7 +99957,7 @@ ${lanes.join("\n")}
99765
99957
  activeSession.post("Profiler.stop", (err, { profile }) => {
99766
99958
  var _a;
99767
99959
  if (!err) {
99768
- if ((_a = statSync2(profilePath)) == null ? void 0 : _a.isDirectory()) {
99960
+ if ((_a = statSync(profilePath)) == null ? void 0 : _a.isDirectory()) {
99769
99961
  profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
99770
99962
  }
99771
99963
  try {
@@ -99885,7 +100077,7 @@ ${lanes.join("\n")}
99885
100077
  let stat;
99886
100078
  if (typeof dirent === "string" || dirent.isSymbolicLink()) {
99887
100079
  const name = combinePaths(path, entry);
99888
- stat = statSync2(name);
100080
+ stat = statSync(name);
99889
100081
  if (!stat) {
99890
100082
  continue;
99891
100083
  }
@@ -99909,7 +100101,7 @@ ${lanes.join("\n")}
99909
100101
  return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
99910
100102
  }
99911
100103
  function fileSystemEntryExists(path, entryKind) {
99912
- const stat = statSync2(path);
100104
+ const stat = statSync(path);
99913
100105
  if (!stat) {
99914
100106
  return false;
99915
100107
  }
@@ -99951,7 +100143,7 @@ ${lanes.join("\n")}
99951
100143
  }
99952
100144
  function getModifiedTime3(path) {
99953
100145
  var _a;
99954
- return (_a = statSync2(path)) == null ? void 0 : _a.mtime;
100146
+ return (_a = statSync(path)) == null ? void 0 : _a.mtime;
99955
100147
  }
99956
100148
  function setModifiedTime(path, time) {
99957
100149
  try {
@@ -308462,6 +308654,7 @@ var require_regression_runner = __commonJS({
308462
308654
  var chromium_isolation_js_1 = require_chromium_isolation();
308463
308655
  var browser_config_js_1 = require_browser_config();
308464
308656
  var spawn_with_tree_kill_js_1 = require_spawn_with_tree_kill();
308657
+ var run_callback_proxy_js_1 = require_run_callback_proxy();
308465
308658
  var SUITE_SPAWN_TIMEOUT_MS = 85 * 60 * 1e3;
308466
308659
  var PACKAGE_DIR = (0, path_1.resolve)(__dirname, "..", "..");
308467
308660
  var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
@@ -308476,6 +308669,7 @@ var require_regression_runner = __commonJS({
308476
308669
  const tarPath = (0, path_1.join)(RUNS_DIR, `regression-${runId}.tar.gz`);
308477
308670
  const startTime = Date.now();
308478
308671
  let logs2 = "";
308672
+ let callbackProxy;
308479
308673
  try {
308480
308674
  await (0, promises_1.mkdir)(extractDir, { recursive: true });
308481
308675
  const tarballUrl = `https://api.github.com/repos/${repoOwner}/${repoName}/tarball/${repoBranch}`;
@@ -308505,19 +308699,40 @@ var require_regression_runner = __commonJS({
308505
308699
  await (0, promises_1.writeFile)((0, path_1.join)(extractDir, ".env"), envContent, "utf-8");
308506
308700
  const jsonResultsPath = (0, path_1.join)(extractDir, "test-results", "results.json");
308507
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
+ }
308508
308719
  const runEnv = {
308509
308720
  ...(0, shared_1.buildSandboxedEnv)(process.env, testVariables),
308510
308721
  ...isolation.env,
308511
308722
  CI: "true",
308512
308723
  PLAYWRIGHT_JSON_OUTPUT_NAME: jsonResultsPath,
308513
- // Callback envs for tests/setup/credentials.ts → persistCredential.
308514
- // Tests that rotate a credential POST back to the server using these.
308515
- VALIDATE_RUN_ID: runId,
308516
- ...opts.validateServerUrl ? { VALIDATE_SERVER_URL: opts.validateServerUrl } : {},
308517
- ...opts.validateRunnerAuthHeader ? { VALIDATE_RUNNER_AUTH: opts.validateRunnerAuthHeader } : {},
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
+ } : {},
308518
308732
  // Owning Fargate taskArn (per-task constant; unset off-Fargate). Forwarded
308519
308733
  // so persistCredential() can prove ownership to the /credential fence.
308520
- ...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 } : {}
308521
308736
  };
308522
308737
  logs2 += `Running playwright --project=${opts.suiteProject ?? "suite"}...
308523
308738
  `;
@@ -308546,6 +308761,8 @@ var require_regression_runner = __commonJS({
308546
308761
  error: String(err)
308547
308762
  };
308548
308763
  } finally {
308764
+ await callbackProxy?.close().catch(() => {
308765
+ });
308549
308766
  (0, promises_1.rm)(extractDir, { recursive: true, force: true }).catch(() => {
308550
308767
  });
308551
308768
  await (0, browser_config_js_1.cleanupPlaywrightTempArtifacts)().catch(() => {
@@ -324789,6 +325006,455 @@ var require_resource_governor = __commonJS({
324789
325006
  }
324790
325007
  });
324791
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
+
324792
325458
  // ../../node_modules/adm-zip/util/constants.js
324793
325459
  var require_constants5 = __commonJS({
324794
325460
  "../../node_modules/adm-zip/util/constants.js"(exports2, module2) {
@@ -325102,7 +325768,7 @@ var require_utils2 = __commonJS({
325102
325768
  module2.exports = Utils;
325103
325769
  Utils.prototype.makeDir = function(folder) {
325104
325770
  const self = this;
325105
- function mkdirSync5(fpath) {
325771
+ function mkdirSync4(fpath) {
325106
325772
  let resolvedPath = fpath.split(self.sep)[0];
325107
325773
  fpath.split(self.sep).forEach(function(name) {
325108
325774
  if (!name || name.substr(-1, 1) === ":") return;
@@ -325120,7 +325786,7 @@ var require_utils2 = __commonJS({
325120
325786
  if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
325121
325787
  });
325122
325788
  }
325123
- mkdirSync5(folder);
325789
+ mkdirSync4(folder);
325124
325790
  };
325125
325791
  Utils.prototype.writeFileTo = function(path, content, overwrite, attr) {
325126
325792
  const self = this;
@@ -337044,6 +337710,8 @@ var require_dist2 = __commonJS({
337044
337710
  __exportStar(require_discover_agent(), exports2);
337045
337711
  __exportStar(require_browser_pool(), exports2);
337046
337712
  __exportStar(require_resource_governor(), exports2);
337713
+ __exportStar(require_local_auth_state(), exports2);
337714
+ __exportStar(require_run_callback_proxy(), exports2);
337047
337715
  __exportStar(require_discover_explorer(), exports2);
337048
337716
  __exportStar(require_discover_feature_explorer(), exports2);
337049
337717
  __exportStar(require_discover_planner(), exports2);
@@ -337320,7 +337988,7 @@ var require_package4 = __commonJS({
337320
337988
  "package.json"(exports2, module2) {
337321
337989
  module2.exports = {
337322
337990
  name: "@validate.qa/runner",
337323
- version: "1.0.41",
337991
+ version: "1.0.43",
337324
337992
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
337325
337993
  bin: {
337326
337994
  "validate-runner": "dist/cli.js"
@@ -337328,7 +337996,7 @@ var require_package4 = __commonJS({
337328
337996
  scripts: {
337329
337997
  build: "tsup",
337330
337998
  dev: "tsx src/cli.ts",
337331
- 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",
337332
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",
337333
338001
  prepublishOnly: "npm run build"
337334
338002
  },
@@ -337407,7 +338075,6 @@ function getConfigDir() {
337407
338075
  }
337408
338076
 
337409
338077
  // src/runner.ts
337410
- var import_fs5 = require("fs");
337411
338078
  var import_path4 = require("path");
337412
338079
  var import_os6 = require("os");
337413
338080
  var import_node_crypto2 = require("crypto");
@@ -337435,23 +338102,33 @@ var import_runner_core6 = __toESM(require_dist2());
337435
338102
 
337436
338103
  // src/runner.ts
337437
338104
  var import_runner_core11 = __toESM(require_dist2());
338105
+ var import_runner_core12 = __toESM(require_dist2());
337438
338106
 
337439
338107
  // src/services/auth-state.ts
338108
+ init_dist();
337440
338109
  var INLINE_LOGIN_TAG = "@inline-login";
337441
338110
  var GUARDED_LOGIN_TAG = "@auth-guarded";
337442
338111
  function sharedAuthInjectEnabled() {
337443
- return process.env.SHARED_AUTH_INJECT === "true";
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);
337444
338120
  }
337445
338121
  function hasInlineLoginSteps(playwrightCode) {
337446
338122
  if (!playwrightCode) return false;
337447
338123
  return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
337448
338124
  }
337449
338125
  function shouldUseAuthState(tags, playwrightCode) {
338126
+ if (!sharedAuthInjectEnabled()) return false;
337450
338127
  if (!Array.isArray(tags)) return false;
337451
338128
  if (tags.includes("@auth")) return false;
337452
338129
  if (tags.includes("@api")) return true;
337453
338130
  if (!tags.includes("@needs-auth")) return false;
337454
- if (sharedAuthInjectEnabled() && tags.includes(GUARDED_LOGIN_TAG)) return true;
338131
+ if (tags.includes(GUARDED_LOGIN_TAG) && hasGeneratedAuthGuard(playwrightCode)) return true;
337455
338132
  const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
337456
338133
  const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(playwrightCode);
337457
338134
  return !tags.includes(INLINE_LOGIN_TAG) && !hasLegacyInlineLogin;
@@ -344161,8 +344838,8 @@ function withMediaReferences(result, refs) {
344161
344838
 
344162
344839
  // src/runner.ts
344163
344840
  init_dist();
344164
- var import_runner_core12 = __toESM(require_dist2());
344165
344841
  var import_runner_core13 = __toESM(require_dist2());
344842
+ var import_runner_core14 = __toESM(require_dist2());
344166
344843
  function fingerprintTest(test) {
344167
344844
  const code = test.framework === "APPIUM" ? [test.appiumCode ?? "", JSON.stringify(test.steps ?? [])].join("\n--steps--\n") : test.playwrightCode ?? "";
344168
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);
@@ -344286,21 +344963,11 @@ var log = {
344286
344963
  warn: (msg) => console.log(`${C2.yellow}\u26A0${C2.reset} ${msg}`),
344287
344964
  dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
344288
344965
  };
344289
- var AUTH_MAX_AGE_MS = 60 * 60 * 1e3;
344290
344966
  function getRunsDir() {
344291
344967
  const dir = process.env.RUNS_DIR || (0, import_path4.join)((0, import_os6.homedir)(), ".validate.qa", "runs");
344292
- if (!(0, import_fs5.existsSync)(dir)) (0, import_fs5.mkdirSync)(dir, { recursive: true });
344968
+ (0, import_runner_core12.ensurePrivateDirectory)(dir);
344293
344969
  return dir;
344294
344970
  }
344295
- function authStatePath(projectId) {
344296
- return (0, import_path4.join)(getRunsDir(), `auth-state-${projectId || "default"}.json`);
344297
- }
344298
- function getValidAuthState(projectId) {
344299
- const path = authStatePath(projectId);
344300
- if (!(0, import_fs5.existsSync)(path)) return void 0;
344301
- const age = Date.now() - (0, import_fs5.statSync)(path).mtimeMs;
344302
- return age < AUTH_MAX_AGE_MS ? path : void 0;
344303
- }
344304
344971
  function buildHeaders(authToken, projectId) {
344305
344972
  return {
344306
344973
  "Content-Type": "application/json",
@@ -344349,7 +345016,13 @@ async function claimRun(serverUrl, headers, runId) {
344349
345016
  if (res.status === 429) return false;
344350
345017
  if (!res.ok) throw new Error(`Claim failed: ${res.status}`);
344351
345018
  const body = await res.json();
344352
- return body.claimed;
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;
344353
345026
  }
344354
345027
  async function postResult(serverUrl, headers, runId, result) {
344355
345028
  const MAX_ATTEMPTS = 3;
@@ -344517,12 +345190,12 @@ async function postCaptureResult(serverUrl, headers, runId, result) {
344517
345190
  });
344518
345191
  if (!res.ok) throw new Error(`Failed to post capture result: ${res.status}`);
344519
345192
  }
344520
- async function postCredentialRotation(serverUrl, headers, runId, body) {
345193
+ async function postCredentialRotation(serverUrl, headers, runId, body, claimEpoch) {
344521
345194
  try {
344522
345195
  const res = await fetch(`${serverUrl}/api/runner/runs/${runId}/credential`, {
344523
345196
  method: "POST",
344524
345197
  headers,
344525
- body: JSON.stringify(body),
345198
+ body: JSON.stringify({ ...body, ...claimEpoch !== void 0 ? { claimEpoch } : {} }),
344526
345199
  signal: AbortSignal.timeout(3e4)
344527
345200
  });
344528
345201
  if (!res.ok) {
@@ -344859,6 +345532,12 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344859
345532
  liveBrowserHandle,
344860
345533
  onLiveBrowserSnapshot: requestLiveBrowserHeartbeat
344861
345534
  });
345535
+ const callbackContext = {
345536
+ runId: run2.runId,
345537
+ serverUrl: opts.serverUrl,
345538
+ authHeader: headers.Authorization,
345539
+ claimEpoch: run2.claimEpoch
345540
+ };
344862
345541
  const startTime = Date.now();
344863
345542
  try {
344864
345543
  if (mobileLeaseError) {
@@ -344888,7 +345567,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344888
345567
  log.fail(`PRE-FLIGHT FAILED - ${preflightError}`);
344889
345568
  return;
344890
345569
  }
344891
- const preflight = await (0, import_runner_core12.resolveBaseUrlPreflight)(run2.baseUrl);
345570
+ const preflight = await (0, import_runner_core13.resolveBaseUrlPreflight)(run2.baseUrl);
344892
345571
  if (preflight.error) {
344893
345572
  await postResult(opts.serverUrl, headers, run2.runId, { status: "ERROR", error: preflight.error });
344894
345573
  log.fail(`PRE-FLIGHT FAILED - ${preflight.error}`);
@@ -344905,31 +345584,72 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344905
345584
  log.fail("ERROR - No auth setup code");
344906
345585
  return;
344907
345586
  }
344908
- const runAuthPath = authStatePath(run2.projectId || opts.projectId || "default");
344909
- log.info(`Auth setup: saving session state to ${runAuthPath}`);
344910
- const result2 = await runNativeWithLiveBrowser({
344911
- playwrightCode: run2.playwrightCode,
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,
344912
345601
  baseUrl: run2.baseUrl,
344913
- testName: "auth-setup",
344914
- testVariables: { ...run2.testVariables, AUTH_FILE: runAuthPath },
344915
- validateContext: {
344916
- runId: run2.runId,
344917
- serverUrl: opts.serverUrl,
344918
- authHeader: headers.Authorization
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;
344919
345620
  }
344920
- });
344921
- const duration2 = Date.now() - startTime;
344922
- await postResult(opts.serverUrl, headers, run2.runId, {
344923
- status: result2.passed ? "PASSED" : "FAILED",
344924
- apiCalls: result2.apiCalls,
344925
- logs: result2.logs,
344926
- error: result2.error,
344927
- duration: duration2
344928
- });
344929
- log[result2.passed ? "ok" : "fail"](
344930
- `Auth setup ${result2.passed ? "PASSED" : "FAILED"} (${duration2}ms)${result2.error ? ` - ${result2.error}` : ""}`
344931
- );
344932
- return;
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
+ }
344933
345653
  }
344934
345654
  if (runMode === "batch-heal") {
344935
345655
  const tests = run2.batchHealTests ?? [];
@@ -344948,7 +345668,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344948
345668
  const batchResult = await (0, import_runner_core10.executeGrokPerBatchHeal)(tests, run2.baseUrl, {
344949
345669
  testVariables: run2.testVariables,
344950
345670
  runNativeTest: runNativeWithLiveBrowser,
344951
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization },
345671
+ validateContext: callbackContext,
344952
345672
  onLog: healLogStream.onLog
344953
345673
  });
344954
345674
  await healLogStream.flushNow();
@@ -344984,6 +345704,15 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
344984
345704
  );
344985
345705
  const describeKind = isSerialDescribe ? "serial" : "non-serial";
344986
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");
344987
345716
  const stickyAppBugs = /* @__PURE__ */ new Map();
344988
345717
  const lastChangeByTitle = /* @__PURE__ */ new Map();
344989
345718
  let currentError = run2.lastFailureError ?? "Test failed";
@@ -345049,7 +345778,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
345049
345778
  playwrightCode: lastHealedCode,
345050
345779
  baseUrl: run2.baseUrl,
345051
345780
  testVariables: run2.testVariables,
345052
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization }
345781
+ storageStatePath: apiHealStorageStatePath,
345782
+ validateContext: callbackContext
345053
345783
  });
345054
345784
  if (finalVerifyResult.passed) {
345055
345785
  log.ok(` Attempt ${attempt} terminal verification PASSED (${bugCount} app_bug, ${passCount} pass)`);
@@ -345069,7 +345799,9 @@ ${finalVerifyResult.logs ?? ""}`;
345069
345799
  finalVerifyResult = await runNativeWithLiveBrowser({
345070
345800
  playwrightCode: lastHealedCode,
345071
345801
  baseUrl: run2.baseUrl,
345072
- testVariables: run2.testVariables
345802
+ testVariables: run2.testVariables,
345803
+ storageStatePath: apiHealStorageStatePath,
345804
+ validateContext: callbackContext
345073
345805
  });
345074
345806
  const stepResults = finalVerifyResult.stepResults ?? [];
345075
345807
  const nonAppBugFails = stepResults.filter(
@@ -345119,7 +345851,8 @@ ${finalVerifyResult.logs ?? ""}`;
345119
345851
  playwrightCode: lastHealedCode,
345120
345852
  baseUrl: run2.baseUrl,
345121
345853
  testVariables: run2.testVariables,
345122
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization }
345854
+ storageStatePath: apiHealStorageStatePath,
345855
+ validateContext: callbackContext
345123
345856
  });
345124
345857
  if (finalVerifyResult.passed) {
345125
345858
  log.ok(` Attempt ${attempt} verification PASSED`);
@@ -345172,7 +345905,7 @@ ${finalVerifyResult.logs ?? ""}`;
345172
345905
  if (classifyErrorMessage(composedError ?? null) === "config_issue") {
345173
345906
  apiHealStatus = "ERROR";
345174
345907
  } else {
345175
- const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
345908
+ const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
345176
345909
  testName: run2.testName ?? run2.testCaseId ?? void 0,
345177
345910
  tags: run2.tags ?? [],
345178
345911
  error: composedError ?? finalVerifyResult?.error ?? null,
@@ -345269,8 +346002,8 @@ ${finalVerifyResult.logs ?? ""}`;
345269
346002
  lastHealAttempt: run2.lastHealAttempt ?? null,
345270
346003
  lastHealedCode: run2.lastHealedCode ?? null,
345271
346004
  failureScreenshot: run2.failureScreenshot ?? null,
345272
- onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
345273
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization },
346005
+ onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
346006
+ validateContext: callbackContext,
345274
346007
  liveBrowserHandle,
345275
346008
  runNativeTest: runNativeWithLiveBrowser
345276
346009
  };
@@ -345312,7 +346045,7 @@ ${finalVerifyResult.logs ?? ""}`;
345312
346045
  let healStatus = result2.passed ? "PASSED" : isConfigIssue ? "ERROR" : "FAILED";
345313
346046
  let healError = result2.error;
345314
346047
  if (!result2.passed && !isConfigIssue) {
345315
- const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
346048
+ const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
345316
346049
  testName: run2.testName ?? run2.testCaseId ?? void 0,
345317
346050
  tags: run2.tags ?? [],
345318
346051
  error: result2.error,
@@ -345383,7 +346116,8 @@ ${finalVerifyResult.logs ?? ""}`;
345383
346116
  // Allow generated `persistCredential(...)` calls inside the suite to
345384
346117
  // POST back to the server with the runner's bearer token.
345385
346118
  validateServerUrl: opts.serverUrl,
345386
- validateRunnerAuthHeader: headers.Authorization
346119
+ validateRunnerAuthHeader: headers.Authorization,
346120
+ claimEpoch: run2.claimEpoch
345387
346121
  });
345388
346122
  const overall = result2.error && result2.testResults.length === 0 ? "ERROR" : result2.passed ? "PASSED" : "FAILED";
345389
346123
  const rr = await fetch(`${opts.serverUrl}/api/runner/runs/${run2.runId}/regression-result`, {
@@ -345438,7 +346172,7 @@ ${finalVerifyResult.logs ?? ""}`;
345438
346172
  playwrightCode: healedCode,
345439
346173
  baseUrl: run2.baseUrl,
345440
346174
  testVariables: run2.testVariables,
345441
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization }
346175
+ validateContext: callbackContext
345442
346176
  });
345443
346177
  results.push({
345444
346178
  testCaseId: failedTest.testCaseId,
@@ -345501,8 +346235,8 @@ ${finalVerifyResult.logs ?? ""}`;
345501
346235
  onAICall: healAudit,
345502
346236
  modelOverride: run2.healerModel,
345503
346237
  consecutiveFails: failedTest.consecutiveFails,
345504
- onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
345505
- validateContext: { runId: run2.runId, serverUrl: opts.serverUrl, authHeader: headers.Authorization },
346238
+ onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
346239
+ validateContext: callbackContext,
345506
346240
  liveBrowserHandle,
345507
346241
  runNativeTest: runNativeWithLiveBrowser
345508
346242
  };
@@ -345707,6 +346441,13 @@ ${finalVerifyResult.logs ?? ""}`;
345707
346441
  }
345708
346442
  return;
345709
346443
  }
346444
+ ctx.onCredentialUpdate = async (input) => postCredentialRotation(
346445
+ opts.serverUrl,
346446
+ headers,
346447
+ run2.runId,
346448
+ { ...input, actor: "ai-explorer" },
346449
+ run2.claimEpoch
346450
+ );
345710
346451
  log.info(`Discovery (${ctx.mode}) on ${ctx.baseUrl}`);
345711
346452
  const { push: pushAudit, drain: drainAudit } = createAuditFlusher(
345712
346453
  `${opts.serverUrl}/api/runner/runs/${run2.runId}/ai-audit-batch`,
@@ -346409,7 +347150,7 @@ ${finalVerifyResult.logs ?? ""}`;
346409
347150
  featureDescription: ctx.featureDescription,
346410
347151
  featurePages: ctx.featurePages,
346411
347152
  appBrief: ctx.appBrief,
346412
- 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)
346413
347154
  });
346414
347155
  if (logBuffer.length > 0) {
346415
347156
  postCaptureLog(opts.serverUrl, headers, run2.runId, ctx.jobId, truncateTextTail(logBuffer.join("\n"), DISCOVERY_LOG_POST_MAX_CHARS, "capture log"));
@@ -346654,7 +347395,7 @@ ${finalVerifyResult.logs ?? ""}`;
346654
347395
  }
346655
347396
  return verify;
346656
347397
  };
346657
- return await (0, import_runner_core13.executeMobileLiveHeal)({
347398
+ return await (0, import_runner_core14.executeMobileLiveHeal)({
346658
347399
  steps: a.steps,
346659
347400
  target: a.target,
346660
347401
  testVariables: a.testVariables,
@@ -346671,7 +347412,7 @@ ${finalVerifyResult.logs ?? ""}`;
346671
347412
  }
346672
347413
  };
346673
347414
  log.info("\u{1FA7A} Mobile AI heal \u2014 Phase 1 (script) \u2192 Phase 2 (live device, lazy)...");
346674
- const heal = await (0, import_runner_core13.executeMobileTestWithHealing)(
347415
+ const heal = await (0, import_runner_core14.executeMobileTestWithHealing)(
346675
347416
  mobileSteps,
346676
347417
  healTarget,
346677
347418
  {
@@ -346684,7 +347425,7 @@ ${finalVerifyResult.logs ?? ""}`;
346684
347425
  healerOptions
346685
347426
  );
346686
347427
  const finalRes = nativeVerify.last;
346687
- const outcome = (0, import_runner_core13.computeMobileHealOutcome)({
347428
+ const outcome = (0, import_runner_core14.computeMobileHealOutcome)({
346688
347429
  heal,
346689
347430
  finalError: finalRes?.error ?? null,
346690
347431
  reproduceError,
@@ -346830,26 +347571,77 @@ ${result2.logs}`);
346830
347571
  }
346831
347572
  return;
346832
347573
  }
346833
- const storageStatePath = shouldUseAuthState(run2.tags, run2.playwrightCode) ? getValidAuthState(run2.projectId || opts.projectId) : void 0;
346834
- if (storageStatePath) {
346835
- log.info(`Using auth state for project ${run2.projectId || opts.projectId}`);
346836
- }
346837
- const result = await runNativeWithLiveBrowser({
346838
- playwrightCode: run2.playwrightCode,
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,
346839
347580
  baseUrl: run2.baseUrl,
346840
347581
  testVariables: run2.testVariables,
346841
- storageStatePath,
346842
- validateContext: {
346843
- runId: run2.runId,
346844
- serverUrl: opts.serverUrl,
346845
- authHeader: headers.Authorization
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
+ );
346846
347603
  }
346847
- });
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
+ })();
346848
347640
  const duration = Date.now() - startTime;
346849
347641
  let runStatus = result.passed ? "PASSED" : "FAILED";
346850
347642
  let runError = result.error;
346851
347643
  if (!result.passed) {
346852
- const rlDetection = (0, import_runner_core12.detectRateLimitBlock)({
347644
+ const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
346853
347645
  testName: run2.testName ?? run2.testCaseId ?? void 0,
346854
347646
  tags: run2.tags ?? [],
346855
347647
  error: result.error,
@@ -346910,6 +347702,12 @@ async function startRunner(opts) {
346910
347702
  process.env.AUTH_TOKEN = authToken;
346911
347703
  process.env.RUNNER_TOKEN = authToken;
346912
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?.();
346913
347711
  const headers = buildHeaders(authToken, projectId);
346914
347712
  console.log("");
346915
347713
  console.log(`${C2.bold}${C2.cyan}validate.qa Local Runner${C2.reset}`);
@@ -347018,7 +347816,7 @@ async function startRunner(opts) {
347018
347816
  capture: envInt("MAX_CAPTURE", DEFAULT_TYPE_LIMITS.capture),
347019
347817
  run: envInt("MAX_RUN", DEFAULT_TYPE_LIMITS.run)
347020
347818
  };
347021
- const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core12.createResourceGovernorOptions)({
347819
+ const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core13.createResourceGovernorOptions)({
347022
347820
  isCloud: IS_CLOUD,
347023
347821
  maxConcurrent: MAX_CONCURRENT,
347024
347822
  typeLimits: TYPE_LIMITS
@@ -347186,8 +347984,8 @@ async function startRunner(opts) {
347186
347984
  android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
347187
347985
  } : { web: true, ios: false, android: false };
347188
347986
  const bridge = enableMobile ? getBridgeState() : void 0;
347189
- const resourceStatus = (0, import_runner_core12.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
347190
- const processInventory = await (0, import_runner_core12.getBrowserProcessInventory)();
347987
+ const resourceStatus = (0, import_runner_core13.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
347988
+ const processInventory = await (0, import_runner_core13.getBrowserProcessInventory)();
347191
347989
  lastInventoryPids = new Set(processInventory.processes.map((p) => p.pid));
347192
347990
  lastInventoryKinds.clear();
347193
347991
  for (const p of processInventory.processes) lastInventoryKinds.set(p.pid, p.kind);
@@ -347217,7 +348015,7 @@ async function startRunner(opts) {
347217
348015
  resourceSnapshot: resourceStatus.snapshot,
347218
348016
  resourceGovernor: resourceStatus.governor,
347219
348017
  processInventory,
347220
- aiQueue: (0, import_runner_core12.getRunnerAIQueueStats)(),
348018
+ aiQueue: (0, import_runner_core13.getRunnerAIQueueStats)(),
347221
348019
  liveBrowsers: import_runner_core11.liveBrowserRegistry.snapshotForHeartbeat(),
347222
348020
  // Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
347223
348021
  // Cached & cheap — see getCachedDoctor above.
@@ -347328,7 +348126,7 @@ async function startRunner(opts) {
347328
348126
  let pidsWatchdogWarned = false;
347329
348127
  const pidsWatchdog = setInterval(() => {
347330
348128
  if (shuttingDown) return;
347331
- const snapshot = (0, import_runner_core12.getResourceSnapshot)();
348129
+ const snapshot = (0, import_runner_core13.getResourceSnapshot)();
347332
348130
  const pct = snapshot.pidsPct;
347333
348131
  if (pct == null || !Number.isFinite(pct)) return;
347334
348132
  if (pct >= PIDS_RECYCLE_PCT) {
@@ -347368,7 +348166,7 @@ async function startRunner(opts) {
347368
348166
  log.dim(` Deferred ${run2.runId} (project ${runProject} at capacity ${getActiveForProject(runProject)}/${projectLimit})`);
347369
348167
  continue;
347370
348168
  }
347371
- const admission = (0, import_runner_core12.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
348169
+ const admission = (0, import_runner_core13.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
347372
348170
  mode: runMode,
347373
348171
  activeRuns,
347374
348172
  activeByType: Object.fromEntries(activeByType),
@@ -347446,9 +348244,9 @@ async function startRunner(opts) {
347446
348244
  leasedDeviceId = freeDeviceId;
347447
348245
  leasedDeviceIds.set(freeDeviceId, run2.runId);
347448
348246
  }
347449
- let claimed = false;
348247
+ let claimEpoch = false;
347450
348248
  try {
347451
- claimed = await claimRun(serverUrl, headers, run2.runId);
348249
+ claimEpoch = await claimRun(serverUrl, headers, run2.runId);
347452
348250
  } catch (err) {
347453
348251
  if (leasedDeviceId) {
347454
348252
  leasedDeviceIds.delete(leasedDeviceId);
@@ -347456,7 +348254,7 @@ async function startRunner(opts) {
347456
348254
  }
347457
348255
  throw err;
347458
348256
  }
347459
- if (!claimed) {
348257
+ if (claimEpoch === false) {
347460
348258
  log.dim(` Skipped ${run2.runId} (already claimed)`);
347461
348259
  if (leasedDeviceId) {
347462
348260
  leasedDeviceIds.delete(leasedDeviceId);
@@ -347464,6 +348262,7 @@ async function startRunner(opts) {
347464
348262
  }
347465
348263
  continue;
347466
348264
  }
348265
+ run2.claimEpoch = claimEpoch;
347467
348266
  activeRuns++;
347468
348267
  incrementType(runMode);
347469
348268
  incrementProject(runProject);