@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.mjs
CHANGED
|
@@ -1588,6 +1588,9 @@ var init_types = __esm({
|
|
|
1588
1588
|
});
|
|
1589
1589
|
|
|
1590
1590
|
// ../shared/dist/constants.js
|
|
1591
|
+
function isEmailFromIdentity(value) {
|
|
1592
|
+
return typeof value === "string" && EMAIL_FROM_IDENTITIES.includes(value);
|
|
1593
|
+
}
|
|
1591
1594
|
function isValidMobileAppId(appId) {
|
|
1592
1595
|
return appId.length > 0 && appId.length <= MOBILE_APP_ID_MAX_LENGTH && MOBILE_APP_ID_PATTERN.test(appId);
|
|
1593
1596
|
}
|
|
@@ -1646,13 +1649,22 @@ function escapeEnvValue(value) {
|
|
|
1646
1649
|
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
1647
1650
|
return `"${escaped}"`;
|
|
1648
1651
|
}
|
|
1649
|
-
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;
|
|
1652
|
+
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;
|
|
1650
1653
|
var init_constants = __esm({
|
|
1651
1654
|
"../shared/dist/constants.js"() {
|
|
1652
1655
|
"use strict";
|
|
1653
1656
|
API_PREFIX = "/api";
|
|
1654
1657
|
SUPPORT_EMAIL = "support@validate.qa";
|
|
1655
1658
|
PLATFORM_ADMIN_EMAIL = "admin@validate.qa";
|
|
1659
|
+
NOREPLY_EMAIL = "noreply@validate.qa";
|
|
1660
|
+
EMAIL_FROM_IDENTITIES = ["noreply", "support", "admin"];
|
|
1661
|
+
ADMIN_EMAIL_LIMITS = {
|
|
1662
|
+
recipientsPerSend: 100,
|
|
1663
|
+
recipientNameLength: 200,
|
|
1664
|
+
subjectLength: 200,
|
|
1665
|
+
bodyLength: 5e4,
|
|
1666
|
+
templateNameLength: 120
|
|
1667
|
+
};
|
|
1656
1668
|
AUDIO_CHUNK_DURATION_MS = 3e4;
|
|
1657
1669
|
ACTION_BATCH_INTERVAL_MS = 2e3;
|
|
1658
1670
|
ACTION_BATCH_MAX_SIZE = 10;
|
|
@@ -1749,7 +1761,18 @@ var init_constants = __esm({
|
|
|
1749
1761
|
"MINIMAX_API_KEY",
|
|
1750
1762
|
"ANTHROPIC_API_KEY",
|
|
1751
1763
|
"NVIDIA_API_KEY",
|
|
1752
|
-
"DEEPSEEK_API_KEY"
|
|
1764
|
+
"DEEPSEEK_API_KEY",
|
|
1765
|
+
// Run-scoped callback capabilities. The parent runner injects these after
|
|
1766
|
+
// sanitization; customer variables must never impersonate the control plane.
|
|
1767
|
+
"AUTH_FILE",
|
|
1768
|
+
"SHARED_AUTH_INJECT",
|
|
1769
|
+
"VALIDATE_RUN_ID",
|
|
1770
|
+
"VALIDATE_CLAIM_EPOCH",
|
|
1771
|
+
"VALIDATE_AUTH_CONFIG_HASH",
|
|
1772
|
+
"VALIDATE_TASK_ARN",
|
|
1773
|
+
"VALIDATE_RUNNER_AUTH",
|
|
1774
|
+
"VALIDATE_SERVER_URL",
|
|
1775
|
+
"VALIDATE_LIVE_BROWSER_STATE_PATH"
|
|
1753
1776
|
]);
|
|
1754
1777
|
SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
1755
1778
|
// Runtime essentials - Node.js and Playwright need these to function
|
|
@@ -5017,12 +5040,23 @@ var init_voice_config = __esm({
|
|
|
5017
5040
|
}
|
|
5018
5041
|
});
|
|
5019
5042
|
|
|
5043
|
+
// ../shared/dist/feature-flags.js
|
|
5044
|
+
function isSharedAuthInjectEnabled(value = process.env.SHARED_AUTH_INJECT) {
|
|
5045
|
+
return value?.trim().toLowerCase() !== "false";
|
|
5046
|
+
}
|
|
5047
|
+
var init_feature_flags = __esm({
|
|
5048
|
+
"../shared/dist/feature-flags.js"() {
|
|
5049
|
+
"use strict";
|
|
5050
|
+
}
|
|
5051
|
+
});
|
|
5052
|
+
|
|
5020
5053
|
// ../shared/dist/index.js
|
|
5021
5054
|
var dist_exports = {};
|
|
5022
5055
|
__export(dist_exports, {
|
|
5023
5056
|
ACTION_BATCH_INTERVAL_MS: () => ACTION_BATCH_INTERVAL_MS,
|
|
5024
5057
|
ACTION_BATCH_MAX_SIZE: () => ACTION_BATCH_MAX_SIZE,
|
|
5025
5058
|
ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
|
|
5059
|
+
ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
|
|
5026
5060
|
AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
|
|
5027
5061
|
API_PREFIX: () => API_PREFIX,
|
|
5028
5062
|
AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
|
|
@@ -5036,6 +5070,7 @@ __export(dist_exports, {
|
|
|
5036
5070
|
DEFAULT_TTS_SPEED: () => DEFAULT_TTS_SPEED,
|
|
5037
5071
|
DEFAULT_TTS_VOICE_ID: () => DEFAULT_TTS_VOICE_ID,
|
|
5038
5072
|
DEFAULT_UI_VARIANT: () => DEFAULT_UI_VARIANT,
|
|
5073
|
+
EMAIL_FROM_IDENTITIES: () => EMAIL_FROM_IDENTITIES,
|
|
5039
5074
|
ENVIRONMENTAL_ERROR_PREFIXES: () => ENVIRONMENTAL_ERROR_PREFIXES,
|
|
5040
5075
|
LEGACY_TTS_VOICE_ID_MAP: () => LEGACY_TTS_VOICE_ID_MAP,
|
|
5041
5076
|
MAX_CREDIT_TOPUP_CENTS: () => MAX_CREDIT_TOPUP_CENTS,
|
|
@@ -5054,6 +5089,7 @@ __export(dist_exports, {
|
|
|
5054
5089
|
MOBILE_SCREENSHOT_INTERVAL_MS: () => MOBILE_SCREENSHOT_INTERVAL_MS,
|
|
5055
5090
|
MOBILE_SNAPSHOT_MAX_SIZE_BYTES: () => MOBILE_SNAPSHOT_MAX_SIZE_BYTES,
|
|
5056
5091
|
MOBILE_STEP_TYPES: () => MOBILE_STEP_TYPES,
|
|
5092
|
+
NOREPLY_EMAIL: () => NOREPLY_EMAIL,
|
|
5057
5093
|
PLAN_DEFINITIONS: () => PLAN_DEFINITIONS,
|
|
5058
5094
|
PLAN_ORDER: () => PLAN_ORDER,
|
|
5059
5095
|
PLATFORM_ADMIN_EMAIL: () => PLATFORM_ADMIN_EMAIL,
|
|
@@ -5130,12 +5166,14 @@ __export(dist_exports, {
|
|
|
5130
5166
|
isCreditPackAmountCents: () => isCreditPackAmountCents,
|
|
5131
5167
|
isDbPricingPlan: () => isDbPricingPlan,
|
|
5132
5168
|
isEditableMobileClassName: () => isEditableMobileClassName,
|
|
5169
|
+
isEmailFromIdentity: () => isEmailFromIdentity,
|
|
5133
5170
|
isEnvironmentalError: () => isEnvironmentalError,
|
|
5134
5171
|
isMobilePlatform: () => isMobilePlatform,
|
|
5135
5172
|
isPricingPlan: () => isPricingPlan,
|
|
5136
5173
|
isRunnableStep: () => isRunnableStep,
|
|
5137
5174
|
isSafeDeepLink: () => isSafeDeepLink,
|
|
5138
5175
|
isSameRegistrableDomainHost: () => isSameRegistrableDomainHost,
|
|
5176
|
+
isSharedAuthInjectEnabled: () => isSharedAuthInjectEnabled,
|
|
5139
5177
|
isSttModelForProvider: () => isSttModelForProvider,
|
|
5140
5178
|
isTestReadyForPlatform: () => isTestReadyForPlatform,
|
|
5141
5179
|
isTrialExpired: () => isTrialExpired,
|
|
@@ -5202,6 +5240,7 @@ var init_dist = __esm({
|
|
|
5202
5240
|
init_test_counting();
|
|
5203
5241
|
init_origin_domain();
|
|
5204
5242
|
init_voice_config();
|
|
5243
|
+
init_feature_flags();
|
|
5205
5244
|
}
|
|
5206
5245
|
});
|
|
5207
5246
|
|
|
@@ -10665,7 +10704,7 @@ var require_transport = __commonJS({
|
|
|
10665
10704
|
"../../node_modules/pino/lib/transport.js"(exports, module) {
|
|
10666
10705
|
"use strict";
|
|
10667
10706
|
var { createRequire } = __require("module");
|
|
10668
|
-
var { existsSync:
|
|
10707
|
+
var { existsSync: existsSync3 } = __require("fs");
|
|
10669
10708
|
var getCallers = require_caller();
|
|
10670
10709
|
var { join: join7, isAbsolute, sep } = __require("path");
|
|
10671
10710
|
var { fileURLToPath } = __require("url");
|
|
@@ -10739,7 +10778,7 @@ var require_transport = __commonJS({
|
|
|
10739
10778
|
return false;
|
|
10740
10779
|
}
|
|
10741
10780
|
}
|
|
10742
|
-
return isAbsolute(path) && !
|
|
10781
|
+
return isAbsolute(path) && !existsSync3(path);
|
|
10743
10782
|
}
|
|
10744
10783
|
function stripQuotes(value) {
|
|
10745
10784
|
const first = value[0];
|
|
@@ -76154,6 +76193,126 @@ export const test = base.extend({
|
|
|
76154
76193
|
}
|
|
76155
76194
|
});
|
|
76156
76195
|
|
|
76196
|
+
// ../runner-core/dist/services/run-callback-proxy.js
|
|
76197
|
+
var require_run_callback_proxy = __commonJS({
|
|
76198
|
+
"../runner-core/dist/services/run-callback-proxy.js"(exports) {
|
|
76199
|
+
"use strict";
|
|
76200
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76201
|
+
exports.startRunCallbackProxy = startRunCallbackProxy;
|
|
76202
|
+
var node_crypto_1 = __require("crypto");
|
|
76203
|
+
var node_http_1 = __require("http");
|
|
76204
|
+
var MAX_CALLBACK_BODY_BYTES = 5 * 1024 * 1024;
|
|
76205
|
+
var CALLBACK_TIMEOUT_MS = 3e4;
|
|
76206
|
+
function sendJson(res, status, body) {
|
|
76207
|
+
const payload = JSON.stringify(body);
|
|
76208
|
+
res.writeHead(status, {
|
|
76209
|
+
"cache-control": "no-store",
|
|
76210
|
+
"content-type": "application/json",
|
|
76211
|
+
"content-length": Buffer.byteLength(payload)
|
|
76212
|
+
});
|
|
76213
|
+
res.end(payload);
|
|
76214
|
+
}
|
|
76215
|
+
async function readRequestBody(req) {
|
|
76216
|
+
const chunks = [];
|
|
76217
|
+
let total = 0;
|
|
76218
|
+
for await (const chunk of req) {
|
|
76219
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
76220
|
+
total += buffer.length;
|
|
76221
|
+
if (total > MAX_CALLBACK_BODY_BYTES)
|
|
76222
|
+
throw new Error("callback body too large");
|
|
76223
|
+
chunks.push(buffer);
|
|
76224
|
+
}
|
|
76225
|
+
return Buffer.concat(chunks);
|
|
76226
|
+
}
|
|
76227
|
+
async function startRunCallbackProxy(input) {
|
|
76228
|
+
const upstream = new URL(input.serverUrl);
|
|
76229
|
+
if (upstream.protocol !== "http:" && upstream.protocol !== "https:") {
|
|
76230
|
+
throw new Error("callback server URL must use http or https");
|
|
76231
|
+
}
|
|
76232
|
+
const runPath = `/api/runner/runs/${encodeURIComponent(input.runId)}`;
|
|
76233
|
+
const allowedPaths = /* @__PURE__ */ new Set([`${runPath}/credential`, `${runPath}/auth-state`]);
|
|
76234
|
+
const token = (0, node_crypto_1.randomBytes)(32).toString("base64url");
|
|
76235
|
+
const authHeader = `Bearer ${token}`;
|
|
76236
|
+
const server3 = (0, node_http_1.createServer)(async (req, res) => {
|
|
76237
|
+
try {
|
|
76238
|
+
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
76239
|
+
if (req.method !== "POST" || !allowedPaths.has(requestUrl.pathname) || requestUrl.search) {
|
|
76240
|
+
sendJson(res, 404, { error: "Callback route not available" });
|
|
76241
|
+
return;
|
|
76242
|
+
}
|
|
76243
|
+
if (req.headers.authorization !== authHeader) {
|
|
76244
|
+
sendJson(res, 401, { error: "Invalid callback capability" });
|
|
76245
|
+
return;
|
|
76246
|
+
}
|
|
76247
|
+
const body = await readRequestBody(req);
|
|
76248
|
+
let parsedBody;
|
|
76249
|
+
try {
|
|
76250
|
+
const parsed = JSON.parse(body.toString("utf8"));
|
|
76251
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
76252
|
+
throw new Error("invalid body");
|
|
76253
|
+
parsedBody = parsed;
|
|
76254
|
+
} catch {
|
|
76255
|
+
sendJson(res, 400, { error: "Callback body must be a JSON object" });
|
|
76256
|
+
return;
|
|
76257
|
+
}
|
|
76258
|
+
const upstreamBody = JSON.stringify({
|
|
76259
|
+
...parsedBody,
|
|
76260
|
+
...input.taskArn ? { taskArn: input.taskArn } : {},
|
|
76261
|
+
...input.claimEpoch !== void 0 ? { claimEpoch: input.claimEpoch } : {},
|
|
76262
|
+
...input.authConfigHash ? { authConfigHash: input.authConfigHash } : {}
|
|
76263
|
+
});
|
|
76264
|
+
const destination = new URL(requestUrl.pathname, upstream);
|
|
76265
|
+
const response = await fetch(destination, {
|
|
76266
|
+
method: "POST",
|
|
76267
|
+
headers: {
|
|
76268
|
+
Authorization: input.upstreamAuthHeader,
|
|
76269
|
+
"Content-Type": "application/json"
|
|
76270
|
+
},
|
|
76271
|
+
body: upstreamBody,
|
|
76272
|
+
redirect: "error",
|
|
76273
|
+
signal: AbortSignal.timeout(CALLBACK_TIMEOUT_MS)
|
|
76274
|
+
});
|
|
76275
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
76276
|
+
res.writeHead(response.status, {
|
|
76277
|
+
"cache-control": "no-store",
|
|
76278
|
+
"content-type": response.headers.get("content-type") ?? "application/json",
|
|
76279
|
+
"content-length": responseBody.length
|
|
76280
|
+
});
|
|
76281
|
+
res.end(responseBody);
|
|
76282
|
+
} catch (error2) {
|
|
76283
|
+
const message = error2 instanceof Error && error2.message === "callback body too large" ? error2.message : "Callback forwarding failed";
|
|
76284
|
+
sendJson(res, message === "callback body too large" ? 413 : 502, { error: message });
|
|
76285
|
+
}
|
|
76286
|
+
});
|
|
76287
|
+
server3.on("clientError", (_error, socket) => socket.destroy());
|
|
76288
|
+
await new Promise((resolve2, reject) => {
|
|
76289
|
+
const onError = (error2) => reject(error2);
|
|
76290
|
+
server3.once("error", onError);
|
|
76291
|
+
server3.listen(0, "127.0.0.1", () => {
|
|
76292
|
+
server3.off("error", onError);
|
|
76293
|
+
resolve2();
|
|
76294
|
+
});
|
|
76295
|
+
});
|
|
76296
|
+
const address = server3.address();
|
|
76297
|
+
if (!address) {
|
|
76298
|
+
server3.close();
|
|
76299
|
+
throw new Error("callback proxy failed to bind");
|
|
76300
|
+
}
|
|
76301
|
+
let closed = false;
|
|
76302
|
+
return {
|
|
76303
|
+
serverUrl: `http://127.0.0.1:${address.port}`,
|
|
76304
|
+
authHeader,
|
|
76305
|
+
close: async () => {
|
|
76306
|
+
if (closed)
|
|
76307
|
+
return;
|
|
76308
|
+
closed = true;
|
|
76309
|
+
await new Promise((resolve2) => server3.close(() => resolve2()));
|
|
76310
|
+
}
|
|
76311
|
+
};
|
|
76312
|
+
}
|
|
76313
|
+
}
|
|
76314
|
+
});
|
|
76315
|
+
|
|
76157
76316
|
// ../runner-core/dist/services/playwright-native.js
|
|
76158
76317
|
var require_playwright_native = __commonJS({
|
|
76159
76318
|
"../runner-core/dist/services/playwright-native.js"(exports) {
|
|
@@ -76198,6 +76357,7 @@ var require_playwright_native = __commonJS({
|
|
|
76198
76357
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
76199
76358
|
exports.scrubHarRequestBody = exports.buildOriginScopedApiFixtures = exports.isAppOriginRequest = exports.resolveApiAuthHeaders = exports.parseApiAuthHeaders = void 0;
|
|
76200
76359
|
exports.executeTestViaNativePlaywright = executeTestViaNativePlaywright2;
|
|
76360
|
+
exports.buildNativePlaywrightChildEnv = buildNativePlaywrightChildEnv;
|
|
76201
76361
|
exports.parseJsonResults = parseJsonResults2;
|
|
76202
76362
|
exports.parseHarToApiSummary = parseHarToApiSummary;
|
|
76203
76363
|
var promises_1 = __require("fs/promises");
|
|
@@ -76209,6 +76369,7 @@ var require_playwright_native = __commonJS({
|
|
|
76209
76369
|
var api_call_redaction_js_1 = require_api_call_redaction();
|
|
76210
76370
|
var har_stream_js_1 = require_har_stream();
|
|
76211
76371
|
var api_auth_headers_js_1 = require_api_auth_headers();
|
|
76372
|
+
var run_callback_proxy_js_1 = require_run_callback_proxy();
|
|
76212
76373
|
var api_auth_headers_js_2 = require_api_auth_headers();
|
|
76213
76374
|
Object.defineProperty(exports, "parseApiAuthHeaders", { enumerable: true, get: function() {
|
|
76214
76375
|
return api_auth_headers_js_2.parseApiAuthHeaders;
|
|
@@ -76231,7 +76392,7 @@ var require_playwright_native = __commonJS({
|
|
|
76231
76392
|
var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
|
|
76232
76393
|
var PW_CLI = __require.resolve("@playwright/test/cli");
|
|
76233
76394
|
async function executeTestViaNativePlaywright2(options) {
|
|
76234
|
-
const { playwrightCode, baseUrl, testName, testVariables, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76395
|
+
const { playwrightCode, baseUrl, testName, testVariables, authStateCapturePath, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76235
76396
|
const logs2 = [];
|
|
76236
76397
|
const screenshots = [];
|
|
76237
76398
|
logs2.push(`\u{1F3AD} Native Playwright starting`);
|
|
@@ -76241,6 +76402,7 @@ var require_playwright_native = __commonJS({
|
|
|
76241
76402
|
logs2.push("\u26A0\uFE0F WARNING: baseUrl is empty - test will likely fail with navigation errors");
|
|
76242
76403
|
}
|
|
76243
76404
|
let runDir = null;
|
|
76405
|
+
let callbackProxy;
|
|
76244
76406
|
try {
|
|
76245
76407
|
runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
|
|
76246
76408
|
await (0, promises_1.mkdir)(runDir, { recursive: true });
|
|
@@ -76500,35 +76662,51 @@ module.exports = {
|
|
|
76500
76662
|
const configPath = (0, path_1.join)(runDir, "playwright.config.js");
|
|
76501
76663
|
await (0, promises_1.writeFile)(configPath, config);
|
|
76502
76664
|
const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
|
|
76503
|
-
const
|
|
76504
|
-
|
|
76665
|
+
const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
|
|
76666
|
+
const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
|
|
76667
|
+
if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
|
|
76668
|
+
try {
|
|
76669
|
+
callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
|
|
76670
|
+
runId: validateContext.runId,
|
|
76671
|
+
serverUrl: validateContext.serverUrl,
|
|
76672
|
+
upstreamAuthHeader: validateContext.authHeader,
|
|
76673
|
+
taskArn: process.env.VALIDATE_TASK_ARN,
|
|
76674
|
+
claimEpoch: callbackClaimEpoch,
|
|
76675
|
+
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
|
|
76676
|
+
});
|
|
76677
|
+
} catch (error3) {
|
|
76678
|
+
logs2.push(`Callback proxy unavailable: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
76679
|
+
}
|
|
76680
|
+
}
|
|
76681
|
+
const childTestVariables = { ...testVariables ?? {} };
|
|
76682
|
+
const trustedRuntimeEnv = {
|
|
76505
76683
|
...isolation.env,
|
|
76506
|
-
//
|
|
76507
|
-
//
|
|
76508
|
-
|
|
76509
|
-
|
|
76510
|
-
|
|
76511
|
-
|
|
76512
|
-
|
|
76684
|
+
// Generated callbacks receive only a random loopback capability. The global
|
|
76685
|
+
// runner bearer token and real control-plane URL stay in the parent process.
|
|
76686
|
+
...callbackProxy && validateContext?.runId ? {
|
|
76687
|
+
VALIDATE_RUN_ID: validateContext.runId,
|
|
76688
|
+
...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
|
|
76689
|
+
VALIDATE_SERVER_URL: callbackProxy.serverUrl,
|
|
76690
|
+
VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
|
|
76691
|
+
} : {},
|
|
76513
76692
|
// Owning Fargate taskArn (a per-task constant set once at task boot in
|
|
76514
76693
|
// execute-one.ts; absent on the local/Railway runner). Read from
|
|
76515
76694
|
// process.env rather than validateContext since it's process-wide, not
|
|
76516
76695
|
// per-run. Lets persistCredential() prove ownership to /credential.
|
|
76517
76696
|
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
|
|
76697
|
+
...authStateCapturePath ? { AUTH_FILE: authStateCapturePath } : {},
|
|
76518
76698
|
...liveBrowserStatePath ? { VALIDATE_LIVE_BROWSER_STATE_PATH: liveBrowserStatePath } : {},
|
|
76519
|
-
//
|
|
76520
|
-
//
|
|
76521
|
-
//
|
|
76522
|
-
// this forward the flag reads as undefined in the test and the early publish is dead
|
|
76523
|
-
// code. Forwarded (not allowlisted) so it stays a per-run toggle; absent → inert.
|
|
76699
|
+
// Preserve an explicit shared-auth kill-switch in the child. Unset defaults on in
|
|
76700
|
+
// both parent and generated __vqaLogin code; "false" must still cross this process
|
|
76701
|
+
// boundary to disable the early publish POST. It remains a per-run toggle.
|
|
76524
76702
|
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
|
|
76525
76703
|
};
|
|
76526
|
-
delete
|
|
76704
|
+
delete childTestVariables.API_AUTH_HEADERS;
|
|
76527
76705
|
logs2.push("\u{1F680} Spawning playwright test...");
|
|
76528
76706
|
const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
|
|
76529
76707
|
let playResult;
|
|
76530
76708
|
try {
|
|
76531
|
-
playResult = await runPlaywright(configPath,
|
|
76709
|
+
playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv);
|
|
76532
76710
|
} finally {
|
|
76533
76711
|
await stopLiveBrowserPolling?.();
|
|
76534
76712
|
}
|
|
@@ -76612,6 +76790,8 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76612
76790
|
error: msg
|
|
76613
76791
|
};
|
|
76614
76792
|
} finally {
|
|
76793
|
+
await callbackProxy?.close().catch(() => {
|
|
76794
|
+
});
|
|
76615
76795
|
if (runDir) {
|
|
76616
76796
|
await (0, promises_1.rm)(runDir, { recursive: true, force: true }).catch(() => {
|
|
76617
76797
|
});
|
|
@@ -76663,9 +76843,9 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76663
76843
|
stopped = true;
|
|
76664
76844
|
};
|
|
76665
76845
|
}
|
|
76666
|
-
function runPlaywright(configPath,
|
|
76846
|
+
function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
|
|
76667
76847
|
return new Promise((resolvePromise) => {
|
|
76668
|
-
const sandboxedEnv = (
|
|
76848
|
+
const sandboxedEnv = buildNativePlaywrightChildEnv(process.env, testVariables, trustedRuntimeEnv);
|
|
76669
76849
|
const proc = (0, spawn_with_tree_kill_js_1.spawnDetachedGroup)(process.execPath, [PW_CLI, "test", "--config", configPath], {
|
|
76670
76850
|
cwd: PACKAGE_DIR,
|
|
76671
76851
|
env: sandboxedEnv,
|
|
@@ -76708,6 +76888,12 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76708
76888
|
});
|
|
76709
76889
|
});
|
|
76710
76890
|
}
|
|
76891
|
+
function buildNativePlaywrightChildEnv(processEnv, testVariables, trustedRuntimeEnv) {
|
|
76892
|
+
return {
|
|
76893
|
+
...(0, shared_1.buildSandboxedEnv)(processEnv, testVariables),
|
|
76894
|
+
...trustedRuntimeEnv ?? {}
|
|
76895
|
+
};
|
|
76896
|
+
}
|
|
76711
76897
|
async function collectScreenshots(dir, screenshots) {
|
|
76712
76898
|
let entries;
|
|
76713
76899
|
try {
|
|
@@ -80429,10 +80615,12 @@ var require_auth_recipe = __commonJS({
|
|
|
80429
80615
|
exports.upsertAuthBlock = upsertAuthBlock;
|
|
80430
80616
|
exports.buildHealAuthStamp = buildHealAuthStamp;
|
|
80431
80617
|
var crypto_1 = __require("crypto");
|
|
80618
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
80432
80619
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
80433
|
-
exports.AUTH_MARKER_VERSION = "
|
|
80620
|
+
exports.AUTH_MARKER_VERSION = "v3";
|
|
80434
80621
|
var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
|
|
80435
80622
|
var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports.AUTH_MARKER_VERSION} <<<`;
|
|
80623
|
+
var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
|
|
80436
80624
|
var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
|
|
80437
80625
|
function computeRecipeHash(input) {
|
|
80438
80626
|
const canonical = JSON.stringify({
|
|
@@ -80510,16 +80698,18 @@ var require_auth_recipe = __commonJS({
|
|
|
80510
80698
|
if (opts?.warmGuard) {
|
|
80511
80699
|
const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
|
|
80512
80700
|
const loginRoute = recipe.route ?? recipe.preNavRoute;
|
|
80513
|
-
lines.push(`
|
|
80701
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
|
|
80702
|
+
lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
|
|
80514
80703
|
if (recipe.authProof.kind === "url") {
|
|
80515
80704
|
const pred = urlProofPredicateExpr(recipe.authProof.route);
|
|
80516
|
-
lines.push(`
|
|
80517
|
-
lines.push(`
|
|
80705
|
+
lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
|
|
80706
|
+
lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
|
|
80518
80707
|
} else if (loginRoute) {
|
|
80519
|
-
lines.push(`
|
|
80708
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
|
|
80520
80709
|
} else {
|
|
80521
|
-
lines.push(`
|
|
80710
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
|
|
80522
80711
|
}
|
|
80712
|
+
lines.push(` }`);
|
|
80523
80713
|
}
|
|
80524
80714
|
lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
|
|
80525
80715
|
for (const cmd of recipe.preNavCmds)
|
|
@@ -80532,11 +80722,11 @@ var require_auth_recipe = __commonJS({
|
|
|
80532
80722
|
lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
|
|
80533
80723
|
lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
|
|
80534
80724
|
if (opts?.warmGuard) {
|
|
80535
|
-
lines.push(` if (process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
80536
|
-
lines.push(` if (process.env.
|
|
80725
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
80726
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.VALIDATE_TASK_ARN && process.env.VALIDATE_CLAIM_EPOCH && process.env.VALIDATE_SERVER_URL && process.env.VALIDATE_RUN_ID) {`);
|
|
80537
80727
|
lines.push(` try {`);
|
|
80538
80728
|
lines.push(` const __vqaState = await page.context().storageState();`);
|
|
80539
|
-
lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN }, timeout: 10000 });`);
|
|
80729
|
+
lines.push(` await page.request.post(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + process.env.VALIDATE_RUN_ID + '/auth-state', { headers: { Authorization: process.env.VALIDATE_RUNNER_AUTH || '' }, data: { storageState: JSON.stringify(__vqaState), taskArn: process.env.VALIDATE_TASK_ARN, claimEpoch: Number(process.env.VALIDATE_CLAIM_EPOCH) }, timeout: 10000 });`);
|
|
80540
80730
|
lines.push(` } catch {}`);
|
|
80541
80731
|
lines.push(` }`);
|
|
80542
80732
|
}
|
|
@@ -80548,6 +80738,8 @@ var require_auth_recipe = __commonJS({
|
|
|
80548
80738
|
"AUTH_FILE",
|
|
80549
80739
|
"SHARED_AUTH_INJECT",
|
|
80550
80740
|
"VALIDATE_RUN_ID",
|
|
80741
|
+
"VALIDATE_CLAIM_EPOCH",
|
|
80742
|
+
"VALIDATE_AUTH_CONFIG_HASH",
|
|
80551
80743
|
"VALIDATE_TASK_ARN",
|
|
80552
80744
|
"VALIDATE_RUNNER_AUTH",
|
|
80553
80745
|
"VALIDATE_SERVER_URL"
|
|
@@ -80720,7 +80912,7 @@ ${tail}`;
|
|
|
80720
80912
|
let result = stripAuthRegions(code);
|
|
80721
80913
|
result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
|
|
80722
80914
|
result = ensureExpectImported(result);
|
|
80723
|
-
const warmGuard =
|
|
80915
|
+
const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
|
|
80724
80916
|
result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
|
|
80725
80917
|
result = ensureLoginCall(result);
|
|
80726
80918
|
const n = countAuthRegions(result);
|
|
@@ -85850,9 +86042,9 @@ ${testOpen}`;
|
|
|
85850
86042
|
basePath: options?.basePath
|
|
85851
86043
|
});
|
|
85852
86044
|
const leakedSensitiveKeys = findSensitiveLiteralLeaks(playwrightCode, options?.sensitiveEnvMap);
|
|
85853
|
-
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && process
|
|
85854
|
-
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]);
|
|
85855
|
-
const tags = authGuardStamped
|
|
86045
|
+
const authGuardStamped = !!options?.authRecipe && rawTags.includes("@needs-auth") && !rawTags.includes("@auth") && (0, shared_1.isSharedAuthInjectEnabled)() && /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
86046
|
+
const baseTags = ensureDiscoveredTag(Array.isArray(test.tags) ? test.tags : ["@discovered", "@regression"]).filter((tag) => tag !== "@auth-guarded");
|
|
86047
|
+
const tags = authGuardStamped ? [...baseTags, "@auth-guarded"] : baseTags;
|
|
85856
86048
|
return {
|
|
85857
86049
|
name: String(test.name || "Unnamed test").slice(0, 200),
|
|
85858
86050
|
description: String(test.description || test.name || "").slice(0, 500),
|
|
@@ -99667,7 +99859,7 @@ ${lanes.join("\n")}
|
|
|
99667
99859
|
return process.memoryUsage().heapUsed;
|
|
99668
99860
|
},
|
|
99669
99861
|
getFileSize(path) {
|
|
99670
|
-
const stat =
|
|
99862
|
+
const stat = statSync(path);
|
|
99671
99863
|
if (stat == null ? void 0 : stat.isFile()) {
|
|
99672
99864
|
return stat.size;
|
|
99673
99865
|
}
|
|
@@ -99711,7 +99903,7 @@ ${lanes.join("\n")}
|
|
|
99711
99903
|
}
|
|
99712
99904
|
};
|
|
99713
99905
|
return nodeSystem;
|
|
99714
|
-
function
|
|
99906
|
+
function statSync(path) {
|
|
99715
99907
|
try {
|
|
99716
99908
|
return _fs.statSync(path, statSyncOptions);
|
|
99717
99909
|
} catch {
|
|
@@ -99770,7 +99962,7 @@ ${lanes.join("\n")}
|
|
|
99770
99962
|
activeSession.post("Profiler.stop", (err, { profile }) => {
|
|
99771
99963
|
var _a;
|
|
99772
99964
|
if (!err) {
|
|
99773
|
-
if ((_a =
|
|
99965
|
+
if ((_a = statSync(profilePath)) == null ? void 0 : _a.isDirectory()) {
|
|
99774
99966
|
profilePath = _path.join(profilePath, `${(/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-")}+P${process.pid}.cpuprofile`);
|
|
99775
99967
|
}
|
|
99776
99968
|
try {
|
|
@@ -99890,7 +100082,7 @@ ${lanes.join("\n")}
|
|
|
99890
100082
|
let stat;
|
|
99891
100083
|
if (typeof dirent === "string" || dirent.isSymbolicLink()) {
|
|
99892
100084
|
const name = combinePaths(path, entry);
|
|
99893
|
-
stat =
|
|
100085
|
+
stat = statSync(name);
|
|
99894
100086
|
if (!stat) {
|
|
99895
100087
|
continue;
|
|
99896
100088
|
}
|
|
@@ -99914,7 +100106,7 @@ ${lanes.join("\n")}
|
|
|
99914
100106
|
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames2, process.cwd(), depth, getAccessibleFileSystemEntries, realpath);
|
|
99915
100107
|
}
|
|
99916
100108
|
function fileSystemEntryExists(path, entryKind) {
|
|
99917
|
-
const stat =
|
|
100109
|
+
const stat = statSync(path);
|
|
99918
100110
|
if (!stat) {
|
|
99919
100111
|
return false;
|
|
99920
100112
|
}
|
|
@@ -99956,7 +100148,7 @@ ${lanes.join("\n")}
|
|
|
99956
100148
|
}
|
|
99957
100149
|
function getModifiedTime3(path) {
|
|
99958
100150
|
var _a;
|
|
99959
|
-
return (_a =
|
|
100151
|
+
return (_a = statSync(path)) == null ? void 0 : _a.mtime;
|
|
99960
100152
|
}
|
|
99961
100153
|
function setModifiedTime(path, time) {
|
|
99962
100154
|
try {
|
|
@@ -308467,6 +308659,7 @@ var require_regression_runner = __commonJS({
|
|
|
308467
308659
|
var chromium_isolation_js_1 = require_chromium_isolation();
|
|
308468
308660
|
var browser_config_js_1 = require_browser_config();
|
|
308469
308661
|
var spawn_with_tree_kill_js_1 = require_spawn_with_tree_kill();
|
|
308662
|
+
var run_callback_proxy_js_1 = require_run_callback_proxy();
|
|
308470
308663
|
var SUITE_SPAWN_TIMEOUT_MS = 85 * 60 * 1e3;
|
|
308471
308664
|
var PACKAGE_DIR = (0, path_1.resolve)(__dirname, "..", "..");
|
|
308472
308665
|
var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
|
|
@@ -308481,6 +308674,7 @@ var require_regression_runner = __commonJS({
|
|
|
308481
308674
|
const tarPath = (0, path_1.join)(RUNS_DIR, `regression-${runId}.tar.gz`);
|
|
308482
308675
|
const startTime = Date.now();
|
|
308483
308676
|
let logs2 = "";
|
|
308677
|
+
let callbackProxy;
|
|
308484
308678
|
try {
|
|
308485
308679
|
await (0, promises_1.mkdir)(extractDir, { recursive: true });
|
|
308486
308680
|
const tarballUrl = `https://api.github.com/repos/${repoOwner}/${repoName}/tarball/${repoBranch}`;
|
|
@@ -308510,19 +308704,40 @@ var require_regression_runner = __commonJS({
|
|
|
308510
308704
|
await (0, promises_1.writeFile)((0, path_1.join)(extractDir, ".env"), envContent, "utf-8");
|
|
308511
308705
|
const jsonResultsPath = (0, path_1.join)(extractDir, "test-results", "results.json");
|
|
308512
308706
|
const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(extractDir);
|
|
308707
|
+
const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
|
|
308708
|
+
const callbackClaimEpoch = Number.isSafeInteger(opts.claimEpoch) ? opts.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
|
|
308709
|
+
if (opts.validateServerUrl && opts.validateRunnerAuthHeader) {
|
|
308710
|
+
try {
|
|
308711
|
+
callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
|
|
308712
|
+
runId,
|
|
308713
|
+
serverUrl: opts.validateServerUrl,
|
|
308714
|
+
upstreamAuthHeader: opts.validateRunnerAuthHeader,
|
|
308715
|
+
taskArn: process.env.VALIDATE_TASK_ARN,
|
|
308716
|
+
claimEpoch: callbackClaimEpoch,
|
|
308717
|
+
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
|
|
308718
|
+
});
|
|
308719
|
+
} catch (error2) {
|
|
308720
|
+
logs2 += `Callback proxy unavailable: ${error2 instanceof Error ? error2.message : String(error2)}
|
|
308721
|
+
`;
|
|
308722
|
+
}
|
|
308723
|
+
}
|
|
308513
308724
|
const runEnv = {
|
|
308514
308725
|
...(0, shared_1.buildSandboxedEnv)(process.env, testVariables),
|
|
308515
308726
|
...isolation.env,
|
|
308516
308727
|
CI: "true",
|
|
308517
308728
|
PLAYWRIGHT_JSON_OUTPUT_NAME: jsonResultsPath,
|
|
308518
|
-
//
|
|
308519
|
-
//
|
|
308520
|
-
|
|
308521
|
-
|
|
308522
|
-
|
|
308729
|
+
// The suite receives a random run-scoped loopback callback, never the
|
|
308730
|
+
// global runner bearer credential.
|
|
308731
|
+
...callbackProxy ? {
|
|
308732
|
+
VALIDATE_RUN_ID: runId,
|
|
308733
|
+
...callbackClaimEpoch !== void 0 ? { VALIDATE_CLAIM_EPOCH: String(callbackClaimEpoch) } : {},
|
|
308734
|
+
VALIDATE_SERVER_URL: callbackProxy.serverUrl,
|
|
308735
|
+
VALIDATE_RUNNER_AUTH: callbackProxy.authHeader
|
|
308736
|
+
} : {},
|
|
308523
308737
|
// Owning Fargate taskArn (per-task constant; unset off-Fargate). Forwarded
|
|
308524
308738
|
// so persistCredential() can prove ownership to the /credential fence.
|
|
308525
|
-
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {}
|
|
308739
|
+
...process.env.VALIDATE_TASK_ARN ? { VALIDATE_TASK_ARN: process.env.VALIDATE_TASK_ARN } : {},
|
|
308740
|
+
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
|
|
308526
308741
|
};
|
|
308527
308742
|
logs2 += `Running playwright --project=${opts.suiteProject ?? "suite"}...
|
|
308528
308743
|
`;
|
|
@@ -308551,6 +308766,8 @@ var require_regression_runner = __commonJS({
|
|
|
308551
308766
|
error: String(err)
|
|
308552
308767
|
};
|
|
308553
308768
|
} finally {
|
|
308769
|
+
await callbackProxy?.close().catch(() => {
|
|
308770
|
+
});
|
|
308554
308771
|
(0, promises_1.rm)(extractDir, { recursive: true, force: true }).catch(() => {
|
|
308555
308772
|
});
|
|
308556
308773
|
await (0, browser_config_js_1.cleanupPlaywrightTempArtifacts)().catch(() => {
|
|
@@ -324794,6 +325011,455 @@ var require_resource_governor = __commonJS({
|
|
|
324794
325011
|
}
|
|
324795
325012
|
});
|
|
324796
325013
|
|
|
325014
|
+
// ../runner-core/dist/services/local-auth-state.js
|
|
325015
|
+
var require_local_auth_state = __commonJS({
|
|
325016
|
+
"../runner-core/dist/services/local-auth-state.js"(exports) {
|
|
325017
|
+
"use strict";
|
|
325018
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
325019
|
+
exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = exports.LOCAL_AUTH_STATE_LOCK_STALE_MS = exports.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = exports.LOCAL_AUTH_STATE_WAIT_MS = exports.LOCAL_AUTH_STATE_MAX_AGE_MS = void 0;
|
|
325020
|
+
exports.shouldSkipWarmAuthSetup = shouldSkipWarmAuthSetup2;
|
|
325021
|
+
exports.shouldSkipDisabledAutomaticAuthSetup = shouldSkipDisabledAutomaticAuthSetup2;
|
|
325022
|
+
exports.localAuthStateFingerprint = localAuthStateFingerprint;
|
|
325023
|
+
exports.ensurePrivateDirectory = ensurePrivateDirectory2;
|
|
325024
|
+
exports.isValidStorageState = isValidStorageState;
|
|
325025
|
+
exports.isValidStorageStateJson = isValidStorageStateJson;
|
|
325026
|
+
exports.isValidLocalAuthStateFile = isValidLocalAuthStateFile;
|
|
325027
|
+
exports.cleanupLocalAuthStateFiles = cleanupLocalAuthStateFiles;
|
|
325028
|
+
exports.cleanupExpiredLocalAuthStateFiles = cleanupExpiredLocalAuthStateFiles2;
|
|
325029
|
+
exports.localAuthStatePaths = localAuthStatePaths2;
|
|
325030
|
+
exports.getValidLocalAuthState = getValidLocalAuthState2;
|
|
325031
|
+
exports.waitForValidLocalAuthState = waitForValidLocalAuthState;
|
|
325032
|
+
exports.waitForLocalAuthStateProducer = waitForLocalAuthStateProducer2;
|
|
325033
|
+
exports.promoteLocalAuthStateCapture = promoteLocalAuthStateCapture2;
|
|
325034
|
+
exports.discardLocalAuthStateCapture = discardLocalAuthStateCapture2;
|
|
325035
|
+
exports.acquireLocalAuthStateLock = acquireLocalAuthStateLock2;
|
|
325036
|
+
exports.localAuthStateFilename = localAuthStateFilename;
|
|
325037
|
+
var node_fs_1 = __require("fs");
|
|
325038
|
+
var node_crypto_1 = __require("crypto");
|
|
325039
|
+
var node_path_1 = __require("path");
|
|
325040
|
+
var auth_recipe_js_1 = require_auth_recipe();
|
|
325041
|
+
exports.LOCAL_AUTH_STATE_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
325042
|
+
exports.LOCAL_AUTH_STATE_WAIT_MS = 3 * 60 * 1e3;
|
|
325043
|
+
exports.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS = 15 * 60 * 1e3;
|
|
325044
|
+
exports.LOCAL_AUTH_STATE_LOCK_STALE_MS = 2 * 60 * 1e3;
|
|
325045
|
+
exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS = 30 * 60 * 1e3;
|
|
325046
|
+
function shouldSkipWarmAuthSetup2(authForceRun, sharedAuthEnabled, cacheWarm) {
|
|
325047
|
+
return sharedAuthEnabled && authForceRun !== true && cacheWarm;
|
|
325048
|
+
}
|
|
325049
|
+
function shouldSkipDisabledAutomaticAuthSetup2(authForceRun, authVerify, sharedAuthEnabled) {
|
|
325050
|
+
return !sharedAuthEnabled && authForceRun !== true && authVerify !== true;
|
|
325051
|
+
}
|
|
325052
|
+
function normalizeOrigin(value) {
|
|
325053
|
+
try {
|
|
325054
|
+
return new URL(value).origin.toLowerCase();
|
|
325055
|
+
} catch {
|
|
325056
|
+
return value.trim().toLowerCase();
|
|
325057
|
+
}
|
|
325058
|
+
}
|
|
325059
|
+
function normalizeAppUrl(value) {
|
|
325060
|
+
try {
|
|
325061
|
+
const url = new URL(value);
|
|
325062
|
+
url.hash = "";
|
|
325063
|
+
return `${url.origin.toLowerCase()}${url.pathname}${url.search}`;
|
|
325064
|
+
} catch {
|
|
325065
|
+
return value.trim();
|
|
325066
|
+
}
|
|
325067
|
+
}
|
|
325068
|
+
function safeSegment(value, fallback) {
|
|
325069
|
+
const safe = value.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 120);
|
|
325070
|
+
return safe || fallback;
|
|
325071
|
+
}
|
|
325072
|
+
function stableVariables(testVariables) {
|
|
325073
|
+
return Object.entries(testVariables).filter(([key]) => !(0, auth_recipe_js_1.isReservedRunnerEnvVar)(key)).sort(([a], [b]) => a.localeCompare(b));
|
|
325074
|
+
}
|
|
325075
|
+
function localAuthStateFingerprint(input) {
|
|
325076
|
+
const payload = JSON.stringify({
|
|
325077
|
+
version: 1,
|
|
325078
|
+
projectId: input.projectId,
|
|
325079
|
+
serverOrigin: normalizeOrigin(input.serverUrl),
|
|
325080
|
+
appUrl: normalizeAppUrl(input.baseUrl),
|
|
325081
|
+
testVariables: stableVariables(input.testVariables)
|
|
325082
|
+
});
|
|
325083
|
+
const key = input.authToken || `validate-local-auth:${input.projectId}`;
|
|
325084
|
+
return (0, node_crypto_1.createHmac)("sha256", key).update(payload).digest("hex").slice(0, 24);
|
|
325085
|
+
}
|
|
325086
|
+
function ensurePrivateDirectory2(dir) {
|
|
325087
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true, mode: 448 });
|
|
325088
|
+
try {
|
|
325089
|
+
(0, node_fs_1.chmodSync)(dir, 448);
|
|
325090
|
+
} catch {
|
|
325091
|
+
}
|
|
325092
|
+
}
|
|
325093
|
+
function removeFile(path) {
|
|
325094
|
+
try {
|
|
325095
|
+
(0, node_fs_1.unlinkSync)(path);
|
|
325096
|
+
} catch {
|
|
325097
|
+
}
|
|
325098
|
+
}
|
|
325099
|
+
function readLockOwner(path) {
|
|
325100
|
+
try {
|
|
325101
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(path, "utf8"));
|
|
325102
|
+
return typeof parsed.token === "string" && parsed.token.length > 0 && Number.isSafeInteger(parsed.pid) && (parsed.pid ?? 0) > 0 ? { token: parsed.token, pid: parsed.pid } : void 0;
|
|
325103
|
+
} catch {
|
|
325104
|
+
return void 0;
|
|
325105
|
+
}
|
|
325106
|
+
}
|
|
325107
|
+
function processIsAlive(pid) {
|
|
325108
|
+
try {
|
|
325109
|
+
process.kill(pid, 0);
|
|
325110
|
+
return true;
|
|
325111
|
+
} catch (error2) {
|
|
325112
|
+
return error2.code === "EPERM";
|
|
325113
|
+
}
|
|
325114
|
+
}
|
|
325115
|
+
function staleLockCanBeReaped(path, now, staleMs) {
|
|
325116
|
+
try {
|
|
325117
|
+
const ageMs = now - (0, node_fs_1.statSync)(path).mtimeMs;
|
|
325118
|
+
if (ageMs < staleMs)
|
|
325119
|
+
return false;
|
|
325120
|
+
const owner = readLockOwner(path);
|
|
325121
|
+
if (owner) {
|
|
325122
|
+
if (!processIsAlive(owner.pid))
|
|
325123
|
+
return true;
|
|
325124
|
+
return ageMs >= Math.max(staleMs * 5, exports.LOCAL_AUTH_STATE_LOCK_ABSOLUTE_STALE_MS);
|
|
325125
|
+
}
|
|
325126
|
+
return ageMs >= staleMs * 5;
|
|
325127
|
+
} catch {
|
|
325128
|
+
return false;
|
|
325129
|
+
}
|
|
325130
|
+
}
|
|
325131
|
+
function isRecord(value) {
|
|
325132
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
325133
|
+
}
|
|
325134
|
+
function isValidStorageState(value) {
|
|
325135
|
+
if (!isRecord(value))
|
|
325136
|
+
return false;
|
|
325137
|
+
const cookies = value.cookies;
|
|
325138
|
+
const origins = value.origins;
|
|
325139
|
+
if (!Array.isArray(cookies) || !Array.isArray(origins))
|
|
325140
|
+
return false;
|
|
325141
|
+
const cookiesValid = cookies.every((cookie) => {
|
|
325142
|
+
if (!isRecord(cookie))
|
|
325143
|
+
return false;
|
|
325144
|
+
return typeof cookie.name === "string" && typeof cookie.value === "string" && typeof cookie.domain === "string" && typeof cookie.path === "string" && typeof cookie.expires === "number" && Number.isFinite(cookie.expires) && typeof cookie.httpOnly === "boolean" && typeof cookie.secure === "boolean" && (cookie.sameSite === "Strict" || cookie.sameSite === "Lax" || cookie.sameSite === "None");
|
|
325145
|
+
});
|
|
325146
|
+
if (!cookiesValid)
|
|
325147
|
+
return false;
|
|
325148
|
+
const originsValid = origins.every((origin) => {
|
|
325149
|
+
if (!isRecord(origin) || typeof origin.origin !== "string" || !Array.isArray(origin.localStorage))
|
|
325150
|
+
return false;
|
|
325151
|
+
return origin.localStorage.every((entry) => isRecord(entry) && typeof entry.name === "string" && typeof entry.value === "string");
|
|
325152
|
+
});
|
|
325153
|
+
if (!originsValid)
|
|
325154
|
+
return false;
|
|
325155
|
+
return cookies.length > 0 || origins.some((origin) => origin.localStorage.length > 0);
|
|
325156
|
+
}
|
|
325157
|
+
function isValidStorageStateJson(value) {
|
|
325158
|
+
try {
|
|
325159
|
+
return isValidStorageState(JSON.parse(value));
|
|
325160
|
+
} catch {
|
|
325161
|
+
return false;
|
|
325162
|
+
}
|
|
325163
|
+
}
|
|
325164
|
+
function isStorageStateShape(value) {
|
|
325165
|
+
if (!value || typeof value !== "object")
|
|
325166
|
+
return false;
|
|
325167
|
+
return isValidStorageState(value);
|
|
325168
|
+
}
|
|
325169
|
+
function isValidLocalAuthStateFile(path) {
|
|
325170
|
+
try {
|
|
325171
|
+
return isStorageStateShape(JSON.parse((0, node_fs_1.readFileSync)(path, "utf8")));
|
|
325172
|
+
} catch {
|
|
325173
|
+
return false;
|
|
325174
|
+
}
|
|
325175
|
+
}
|
|
325176
|
+
function cleanupLocalAuthStateFiles(runsDir, projectId, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325177
|
+
ensurePrivateDirectory2(runsDir);
|
|
325178
|
+
const projectSegment = safeSegment(projectId, "default");
|
|
325179
|
+
const legacyName = `auth-state-${projectSegment}.json`;
|
|
325180
|
+
const prefix = `auth-state-${projectSegment}-`;
|
|
325181
|
+
for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
|
|
325182
|
+
if (name === legacyName) {
|
|
325183
|
+
removeFile((0, node_path_1.join)(runsDir, name));
|
|
325184
|
+
continue;
|
|
325185
|
+
}
|
|
325186
|
+
if (!name.startsWith(prefix))
|
|
325187
|
+
continue;
|
|
325188
|
+
const path = (0, node_path_1.join)(runsDir, name);
|
|
325189
|
+
try {
|
|
325190
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
|
|
325191
|
+
if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
|
|
325192
|
+
removeFile(path);
|
|
325193
|
+
}
|
|
325194
|
+
} catch {
|
|
325195
|
+
}
|
|
325196
|
+
}
|
|
325197
|
+
}
|
|
325198
|
+
function cleanupExpiredLocalAuthStateFiles2(runsDir, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325199
|
+
ensurePrivateDirectory2(runsDir);
|
|
325200
|
+
for (const name of (0, node_fs_1.readdirSync)(runsDir)) {
|
|
325201
|
+
if (!name.startsWith("auth-state-"))
|
|
325202
|
+
continue;
|
|
325203
|
+
const path = (0, node_path_1.join)(runsDir, name);
|
|
325204
|
+
try {
|
|
325205
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs) {
|
|
325206
|
+
if (!name.endsWith(".lock") || staleLockCanBeReaped(path, now, maxAgeMs))
|
|
325207
|
+
removeFile(path);
|
|
325208
|
+
}
|
|
325209
|
+
} catch {
|
|
325210
|
+
}
|
|
325211
|
+
}
|
|
325212
|
+
}
|
|
325213
|
+
function localAuthStatePaths2(runsDir, input, runId) {
|
|
325214
|
+
cleanupLocalAuthStateFiles(runsDir, input.projectId);
|
|
325215
|
+
const projectSegment = safeSegment(input.projectId, "default");
|
|
325216
|
+
const runSegment = safeSegment(runId, `${process.pid}`);
|
|
325217
|
+
const fingerprint = localAuthStateFingerprint(input);
|
|
325218
|
+
const cachePath = (0, node_path_1.join)(runsDir, `auth-state-${projectSegment}-${fingerprint}.json`);
|
|
325219
|
+
return {
|
|
325220
|
+
cachePath,
|
|
325221
|
+
capturePath: `${cachePath}.capture-${runSegment}-${process.pid}.tmp`,
|
|
325222
|
+
fingerprint,
|
|
325223
|
+
lockKey: `${normalizeOrigin(input.serverUrl)}:${projectSegment}:${fingerprint}`,
|
|
325224
|
+
lockPath: `${cachePath}.lock`
|
|
325225
|
+
};
|
|
325226
|
+
}
|
|
325227
|
+
function getValidLocalAuthState2(path, now = Date.now(), maxAgeMs = exports.LOCAL_AUTH_STATE_MAX_AGE_MS) {
|
|
325228
|
+
if (!(0, node_fs_1.existsSync)(path))
|
|
325229
|
+
return void 0;
|
|
325230
|
+
try {
|
|
325231
|
+
if (now - (0, node_fs_1.statSync)(path).mtimeMs >= maxAgeMs || !isValidLocalAuthStateFile(path)) {
|
|
325232
|
+
removeFile(path);
|
|
325233
|
+
return void 0;
|
|
325234
|
+
}
|
|
325235
|
+
return path;
|
|
325236
|
+
} catch {
|
|
325237
|
+
return void 0;
|
|
325238
|
+
}
|
|
325239
|
+
}
|
|
325240
|
+
async function waitForValidLocalAuthState(path, timeoutMs = exports.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100) {
|
|
325241
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
325242
|
+
do {
|
|
325243
|
+
const valid = getValidLocalAuthState2(path);
|
|
325244
|
+
if (valid)
|
|
325245
|
+
return valid;
|
|
325246
|
+
const remaining = deadline - Date.now();
|
|
325247
|
+
if (remaining <= 0)
|
|
325248
|
+
return void 0;
|
|
325249
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(Math.max(10, pollMs), remaining)));
|
|
325250
|
+
} while (true);
|
|
325251
|
+
}
|
|
325252
|
+
async function waitForLocalAuthStateProducer2(path, lockKey, startupGraceMs = 2e3, maxWaitMs = exports.LOCAL_AUTH_STATE_WAIT_MS, pollMs = 100, lockPath) {
|
|
325253
|
+
const startedAt = Date.now();
|
|
325254
|
+
const producerIsActive = () => lockTails.has(lockKey) || !!lockPath && (0, node_fs_1.existsSync)(lockPath);
|
|
325255
|
+
let sawProducer = producerIsActive();
|
|
325256
|
+
do {
|
|
325257
|
+
const valid = getValidLocalAuthState2(path);
|
|
325258
|
+
if (valid)
|
|
325259
|
+
return valid;
|
|
325260
|
+
const producerActive = producerIsActive();
|
|
325261
|
+
if (producerActive)
|
|
325262
|
+
sawProducer = true;
|
|
325263
|
+
const elapsed = Date.now() - startedAt;
|
|
325264
|
+
if (sawProducer && !producerActive)
|
|
325265
|
+
return void 0;
|
|
325266
|
+
if (!sawProducer && elapsed >= Math.max(0, startupGraceMs))
|
|
325267
|
+
return void 0;
|
|
325268
|
+
if (elapsed >= Math.max(0, maxWaitMs))
|
|
325269
|
+
return void 0;
|
|
325270
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.max(10, pollMs)));
|
|
325271
|
+
} while (true);
|
|
325272
|
+
}
|
|
325273
|
+
function promoteLocalAuthStateCapture2(capturePath, cachePath, replaceExisting = false, lease) {
|
|
325274
|
+
if (lease && !lease.isOwner()) {
|
|
325275
|
+
removeFile(capturePath);
|
|
325276
|
+
return false;
|
|
325277
|
+
}
|
|
325278
|
+
if (!(0, node_fs_1.existsSync)(capturePath) || !isValidLocalAuthStateFile(capturePath)) {
|
|
325279
|
+
removeFile(capturePath);
|
|
325280
|
+
return false;
|
|
325281
|
+
}
|
|
325282
|
+
try {
|
|
325283
|
+
(0, node_fs_1.chmodSync)(capturePath, 384);
|
|
325284
|
+
} catch {
|
|
325285
|
+
}
|
|
325286
|
+
try {
|
|
325287
|
+
if (lease && !lease.isOwner()) {
|
|
325288
|
+
removeFile(capturePath);
|
|
325289
|
+
return false;
|
|
325290
|
+
}
|
|
325291
|
+
if ((0, node_fs_1.existsSync)(cachePath)) {
|
|
325292
|
+
if (!replaceExisting && isValidLocalAuthStateFile(cachePath)) {
|
|
325293
|
+
removeFile(capturePath);
|
|
325294
|
+
return true;
|
|
325295
|
+
}
|
|
325296
|
+
removeFile(cachePath);
|
|
325297
|
+
}
|
|
325298
|
+
if (lease && !lease.isOwner()) {
|
|
325299
|
+
removeFile(capturePath);
|
|
325300
|
+
return false;
|
|
325301
|
+
}
|
|
325302
|
+
(0, node_fs_1.renameSync)(capturePath, cachePath);
|
|
325303
|
+
try {
|
|
325304
|
+
(0, node_fs_1.chmodSync)(cachePath, 384);
|
|
325305
|
+
} catch {
|
|
325306
|
+
}
|
|
325307
|
+
return true;
|
|
325308
|
+
} catch {
|
|
325309
|
+
const winnerIsValid = (0, node_fs_1.existsSync)(cachePath) && isValidLocalAuthStateFile(cachePath);
|
|
325310
|
+
removeFile(capturePath);
|
|
325311
|
+
return winnerIsValid;
|
|
325312
|
+
}
|
|
325313
|
+
}
|
|
325314
|
+
function discardLocalAuthStateCapture2(capturePath) {
|
|
325315
|
+
removeFile(capturePath);
|
|
325316
|
+
}
|
|
325317
|
+
var lockTails = /* @__PURE__ */ new Map();
|
|
325318
|
+
function publishCompleteLockOwner(lockPath, owner, linkFile) {
|
|
325319
|
+
const metadata = JSON.stringify(owner);
|
|
325320
|
+
const tempPath = `${lockPath}.${owner.pid}.${owner.token}.tmp`;
|
|
325321
|
+
let createdLock = false;
|
|
325322
|
+
try {
|
|
325323
|
+
(0, node_fs_1.writeFileSync)(tempPath, metadata, { flag: "wx", mode: 384 });
|
|
325324
|
+
try {
|
|
325325
|
+
linkFile(tempPath, lockPath);
|
|
325326
|
+
createdLock = true;
|
|
325327
|
+
return (0, node_fs_1.openSync)(lockPath, "r+");
|
|
325328
|
+
} catch (error2) {
|
|
325329
|
+
const code = error2.code;
|
|
325330
|
+
if (!["EPERM", "EACCES", "EXDEV", "ENOSYS", "ENOTSUP", "EOPNOTSUPP"].includes(code ?? "")) {
|
|
325331
|
+
throw error2;
|
|
325332
|
+
}
|
|
325333
|
+
let fd;
|
|
325334
|
+
try {
|
|
325335
|
+
fd = (0, node_fs_1.openSync)(lockPath, "wx+", 384);
|
|
325336
|
+
createdLock = true;
|
|
325337
|
+
(0, node_fs_1.writeFileSync)(fd, metadata, { encoding: "utf8" });
|
|
325338
|
+
(0, node_fs_1.fsyncSync)(fd);
|
|
325339
|
+
return fd;
|
|
325340
|
+
} catch (fallbackError) {
|
|
325341
|
+
if (fd !== void 0) {
|
|
325342
|
+
try {
|
|
325343
|
+
(0, node_fs_1.closeSync)(fd);
|
|
325344
|
+
} catch {
|
|
325345
|
+
}
|
|
325346
|
+
}
|
|
325347
|
+
if (createdLock)
|
|
325348
|
+
removeFile(lockPath);
|
|
325349
|
+
throw fallbackError;
|
|
325350
|
+
}
|
|
325351
|
+
}
|
|
325352
|
+
} catch (error2) {
|
|
325353
|
+
if (createdLock && readLockOwner(lockPath)?.token === owner.token)
|
|
325354
|
+
removeFile(lockPath);
|
|
325355
|
+
throw error2;
|
|
325356
|
+
} finally {
|
|
325357
|
+
removeFile(tempPath);
|
|
325358
|
+
}
|
|
325359
|
+
}
|
|
325360
|
+
async function acquireExclusiveFileLock(lockPath, linkFile = node_fs_1.linkSync) {
|
|
325361
|
+
ensurePrivateDirectory2((0, node_path_1.dirname)(lockPath));
|
|
325362
|
+
const startedAt = Date.now();
|
|
325363
|
+
while (true) {
|
|
325364
|
+
try {
|
|
325365
|
+
const ownerToken = (0, node_crypto_1.randomBytes)(24).toString("base64url");
|
|
325366
|
+
const fd = publishCompleteLockOwner(lockPath, { token: ownerToken, pid: process.pid }, linkFile);
|
|
325367
|
+
const ownsPath = () => {
|
|
325368
|
+
try {
|
|
325369
|
+
const owner = readLockOwner(lockPath);
|
|
325370
|
+
return owner?.token === ownerToken && owner.pid === process.pid;
|
|
325371
|
+
} catch {
|
|
325372
|
+
return false;
|
|
325373
|
+
}
|
|
325374
|
+
};
|
|
325375
|
+
const heartbeat = setInterval(() => {
|
|
325376
|
+
try {
|
|
325377
|
+
if (!ownsPath()) {
|
|
325378
|
+
clearInterval(heartbeat);
|
|
325379
|
+
return;
|
|
325380
|
+
}
|
|
325381
|
+
const now = /* @__PURE__ */ new Date();
|
|
325382
|
+
(0, node_fs_1.futimesSync)(fd, now, now);
|
|
325383
|
+
} catch {
|
|
325384
|
+
clearInterval(heartbeat);
|
|
325385
|
+
}
|
|
325386
|
+
}, Math.max(1e3, Math.floor(exports.LOCAL_AUTH_STATE_LOCK_STALE_MS / 3)));
|
|
325387
|
+
heartbeat.unref();
|
|
325388
|
+
let released = false;
|
|
325389
|
+
const release = (() => {
|
|
325390
|
+
if (released)
|
|
325391
|
+
return;
|
|
325392
|
+
released = true;
|
|
325393
|
+
clearInterval(heartbeat);
|
|
325394
|
+
try {
|
|
325395
|
+
if (ownsPath())
|
|
325396
|
+
removeFile(lockPath);
|
|
325397
|
+
} catch {
|
|
325398
|
+
}
|
|
325399
|
+
try {
|
|
325400
|
+
(0, node_fs_1.closeSync)(fd);
|
|
325401
|
+
} catch {
|
|
325402
|
+
}
|
|
325403
|
+
});
|
|
325404
|
+
release.isOwner = () => !released && ownsPath();
|
|
325405
|
+
return release;
|
|
325406
|
+
} catch (error2) {
|
|
325407
|
+
const code = error2.code;
|
|
325408
|
+
if (code !== "EEXIST")
|
|
325409
|
+
throw error2;
|
|
325410
|
+
try {
|
|
325411
|
+
if (staleLockCanBeReaped(lockPath, Date.now(), exports.LOCAL_AUTH_STATE_LOCK_STALE_MS)) {
|
|
325412
|
+
removeFile(lockPath);
|
|
325413
|
+
continue;
|
|
325414
|
+
}
|
|
325415
|
+
} catch {
|
|
325416
|
+
continue;
|
|
325417
|
+
}
|
|
325418
|
+
if (Date.now() - startedAt >= exports.LOCAL_AUTH_STATE_WAIT_MS) {
|
|
325419
|
+
throw new Error(`Timed out waiting for auth-state lock: ${(0, node_path_1.basename)(lockPath)}`);
|
|
325420
|
+
}
|
|
325421
|
+
await new Promise((resolve2) => setTimeout(resolve2, 100));
|
|
325422
|
+
}
|
|
325423
|
+
}
|
|
325424
|
+
}
|
|
325425
|
+
async function acquireLocalAuthStateLock2(key, lockPath, linkFile = node_fs_1.linkSync) {
|
|
325426
|
+
const previous = lockTails.get(key) ?? Promise.resolve();
|
|
325427
|
+
let unlockCurrent;
|
|
325428
|
+
const current = new Promise((resolve2) => {
|
|
325429
|
+
unlockCurrent = resolve2;
|
|
325430
|
+
});
|
|
325431
|
+
const tail = previous.then(() => current);
|
|
325432
|
+
lockTails.set(key, tail);
|
|
325433
|
+
await previous;
|
|
325434
|
+
let releaseFileLock;
|
|
325435
|
+
try {
|
|
325436
|
+
if (lockPath)
|
|
325437
|
+
releaseFileLock = await acquireExclusiveFileLock(lockPath, linkFile);
|
|
325438
|
+
} catch (error2) {
|
|
325439
|
+
unlockCurrent();
|
|
325440
|
+
if (lockTails.get(key) === tail)
|
|
325441
|
+
lockTails.delete(key);
|
|
325442
|
+
throw error2;
|
|
325443
|
+
}
|
|
325444
|
+
let released = false;
|
|
325445
|
+
const release = (() => {
|
|
325446
|
+
if (released)
|
|
325447
|
+
return;
|
|
325448
|
+
released = true;
|
|
325449
|
+
releaseFileLock?.();
|
|
325450
|
+
unlockCurrent();
|
|
325451
|
+
if (lockTails.get(key) === tail)
|
|
325452
|
+
lockTails.delete(key);
|
|
325453
|
+
});
|
|
325454
|
+
release.isOwner = () => !released && (releaseFileLock?.isOwner() ?? true);
|
|
325455
|
+
return release;
|
|
325456
|
+
}
|
|
325457
|
+
function localAuthStateFilename(path) {
|
|
325458
|
+
return (0, node_path_1.basename)(path);
|
|
325459
|
+
}
|
|
325460
|
+
}
|
|
325461
|
+
});
|
|
325462
|
+
|
|
324797
325463
|
// ../../node_modules/adm-zip/util/constants.js
|
|
324798
325464
|
var require_constants5 = __commonJS({
|
|
324799
325465
|
"../../node_modules/adm-zip/util/constants.js"(exports, module) {
|
|
@@ -325107,7 +325773,7 @@ var require_utils2 = __commonJS({
|
|
|
325107
325773
|
module.exports = Utils;
|
|
325108
325774
|
Utils.prototype.makeDir = function(folder) {
|
|
325109
325775
|
const self = this;
|
|
325110
|
-
function
|
|
325776
|
+
function mkdirSync4(fpath) {
|
|
325111
325777
|
let resolvedPath = fpath.split(self.sep)[0];
|
|
325112
325778
|
fpath.split(self.sep).forEach(function(name) {
|
|
325113
325779
|
if (!name || name.substr(-1, 1) === ":") return;
|
|
@@ -325125,7 +325791,7 @@ var require_utils2 = __commonJS({
|
|
|
325125
325791
|
if (stat && stat.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
|
|
325126
325792
|
});
|
|
325127
325793
|
}
|
|
325128
|
-
|
|
325794
|
+
mkdirSync4(folder);
|
|
325129
325795
|
};
|
|
325130
325796
|
Utils.prototype.writeFileTo = function(path, content, overwrite, attr) {
|
|
325131
325797
|
const self = this;
|
|
@@ -337049,6 +337715,8 @@ var require_dist2 = __commonJS({
|
|
|
337049
337715
|
__exportStar(require_discover_agent(), exports);
|
|
337050
337716
|
__exportStar(require_browser_pool(), exports);
|
|
337051
337717
|
__exportStar(require_resource_governor(), exports);
|
|
337718
|
+
__exportStar(require_local_auth_state(), exports);
|
|
337719
|
+
__exportStar(require_run_callback_proxy(), exports);
|
|
337052
337720
|
__exportStar(require_discover_explorer(), exports);
|
|
337053
337721
|
__exportStar(require_discover_feature_explorer(), exports);
|
|
337054
337722
|
__exportStar(require_discover_planner(), exports);
|
|
@@ -337325,7 +337993,7 @@ var require_package4 = __commonJS({
|
|
|
337325
337993
|
"package.json"(exports, module) {
|
|
337326
337994
|
module.exports = {
|
|
337327
337995
|
name: "@validate.qa/runner",
|
|
337328
|
-
version: "1.0.
|
|
337996
|
+
version: "1.0.43",
|
|
337329
337997
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
337330
337998
|
bin: {
|
|
337331
337999
|
"validate-runner": "dist/cli.js"
|
|
@@ -337333,7 +338001,7 @@ var require_package4 = __commonJS({
|
|
|
337333
338001
|
scripts: {
|
|
337334
338002
|
build: "tsup",
|
|
337335
338003
|
dev: "tsx src/cli.ts",
|
|
337336
|
-
test: "node --import tsx --test src/version-check.test.ts",
|
|
338004
|
+
test: "node --import tsx --test src/version-check.test.ts src/services/auth-state.test.ts",
|
|
337337
338005
|
"test:mobile": "node --import tsx --test --test-concurrency=2 src/runner.mobile-ai-env.test.ts src/mobile-device-queue.test.ts src/device-lease.test.ts src/result-payload-budget.test.ts src/services/appium-*.test.ts src/services/*mobile*.test.ts src/services/mobile/*.test.ts",
|
|
337338
338006
|
prepublishOnly: "npm run build"
|
|
337339
338007
|
},
|
|
@@ -337412,7 +338080,6 @@ function getConfigDir() {
|
|
|
337412
338080
|
}
|
|
337413
338081
|
|
|
337414
338082
|
// src/runner.ts
|
|
337415
|
-
import { existsSync as existsSync3, statSync, mkdirSync as mkdirSync4 } from "fs";
|
|
337416
338083
|
import { join as join6 } from "path";
|
|
337417
338084
|
import { homedir as homedir3 } from "os";
|
|
337418
338085
|
import { createHash } from "crypto";
|
|
@@ -337440,23 +338107,33 @@ var import_runner_core6 = __toESM(require_dist2());
|
|
|
337440
338107
|
|
|
337441
338108
|
// src/runner.ts
|
|
337442
338109
|
var import_runner_core11 = __toESM(require_dist2());
|
|
338110
|
+
var import_runner_core12 = __toESM(require_dist2());
|
|
337443
338111
|
|
|
337444
338112
|
// src/services/auth-state.ts
|
|
338113
|
+
init_dist();
|
|
337445
338114
|
var INLINE_LOGIN_TAG = "@inline-login";
|
|
337446
338115
|
var GUARDED_LOGIN_TAG = "@auth-guarded";
|
|
337447
338116
|
function sharedAuthInjectEnabled() {
|
|
337448
|
-
return
|
|
338117
|
+
return isSharedAuthInjectEnabled();
|
|
338118
|
+
}
|
|
338119
|
+
function hasGeneratedAuthGuard(playwrightCode) {
|
|
338120
|
+
if (!playwrightCode) return false;
|
|
338121
|
+
return /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
338122
|
+
}
|
|
338123
|
+
function shouldCaptureAuthState(tags, playwrightCode) {
|
|
338124
|
+
return sharedAuthInjectEnabled() && Array.isArray(tags) && tags.includes("@needs-auth") && tags.includes(GUARDED_LOGIN_TAG) && !tags.includes("@auth") && hasGeneratedAuthGuard(playwrightCode);
|
|
337449
338125
|
}
|
|
337450
338126
|
function hasInlineLoginSteps(playwrightCode) {
|
|
337451
338127
|
if (!playwrightCode) return false;
|
|
337452
338128
|
return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
|
|
337453
338129
|
}
|
|
337454
338130
|
function shouldUseAuthState(tags, playwrightCode) {
|
|
338131
|
+
if (!sharedAuthInjectEnabled()) return false;
|
|
337455
338132
|
if (!Array.isArray(tags)) return false;
|
|
337456
338133
|
if (tags.includes("@auth")) return false;
|
|
337457
338134
|
if (tags.includes("@api")) return true;
|
|
337458
338135
|
if (!tags.includes("@needs-auth")) return false;
|
|
337459
|
-
if (
|
|
338136
|
+
if (tags.includes(GUARDED_LOGIN_TAG) && hasGeneratedAuthGuard(playwrightCode)) return true;
|
|
337460
338137
|
const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
|
|
337461
338138
|
const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(playwrightCode);
|
|
337462
338139
|
return !tags.includes(INLINE_LOGIN_TAG) && !hasLegacyInlineLogin;
|
|
@@ -344169,8 +344846,8 @@ function withMediaReferences(result, refs) {
|
|
|
344169
344846
|
|
|
344170
344847
|
// src/runner.ts
|
|
344171
344848
|
init_dist();
|
|
344172
|
-
var import_runner_core12 = __toESM(require_dist2());
|
|
344173
344849
|
var import_runner_core13 = __toESM(require_dist2());
|
|
344850
|
+
var import_runner_core14 = __toESM(require_dist2());
|
|
344174
344851
|
function fingerprintTest(test) {
|
|
344175
344852
|
const code = test.framework === "APPIUM" ? [test.appiumCode ?? "", JSON.stringify(test.steps ?? [])].join("\n--steps--\n") : test.playwrightCode ?? "";
|
|
344176
344853
|
return createHash("sha256").update(test.name).update("\n--suite--\n").update(test.suiteName ?? "").update("\n--type--\n").update(test.testType ?? "").update("\n--framework--\n").update(test.framework ?? "").update("\n--code--\n").update(code).digest("hex").slice(0, 24);
|
|
@@ -344294,21 +344971,11 @@ var log = {
|
|
|
344294
344971
|
warn: (msg) => console.log(`${C2.yellow}\u26A0${C2.reset} ${msg}`),
|
|
344295
344972
|
dim: (msg) => console.log(`${C2.gray}${msg}${C2.reset}`)
|
|
344296
344973
|
};
|
|
344297
|
-
var AUTH_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
344298
344974
|
function getRunsDir() {
|
|
344299
344975
|
const dir = process.env.RUNS_DIR || join6(homedir3(), ".validate.qa", "runs");
|
|
344300
|
-
|
|
344976
|
+
(0, import_runner_core12.ensurePrivateDirectory)(dir);
|
|
344301
344977
|
return dir;
|
|
344302
344978
|
}
|
|
344303
|
-
function authStatePath(projectId) {
|
|
344304
|
-
return join6(getRunsDir(), `auth-state-${projectId || "default"}.json`);
|
|
344305
|
-
}
|
|
344306
|
-
function getValidAuthState(projectId) {
|
|
344307
|
-
const path = authStatePath(projectId);
|
|
344308
|
-
if (!existsSync3(path)) return void 0;
|
|
344309
|
-
const age = Date.now() - statSync(path).mtimeMs;
|
|
344310
|
-
return age < AUTH_MAX_AGE_MS ? path : void 0;
|
|
344311
|
-
}
|
|
344312
344979
|
function buildHeaders(authToken, projectId) {
|
|
344313
344980
|
return {
|
|
344314
344981
|
"Content-Type": "application/json",
|
|
@@ -344357,7 +345024,13 @@ async function claimRun(serverUrl, headers, runId) {
|
|
|
344357
345024
|
if (res.status === 429) return false;
|
|
344358
345025
|
if (!res.ok) throw new Error(`Claim failed: ${res.status}`);
|
|
344359
345026
|
const body = await res.json();
|
|
344360
|
-
|
|
345027
|
+
if (!body.claimed) {
|
|
345028
|
+
throw new Error("Claim response did not confirm ownership");
|
|
345029
|
+
}
|
|
345030
|
+
if (typeof body.claimEpoch !== "number" || !Number.isSafeInteger(body.claimEpoch) || body.claimEpoch < 0) {
|
|
345031
|
+
throw new Error("Claim response did not include a valid ownership epoch");
|
|
345032
|
+
}
|
|
345033
|
+
return body.claimEpoch;
|
|
344361
345034
|
}
|
|
344362
345035
|
async function postResult(serverUrl, headers, runId, result) {
|
|
344363
345036
|
const MAX_ATTEMPTS = 3;
|
|
@@ -344525,12 +345198,12 @@ async function postCaptureResult(serverUrl, headers, runId, result) {
|
|
|
344525
345198
|
});
|
|
344526
345199
|
if (!res.ok) throw new Error(`Failed to post capture result: ${res.status}`);
|
|
344527
345200
|
}
|
|
344528
|
-
async function postCredentialRotation(serverUrl, headers, runId, body) {
|
|
345201
|
+
async function postCredentialRotation(serverUrl, headers, runId, body, claimEpoch) {
|
|
344529
345202
|
try {
|
|
344530
345203
|
const res = await fetch(`${serverUrl}/api/runner/runs/${runId}/credential`, {
|
|
344531
345204
|
method: "POST",
|
|
344532
345205
|
headers,
|
|
344533
|
-
body: JSON.stringify(body),
|
|
345206
|
+
body: JSON.stringify({ ...body, ...claimEpoch !== void 0 ? { claimEpoch } : {} }),
|
|
344534
345207
|
signal: AbortSignal.timeout(3e4)
|
|
344535
345208
|
});
|
|
344536
345209
|
if (!res.ok) {
|
|
@@ -344867,6 +345540,12 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344867
345540
|
liveBrowserHandle,
|
|
344868
345541
|
onLiveBrowserSnapshot: requestLiveBrowserHeartbeat
|
|
344869
345542
|
});
|
|
345543
|
+
const callbackContext = {
|
|
345544
|
+
runId: run2.runId,
|
|
345545
|
+
serverUrl: opts.serverUrl,
|
|
345546
|
+
authHeader: headers.Authorization,
|
|
345547
|
+
claimEpoch: run2.claimEpoch
|
|
345548
|
+
};
|
|
344870
345549
|
const startTime = Date.now();
|
|
344871
345550
|
try {
|
|
344872
345551
|
if (mobileLeaseError) {
|
|
@@ -344896,7 +345575,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344896
345575
|
log.fail(`PRE-FLIGHT FAILED - ${preflightError}`);
|
|
344897
345576
|
return;
|
|
344898
345577
|
}
|
|
344899
|
-
const preflight = await (0,
|
|
345578
|
+
const preflight = await (0, import_runner_core13.resolveBaseUrlPreflight)(run2.baseUrl);
|
|
344900
345579
|
if (preflight.error) {
|
|
344901
345580
|
await postResult(opts.serverUrl, headers, run2.runId, { status: "ERROR", error: preflight.error });
|
|
344902
345581
|
log.fail(`PRE-FLIGHT FAILED - ${preflight.error}`);
|
|
@@ -344913,31 +345592,72 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344913
345592
|
log.fail("ERROR - No auth setup code");
|
|
344914
345593
|
return;
|
|
344915
345594
|
}
|
|
344916
|
-
const
|
|
344917
|
-
|
|
344918
|
-
|
|
344919
|
-
|
|
345595
|
+
const sharedAuthEnabled = sharedAuthInjectEnabled();
|
|
345596
|
+
if ((0, import_runner_core12.shouldSkipDisabledAutomaticAuthSetup)(run2.authForceRun, run2.authVerify, sharedAuthEnabled)) {
|
|
345597
|
+
const duration2 = Date.now() - startTime;
|
|
345598
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345599
|
+
status: "PASSED",
|
|
345600
|
+
logs: "Automatic auth prewarm skipped: shared session reuse is disabled.",
|
|
345601
|
+
duration: duration2
|
|
345602
|
+
});
|
|
345603
|
+
log.info(`Auth prewarm skipped (${duration2}ms) - SHARED_AUTH_INJECT=false`);
|
|
345604
|
+
return;
|
|
345605
|
+
}
|
|
345606
|
+
const authPaths2 = sharedAuthEnabled ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
345607
|
+
projectId: run2.projectId || opts.projectId || "default",
|
|
345608
|
+
serverUrl: opts.serverUrl,
|
|
344920
345609
|
baseUrl: run2.baseUrl,
|
|
344921
|
-
|
|
344922
|
-
|
|
344923
|
-
|
|
344924
|
-
|
|
344925
|
-
|
|
344926
|
-
|
|
345610
|
+
testVariables: run2.testVariables,
|
|
345611
|
+
authToken: opts.authToken
|
|
345612
|
+
}, run2.runId) : void 0;
|
|
345613
|
+
const releaseAuthLock2 = authPaths2 ? await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths2.lockKey, authPaths2.lockPath) : void 0;
|
|
345614
|
+
try {
|
|
345615
|
+
if (authPaths2 && (0, import_runner_core12.shouldSkipWarmAuthSetup)(
|
|
345616
|
+
run2.authForceRun,
|
|
345617
|
+
sharedAuthEnabled,
|
|
345618
|
+
!!(0, import_runner_core12.getValidLocalAuthState)(authPaths2.cachePath)
|
|
345619
|
+
)) {
|
|
345620
|
+
const duration3 = Date.now() - startTime;
|
|
345621
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345622
|
+
status: "PASSED",
|
|
345623
|
+
logs: "Auth setup skipped: the credential-bound local session is already warm.",
|
|
345624
|
+
duration: duration3
|
|
345625
|
+
});
|
|
345626
|
+
log.ok(`Auth setup skipped (${duration3}ms) - local session already warm`);
|
|
345627
|
+
return;
|
|
344927
345628
|
}
|
|
344928
|
-
|
|
344929
|
-
|
|
344930
|
-
|
|
344931
|
-
|
|
344932
|
-
|
|
344933
|
-
|
|
344934
|
-
|
|
344935
|
-
|
|
344936
|
-
|
|
344937
|
-
|
|
344938
|
-
|
|
344939
|
-
|
|
344940
|
-
|
|
345629
|
+
log.info(run2.authVerify ? "Auth verification: replaying the current login recipe" : sharedAuthEnabled ? "Auth setup: capturing a credential-bound local session" : "Auth setup: running login with shared session reuse disabled");
|
|
345630
|
+
const capturePath = sharedAuthEnabled && !run2.authVerify ? authPaths2?.capturePath : void 0;
|
|
345631
|
+
const result2 = await runNativeWithLiveBrowser({
|
|
345632
|
+
playwrightCode: run2.playwrightCode,
|
|
345633
|
+
baseUrl: run2.baseUrl,
|
|
345634
|
+
testName: "auth-setup",
|
|
345635
|
+
testVariables: run2.testVariables,
|
|
345636
|
+
authStateCapturePath: capturePath,
|
|
345637
|
+
validateContext: callbackContext
|
|
345638
|
+
});
|
|
345639
|
+
const captured = !!capturePath && !!authPaths2 && result2.passed && !result2.suspiciousPass && (0, import_runner_core12.promoteLocalAuthStateCapture)(
|
|
345640
|
+
capturePath,
|
|
345641
|
+
authPaths2.cachePath,
|
|
345642
|
+
run2.authForceRun === true,
|
|
345643
|
+
releaseAuthLock2
|
|
345644
|
+
);
|
|
345645
|
+
const duration2 = Date.now() - startTime;
|
|
345646
|
+
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
345647
|
+
status: result2.passed ? "PASSED" : "FAILED",
|
|
345648
|
+
apiCalls: result2.apiCalls,
|
|
345649
|
+
logs: result2.logs,
|
|
345650
|
+
error: result2.error,
|
|
345651
|
+
duration: duration2
|
|
345652
|
+
});
|
|
345653
|
+
log[result2.passed ? "ok" : "fail"](
|
|
345654
|
+
`Auth setup ${result2.passed ? "PASSED" : "FAILED"} (${duration2}ms)${captured ? " - session cached" : ""}${result2.error ? ` - ${result2.error}` : ""}`
|
|
345655
|
+
);
|
|
345656
|
+
return;
|
|
345657
|
+
} finally {
|
|
345658
|
+
if (authPaths2?.capturePath) (0, import_runner_core12.discardLocalAuthStateCapture)(authPaths2.capturePath);
|
|
345659
|
+
releaseAuthLock2?.();
|
|
345660
|
+
}
|
|
344941
345661
|
}
|
|
344942
345662
|
if (runMode === "batch-heal") {
|
|
344943
345663
|
const tests = run2.batchHealTests ?? [];
|
|
@@ -344956,7 +345676,7 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344956
345676
|
const batchResult = await (0, import_runner_core10.executeGrokPerBatchHeal)(tests, run2.baseUrl, {
|
|
344957
345677
|
testVariables: run2.testVariables,
|
|
344958
345678
|
runNativeTest: runNativeWithLiveBrowser,
|
|
344959
|
-
validateContext:
|
|
345679
|
+
validateContext: callbackContext,
|
|
344960
345680
|
onLog: healLogStream.onLog
|
|
344961
345681
|
});
|
|
344962
345682
|
await healLogStream.flushNow();
|
|
@@ -344992,6 +345712,15 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
344992
345712
|
);
|
|
344993
345713
|
const describeKind = isSerialDescribe ? "serial" : "non-serial";
|
|
344994
345714
|
log.info(`API test heal - ${subTestCount} sub-test(s) [${describeKind}], ${failingSubTestCount} budgeted, ${MAX_API_HEAL_ATTEMPTS} attempts`);
|
|
345715
|
+
const apiHealAuthPaths = shouldUseAuthState(run2.tags, run2.playwrightCode) ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
345716
|
+
projectId: run2.projectId || opts.projectId,
|
|
345717
|
+
serverUrl: opts.serverUrl,
|
|
345718
|
+
baseUrl: run2.baseUrl,
|
|
345719
|
+
testVariables: run2.testVariables,
|
|
345720
|
+
authToken: opts.authToken
|
|
345721
|
+
}, run2.runId) : void 0;
|
|
345722
|
+
const apiHealStorageStatePath = apiHealAuthPaths ? (0, import_runner_core12.getValidLocalAuthState)(apiHealAuthPaths.cachePath) : void 0;
|
|
345723
|
+
if (apiHealStorageStatePath) log.info("API heal verification is using saved auth state");
|
|
344995
345724
|
const stickyAppBugs = /* @__PURE__ */ new Map();
|
|
344996
345725
|
const lastChangeByTitle = /* @__PURE__ */ new Map();
|
|
344997
345726
|
let currentError = run2.lastFailureError ?? "Test failed";
|
|
@@ -345057,7 +345786,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
345057
345786
|
playwrightCode: lastHealedCode,
|
|
345058
345787
|
baseUrl: run2.baseUrl,
|
|
345059
345788
|
testVariables: run2.testVariables,
|
|
345060
|
-
|
|
345789
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345790
|
+
validateContext: callbackContext
|
|
345061
345791
|
});
|
|
345062
345792
|
if (finalVerifyResult.passed) {
|
|
345063
345793
|
log.ok(` Attempt ${attempt} terminal verification PASSED (${bugCount} app_bug, ${passCount} pass)`);
|
|
@@ -345077,7 +345807,9 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345077
345807
|
finalVerifyResult = await runNativeWithLiveBrowser({
|
|
345078
345808
|
playwrightCode: lastHealedCode,
|
|
345079
345809
|
baseUrl: run2.baseUrl,
|
|
345080
|
-
testVariables: run2.testVariables
|
|
345810
|
+
testVariables: run2.testVariables,
|
|
345811
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345812
|
+
validateContext: callbackContext
|
|
345081
345813
|
});
|
|
345082
345814
|
const stepResults = finalVerifyResult.stepResults ?? [];
|
|
345083
345815
|
const nonAppBugFails = stepResults.filter(
|
|
@@ -345127,7 +345859,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345127
345859
|
playwrightCode: lastHealedCode,
|
|
345128
345860
|
baseUrl: run2.baseUrl,
|
|
345129
345861
|
testVariables: run2.testVariables,
|
|
345130
|
-
|
|
345862
|
+
storageStatePath: apiHealStorageStatePath,
|
|
345863
|
+
validateContext: callbackContext
|
|
345131
345864
|
});
|
|
345132
345865
|
if (finalVerifyResult.passed) {
|
|
345133
345866
|
log.ok(` Attempt ${attempt} verification PASSED`);
|
|
@@ -345180,7 +345913,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345180
345913
|
if (classifyErrorMessage(composedError ?? null) === "config_issue") {
|
|
345181
345914
|
apiHealStatus = "ERROR";
|
|
345182
345915
|
} else {
|
|
345183
|
-
const rlDetection = (0,
|
|
345916
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
345184
345917
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
345185
345918
|
tags: run2.tags ?? [],
|
|
345186
345919
|
error: composedError ?? finalVerifyResult?.error ?? null,
|
|
@@ -345277,8 +346010,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345277
346010
|
lastHealAttempt: run2.lastHealAttempt ?? null,
|
|
345278
346011
|
lastHealedCode: run2.lastHealedCode ?? null,
|
|
345279
346012
|
failureScreenshot: run2.failureScreenshot ?? null,
|
|
345280
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
|
|
345281
|
-
validateContext:
|
|
346013
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
|
|
346014
|
+
validateContext: callbackContext,
|
|
345282
346015
|
liveBrowserHandle,
|
|
345283
346016
|
runNativeTest: runNativeWithLiveBrowser
|
|
345284
346017
|
};
|
|
@@ -345320,7 +346053,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345320
346053
|
let healStatus = result2.passed ? "PASSED" : isConfigIssue ? "ERROR" : "FAILED";
|
|
345321
346054
|
let healError = result2.error;
|
|
345322
346055
|
if (!result2.passed && !isConfigIssue) {
|
|
345323
|
-
const rlDetection = (0,
|
|
346056
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
345324
346057
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
345325
346058
|
tags: run2.tags ?? [],
|
|
345326
346059
|
error: result2.error,
|
|
@@ -345391,7 +346124,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345391
346124
|
// Allow generated `persistCredential(...)` calls inside the suite to
|
|
345392
346125
|
// POST back to the server with the runner's bearer token.
|
|
345393
346126
|
validateServerUrl: opts.serverUrl,
|
|
345394
|
-
validateRunnerAuthHeader: headers.Authorization
|
|
346127
|
+
validateRunnerAuthHeader: headers.Authorization,
|
|
346128
|
+
claimEpoch: run2.claimEpoch
|
|
345395
346129
|
});
|
|
345396
346130
|
const overall = result2.error && result2.testResults.length === 0 ? "ERROR" : result2.passed ? "PASSED" : "FAILED";
|
|
345397
346131
|
const rr = await fetch(`${opts.serverUrl}/api/runner/runs/${run2.runId}/regression-result`, {
|
|
@@ -345446,7 +346180,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345446
346180
|
playwrightCode: healedCode,
|
|
345447
346181
|
baseUrl: run2.baseUrl,
|
|
345448
346182
|
testVariables: run2.testVariables,
|
|
345449
|
-
validateContext:
|
|
346183
|
+
validateContext: callbackContext
|
|
345450
346184
|
});
|
|
345451
346185
|
results.push({
|
|
345452
346186
|
testCaseId: failedTest.testCaseId,
|
|
@@ -345509,8 +346243,8 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345509
346243
|
onAICall: healAudit,
|
|
345510
346244
|
modelOverride: run2.healerModel,
|
|
345511
346245
|
consecutiveFails: failedTest.consecutiveFails,
|
|
345512
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }),
|
|
345513
|
-
validateContext:
|
|
346246
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-healer" }, run2.claimEpoch),
|
|
346247
|
+
validateContext: callbackContext,
|
|
345514
346248
|
liveBrowserHandle,
|
|
345515
346249
|
runNativeTest: runNativeWithLiveBrowser
|
|
345516
346250
|
};
|
|
@@ -345715,6 +346449,13 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
345715
346449
|
}
|
|
345716
346450
|
return;
|
|
345717
346451
|
}
|
|
346452
|
+
ctx.onCredentialUpdate = async (input) => postCredentialRotation(
|
|
346453
|
+
opts.serverUrl,
|
|
346454
|
+
headers,
|
|
346455
|
+
run2.runId,
|
|
346456
|
+
{ ...input, actor: "ai-explorer" },
|
|
346457
|
+
run2.claimEpoch
|
|
346458
|
+
);
|
|
345718
346459
|
log.info(`Discovery (${ctx.mode}) on ${ctx.baseUrl}`);
|
|
345719
346460
|
const { push: pushAudit, drain: drainAudit } = createAuditFlusher(
|
|
345720
346461
|
`${opts.serverUrl}/api/runner/runs/${run2.runId}/ai-audit-batch`,
|
|
@@ -346417,7 +347158,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346417
347158
|
featureDescription: ctx.featureDescription,
|
|
346418
347159
|
featurePages: ctx.featurePages,
|
|
346419
347160
|
appBrief: ctx.appBrief,
|
|
346420
|
-
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" })
|
|
347161
|
+
onCredentialUpdate: async (input) => postCredentialRotation(opts.serverUrl, headers, run2.runId, { ...input, actor: "ai-capture" }, run2.claimEpoch)
|
|
346421
347162
|
});
|
|
346422
347163
|
if (logBuffer.length > 0) {
|
|
346423
347164
|
postCaptureLog(opts.serverUrl, headers, run2.runId, ctx.jobId, truncateTextTail(logBuffer.join("\n"), DISCOVERY_LOG_POST_MAX_CHARS, "capture log"));
|
|
@@ -346662,7 +347403,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346662
347403
|
}
|
|
346663
347404
|
return verify;
|
|
346664
347405
|
};
|
|
346665
|
-
return await (0,
|
|
347406
|
+
return await (0, import_runner_core14.executeMobileLiveHeal)({
|
|
346666
347407
|
steps: a.steps,
|
|
346667
347408
|
target: a.target,
|
|
346668
347409
|
testVariables: a.testVariables,
|
|
@@ -346679,7 +347420,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346679
347420
|
}
|
|
346680
347421
|
};
|
|
346681
347422
|
log.info("\u{1FA7A} Mobile AI heal \u2014 Phase 1 (script) \u2192 Phase 2 (live device, lazy)...");
|
|
346682
|
-
const heal = await (0,
|
|
347423
|
+
const heal = await (0, import_runner_core14.executeMobileTestWithHealing)(
|
|
346683
347424
|
mobileSteps,
|
|
346684
347425
|
healTarget,
|
|
346685
347426
|
{
|
|
@@ -346692,7 +347433,7 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346692
347433
|
healerOptions
|
|
346693
347434
|
);
|
|
346694
347435
|
const finalRes = nativeVerify.last;
|
|
346695
|
-
const outcome = (0,
|
|
347436
|
+
const outcome = (0, import_runner_core14.computeMobileHealOutcome)({
|
|
346696
347437
|
heal,
|
|
346697
347438
|
finalError: finalRes?.error ?? null,
|
|
346698
347439
|
reproduceError,
|
|
@@ -346838,26 +347579,77 @@ ${result2.logs}`);
|
|
|
346838
347579
|
}
|
|
346839
347580
|
return;
|
|
346840
347581
|
}
|
|
346841
|
-
const
|
|
346842
|
-
|
|
346843
|
-
|
|
346844
|
-
|
|
346845
|
-
|
|
346846
|
-
|
|
347582
|
+
const projectId = run2.projectId || opts.projectId;
|
|
347583
|
+
const authStateExpected = shouldUseAuthState(run2.tags, run2.playwrightCode);
|
|
347584
|
+
const captureAuth = shouldCaptureAuthState(run2.tags, run2.playwrightCode);
|
|
347585
|
+
const authPaths = authStateExpected || captureAuth ? (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
347586
|
+
projectId,
|
|
347587
|
+
serverUrl: opts.serverUrl,
|
|
346847
347588
|
baseUrl: run2.baseUrl,
|
|
346848
347589
|
testVariables: run2.testVariables,
|
|
346849
|
-
|
|
346850
|
-
|
|
346851
|
-
|
|
346852
|
-
|
|
346853
|
-
|
|
347590
|
+
authToken: opts.authToken
|
|
347591
|
+
}, run2.runId) : void 0;
|
|
347592
|
+
let releaseAuthLock;
|
|
347593
|
+
let storageStatePath;
|
|
347594
|
+
if (authPaths && authStateExpected) {
|
|
347595
|
+
releaseAuthLock = await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
|
|
347596
|
+
storageStatePath = (0, import_runner_core12.getValidLocalAuthState)(authPaths.cachePath);
|
|
347597
|
+
if (storageStatePath || !captureAuth) {
|
|
347598
|
+
releaseAuthLock();
|
|
347599
|
+
releaseAuthLock = void 0;
|
|
347600
|
+
}
|
|
347601
|
+
if (!storageStatePath && !captureAuth && sharedAuthInjectEnabled()) {
|
|
347602
|
+
log.info(`Waiting for the auth producer to warm project ${projectId}`);
|
|
347603
|
+
storageStatePath = await (0, import_runner_core12.waitForLocalAuthStateProducer)(
|
|
347604
|
+
authPaths.cachePath,
|
|
347605
|
+
authPaths.lockKey,
|
|
347606
|
+
2e3,
|
|
347607
|
+
void 0,
|
|
347608
|
+
100,
|
|
347609
|
+
authPaths.lockPath
|
|
347610
|
+
);
|
|
346854
347611
|
}
|
|
346855
|
-
}
|
|
347612
|
+
}
|
|
347613
|
+
if (storageStatePath) {
|
|
347614
|
+
log.info(`Using auth state for project ${projectId}`);
|
|
347615
|
+
}
|
|
347616
|
+
const runAuthPath = captureAuth ? authPaths?.capturePath : void 0;
|
|
347617
|
+
const result = await (async () => {
|
|
347618
|
+
try {
|
|
347619
|
+
return await runNativeWithLiveBrowser({
|
|
347620
|
+
playwrightCode: run2.playwrightCode,
|
|
347621
|
+
baseUrl: run2.baseUrl,
|
|
347622
|
+
testVariables: run2.testVariables,
|
|
347623
|
+
authStateCapturePath: runAuthPath,
|
|
347624
|
+
storageStatePath,
|
|
347625
|
+
validateContext: callbackContext
|
|
347626
|
+
});
|
|
347627
|
+
} finally {
|
|
347628
|
+
if (runAuthPath && authPaths) {
|
|
347629
|
+
if ((0, import_runner_core12.getValidLocalAuthState)(runAuthPath)) {
|
|
347630
|
+
const releasePromotionLock = releaseAuthLock ? void 0 : await (0, import_runner_core12.acquireLocalAuthStateLock)(authPaths.lockKey, authPaths.lockPath);
|
|
347631
|
+
try {
|
|
347632
|
+
const captured = (0, import_runner_core12.promoteLocalAuthStateCapture)(
|
|
347633
|
+
runAuthPath,
|
|
347634
|
+
authPaths.cachePath,
|
|
347635
|
+
!!storageStatePath,
|
|
347636
|
+
releaseAuthLock ?? releasePromotionLock
|
|
347637
|
+
);
|
|
347638
|
+
if (captured) log.info(`Captured auth state for project ${projectId}`);
|
|
347639
|
+
} finally {
|
|
347640
|
+
releasePromotionLock?.();
|
|
347641
|
+
}
|
|
347642
|
+
}
|
|
347643
|
+
(0, import_runner_core12.discardLocalAuthStateCapture)(runAuthPath);
|
|
347644
|
+
}
|
|
347645
|
+
releaseAuthLock?.();
|
|
347646
|
+
}
|
|
347647
|
+
})();
|
|
346856
347648
|
const duration = Date.now() - startTime;
|
|
346857
347649
|
let runStatus = result.passed ? "PASSED" : "FAILED";
|
|
346858
347650
|
let runError = result.error;
|
|
346859
347651
|
if (!result.passed) {
|
|
346860
|
-
const rlDetection = (0,
|
|
347652
|
+
const rlDetection = (0, import_runner_core13.detectRateLimitBlock)({
|
|
346861
347653
|
testName: run2.testName ?? run2.testCaseId ?? void 0,
|
|
346862
347654
|
tags: run2.tags ?? [],
|
|
346863
347655
|
error: result.error,
|
|
@@ -346918,6 +347710,12 @@ async function startRunner(opts) {
|
|
|
346918
347710
|
process.env.AUTH_TOKEN = authToken;
|
|
346919
347711
|
process.env.RUNNER_TOKEN = authToken;
|
|
346920
347712
|
if (projectId) process.env.PROJECT_ID = projectId;
|
|
347713
|
+
(0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir());
|
|
347714
|
+
const authCleanupTimer = setInterval(
|
|
347715
|
+
() => (0, import_runner_core12.cleanupExpiredLocalAuthStateFiles)(getRunsDir()),
|
|
347716
|
+
import_runner_core12.LOCAL_AUTH_STATE_CLEANUP_INTERVAL_MS
|
|
347717
|
+
);
|
|
347718
|
+
authCleanupTimer.unref?.();
|
|
346921
347719
|
const headers = buildHeaders(authToken, projectId);
|
|
346922
347720
|
console.log("");
|
|
346923
347721
|
console.log(`${C2.bold}${C2.cyan}validate.qa Local Runner${C2.reset}`);
|
|
@@ -347026,7 +347824,7 @@ async function startRunner(opts) {
|
|
|
347026
347824
|
capture: envInt("MAX_CAPTURE", DEFAULT_TYPE_LIMITS.capture),
|
|
347027
347825
|
run: envInt("MAX_RUN", DEFAULT_TYPE_LIMITS.run)
|
|
347028
347826
|
};
|
|
347029
|
-
const RESOURCE_GOVERNOR_OPTIONS = (0,
|
|
347827
|
+
const RESOURCE_GOVERNOR_OPTIONS = (0, import_runner_core13.createResourceGovernorOptions)({
|
|
347030
347828
|
isCloud: IS_CLOUD,
|
|
347031
347829
|
maxConcurrent: MAX_CONCURRENT,
|
|
347032
347830
|
typeLimits: TYPE_LIMITS
|
|
@@ -347194,8 +347992,8 @@ async function startRunner(opts) {
|
|
|
347194
347992
|
android: devices.some((d) => d.platform === "ANDROID" && d.isAvailable && d.state === "BOOTED")
|
|
347195
347993
|
} : { web: true, ios: false, android: false };
|
|
347196
347994
|
const bridge = enableMobile ? getBridgeState() : void 0;
|
|
347197
|
-
const resourceStatus = (0,
|
|
347198
|
-
const processInventory = await (0,
|
|
347995
|
+
const resourceStatus = (0, import_runner_core13.getResourceGovernorStatus)(RESOURCE_GOVERNOR_OPTIONS, Object.fromEntries(activeByType));
|
|
347996
|
+
const processInventory = await (0, import_runner_core13.getBrowserProcessInventory)();
|
|
347199
347997
|
lastInventoryPids = new Set(processInventory.processes.map((p) => p.pid));
|
|
347200
347998
|
lastInventoryKinds.clear();
|
|
347201
347999
|
for (const p of processInventory.processes) lastInventoryKinds.set(p.pid, p.kind);
|
|
@@ -347225,7 +348023,7 @@ async function startRunner(opts) {
|
|
|
347225
348023
|
resourceSnapshot: resourceStatus.snapshot,
|
|
347226
348024
|
resourceGovernor: resourceStatus.governor,
|
|
347227
348025
|
processInventory,
|
|
347228
|
-
aiQueue: (0,
|
|
348026
|
+
aiQueue: (0, import_runner_core13.getRunnerAIQueueStats)(),
|
|
347229
348027
|
liveBrowsers: import_runner_core11.liveBrowserRegistry.snapshotForHeartbeat(),
|
|
347230
348028
|
// Pre-flight checks (ANDROID_HOME, Xcode, Appium drivers, adb, etc).
|
|
347231
348029
|
// Cached & cheap — see getCachedDoctor above.
|
|
@@ -347336,7 +348134,7 @@ async function startRunner(opts) {
|
|
|
347336
348134
|
let pidsWatchdogWarned = false;
|
|
347337
348135
|
const pidsWatchdog = setInterval(() => {
|
|
347338
348136
|
if (shuttingDown) return;
|
|
347339
|
-
const snapshot = (0,
|
|
348137
|
+
const snapshot = (0, import_runner_core13.getResourceSnapshot)();
|
|
347340
348138
|
const pct = snapshot.pidsPct;
|
|
347341
348139
|
if (pct == null || !Number.isFinite(pct)) return;
|
|
347342
348140
|
if (pct >= PIDS_RECYCLE_PCT) {
|
|
@@ -347376,7 +348174,7 @@ async function startRunner(opts) {
|
|
|
347376
348174
|
log.dim(` Deferred ${run2.runId} (project ${runProject} at capacity ${getActiveForProject(runProject)}/${projectLimit})`);
|
|
347377
348175
|
continue;
|
|
347378
348176
|
}
|
|
347379
|
-
const admission = (0,
|
|
348177
|
+
const admission = (0, import_runner_core13.evaluateRunAdmission)(RESOURCE_GOVERNOR_OPTIONS, {
|
|
347380
348178
|
mode: runMode,
|
|
347381
348179
|
activeRuns,
|
|
347382
348180
|
activeByType: Object.fromEntries(activeByType),
|
|
@@ -347454,9 +348252,9 @@ async function startRunner(opts) {
|
|
|
347454
348252
|
leasedDeviceId = freeDeviceId;
|
|
347455
348253
|
leasedDeviceIds.set(freeDeviceId, run2.runId);
|
|
347456
348254
|
}
|
|
347457
|
-
let
|
|
348255
|
+
let claimEpoch = false;
|
|
347458
348256
|
try {
|
|
347459
|
-
|
|
348257
|
+
claimEpoch = await claimRun(serverUrl, headers, run2.runId);
|
|
347460
348258
|
} catch (err) {
|
|
347461
348259
|
if (leasedDeviceId) {
|
|
347462
348260
|
leasedDeviceIds.delete(leasedDeviceId);
|
|
@@ -347464,7 +348262,7 @@ async function startRunner(opts) {
|
|
|
347464
348262
|
}
|
|
347465
348263
|
throw err;
|
|
347466
348264
|
}
|
|
347467
|
-
if (
|
|
348265
|
+
if (claimEpoch === false) {
|
|
347468
348266
|
log.dim(` Skipped ${run2.runId} (already claimed)`);
|
|
347469
348267
|
if (leasedDeviceId) {
|
|
347470
348268
|
leasedDeviceIds.delete(leasedDeviceId);
|
|
@@ -347472,6 +348270,7 @@ async function startRunner(opts) {
|
|
|
347472
348270
|
}
|
|
347473
348271
|
continue;
|
|
347474
348272
|
}
|
|
348273
|
+
run2.claimEpoch = claimEpoch;
|
|
347475
348274
|
activeRuns++;
|
|
347476
348275
|
incrementType(runMode);
|
|
347477
348276
|
incrementProject(runProject);
|