@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.
- package/dist/cli.js +931 -132
- package/dist/cli.mjs +931 -132
- 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:
|
|
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) && !
|
|
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
|
|
76499
|
-
|
|
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
|
-
//
|
|
76502
|
-
//
|
|
76503
|
-
|
|
76504
|
-
|
|
76505
|
-
|
|
76506
|
-
|
|
76507
|
-
|
|
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
|
-
//
|
|
76515
|
-
//
|
|
76516
|
-
//
|
|
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
|
|
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,
|
|
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,
|
|
76841
|
+
function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
|
|
76662
76842
|
return new Promise((resolvePromise) => {
|
|
76663
|
-
const sandboxedEnv = (
|
|
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 = "
|
|
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(`
|
|
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(`
|
|
80512
|
-
lines.push(`
|
|
80700
|
+
lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
|
|
80701
|
+
lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
|
|
80513
80702
|
} else if (loginRoute) {
|
|
80514
|
-
lines.push(`
|
|
80703
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
|
|
80515
80704
|
} else {
|
|
80516
|
-
lines.push(`
|
|
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.
|
|
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 =
|
|
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
|
|
85849
|
-
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]);
|
|
85850
|
-
const tags = authGuardStamped
|
|
86040
|
+
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && (0, shared_1.isSharedAuthInjectEnabled)() && /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
86041
|
+
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]).filter((tag) => tag !== "@auth-guarded");
|
|
86042
|
+
const tags = authGuardStamped ? [...baseTags, "@auth-guarded"] : baseTags;
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
//
|
|
308514
|
-
//
|
|
308515
|
-
|
|
308516
|
-
|
|
308517
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
344909
|
-
|
|
344910
|
-
|
|
344911
|
-
|
|
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
|
-
|
|
344914
|
-
|
|
344915
|
-
|
|
344916
|
-
|
|
344917
|
-
|
|
344918
|
-
|
|
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
|
-
|
|
344922
|
-
|
|
344923
|
-
|
|
344924
|
-
|
|
344925
|
-
|
|
344926
|
-
|
|
344927
|
-
|
|
344928
|
-
|
|
344929
|
-
|
|
344930
|
-
|
|
344931
|
-
|
|
344932
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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,
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
346834
|
-
|
|
346835
|
-
|
|
346836
|
-
|
|
346837
|
-
|
|
346838
|
-
|
|
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
|
-
|
|
346842
|
-
|
|
346843
|
-
|
|
346844
|
-
|
|
346845
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
347190
|
-
const processInventory = await (0,
|
|
347987
|
+
const resourceStatus = (0, import_runner_core13.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
|
|
347988
|
+
const processInventory = await (0, import_runner_core13.getBrowserProcessInventory)();
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
348247
|
+
let claimEpoch = false;
|
|
347450
348248
|
try {
|
|
347451
|
-
|
|
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 (
|
|
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);
|