@validate.qa/runner 1.0.43 → 1.0.45
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 +479 -59
- package/dist/cli.mjs +479 -59
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4779,6 +4779,204 @@ var init_origin_domain = __esm({
|
|
|
4779
4779
|
}
|
|
4780
4780
|
});
|
|
4781
4781
|
|
|
4782
|
+
// ../shared/dist/auth-plan.js
|
|
4783
|
+
function hasInlineLoginSteps(playwrightCode) {
|
|
4784
|
+
if (!playwrightCode)
|
|
4785
|
+
return false;
|
|
4786
|
+
return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
|
|
4787
|
+
}
|
|
4788
|
+
function hasGeneratedAuthGuard(playwrightCode) {
|
|
4789
|
+
if (!playwrightCode)
|
|
4790
|
+
return false;
|
|
4791
|
+
return /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
4792
|
+
}
|
|
4793
|
+
function legacyNeedsCookieState(tags, code, sharedAuthInjectEnabled2) {
|
|
4794
|
+
if (tags.includes("@auth"))
|
|
4795
|
+
return false;
|
|
4796
|
+
if (tags.includes("@api"))
|
|
4797
|
+
return true;
|
|
4798
|
+
if (!tags.includes("@needs-auth"))
|
|
4799
|
+
return false;
|
|
4800
|
+
if (sharedAuthInjectEnabled2 && tags.includes("@auth-guarded") && hasGeneratedAuthGuard(code))
|
|
4801
|
+
return true;
|
|
4802
|
+
const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
|
|
4803
|
+
const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(code);
|
|
4804
|
+
return !tags.includes("@inline-login") && !hasLegacyInlineLogin;
|
|
4805
|
+
}
|
|
4806
|
+
function explicitAuthTypeTags(t) {
|
|
4807
|
+
const found = /* @__PURE__ */ new Set();
|
|
4808
|
+
for (const tag of t) {
|
|
4809
|
+
if (AUTH_TYPE_TAGS.includes(tag))
|
|
4810
|
+
found.add(tag);
|
|
4811
|
+
else if (AUTH_TAG_ALIASES[tag])
|
|
4812
|
+
found.add(AUTH_TAG_ALIASES[tag]);
|
|
4813
|
+
}
|
|
4814
|
+
return [...found];
|
|
4815
|
+
}
|
|
4816
|
+
function invalidPlan(configurationError) {
|
|
4817
|
+
return {
|
|
4818
|
+
version: 1,
|
|
4819
|
+
source: "invalid",
|
|
4820
|
+
acquisition: "unsupported",
|
|
4821
|
+
presentation: "none",
|
|
4822
|
+
needsCookieState: false,
|
|
4823
|
+
needsStandingHeaders: false,
|
|
4824
|
+
standingHeadersMode: "none",
|
|
4825
|
+
needsLoginCreds: false,
|
|
4826
|
+
requiredEnvKeys: [],
|
|
4827
|
+
configurationError
|
|
4828
|
+
};
|
|
4829
|
+
}
|
|
4830
|
+
function explicitPlan(authTypeTag2, acquisition, presentation, opts) {
|
|
4831
|
+
return {
|
|
4832
|
+
version: 1,
|
|
4833
|
+
source: "explicit",
|
|
4834
|
+
authTypeTag: authTypeTag2,
|
|
4835
|
+
acquisition,
|
|
4836
|
+
presentation,
|
|
4837
|
+
needsCookieState: opts.cookie,
|
|
4838
|
+
needsStandingHeaders: opts.standingMode === "required",
|
|
4839
|
+
standingHeadersMode: opts.standingMode,
|
|
4840
|
+
needsLoginCreds: TEST_USER_ENV_KEYS.every((k) => opts.requiredEnvKeys.includes(k)),
|
|
4841
|
+
requiredEnvKeys: opts.requiredEnvKeys
|
|
4842
|
+
};
|
|
4843
|
+
}
|
|
4844
|
+
function deriveAuthPlan(tags, code, opts) {
|
|
4845
|
+
const t = Array.isArray(tags) ? tags : [];
|
|
4846
|
+
const sharedFlag = opts?.sharedAuthInjectEnabled ?? false;
|
|
4847
|
+
const isApi = t.includes("@api");
|
|
4848
|
+
const explicit = isApi ? explicitAuthTypeTags(t) : [];
|
|
4849
|
+
if (explicit.length > 0) {
|
|
4850
|
+
if (t.includes("@auth")) {
|
|
4851
|
+
return invalidPlan("@auth (self-login UI) cannot be combined with an explicit @api auth-type tag");
|
|
4852
|
+
}
|
|
4853
|
+
if (explicit.length > 1) {
|
|
4854
|
+
return invalidPlan(`conflicting auth-type tags: ${explicit.join(", ")}`);
|
|
4855
|
+
}
|
|
4856
|
+
switch (explicit[0]) {
|
|
4857
|
+
case AUTH_TAG_UNSUPPORTED:
|
|
4858
|
+
return {
|
|
4859
|
+
...invalidPlan("auth scheme not yet drivable (e.g. HMAC / mTLS / provider-replay) \u2014 needs manual configuration")
|
|
4860
|
+
};
|
|
4861
|
+
case AUTH_TAG_NONE:
|
|
4862
|
+
return explicitPlan(AUTH_TAG_NONE, "none", "none", { cookie: false, standingMode: "none", requiredEnvKeys: [] });
|
|
4863
|
+
case AUTH_TAG_STANDING:
|
|
4864
|
+
return explicitPlan(AUTH_TAG_STANDING, "standing-secret", "standing-header", { cookie: false, standingMode: "required", requiredEnvKeys: [API_AUTH_HEADERS_ENV_KEY] });
|
|
4865
|
+
case AUTH_TAG_LOGIN_BEARER:
|
|
4866
|
+
return explicitPlan(AUTH_TAG_LOGIN_BEARER, "request-login", "bearer", { cookie: false, standingMode: "none", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
4867
|
+
case AUTH_TAG_LOGIN_COOKIE:
|
|
4868
|
+
return explicitPlan(AUTH_TAG_LOGIN_COOKIE, "request-login", "cookie", { cookie: false, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
4869
|
+
case AUTH_TAG_COOKIE_STATE:
|
|
4870
|
+
return explicitPlan(AUTH_TAG_COOKIE_STATE, "browser-bootstrap", "cookie", { cookie: true, standingMode: "legacy-if-configured", requiredEnvKeys: [...TEST_USER_ENV_KEYS] });
|
|
4871
|
+
}
|
|
4872
|
+
}
|
|
4873
|
+
const cookie = legacyNeedsCookieState(t, code, sharedFlag);
|
|
4874
|
+
const authedLegacy = t.includes("@auth") || t.includes("@needs-auth") || isApi;
|
|
4875
|
+
return {
|
|
4876
|
+
version: 1,
|
|
4877
|
+
source: "legacy",
|
|
4878
|
+
acquisition: cookie ? "browser-bootstrap" : authedLegacy ? "request-login" : "none",
|
|
4879
|
+
presentation: authedLegacy ? "cookie" : "none",
|
|
4880
|
+
needsCookieState: cookie,
|
|
4881
|
+
needsStandingHeaders: false,
|
|
4882
|
+
// A legacy bare-@api test keeps injecting standing headers IF the project configured them
|
|
4883
|
+
// (preserving today's behavior) — but does not require them. Non-@api legacy tests never inject.
|
|
4884
|
+
standingHeadersMode: isApi ? "legacy-if-configured" : "none",
|
|
4885
|
+
needsLoginCreds: authedLegacy,
|
|
4886
|
+
requiredEnvKeys: authedLegacy ? [...TEST_USER_ENV_KEYS] : []
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4889
|
+
function authTypeTag(tags) {
|
|
4890
|
+
const t = Array.isArray(tags) ? tags : [];
|
|
4891
|
+
const explicit = explicitAuthTypeTags(t);
|
|
4892
|
+
return explicit.length === 1 ? explicit[0] : void 0;
|
|
4893
|
+
}
|
|
4894
|
+
var AUTH_TAG_COOKIE_STATE, AUTH_TAG_LOGIN_COOKIE, AUTH_TAG_LOGIN_BEARER, AUTH_TAG_STANDING, AUTH_TAG_NONE, AUTH_TAG_UNSUPPORTED, AUTH_TYPE_TAGS, AUTH_TAG_ALIASES, TEST_USER_ENV_KEYS, API_AUTH_HEADERS_ENV_KEY;
|
|
4895
|
+
var init_auth_plan = __esm({
|
|
4896
|
+
"../shared/dist/auth-plan.js"() {
|
|
4897
|
+
"use strict";
|
|
4898
|
+
AUTH_TAG_COOKIE_STATE = "@auth-cookie-state";
|
|
4899
|
+
AUTH_TAG_LOGIN_COOKIE = "@auth-login-cookie";
|
|
4900
|
+
AUTH_TAG_LOGIN_BEARER = "@auth-login-bearer";
|
|
4901
|
+
AUTH_TAG_STANDING = "@auth-standing";
|
|
4902
|
+
AUTH_TAG_NONE = "@auth-none";
|
|
4903
|
+
AUTH_TAG_UNSUPPORTED = "@auth-unsupported";
|
|
4904
|
+
AUTH_TYPE_TAGS = [
|
|
4905
|
+
AUTH_TAG_COOKIE_STATE,
|
|
4906
|
+
AUTH_TAG_LOGIN_COOKIE,
|
|
4907
|
+
AUTH_TAG_LOGIN_BEARER,
|
|
4908
|
+
AUTH_TAG_STANDING,
|
|
4909
|
+
AUTH_TAG_NONE,
|
|
4910
|
+
AUTH_TAG_UNSUPPORTED
|
|
4911
|
+
];
|
|
4912
|
+
AUTH_TAG_ALIASES = {
|
|
4913
|
+
"@auth-cookie": AUTH_TAG_COOKIE_STATE,
|
|
4914
|
+
"@auth-bearer": AUTH_TAG_LOGIN_BEARER,
|
|
4915
|
+
"@auth-token": AUTH_TAG_LOGIN_BEARER,
|
|
4916
|
+
"@auth-api-key": AUTH_TAG_STANDING,
|
|
4917
|
+
"@auth-standing-header": AUTH_TAG_STANDING
|
|
4918
|
+
};
|
|
4919
|
+
TEST_USER_ENV_KEYS = ["TEST_USER_EMAIL", "TEST_USER_PASSWORD"];
|
|
4920
|
+
API_AUTH_HEADERS_ENV_KEY = "API_AUTH_HEADERS";
|
|
4921
|
+
}
|
|
4922
|
+
});
|
|
4923
|
+
|
|
4924
|
+
// ../shared/dist/redact-needles.js
|
|
4925
|
+
function escapeRegExp(s) {
|
|
4926
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4927
|
+
}
|
|
4928
|
+
function buildNeedleMatcher(needles) {
|
|
4929
|
+
const usable = Array.from(new Set(needles.filter((n) => typeof n === "string" && n.length >= MIN_NEEDLE_LENGTH))).sort((a, b) => b.length - a.length);
|
|
4930
|
+
if (usable.length === 0)
|
|
4931
|
+
return null;
|
|
4932
|
+
return new RegExp(usable.map(escapeRegExp).join("|"), "g");
|
|
4933
|
+
}
|
|
4934
|
+
function redactNeedlesText(text, matcher) {
|
|
4935
|
+
if (!matcher || !text)
|
|
4936
|
+
return text;
|
|
4937
|
+
matcher.lastIndex = 0;
|
|
4938
|
+
const out = text.replace(matcher, REDACTED);
|
|
4939
|
+
return out === text ? text : out;
|
|
4940
|
+
}
|
|
4941
|
+
function redactNeedlesDeep(value, matcher) {
|
|
4942
|
+
if (!matcher)
|
|
4943
|
+
return value;
|
|
4944
|
+
if (typeof value === "string") {
|
|
4945
|
+
const scrubbed = redactNeedlesText(value, matcher);
|
|
4946
|
+
return scrubbed === value ? value : scrubbed;
|
|
4947
|
+
}
|
|
4948
|
+
if (Array.isArray(value)) {
|
|
4949
|
+
let changed = false;
|
|
4950
|
+
const next = value.map((item) => {
|
|
4951
|
+
const scrubbed = redactNeedlesDeep(item, matcher);
|
|
4952
|
+
if (scrubbed !== item)
|
|
4953
|
+
changed = true;
|
|
4954
|
+
return scrubbed;
|
|
4955
|
+
});
|
|
4956
|
+
return changed ? next : value;
|
|
4957
|
+
}
|
|
4958
|
+
if (value !== null && typeof value === "object") {
|
|
4959
|
+
let changed = false;
|
|
4960
|
+
const next = {};
|
|
4961
|
+
for (const [k, v] of Object.entries(value)) {
|
|
4962
|
+
const scrubbed = redactNeedlesDeep(v, matcher);
|
|
4963
|
+
if (scrubbed !== v)
|
|
4964
|
+
changed = true;
|
|
4965
|
+
next[k] = scrubbed;
|
|
4966
|
+
}
|
|
4967
|
+
return changed ? next : value;
|
|
4968
|
+
}
|
|
4969
|
+
return value;
|
|
4970
|
+
}
|
|
4971
|
+
var REDACTED, MIN_NEEDLE_LENGTH;
|
|
4972
|
+
var init_redact_needles = __esm({
|
|
4973
|
+
"../shared/dist/redact-needles.js"() {
|
|
4974
|
+
"use strict";
|
|
4975
|
+
REDACTED = "[REDACTED]";
|
|
4976
|
+
MIN_NEEDLE_LENGTH = 8;
|
|
4977
|
+
}
|
|
4978
|
+
});
|
|
4979
|
+
|
|
4782
4980
|
// ../shared/dist/voice-config.js
|
|
4783
4981
|
function normalizeTtsVoiceId(voiceId) {
|
|
4784
4982
|
if (!voiceId)
|
|
@@ -5053,8 +5251,16 @@ __export(dist_exports, {
|
|
|
5053
5251
|
ADDON_DEFINITIONS: () => ADDON_DEFINITIONS,
|
|
5054
5252
|
ADMIN_EMAIL_LIMITS: () => ADMIN_EMAIL_LIMITS,
|
|
5055
5253
|
AI_CREDIT_MARGIN: () => AI_CREDIT_MARGIN,
|
|
5254
|
+
API_AUTH_HEADERS_ENV_KEY: () => API_AUTH_HEADERS_ENV_KEY,
|
|
5056
5255
|
API_PREFIX: () => API_PREFIX,
|
|
5057
5256
|
AUDIO_CHUNK_DURATION_MS: () => AUDIO_CHUNK_DURATION_MS,
|
|
5257
|
+
AUTH_TAG_COOKIE_STATE: () => AUTH_TAG_COOKIE_STATE,
|
|
5258
|
+
AUTH_TAG_LOGIN_BEARER: () => AUTH_TAG_LOGIN_BEARER,
|
|
5259
|
+
AUTH_TAG_LOGIN_COOKIE: () => AUTH_TAG_LOGIN_COOKIE,
|
|
5260
|
+
AUTH_TAG_NONE: () => AUTH_TAG_NONE,
|
|
5261
|
+
AUTH_TAG_STANDING: () => AUTH_TAG_STANDING,
|
|
5262
|
+
AUTH_TAG_UNSUPPORTED: () => AUTH_TAG_UNSUPPORTED,
|
|
5263
|
+
AUTH_TYPE_TAGS: () => AUTH_TYPE_TAGS,
|
|
5058
5264
|
COST_PER_M: () => COST_PER_M,
|
|
5059
5265
|
CREDIT_PACKS_CENTS: () => CREDIT_PACKS_CENTS,
|
|
5060
5266
|
CREDIT_PRICES_CENTS: () => CREDIT_PRICES_CENTS,
|
|
@@ -5097,6 +5303,7 @@ __export(dist_exports, {
|
|
|
5097
5303
|
STT_MODEL_OPTIONS: () => STT_MODEL_OPTIONS,
|
|
5098
5304
|
STT_PROVIDER_OPTIONS: () => STT_PROVIDER_OPTIONS,
|
|
5099
5305
|
SUPPORT_EMAIL: () => SUPPORT_EMAIL,
|
|
5306
|
+
TEST_USER_ENV_KEYS: () => TEST_USER_ENV_KEYS,
|
|
5100
5307
|
TRANSIENT_CONTROL_ROLES: () => TRANSIENT_CONTROL_ROLES,
|
|
5101
5308
|
TRIAL_DURATION_DAYS: () => TRIAL_DURATION_DAYS,
|
|
5102
5309
|
TTS_MODEL_OPTIONS: () => TTS_MODEL_OPTIONS,
|
|
@@ -5106,10 +5313,12 @@ __export(dist_exports, {
|
|
|
5106
5313
|
accumulateStatements: () => accumulateStatements,
|
|
5107
5314
|
aiCostToCreditCents: () => aiCostToCreditCents,
|
|
5108
5315
|
analyzeMobileSelectorQuality: () => analyzeMobileSelectorQuality,
|
|
5316
|
+
authTypeTag: () => authTypeTag,
|
|
5109
5317
|
bugFoundQuarantineWhere: () => bugFoundQuarantineWhere,
|
|
5110
5318
|
buildAppiumCode: () => buildAppiumCode,
|
|
5111
5319
|
buildLaunchStep: () => buildLaunchStep,
|
|
5112
5320
|
buildMobileStep: () => buildMobileStep,
|
|
5321
|
+
buildNeedleMatcher: () => buildNeedleMatcher,
|
|
5113
5322
|
buildSandboxedEnv: () => buildSandboxedEnv,
|
|
5114
5323
|
buildSanitizedAgentEnv: () => buildSanitizedAgentEnv,
|
|
5115
5324
|
buildStepTarget: () => buildStepTarget,
|
|
@@ -5125,6 +5334,7 @@ __export(dist_exports, {
|
|
|
5125
5334
|
controlSignature: () => controlSignature,
|
|
5126
5335
|
countInnerTests: () => countInnerTests,
|
|
5127
5336
|
decideHarPosted: () => decideHarPosted,
|
|
5337
|
+
deriveAuthPlan: () => deriveAuthPlan,
|
|
5128
5338
|
deriveProjectKey: () => deriveProjectKey,
|
|
5129
5339
|
detectCrossOriginHtmlAsJson: () => detectCrossOriginHtmlAsJson,
|
|
5130
5340
|
detectsErrorBannerAssertion: () => detectsErrorBannerAssertion,
|
|
@@ -5192,6 +5402,8 @@ __export(dist_exports, {
|
|
|
5192
5402
|
planHasBillingPortal: () => planHasBillingPortal,
|
|
5193
5403
|
planRequiresCheckout: () => planRequiresCheckout,
|
|
5194
5404
|
platformTag: () => platformTag,
|
|
5405
|
+
redactNeedlesDeep: () => redactNeedlesDeep,
|
|
5406
|
+
redactNeedlesText: () => redactNeedlesText,
|
|
5195
5407
|
registrableDomain: () => registrableDomain,
|
|
5196
5408
|
resolveCrossOriginConfigOverride: () => resolveCrossOriginConfigOverride,
|
|
5197
5409
|
resolveMobileStepValuePlaceholders: () => resolveMobileStepValuePlaceholders,
|
|
@@ -5234,6 +5446,8 @@ var init_dist = __esm({
|
|
|
5234
5446
|
init_agent_env();
|
|
5235
5447
|
init_test_counting();
|
|
5236
5448
|
init_origin_domain();
|
|
5449
|
+
init_auth_plan();
|
|
5450
|
+
init_redact_needles();
|
|
5237
5451
|
init_voice_config();
|
|
5238
5452
|
init_feature_flags();
|
|
5239
5453
|
}
|
|
@@ -52401,11 +52615,11 @@ var require_leac = __commonJS({
|
|
|
52401
52615
|
if (r.str.length === 0) {
|
|
52402
52616
|
throw new Error(`Rule #${i} ("${r.name}") has empty "str" property, which is not allowed.`);
|
|
52403
52617
|
}
|
|
52404
|
-
return new RegExp(
|
|
52618
|
+
return new RegExp(escapeRegExp2(r.str), "y");
|
|
52405
52619
|
}
|
|
52406
|
-
return new RegExp(
|
|
52620
|
+
return new RegExp(escapeRegExp2(r.name), "y");
|
|
52407
52621
|
}
|
|
52408
|
-
function
|
|
52622
|
+
function escapeRegExp2(str) {
|
|
52409
52623
|
return str.replace(/[-[\]{}()*+!<=:?./\\^$|#\s,]/g, "\\$&");
|
|
52410
52624
|
}
|
|
52411
52625
|
function toSticky(re) {
|
|
@@ -68378,7 +68592,7 @@ var require_discover_ai_planner = __commonJS({
|
|
|
68378
68592
|
"../runner-core/dist/services/discover-ai-planner.js"(exports2) {
|
|
68379
68593
|
"use strict";
|
|
68380
68594
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
68381
|
-
exports2.SCENARIO_SYSTEM_PROMPT = void 0;
|
|
68595
|
+
exports2.PLANNER_STRENGTHEN_GATE_PROMPT = exports2.SCENARIO_SYSTEM_PROMPT = void 0;
|
|
68382
68596
|
exports2.getAIPlannerMaxTokens = getAIPlannerMaxTokens;
|
|
68383
68597
|
exports2.getSnapshotConfidence = getSnapshotConfidence;
|
|
68384
68598
|
exports2.compressSiteMap = compressSiteMap;
|
|
@@ -68961,7 +69175,7 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
|
|
|
68961
69175
|
4. **Multi-page journeys** - only when transitions were actually observed
|
|
68962
69176
|
5. **Bug reports** - for observed 500 errors, crashes, or broken features
|
|
68963
69177
|
6. **Steps are human-readable** - "Fill the email field", not "page.getByLabel('Email').fill()"
|
|
68964
|
-
7. **Expected outcomes are plain-English success criteria** - describe the user-visible product result, not the controls touched along the way. Steps already cover buttons, inputs, links, fields, headings, tabs, dialogs, and menus. Expected outcomes should say what the user can now do or rely on (record saved, message sent, search results filtered, validation explained, dashboard accessible). Include durable routes or exact copy only when they are the real product outcome; do NOT list "heading visible", "button visible", "search input visible", or similar element inventory unless that exact control is the behavior under test. **Outcome-strength
|
|
69178
|
+
7. **Expected outcomes are plain-English success criteria** - describe the user-visible product result, not the controls touched along the way. Steps already cover buttons, inputs, links, fields, headings, tabs, dialogs, and menus. Expected outcomes should say what the user can now do or rely on (record saved, message sent, search results filtered, validation explained, dashboard accessible). Include durable routes or exact copy only when they are the real product outcome; do NOT list "heading visible", "button visible", "search input visible", or similar element inventory unless that exact control is the behavior under test. **Outcome-strength check (per outcome, applies to EVERY scenario type):** evaluate each expectedOutcome separately. Delete it if it would already be true immediately before the scenario's primary action \u2014 UNLESS navigation, redirect, or revealing that state is itself the behavior under test. Keep at least one evidence-backed discriminator that proves the primary behavior actually occurred: specific result content or value, a created or removed entity, a changed count or status, persisted state after reload, or handled validation copy. Every value, count-change, persistence, creation, or removal assertion MUST trace to an earlier evidence-backed step that establishes the baseline or exact target. If the evidence does not support such a step, drop the assertion; do not invent setup. **Silent-failure BUG_REPORT exception:** apply ONLY when evidence explicitly records an action as broken because its intended state change did not occur. It does NOT apply to expected validation, permission denial, unmet prerequisites, loading or rate-limit states, or any non-BUG scenario. Prove the unchanged state with an evidence-backed before/after discriminator tied to the attempted run-scoped target \u2014 not merely "no toast", "no redirect", or "the form is still visible". Never invent copy to satisfy this check \u2014 Rule 1 still governs what you may assert.
|
|
68965
69179
|
8. **Auth-aware** - mark scenarios that need login. Use process.env.TEST_USER_EMAIL / PASSWORD
|
|
68966
69180
|
9. **No duplicates** - check existing test names before adding
|
|
68967
69181
|
10. **All tests start from the landing page** - every generated test navigates to \`/\` first and uses the app's own navigation (clicking links/buttons) to reach the target page. Steps must reflect this: say "Navigate to landing page and click Sign In" not "Navigate to /auth/login". Deep-linking to internal routes causes hydration failures in SPAs. \`targetPages\` lists the pages involved in the flow.
|
|
@@ -68974,7 +69188,7 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
|
|
|
68974
69188
|
Example - BAD happy path: \`["Navigate to /feature", "Click Create", "Verify dialog opens"]\` \u2192 only tests the trigger.
|
|
68975
69189
|
Example - GOOD happy path (generic, async): \`["Navigate to landing and log in", "Navigate to the feature page", "Note the current count of items on the list", "Click the primary action button", "Fill required inputs and submit", "Wait for completion signal (up to 60s)", "Verify the item count increased by 1", "Open the newly created item", "Verify its content is non-empty and contains the input I provided"]\`
|
|
68976
69190
|
Example - GOOD happy path (sync CRUD): \`["Navigate to landing and log in", "Navigate to the list page", "Click New", "Fill required fields", "Click Save", "Verify the new record appears in the list", "Click the record and verify its detail view shows the values I entered"]\`
|
|
68977
|
-
12. **Auth happy paths require concrete session proof.** A login/auth HAPPY_PATH is incomplete if it only proves "user reaches dashboard" or "authenticated page is visible." It MUST verify at least one durable signed-in signal that was observed in the evidence: user/account menu opens, Logout/Sign out is available, the observed display name/email/avatar/account item is present, authenticated navigation remains available after a reload, or another app-specific session control is usable. Prefer concrete observed labels from the evidence (for example "Logout", "Sign out", "Account", "Profile", or the user's own displayed name/email from evidence) over generic phrases like "personalized content." If the explorer only observed a redirect and no signed-in
|
|
69191
|
+
12. **Auth happy paths require concrete session proof.** A login/auth HAPPY_PATH is incomplete if it only proves "user reaches dashboard" or "authenticated page is visible." It MUST verify at least one durable signed-in signal that was observed in the evidence: user/account menu opens, Logout/Sign out is available, the observed display name/email/avatar/account item is present, authenticated navigation remains available after a reload, or another app-specific session control is usable. Prefer concrete observed labels from the evidence (for example "Logout", "Sign out", "Account", "Profile", or the user's own displayed name/email from evidence) over generic phrases like "personalized content." If the explorer only observed a redirect and no concrete signed-in control, do NOT invent one or instruct the executor to "open the account/user menu" \u2014 assert only the exact observed redirect (plus any directly supported transition, e.g. the login form is no longer shown), treat the redirect and any observed transition as transition outcomes \u2014 not concrete session proof \u2014 assign each its evidence-supported reliability, and name the missing concrete signal as a coverage gap in reasoning. Never substitute a menu of generic alternatives ("such as Logout, Profile, or \u2026") for one evidenced signal in a step or expected outcome.
|
|
68978
69192
|
13. **Independent execution contract.** Every scenario MUST pass when run alone, first, last, in random order, and in parallel against an environment with no relevant feature data except explicitly guaranteed immutable fixtures. A scenario that depends on a sibling scenario's side effects (e.g. "delete the conversation that another test created") is invalid. If two flows are inherently coupled, bundle them as a single USER_JOURNEY scenario.
|
|
68979
69193
|
14. **Scenario-owned state.** If a scenario reads, searches, opens, updates, messages, cancels, or deletes an entity, that scenario MUST create or seed the entity in its own setup steps. "Use an existing record" / "the conversation in the demo account" / "the existing user named X" are forbidden phrasings \u2014 they assume mutable pre-existing app data. Carve-out: baseline observations (current count, list length, "the dashboard heading is visible") are allowed as prerequisite reads ONLY when the durable assertion targets a self-created entity. Phrase setup as: "Click New, fill required fields with run-scoped values, submit and verify the new record appears, THEN proceed to <feature under test>."
|
|
68980
69194
|
15. **Run-scoped test data.** Generated entities MUST use unique, run-scoped identifiers \u2014 phrase as "test message <Date.now()>" or "QA-<unique id>" in steps. Subsequent assertions MUST target that unique identifier, not "the first item", "the latest message", "the last row", or any visible display name observed during exploration. Hardcoding any specific display name, username, email, conversation title, or other identifier observed in the explorer's snapshot is forbidden \u2014 that data is incidental, not a stable contract. The same identifiers will not exist (or will look different) when the test runs in a fresh environment or against a different account.
|
|
@@ -69037,6 +69251,9 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
|
|
|
69037
69251
|
Citing observed labels in \`expectedOutcomes\` is fine \u2014 that's asserting on stable product signals, not implementation specifics.
|
|
69038
69252
|
|
|
69039
69253
|
## Coverage Strategy
|
|
69254
|
+
**Coverage allocation \u2014 order matters.** The test budget is a ceiling, never a target. Before writing JSON, privately inventory each distinct behavior the evidence explicitly RECORDS: a SUCCESS, EMPTY_STATE, VALIDATION_ERROR, ERROR, BUG, an Explorer-Recorded Journey outcome, or an observed page/state transition. Assign each recorded behavior to exactly one scenario as its primary coverage owner \u2014 this does not prohibit repeating evidence-backed prerequisite setup and verification required by Rules 13\u201317. Bundle input variants that exercise the same purpose (e.g. two malformed-parameter cases that both return the same validation) into that one scenario. Use Page Intelligence only to make sure every described surface is reached \u2014 a merely listed control, route, provider, tab, or input does NOT by itself prove a behavior or earn its own scenario. Do NOT add an inferred scenario just to fill the budget. If a recorded behavior cannot be tested independently without unsupported mutable state, name it as a coverage gap (rule 17) rather than pretending it is self-contained. Treat a page/state transition as a distinct behavior only when navigation or revealing that state is itself the recorded product outcome; otherwise keep it as a step within the owning scenario \u2014 Page Intelligence creates no additional coverage obligation.
|
|
69255
|
+
|
|
69256
|
+
Apply the per-surface minimums below only where the evidence actually supports them (evidence support includes Rule 1's trigger-only structural exception):
|
|
69040
69257
|
- For every form: at least 1 HAPPY_PATH (submit valid data AND verify durable result per rule 11) + 1 EDGE_CASE (empty/invalid submission)
|
|
69041
69258
|
- For auth flows: login happy path (verify the post-login route AND a concrete signed-in/session signal per rule 12) + validation (wrong password, empty fields)
|
|
69042
69259
|
- For CRUD: each scenario MUST own the entity it operates on. Read/search/update/delete/cancel-on-existing/view-existing scenarios MUST include a create step earlier in the same scenario (or declare an immutable seed fixture by name). Never assume a record from prior runs or sibling tests exists. Splitting into atomic create/read/update/delete tests is fine ONLY when each test creates its own entity. When create/delete are tightly coupled (e.g. delete-only with no create flow elsewhere), bundle into a single USER_JOURNEY: create \u2192 verify \u2192 delete \u2192 verify removal.
|
|
@@ -69066,11 +69283,11 @@ Return ONLY one valid JSON object matching the schema below. Do NOT include <thi
|
|
|
69066
69283
|
{ "action": "Fill the email field with test credentials" },
|
|
69067
69284
|
{ "action": "Fill the password field with test credentials" },
|
|
69068
69285
|
{ "action": "Click the Sign In button" },
|
|
69069
|
-
{ "action": "
|
|
69286
|
+
{ "action": "After reaching the observed post-login page, reload it" }
|
|
69070
69287
|
],
|
|
69071
69288
|
"expectedOutcomes": [
|
|
69072
|
-
{ "type": "url", "description": "User reaches /dashboard after signing in", "reliability": "high" },
|
|
69073
|
-
{ "type": "element-
|
|
69289
|
+
{ "type": "url", "description": "User reaches /dashboard after signing in and remains there after reload", "reliability": "high" },
|
|
69290
|
+
{ "type": "element-hidden", "description": "The login form does not return after reload", "reliability": "medium" }
|
|
69074
69291
|
],
|
|
69075
69292
|
"targetPages": ["/auth/login", "/dashboard"],
|
|
69076
69293
|
"prerequisites": []
|
|
@@ -69100,6 +69317,46 @@ Each entry is exactly one of:
|
|
|
69100
69317
|
3 = medium (navigation, secondary features)
|
|
69101
69318
|
4 = low (edge cases, cosmetic)
|
|
69102
69319
|
5 = nice-to-have`;
|
|
69320
|
+
exports2.PLANNER_STRENGTHEN_GATE_PROMPT = `You are a senior QA lead performing ONE design-audit pass over a test PLAN another model wrote for a single web feature. The plan will be handed to a LIVE generator that opens the real app, follows each scenario's steps, observes the DOM, and writes the concrete assertions itself. Division of labor you MUST respect:
|
|
69321
|
+
- The PLAN owns the ORACLE: WHAT must be true for the behavior to be correct, stated in plain English.
|
|
69322
|
+
- The GENERATOR owns BINDING: locators and the exact runtime values it reads off the live page.
|
|
69323
|
+
Never write an expectation whose truth is decided by looking at the current app ("whatever the page shows"): that lets a live bug become the expected result. State the intended truth; the generator will bind it and report a mismatch if reality disagrees.
|
|
69324
|
+
|
|
69325
|
+
You are given (1) the EXACT evidence the plan was based on \u2014 the ONLY ground truth; you may use it to know WHAT behaviors exist, but you must NEVER introduce a behavior, route, element, or effect not present in or directly implied by it \u2014 and (2) the PLAN as JSON. Return the complete corrected plan.
|
|
69326
|
+
|
|
69327
|
+
## Rule 0 \u2014 REPAIR OR DROP, never merely describe (highest priority)
|
|
69328
|
+
Do NOT write a reasoning note about a fix you did not actually apply. Every change you claim must be present in the scenarios you output: if you say you strengthened an outcome, the new outcome text must be there; if you say you merged or removed a duplicate, NO surviving scenario may share the same primary action + outcome. If a defect cannot be repaired from the evidence, DROP the scenario or FLAG it \u2014 do not keep a known-weak scenario with an excuse. Flag-and-keep is forbidden.
|
|
69329
|
+
|
|
69330
|
+
Your only legal moves per scenario are: KEEP unchanged \xB7 REWRITE using a grounded fact \xB7 DROP as unsupported/duplicate \xB7 FLAG as needs-more-evidence (low reliability, honest note). You may never FORCE-REWRITE by inventing an effect the evidence does not show (a visible Delete control does not tell you whether delete removes, archives, hides, or disables \u2014 do not assume).
|
|
69331
|
+
|
|
69332
|
+
## Rule 1 \u2014 Classify each scenario, then require the matching oracle
|
|
69333
|
+
Identify the scenario's archetype and hold its expectedOutcomes to the right shape. An outcome is only valid if it would be FALSE under a plausible FAULT for that archetype:
|
|
69334
|
+
|
|
69335
|
+
- CREATE/EDIT/DELETE (mutation): the scenario must control its own target \u2014 create the entity it acts on with a self-chosen unique identifier (create \u2192 act \u2192 verify), or declare a prerequisite that resolves it (a fixture or a stable immutable reference). Never depend on unspecified pre-existing/ambient rows ("delete the first row", "open an existing order"). Oracle = the owned entity's before\u2192after: it appears after create / reflects the edit / is absent after delete (verify by the unique identifier, ideally after reload), while an unrelated sentinel is unaffected. Plausible faults to defeat: no-op, wrong-target, duplicate, non-persistence-after-reload, over-broad delete.
|
|
69336
|
+
- LIST/READ/DETAIL: oracle = the returned content corresponds to what was requested (the opened item's identifier matches the one selected). No before\u2192after is required.
|
|
69337
|
+
- FILTER/SEARCH/SORT: oracle = a delta \u2014 a known in-scope item remains AND a known out-of-scope item disappears (or order changes as specified). "Matching items are shown" alone is a no-op (a broken filter that shows everything passes).
|
|
69338
|
+
- VALIDATION/NEGATIVE: oracle must distinguish "the action was correctly rejected by validation" from "nothing happened / a dead control". Prefer the grounded error signal the evidence recorded (exact message or the specific field marked invalid) plus the absence of the forbidden effect (no entity created). If no error signal was observed, keep it structural at low reliability and FLAG.
|
|
69339
|
+
- NAVIGATION/REDIRECT/AUTH-GUARD: the destination is a legitimate primary effect \u2014 assert the route AND one observed landing signal (heading/content) so a redirect to a broken page fails. Route-presence as an outcome on a NON-navigation scenario is padding \u2014 drop it.
|
|
69340
|
+
- TOGGLE/IDEMPOTENT: assert the changed attribute/state (e.g. pressed/checked/disabled), or for idempotent actions that no duplicate is created \u2014 not mere visibility.
|
|
69341
|
+
- EXTERNAL INTEGRATION: assert the outbound URL/payload contains the expected parameters (drawn from evidence), not a frozen full string.
|
|
69342
|
+
- STATIC CONTENT: presence/label oracle is acceptable; do not inflate it. Verify shared chrome (nav/footer) ONCE, not per page.
|
|
69343
|
+
- APP BUG: exactly one BUG_REPORT per distinct root cause; assert the observed defective effect. Console errors, thrown exceptions, and network status codes are NOT page text \u2014 assert the observable page effect (no toast / no redirect / form unchanged), or set the outcome type to the right probe.
|
|
69344
|
+
|
|
69345
|
+
## Rule 2 \u2014 Exact values: judge by ORIGIN, not specificity
|
|
69346
|
+
- DROP as brittle: mutable ambient snapshots the test does not control \u2014 current counts ("8 cards"), seeded record names, other users' data, opaque IDs/slugs, phone numbers. Phrase these relationally ("the account's active business name", "the created item", "\u22651 result").
|
|
69347
|
+
- KEEP exact: values the SCENARIO itself introduced (a unique title it typed \u2014 assert it propagates), app-authored static strings (validation messages, headings, template copy, fixed error codes), and stable contract/derived facts (a contractual price, total = qty \xD7 unit price). These are real oracles, not brittleness.
|
|
69348
|
+
|
|
69349
|
+
## Rule 3 \u2014 Do not build on flows the evidence shows are broken or unrepeatable
|
|
69350
|
+
If evidence flags a route/flow as broken, a precondition as unobtainable (e.g. a single-use token already consumed, an account locked out), or a mutation as irreversible/one-shot on a shared account, you MUST NOT emit happy-path scenarios that depend on that flow succeeding or repeating. Reconcile: drop the dependent happy path, or route it through an alternate path the evidence actually shows.
|
|
69351
|
+
|
|
69352
|
+
## Rule 4 \u2014 Suite economy and completeness
|
|
69353
|
+
Budget is a ceiling, not a target. Merge scenarios that collide on (route + primary action + outcome-class). Do not enumerate one scenario per item across a long list when 1\u20133 representatives suffice. Reclaim freed budget to cover a DISTINCT behavior the evidence recorded that the plan left untested (swap, do not inflate). An action inferred only from a merely-listed control the evidence never showed exercised should be dropped and FLAGGED as a survey gap, not asserted.
|
|
69354
|
+
|
|
69355
|
+
## Rule 5 \u2014 reliability is honest
|
|
69356
|
+
high = the effect and its trigger are both directly observed in evidence; medium = observed but partial/indirect; low = structural or inferred (must be FLAGGED). Never launder an unsupported claim by labelling it low \u2014 if it is invented, drop it.
|
|
69357
|
+
|
|
69358
|
+
## Output
|
|
69359
|
+
Keep the exact JSON schema { "featureGroups":[...], "scenarios":[...], "reasoning":"..." }; each scenario keeps name, type, priority, variant, featureGroup, goal, steps[], expectedOutcomes[{type,description,reliability}], targetPages, prerequisites. In "reasoning", state ONLY changes you actually made (kept/rewritten/dropped/merged/flagged counts). Return ONLY the complete corrected JSON object \u2014 no markdown, no prose outside JSON.`;
|
|
69103
69360
|
function coerceRawPrerequisites(raw) {
|
|
69104
69361
|
if (!Array.isArray(raw))
|
|
69105
69362
|
return [];
|
|
@@ -69883,6 +70140,95 @@ Each entry is exactly one of:
|
|
|
69883
70140
|
}
|
|
69884
70141
|
}
|
|
69885
70142
|
}
|
|
70143
|
+
const gateFlag = (process.env.PLANNER_STRENGTHEN_GATE || "").trim().toLowerCase();
|
|
70144
|
+
const gateEnabled = gateFlag !== "false" && gateFlag !== "0";
|
|
70145
|
+
if (gateEnabled && context.mode !== "survey" && !options.systemPromptOverride && !options.userPromptOverride && !extraPlannerCallUsed && scenarios.length > 0) {
|
|
70146
|
+
const gateModel = options.gateModelOverride ? (0, ai_provider_js_1.resolveModelForTask)(options.gateModelOverride, "agent") : agentModel;
|
|
70147
|
+
const gateClient = options.gateModelOverride ? (0, ai_provider_js_1.getRunnerClientForModel)(options.gateModelOverride) : aiClient;
|
|
70148
|
+
const gateMaxTokens = getAIPlannerMaxTokens(gateModel);
|
|
70149
|
+
const gatePlan = { featureGroups, scenarios, reasoning: aiOutput.reasoning ?? "" };
|
|
70150
|
+
const gateUser = `EVIDENCE (ground truth):
|
|
70151
|
+
${userPrompt}
|
|
70152
|
+
|
|
70153
|
+
=====
|
|
70154
|
+
|
|
70155
|
+
PLAN TO AUDIT AND REPAIR (JSON):
|
|
70156
|
+
${JSON.stringify(gatePlan)}`;
|
|
70157
|
+
const gateStart = Date.now();
|
|
70158
|
+
let gateResponse = null;
|
|
70159
|
+
try {
|
|
70160
|
+
extraPlannerCallUsed = true;
|
|
70161
|
+
gateResponse = await (0, ai_retry_js_1.resilientAICall)((signal, arm) => (0, ai_queue_js_1.queuedAICall)(() => {
|
|
70162
|
+
arm();
|
|
70163
|
+
return gateClient.chat.completions.create({
|
|
70164
|
+
model: gateModel,
|
|
70165
|
+
messages: [
|
|
70166
|
+
{ role: "system", content: exports2.PLANNER_STRENGTHEN_GATE_PROMPT },
|
|
70167
|
+
{ role: "user", content: gateUser }
|
|
70168
|
+
],
|
|
70169
|
+
max_tokens: gateMaxTokens,
|
|
70170
|
+
temperature: 0.2,
|
|
70171
|
+
...(0, ai_provider_js_1.getJsonObjectResponseFormat)(gateModel),
|
|
70172
|
+
...(0, ai_provider_js_1.getNoThinkingRequestOptions)(gateModel)
|
|
70173
|
+
}, { signal });
|
|
70174
|
+
}), {
|
|
70175
|
+
label: "ai-planner-strengthen-gate",
|
|
70176
|
+
deferTimeoutUntilArmed: true,
|
|
70177
|
+
onRetry: (attempt, reason, delay) => logs2.push(`AI planner strengthen-gate call failed (${reason}) \u2014 retry ${attempt} in ${Math.round(delay / 1e3)}s`)
|
|
70178
|
+
});
|
|
70179
|
+
} catch (err) {
|
|
70180
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70181
|
+
logs2.push(`AI planner strengthen-gate failed (${message}) \u2014 keeping pre-gate result`);
|
|
70182
|
+
}
|
|
70183
|
+
if (gateResponse) {
|
|
70184
|
+
try {
|
|
70185
|
+
options.onAICall?.({
|
|
70186
|
+
iteration: 1,
|
|
70187
|
+
groupId: `ai-planner-${context.projectId.slice(0, 8)}`,
|
|
70188
|
+
callType: "discoveryAIPlannerStrengthenGate",
|
|
70189
|
+
model: gateModel,
|
|
70190
|
+
provider: (0, ai_provider_js_1.getProviderForModel)(gateModel),
|
|
70191
|
+
durationMs: Date.now() - gateStart,
|
|
70192
|
+
promptTokens: gateResponse.usage?.prompt_tokens,
|
|
70193
|
+
completionTokens: gateResponse.usage?.completion_tokens,
|
|
70194
|
+
totalTokens: gateResponse.usage?.total_tokens,
|
|
70195
|
+
assistantMessage: gateResponse.choices[0]?.message?.content,
|
|
70196
|
+
requestMessageCount: 2
|
|
70197
|
+
});
|
|
70198
|
+
} catch {
|
|
70199
|
+
}
|
|
70200
|
+
const gateRaw = gateResponse.choices[0]?.message?.content?.trim();
|
|
70201
|
+
if (gateRaw) {
|
|
70202
|
+
let gateOut = null;
|
|
70203
|
+
try {
|
|
70204
|
+
gateOut = JSON.parse((0, strip_json_fences_js_1.stripJsonFences)(gateRaw));
|
|
70205
|
+
} catch (err) {
|
|
70206
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70207
|
+
logs2.push(`AI planner strengthen-gate returned invalid JSON (${message}) \u2014 keeping pre-gate result`);
|
|
70208
|
+
}
|
|
70209
|
+
if (gateOut) {
|
|
70210
|
+
try {
|
|
70211
|
+
const vg = validateAIPlannerOutput(gateOut, siteMap, context.existingTestNames, context.testBudget, evidence.recordedJourneys, context.mode, context.focusAreas);
|
|
70212
|
+
const preGateCount = scenarios.length;
|
|
70213
|
+
const keepsCoverage = vg.scenarios.length >= Math.ceil(preGateCount * 0.5);
|
|
70214
|
+
const noWorseGaps = vg.coverageGaps.length <= coverageGaps.length;
|
|
70215
|
+
if (vg.scenarios.length > 0 && keepsCoverage && noWorseGaps) {
|
|
70216
|
+
logs2.push(`AI planner strengthen-gate [${gateModel}]: ${preGateCount}\u2192${vg.scenarios.length} scenarios, gaps ${coverageGaps.length}\u2192${vg.coverageGaps.length}; taking gated result`);
|
|
70217
|
+
scenarios = vg.scenarios;
|
|
70218
|
+
featureGroups = vg.featureGroups;
|
|
70219
|
+
warnings = [...warnings, ...vg.warnings];
|
|
70220
|
+
coverageGaps = vg.coverageGaps;
|
|
70221
|
+
} else {
|
|
70222
|
+
logs2.push(`AI planner strengthen-gate rejected (scen ${preGateCount}\u2192${vg.scenarios.length}, gaps ${coverageGaps.length}\u2192${vg.coverageGaps.length}); keeping pre-gate result`);
|
|
70223
|
+
}
|
|
70224
|
+
} catch (err) {
|
|
70225
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
70226
|
+
logs2.push(`AI planner strengthen-gate validation threw (${message}) \u2014 keeping pre-gate result`);
|
|
70227
|
+
}
|
|
70228
|
+
}
|
|
70229
|
+
}
|
|
70230
|
+
}
|
|
70231
|
+
}
|
|
69886
70232
|
const totalFinal = scenarios.length + coverageGaps.length;
|
|
69887
70233
|
const rejectionRateFinal = totalFinal > 0 ? coverageGaps.length / totalFinal : 0;
|
|
69888
70234
|
if (rejectionRateFinal > 0.5 && totalFinal >= 3) {
|
|
@@ -73662,7 +74008,7 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
73662
74008
|
// OpenAI-style secret key
|
|
73663
74009
|
];
|
|
73664
74010
|
var EMAIL_RE = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
73665
|
-
function
|
|
74011
|
+
function escapeRegExp2(value) {
|
|
73666
74012
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
73667
74013
|
}
|
|
73668
74014
|
function collectSecretVars(testVariables, minLen, keyRe) {
|
|
@@ -73681,7 +74027,7 @@ var require_mobile_heal_postprocess = __commonJS({
|
|
|
73681
74027
|
return text;
|
|
73682
74028
|
let out = text;
|
|
73683
74029
|
for (const { key, value } of collectSecretVars(testVariables, MIN_REDACT_LEN, REDACT_KEY_RE)) {
|
|
73684
|
-
out = out.replace(new RegExp(
|
|
74030
|
+
out = out.replace(new RegExp(escapeRegExp2(value), "g"), `{{${key}}}`);
|
|
73685
74031
|
}
|
|
73686
74032
|
for (const source of TOKEN_SOURCES) {
|
|
73687
74033
|
out = out.replace(new RegExp(source, "g"), "[REDACTED]");
|
|
@@ -76081,7 +76427,7 @@ var require_api_auth_headers = __commonJS({
|
|
|
76081
76427
|
"../runner-core/dist/services/api-auth-headers.js"(exports2) {
|
|
76082
76428
|
"use strict";
|
|
76083
76429
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
76084
|
-
exports2.parseApiAuthHeaders =
|
|
76430
|
+
exports2.parseApiAuthHeaders = parseApiAuthHeaders2;
|
|
76085
76431
|
exports2.resolveApiAuthHeaders = resolveApiAuthHeaders;
|
|
76086
76432
|
exports2.isAppOriginRequest = isAppOriginRequest;
|
|
76087
76433
|
exports2.buildOriginScopedApiFixtures = buildOriginScopedApiFixtures;
|
|
@@ -76094,7 +76440,7 @@ var require_api_auth_headers = __commonJS({
|
|
|
76094
76440
|
}
|
|
76095
76441
|
return false;
|
|
76096
76442
|
}
|
|
76097
|
-
function
|
|
76443
|
+
function parseApiAuthHeaders2(raw) {
|
|
76098
76444
|
if (!raw || typeof raw !== "string")
|
|
76099
76445
|
return void 0;
|
|
76100
76446
|
const trimmed = raw.trim();
|
|
@@ -76118,9 +76464,9 @@ var require_api_auth_headers = __commonJS({
|
|
|
76118
76464
|
function resolveApiAuthHeaders(input) {
|
|
76119
76465
|
if (input.explicit)
|
|
76120
76466
|
return input.explicit;
|
|
76121
|
-
if (
|
|
76467
|
+
if (input.standingHeadersMode === "none")
|
|
76122
76468
|
return void 0;
|
|
76123
|
-
return
|
|
76469
|
+
return parseApiAuthHeaders2(input.apiAuthHeadersVar);
|
|
76124
76470
|
}
|
|
76125
76471
|
function isAppOriginRequest(url, baseUrl) {
|
|
76126
76472
|
if (typeof url !== "string" || !baseUrl)
|
|
@@ -76387,7 +76733,7 @@ var require_playwright_native = __commonJS({
|
|
|
76387
76733
|
var RUNS_DIR = process.env.RUNS_DIR || (0, path_1.join)(PACKAGE_DIR, ".qa-runs");
|
|
76388
76734
|
var PW_CLI = require.resolve("@playwright/test/cli");
|
|
76389
76735
|
async function executeTestViaNativePlaywright2(options) {
|
|
76390
|
-
const { playwrightCode, baseUrl, testName, testVariables, authStateCapturePath, storageStatePath, extraHttpHeaders, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76736
|
+
const { playwrightCode, baseUrl, testName, testVariables, authStateCapturePath, storageStatePath, extraHttpHeaders, authStandingMode, validateContext, liveBrowserHandle, onLiveBrowserSnapshot } = options;
|
|
76391
76737
|
const logs2 = [];
|
|
76392
76738
|
const screenshots = [];
|
|
76393
76739
|
logs2.push(`\u{1F3AD} Native Playwright starting`);
|
|
@@ -76397,16 +76743,19 @@ var require_playwright_native = __commonJS({
|
|
|
76397
76743
|
logs2.push("\u26A0\uFE0F WARNING: baseUrl is empty - test will likely fail with navigation errors");
|
|
76398
76744
|
}
|
|
76399
76745
|
let runDir = null;
|
|
76746
|
+
let standingNeedleMatcher = null;
|
|
76400
76747
|
let callbackProxy;
|
|
76401
76748
|
try {
|
|
76402
76749
|
runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
|
|
76403
76750
|
await (0, promises_1.mkdir)(runDir, { recursive: true });
|
|
76404
76751
|
const isApiTest2 = playwrightCode.includes("@api") || playwrightCode.includes("{ request }") && !playwrightCode.includes("{ page");
|
|
76752
|
+
const effectiveStandingMode = isApiTest2 ? authStandingMode ?? "legacy-if-configured" : "none";
|
|
76405
76753
|
const resolvedExtraHeaders = (0, api_auth_headers_js_1.resolveApiAuthHeaders)({
|
|
76406
76754
|
explicit: extraHttpHeaders,
|
|
76407
|
-
|
|
76755
|
+
standingHeadersMode: effectiveStandingMode,
|
|
76408
76756
|
apiAuthHeadersVar: testVariables?.API_AUTH_HEADERS
|
|
76409
76757
|
});
|
|
76758
|
+
standingNeedleMatcher = (0, shared_1.buildNeedleMatcher)(resolvedExtraHeaders ? Object.values(resolvedExtraHeaders) : []);
|
|
76410
76759
|
const cssDisableSetup = `
|
|
76411
76760
|
const __qaLiveBrowserStateFile = process.env.VALIDATE_LIVE_BROWSER_STATE_PATH;
|
|
76412
76761
|
let __qaLiveBrowserInterval: ReturnType<typeof setInterval> | null = null;
|
|
@@ -76601,6 +76950,18 @@ test.afterEach(async ({ page }, testInfo) => {
|
|
|
76601
76950
|
logs2.push("\u26A0 Could not find @playwright/test import - animation disabling NOT injected");
|
|
76602
76951
|
}
|
|
76603
76952
|
const useScopedApiHeaders = isApiTest2 && !!resolvedExtraHeaders && !!importMatch && !!baseUrl;
|
|
76953
|
+
const authDelivery = { standingHeadersConfigured: !!resolvedExtraHeaders, standingHeadersInjected: useScopedApiHeaders };
|
|
76954
|
+
if (resolvedExtraHeaders && !useScopedApiHeaders) {
|
|
76955
|
+
logs2.push("\u26A0 Standing API auth headers were configured but could not be installed origin-scoped \u2014 failing closed (no global fallback).");
|
|
76956
|
+
return {
|
|
76957
|
+
passed: false,
|
|
76958
|
+
stepResults: [],
|
|
76959
|
+
screenshots: [],
|
|
76960
|
+
logs: logs2.join("\n"),
|
|
76961
|
+
error: 'CONFIG_ISSUE: Standing API auth headers were configured but could not be installed origin-scoped (needs a rewritable `import { test } from "@playwright/test"` and BASE_URL); refusing to attach them globally to avoid an off-origin credential leak.',
|
|
76962
|
+
authDelivery
|
|
76963
|
+
};
|
|
76964
|
+
}
|
|
76604
76965
|
let instrumentedCode;
|
|
76605
76966
|
if (isApiTest2) {
|
|
76606
76967
|
instrumentedCode = useScopedApiHeaders ? playwrightCode.replace(importMatch[0], importMatch[0].replace(/from\s+['"]@playwright\/test['"]/, "from './qa-api-fixtures'")) : playwrightCode;
|
|
@@ -76624,7 +76985,7 @@ test.afterEach(async ({ page }, testInfo) => {
|
|
|
76624
76985
|
const absHarPath = wantHar ? (0, path_1.join)(runDir, "session.har").replace(/\\/g, "/") : null;
|
|
76625
76986
|
const recordHarLine = absHarPath ? `recordHar: { path: ${JSON.stringify(absHarPath)}, mode: 'full' },` : "";
|
|
76626
76987
|
const storageStateLine = storageStatePath ? `storageState: ${JSON.stringify(storageStatePath.replace(/\\/g, "/"))},` : "";
|
|
76627
|
-
const extraHeadersLine =
|
|
76988
|
+
const extraHeadersLine = "";
|
|
76628
76989
|
const launchArgs = IS_CLOUD2 ? "args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled', '--disable-dev-shm-usage', '--disable-gpu', '--disable-software-rasterizer', '--no-zygote', '--disable-accelerated-2d-canvas', '--js-flags=--max-old-space-size=512']," : "args: ['--disable-blink-features=AutomationControlled'],";
|
|
76629
76990
|
const config = `
|
|
76630
76991
|
// Plain object export - no require('@playwright/test') needed.
|
|
@@ -76762,7 +77123,7 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76762
77123
|
logs2.push(`\u26A0 HAR parse skipped: ${msg.slice(0, 200)}`);
|
|
76763
77124
|
}
|
|
76764
77125
|
}
|
|
76765
|
-
|
|
77126
|
+
const nativeResult = {
|
|
76766
77127
|
passed,
|
|
76767
77128
|
stepResults,
|
|
76768
77129
|
screenshots,
|
|
@@ -76771,19 +77132,24 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
76771
77132
|
harApiCalls,
|
|
76772
77133
|
logs: logs2.join("\n"),
|
|
76773
77134
|
error: passed ? void 0 : error2,
|
|
76774
|
-
suspiciousPass
|
|
77135
|
+
suspiciousPass,
|
|
77136
|
+
authDelivery
|
|
76775
77137
|
};
|
|
77138
|
+
if (!standingNeedleMatcher)
|
|
77139
|
+
return nativeResult;
|
|
77140
|
+
return (0, shared_1.redactNeedlesDeep)(nativeResult, standingNeedleMatcher);
|
|
76776
77141
|
} catch (fatalErr) {
|
|
76777
77142
|
const msg = fatalErr instanceof Error ? fatalErr.message : String(fatalErr);
|
|
76778
77143
|
logs2.push(`
|
|
76779
77144
|
\u{1F4A5} Fatal error: ${msg}`);
|
|
76780
|
-
|
|
77145
|
+
const fatalResult = {
|
|
76781
77146
|
passed: false,
|
|
76782
77147
|
stepResults: [],
|
|
76783
77148
|
screenshots,
|
|
76784
77149
|
logs: logs2.join("\n"),
|
|
76785
77150
|
error: msg
|
|
76786
77151
|
};
|
|
77152
|
+
return standingNeedleMatcher ? (0, shared_1.redactNeedlesDeep)(fatalResult, standingNeedleMatcher) : fatalResult;
|
|
76787
77153
|
} finally {
|
|
76788
77154
|
await callbackProxy?.close().catch(() => {
|
|
76789
77155
|
});
|
|
@@ -85887,7 +86253,7 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
85887
86253
|
}
|
|
85888
86254
|
return result;
|
|
85889
86255
|
}
|
|
85890
|
-
function
|
|
86256
|
+
function escapeRegExp2(value) {
|
|
85891
86257
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
85892
86258
|
}
|
|
85893
86259
|
function buildSensitiveEnvReplacements(testVariables) {
|
|
@@ -85909,7 +86275,7 @@ ${elementContext}${formContext}${observationContext}${interactionContext}${lowCo
|
|
|
85909
86275
|
for (const replacement of buildSensitiveEnvReplacements(testVariables)) {
|
|
85910
86276
|
if (replacement.value.includes("\n") || replacement.value.includes("\r"))
|
|
85911
86277
|
continue;
|
|
85912
|
-
const literalPattern = new RegExp(`(['"\`])${
|
|
86278
|
+
const literalPattern = new RegExp(`(['"\`])${escapeRegExp2(replacement.value)}\\1`, "g");
|
|
85913
86279
|
result = result.replace(literalPattern, replacement.envRef);
|
|
85914
86280
|
}
|
|
85915
86281
|
return result;
|
|
@@ -255165,7 +255531,7 @@ ${newComment.split("\n").map((c) => ` * ${c}`).join("\n")}
|
|
|
255165
255531
|
}
|
|
255166
255532
|
}
|
|
255167
255533
|
return result;
|
|
255168
|
-
function
|
|
255534
|
+
function escapeRegExp2(str) {
|
|
255169
255535
|
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
|
|
255170
255536
|
}
|
|
255171
255537
|
function getTodoCommentsRegExp() {
|
|
@@ -255173,7 +255539,7 @@ ${newComment.split("\n").map((c) => ` * ${c}`).join("\n")}
|
|
|
255173
255539
|
const multiLineCommentStart = /(?:\/\*+\s*)/.source;
|
|
255174
255540
|
const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source;
|
|
255175
255541
|
const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";
|
|
255176
|
-
const literals = "(?:" + map2(descriptors, (d) => "(" +
|
|
255542
|
+
const literals = "(?:" + map2(descriptors, (d) => "(" + escapeRegExp2(d.text) + ")").join("|") + ")";
|
|
255177
255543
|
const endOfLineOrEndOfComment = /(?:$|\*\/)/.source;
|
|
255178
255544
|
const messageRemainder = /(?:.*?)/.source;
|
|
255179
255545
|
const messagePortion = "(" + literals + messageRemainder + ")";
|
|
@@ -303433,7 +303799,7 @@ ${(verifyResult.logs ?? "").slice(0, 2e3)}`;
|
|
|
303433
303799
|
confidence: "low"
|
|
303434
303800
|
};
|
|
303435
303801
|
}
|
|
303436
|
-
function
|
|
303802
|
+
function escapeRegExp2(s) {
|
|
303437
303803
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
303438
303804
|
}
|
|
303439
303805
|
function tryDeterministicStrictModeFix(code, error2) {
|
|
@@ -303456,10 +303822,10 @@ ${(verifyResult.logs ?? "").slice(0, 2e3)}`;
|
|
|
303456
303822
|
const [, , role, , name, restOfOptions] = violated;
|
|
303457
303823
|
if (/exact\s*:\s*true/.test(restOfOptions))
|
|
303458
303824
|
return null;
|
|
303459
|
-
const akaPattern = new RegExp(`aka\\s+getByRole\\(\\s*(['"\`])${
|
|
303825
|
+
const akaPattern = new RegExp(`aka\\s+getByRole\\(\\s*(['"\`])${escapeRegExp2(role)}\\1\\s*,\\s*\\{[^}]*?name:\\s*(['"\`])${escapeRegExp2(name)}\\2[^}]*?exact:\\s*true[^}]*\\}\\s*\\)`);
|
|
303460
303826
|
if (!akaPattern.test(error2))
|
|
303461
303827
|
return null;
|
|
303462
|
-
const codePattern = new RegExp(`(getByRole\\(\\s*(['"\`])${
|
|
303828
|
+
const codePattern = new RegExp(`(getByRole\\(\\s*(['"\`])${escapeRegExp2(role)}\\2\\s*,\\s*\\{[^}]*?name:\\s*(['"\`])${escapeRegExp2(name)}\\3)([^}]*\\})`, "g");
|
|
303463
303829
|
let modified = false;
|
|
303464
303830
|
const patched = code.replace(codePattern, (full, prefix, _q1, _q2, suffix) => {
|
|
303465
303831
|
if (/exact\s*:\s*true/.test(suffix))
|
|
@@ -303476,11 +303842,11 @@ ${(verifyResult.logs ?? "").slice(0, 2e3)}`;
|
|
|
303476
303842
|
const [, , text, optionsBlock] = violated;
|
|
303477
303843
|
if (optionsBlock && /exact\s*:\s*true/.test(optionsBlock))
|
|
303478
303844
|
return null;
|
|
303479
|
-
const akaPattern = new RegExp(`aka\\s+getByText\\(\\s*(['"\`])${
|
|
303845
|
+
const akaPattern = new RegExp(`aka\\s+getByText\\(\\s*(['"\`])${escapeRegExp2(text)}\\1\\s*,\\s*\\{[^}]*?exact:\\s*true[^}]*\\}\\s*\\)`);
|
|
303480
303846
|
if (!akaPattern.test(error2))
|
|
303481
303847
|
return null;
|
|
303482
|
-
const noOptsPattern = new RegExp(`getByText\\(\\s*(['"\`])${
|
|
303483
|
-
const withOptsPattern = new RegExp(`(getByText\\(\\s*(['"\`])${
|
|
303848
|
+
const noOptsPattern = new RegExp(`getByText\\(\\s*(['"\`])${escapeRegExp2(text)}\\1\\s*\\)`, "g");
|
|
303849
|
+
const withOptsPattern = new RegExp(`(getByText\\(\\s*(['"\`])${escapeRegExp2(text)}\\2\\s*,\\s*\\{)([^}]*\\})`, "g");
|
|
303484
303850
|
let modified = false;
|
|
303485
303851
|
let patched = code.replace(noOptsPattern, (_full, q) => {
|
|
303486
303852
|
modified = true;
|
|
@@ -315953,6 +316319,8 @@ var require_auth_capture_run = __commonJS({
|
|
|
315953
316319
|
"../runner-core/dist/services/auth-capture-run.js"(exports2) {
|
|
315954
316320
|
"use strict";
|
|
315955
316321
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
316322
|
+
exports2.filterLoginCaptureCredentials = filterLoginCaptureCredentials;
|
|
316323
|
+
exports2.buildLoginCaptureCredentials = buildLoginCaptureCredentials;
|
|
315956
316324
|
exports2.getLoginCaptureMaxIterations = getLoginCaptureMaxIterations;
|
|
315957
316325
|
exports2.harvestAuthProofLive = harvestAuthProofLive;
|
|
315958
316326
|
exports2.findCredentialLiteralLeaks = findCredentialLiteralLeaks;
|
|
@@ -315968,6 +316336,26 @@ var require_auth_capture_run = __commonJS({
|
|
|
315968
316336
|
var playwright_native_js_1 = require_playwright_native();
|
|
315969
316337
|
var scrub_credentials_js_1 = require_scrub_credentials();
|
|
315970
316338
|
var credential_tools_js_1 = require_credential_tools();
|
|
316339
|
+
var LOGIN_CAPTURE_CONTROL_VARIABLES = /* @__PURE__ */ new Set([
|
|
316340
|
+
"BASE_URL",
|
|
316341
|
+
"DISCOVERY_SHOULD_REGISTER",
|
|
316342
|
+
"DISCOVERY_AUTH_MODE"
|
|
316343
|
+
]);
|
|
316344
|
+
function filterLoginCaptureCredentials(variables) {
|
|
316345
|
+
const credentials = {};
|
|
316346
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
316347
|
+
if ((0, auth_recipe_js_1.isReservedRunnerEnvVar)(key) || LOGIN_CAPTURE_CONTROL_VARIABLES.has(key))
|
|
316348
|
+
continue;
|
|
316349
|
+
credentials[key] = value;
|
|
316350
|
+
}
|
|
316351
|
+
return credentials;
|
|
316352
|
+
}
|
|
316353
|
+
function buildLoginCaptureCredentials(variableContext) {
|
|
316354
|
+
return filterLoginCaptureCredentials({
|
|
316355
|
+
...variableContext.publicTestVariables,
|
|
316356
|
+
...variableContext.allTestVariables
|
|
316357
|
+
});
|
|
316358
|
+
}
|
|
315971
316359
|
var DEFAULT_MAX_LOGIN_CAPTURE_ITERATIONS = 12;
|
|
315972
316360
|
function readPositiveIntEnv2(env, name, fallback) {
|
|
315973
316361
|
const raw = env[name]?.trim();
|
|
@@ -316175,7 +316563,8 @@ ${keyLines || "- (none provided)"}
|
|
|
316175
316563
|
return false;
|
|
316176
316564
|
}
|
|
316177
316565
|
async function runLoginCaptureRun(args) {
|
|
316178
|
-
const { loginBrowser, baseUrl,
|
|
316566
|
+
const { loginBrowser, baseUrl, projectId, model, onAudit, logs: logs2 } = args;
|
|
316567
|
+
const credentials = filterLoginCaptureCredentials(args.credentials);
|
|
316179
316568
|
if ((0, agentic_cli_constants_js_1.getAgenticEngine)(model) !== "native") {
|
|
316180
316569
|
logs2(` auth-capture skipped: engine for ${model} is not native (V1 is native-engine-only)`);
|
|
316181
316570
|
return null;
|
|
@@ -321437,9 +321826,9 @@ var require_tool_ledger = __commonJS({
|
|
|
321437
321826
|
const ledgerPath = (0, node_path_1.join)(runDir, "discovery-events.jsonl");
|
|
321438
321827
|
let checkpointIndex = 0;
|
|
321439
321828
|
let headerWritten = false;
|
|
321440
|
-
const
|
|
321829
|
+
const escapeRegExp2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
321441
321830
|
const credentialScrubbers = Array.from(credentialEnvMap.entries()).filter(([, value]) => typeof value === "string" && value.length > 0).sort((a, b) => b[1].length - a[1].length).map(([envName, value]) => ({
|
|
321442
|
-
re: new RegExp(
|
|
321831
|
+
re: new RegExp(escapeRegExp2(value), "g"),
|
|
321443
321832
|
replacement: `[REDACTED_${envName}]`
|
|
321444
321833
|
}));
|
|
321445
321834
|
const scrubCredentials = (text) => {
|
|
@@ -323314,7 +323703,7 @@ Respond with ONLY valid JSON:
|
|
|
323314
323703
|
pushLog("auth-prologue: AUTH_RECIPE_STAMP on + native engine, but no login-browser factory supplied (runner stage 2b) \u2014 skipping producer");
|
|
323315
323704
|
return null;
|
|
323316
323705
|
}
|
|
323317
|
-
const credentials =
|
|
323706
|
+
const credentials = (0, auth_capture_run_js_1.buildLoginCaptureCredentials)(ctx.variableContext);
|
|
323318
323707
|
const run2 = (async () => {
|
|
323319
323708
|
let lease = null;
|
|
323320
323709
|
try {
|
|
@@ -323599,6 +323988,7 @@ Explore complete: ${exploreResult.pagesDiscovered} pages, ${exploreResult.elemen
|
|
|
323599
323988
|
testBudget
|
|
323600
323989
|
}, {
|
|
323601
323990
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
323991
|
+
gateModelOverride: ctx.planGateModel,
|
|
323602
323992
|
onAICall
|
|
323603
323993
|
});
|
|
323604
323994
|
logs2.push(...planResult.logs);
|
|
@@ -324177,6 +324567,7 @@ Plan (reused): ${resumeSavedScenarios.length} scenarios from the paused run.`);
|
|
|
324177
324567
|
testBudget: featureTestBudget
|
|
324178
324568
|
}, {
|
|
324179
324569
|
modelOverride: ctx.planModel ?? ctx.generateModel,
|
|
324570
|
+
gateModelOverride: ctx.planGateModel,
|
|
324180
324571
|
onAICall
|
|
324181
324572
|
});
|
|
324182
324573
|
logs2.push(...planResult.logs);
|
|
@@ -337611,9 +338002,9 @@ var require_dist2 = __commonJS({
|
|
|
337611
338002
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
|
|
337612
338003
|
};
|
|
337613
338004
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
337614
|
-
exports2.
|
|
337615
|
-
exports2.
|
|
337616
|
-
exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = void 0;
|
|
338005
|
+
exports2.extractZipSafely = exports2.downloadArchiveCapped = exports2.detectArchiveKind = exports2.acquireRepoForImport = exports2.runFrameworkImportOnRunner = exports2.getAgenticEngine = exports2.resolveRefFromSnapshot = exports2.replayAuthRecipeIntoBrowser = exports2.runRegisterCaptureRun = exports2.runLoginCaptureRun = exports2.filterLoginCaptureCredentials = exports2.buildLoginCaptureCredentials = exports2.isReservedRunnerEnvVar = exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = exports2.buildHealAuthStamp = exports2.extractAuthRegion = exports2.upsertAuthBlock = exports2.buildAuthRecipe = exports2.commandToReplayStep = exports2.buildReplayPlan = exports2.absoluteRoute = exports2.renderReplaySettleArrow = exports2.renderReplayClickArrow = exports2.renderAuthFn = exports2.canonicalizeRoute = exports2.executeGrokPerBatchHeal = exports2.executeGrokPerTestHeal = exports2.headerVal = exports2.classifyAuthType = exports2.isTextExcerptable = exports2.isStaticAssetByExt = exports2.isApiCall = exports2.buildApiCallSummary = exports2.computeMobileHealOutcome = exports2.detectMobileStepSecretLeak = exports2.redactMobileSecretsFromText = exports2.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports2.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports2.executeMobileLiveHeal = exports2.executeMobileTestWithHealing = exports2.executeMobileScriptDrivenHeal = exports2.prepareMobileScenarioAppState = exports2.runMobileGeneratePhase = exports2.runMobileAIPlanner = exports2.buildMobileSiteMap = exports2.formatMobileEvidenceForPlanner = exports2.MOBILE_SCENARIO_SYSTEM_PROMPT = exports2.MOBILE_SURVEY_SYSTEM_PROMPT = exports2.runMobileExplorePhase = void 0;
|
|
338006
|
+
exports2.decideAppRenderType = exports2.detectCanvasApp = exports2.detectRateLimitBlock = exports2.isSafeCanonicalRedirect = exports2.resolveBaseUrlPreflight = exports2.checkBaseUrlRedirect = exports2.stripJsonFences = exports2.scrubCredentials = exports2.withAIRetry = exports2.snapshotMatchingChildren = exports2.findNewChromiumChildPid = exports2.killProcessTree = exports2.gracefulCloseWithTreeKill = exports2.liveBrowserRegistry = exports2.REPO_SWITCH_REMOVED_FRACTION = exports2.MAX_MANIFEST_FILES = exports2.toDiffSummary = exports2.isLikelyTestFile = exports2.sanitizeRepoRelPath = exports2.sha256Hex = exports2.hashFiles = exports2.enumerateTestFiles = exports2.buildNextManifest = exports2.detectRepoSwitch = exports2.classifyImportDiff = exports2.parseImportManifest = exports2.REPO_ANALYSIS_SCHEMA_VERSION = exports2.validateSourceEndpoints = exports2.validateAuthoredTest = exports2.validateRepoMapFile = exports2.validateRepoMemoryFile = exports2.buildTestingStandardsSection = exports2.buildRepoAuthorSystemRules = exports2.buildRepoAuthorKickoff = exports2.buildRepoAuthorContractMd = exports2.buildRepoMapSystemRules = exports2.buildRepoMapKickoff = exports2.buildRepoMapContractMd = exports2.buildRepoMemorySystemRules = exports2.buildRepoMemoryKickoff = exports2.buildRepoMemoryContractMd = exports2.runRepoAnalysisOnRunner = exports2.IMPORT_SCHEMA_VERSION = exports2.validateImportResultFile = exports2.validateImportedTest = exports2.buildImportSystemRules = exports2.buildImportKickoff = exports2.buildImportContractMd = exports2.MAX_ARCHIVE_BYTES = exports2.isUnsafeEntryName = void 0;
|
|
338007
|
+
exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = exports2.modelSupportsVision = exports2.decideCanvasStrategy = void 0;
|
|
337617
338008
|
__exportStar(require_ai_provider(), exports2);
|
|
337618
338009
|
__exportStar(require_mobile_types(), exports2);
|
|
337619
338010
|
__exportStar(require_mobile_source_parser(), exports2);
|
|
@@ -337762,6 +338153,12 @@ var require_dist2 = __commonJS({
|
|
|
337762
338153
|
return auth_recipe_js_1.isReservedRunnerEnvVar;
|
|
337763
338154
|
} });
|
|
337764
338155
|
var auth_capture_run_js_1 = require_auth_capture_run();
|
|
338156
|
+
Object.defineProperty(exports2, "buildLoginCaptureCredentials", { enumerable: true, get: function() {
|
|
338157
|
+
return auth_capture_run_js_1.buildLoginCaptureCredentials;
|
|
338158
|
+
} });
|
|
338159
|
+
Object.defineProperty(exports2, "filterLoginCaptureCredentials", { enumerable: true, get: function() {
|
|
338160
|
+
return auth_capture_run_js_1.filterLoginCaptureCredentials;
|
|
338161
|
+
} });
|
|
337765
338162
|
Object.defineProperty(exports2, "runLoginCaptureRun", { enumerable: true, get: function() {
|
|
337766
338163
|
return auth_capture_run_js_1.runLoginCaptureRun;
|
|
337767
338164
|
} });
|
|
@@ -337988,7 +338385,7 @@ var require_package4 = __commonJS({
|
|
|
337988
338385
|
"package.json"(exports2, module2) {
|
|
337989
338386
|
module2.exports = {
|
|
337990
338387
|
name: "@validate.qa/runner",
|
|
337991
|
-
version: "1.0.
|
|
338388
|
+
version: "1.0.45",
|
|
337992
338389
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
337993
338390
|
bin: {
|
|
337994
338391
|
"validate-runner": "dist/cli.js"
|
|
@@ -338106,32 +338503,24 @@ var import_runner_core12 = __toESM(require_dist2());
|
|
|
338106
338503
|
|
|
338107
338504
|
// src/services/auth-state.ts
|
|
338108
338505
|
init_dist();
|
|
338109
|
-
var INLINE_LOGIN_TAG = "@inline-login";
|
|
338110
338506
|
var GUARDED_LOGIN_TAG = "@auth-guarded";
|
|
338111
338507
|
function sharedAuthInjectEnabled() {
|
|
338112
338508
|
return isSharedAuthInjectEnabled();
|
|
338113
338509
|
}
|
|
338114
|
-
function
|
|
338510
|
+
function hasGeneratedAuthGuard2(playwrightCode) {
|
|
338115
338511
|
if (!playwrightCode) return false;
|
|
338116
338512
|
return /\/\/ >>> VALIDATE_QA_AUTH v\d+\b[\s\S]*?process\.env\.AUTH_FILE[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH v\d+\b/.test(playwrightCode);
|
|
338117
338513
|
}
|
|
338118
338514
|
function shouldCaptureAuthState(tags, playwrightCode) {
|
|
338119
|
-
return sharedAuthInjectEnabled() && Array.isArray(tags) && tags.includes("@needs-auth") && tags.includes(GUARDED_LOGIN_TAG) && !tags.includes("@auth") &&
|
|
338120
|
-
}
|
|
338121
|
-
function hasInlineLoginSteps(playwrightCode) {
|
|
338122
|
-
if (!playwrightCode) return false;
|
|
338123
|
-
return /process\.env\.TEST_USER_EMAIL!?/.test(playwrightCode) && /process\.env\.TEST_USER_PASSWORD!?/.test(playwrightCode);
|
|
338515
|
+
return sharedAuthInjectEnabled() && Array.isArray(tags) && tags.includes("@needs-auth") && tags.includes(GUARDED_LOGIN_TAG) && !tags.includes("@auth") && hasGeneratedAuthGuard2(playwrightCode);
|
|
338124
338516
|
}
|
|
338125
338517
|
function shouldUseAuthState(tags, playwrightCode) {
|
|
338126
338518
|
if (!sharedAuthInjectEnabled()) return false;
|
|
338127
338519
|
if (!Array.isArray(tags)) return false;
|
|
338128
|
-
|
|
338129
|
-
|
|
338130
|
-
|
|
338131
|
-
|
|
338132
|
-
const isGeneratedVariant = tags.includes("@edge") || tags.includes("@bug");
|
|
338133
|
-
const hasLegacyInlineLogin = tags.includes("@discovered") && !isGeneratedVariant && hasInlineLoginSteps(playwrightCode);
|
|
338134
|
-
return !tags.includes(INLINE_LOGIN_TAG) && !hasLegacyInlineLogin;
|
|
338520
|
+
return authPlanFor(tags, playwrightCode).needsCookieState;
|
|
338521
|
+
}
|
|
338522
|
+
function authPlanFor(tags, playwrightCode) {
|
|
338523
|
+
return deriveAuthPlan(tags, playwrightCode, { sharedAuthInjectEnabled: sharedAuthInjectEnabled() });
|
|
338135
338524
|
}
|
|
338136
338525
|
|
|
338137
338526
|
// src/version-check.ts
|
|
@@ -345527,7 +345916,18 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
345527
345916
|
requestLiveBrowserHeartbeat();
|
|
345528
345917
|
}, 5e3);
|
|
345529
345918
|
liveBrowserHeartbeatInterval.unref?.();
|
|
345919
|
+
const authPlan = authPlanFor(run2.tags, run2.playwrightCode);
|
|
345920
|
+
const runAuthCachePath = (0, import_runner_core12.localAuthStatePaths)(getRunsDir(), {
|
|
345921
|
+
projectId: run2.projectId || opts.projectId || "default",
|
|
345922
|
+
serverUrl: opts.serverUrl,
|
|
345923
|
+
baseUrl: run2.baseUrl,
|
|
345924
|
+
testVariables: run2.testVariables,
|
|
345925
|
+
authToken: opts.authToken
|
|
345926
|
+
}, run2.runId).cachePath;
|
|
345927
|
+
const wrapperStorageStatePath = authPlan.needsCookieState && runMode !== "auth-setup" ? (0, import_runner_core12.getValidLocalAuthState)(runAuthCachePath) : void 0;
|
|
345530
345928
|
const runNativeWithLiveBrowser = (nativeOptions) => (0, import_runner_core.executeTestViaNativePlaywright)({
|
|
345929
|
+
authStandingMode: authPlan.standingHeadersMode,
|
|
345930
|
+
storageStatePath: wrapperStorageStatePath,
|
|
345531
345931
|
...nativeOptions,
|
|
345532
345932
|
liveBrowserHandle,
|
|
345533
345933
|
onLiveBrowserSnapshot: requestLiveBrowserHeartbeat
|
|
@@ -345555,11 +345955,24 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
|
|
|
345555
345955
|
if (!run2.baseUrl) {
|
|
345556
345956
|
preflightError = "Pre-flight failed: Missing BASE_URL. Configure it in Test Data \u2192 Test Variables.";
|
|
345557
345957
|
} else {
|
|
345558
|
-
const
|
|
345559
|
-
if (
|
|
345560
|
-
preflightError =
|
|
345561
|
-
} else if (
|
|
345562
|
-
preflightError = "Pre-flight failed:
|
|
345958
|
+
const validStanding = (0, import_runner_core13.parseApiAuthHeaders)(run2.testVariables?.["API_AUTH_HEADERS"]) !== void 0;
|
|
345959
|
+
if (authPlan.configurationError) {
|
|
345960
|
+
preflightError = `Pre-flight failed: ${authPlan.configurationError}. Fix the test's auth-type tag before running.`;
|
|
345961
|
+
} else if (authPlan.needsStandingHeaders && !validStanding) {
|
|
345962
|
+
preflightError = "Pre-flight failed: This @auth-standing @api test requires a valid API_AUTH_HEADERS JSON object (header name \u2192 value).";
|
|
345963
|
+
} else {
|
|
345964
|
+
const legacyHeaderBypass = authPlan.source === "legacy" && authPlan.standingHeadersMode === "legacy-if-configured" && validStanding;
|
|
345965
|
+
const code = run2.playwrightCode ?? "";
|
|
345966
|
+
const codeProvided = code.trim().length > 0;
|
|
345967
|
+
const codeUsesCreds = codeProvided && /TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin/.test(code);
|
|
345968
|
+
const tags = Array.isArray(run2.tags) ? run2.tags : [];
|
|
345969
|
+
const isHallucinationShape = codeProvided && !codeUsesCreds && tags.includes("@inline-login") && !tags.includes("@api");
|
|
345970
|
+
const requireCreds = authPlan.needsLoginCreds && !legacyHeaderBypass && !isHallucinationShape;
|
|
345971
|
+
if (requireCreds && !run2.testVariables?.["TEST_USER_EMAIL"]?.trim()) {
|
|
345972
|
+
preflightError = "Pre-flight failed: Missing TEST_USER_EMAIL. Tests that log in (tagged @auth, @needs-auth, @api, @auth-cookie-state, @auth-login-cookie, or @auth-login-bearer) require login credentials.";
|
|
345973
|
+
} else if (requireCreds && !run2.testVariables?.["TEST_USER_PASSWORD"]?.trim()) {
|
|
345974
|
+
preflightError = "Pre-flight failed: Missing TEST_USER_PASSWORD. Tests that log in (tagged @auth, @needs-auth, @api, @auth-cookie-state, @auth-login-cookie, or @auth-login-bearer) require login credentials.";
|
|
345975
|
+
}
|
|
345563
345976
|
}
|
|
345564
345977
|
}
|
|
345565
345978
|
if (preflightError) {
|
|
@@ -346168,10 +346581,13 @@ ${finalVerifyResult.logs ?? ""}`;
|
|
|
346168
346581
|
});
|
|
346169
346582
|
if (healRes.ok) {
|
|
346170
346583
|
const { healedCode } = await healRes.json();
|
|
346584
|
+
const failedTestPlan = authPlanFor(failedTest.tags, healedCode);
|
|
346171
346585
|
const verifyResult = await runNativeWithLiveBrowser({
|
|
346172
346586
|
playwrightCode: healedCode,
|
|
346173
346587
|
baseUrl: run2.baseUrl,
|
|
346174
346588
|
testVariables: run2.testVariables,
|
|
346589
|
+
storageStatePath: failedTestPlan.needsCookieState ? (0, import_runner_core12.getValidLocalAuthState)(runAuthCachePath) : void 0,
|
|
346590
|
+
authStandingMode: failedTestPlan.standingHeadersMode,
|
|
346175
346591
|
validateContext: callbackContext
|
|
346176
346592
|
});
|
|
346177
346593
|
results.push({
|
|
@@ -347655,6 +348071,10 @@ ${result2.logs}`);
|
|
|
347655
348071
|
log.warn(`\u26A0 Rate-limit block detected - posting ERROR (not FAILED) to skip heal/quarantine`);
|
|
347656
348072
|
}
|
|
347657
348073
|
}
|
|
348074
|
+
if (runStatus === "FAILED" && classifyErrorMessage(runError ?? null) === "config_issue") {
|
|
348075
|
+
runStatus = "ERROR";
|
|
348076
|
+
log.warn(`\u26A0 CONFIG_ISSUE from native execution - posting ERROR (not FAILED) to skip heal/quarantine`);
|
|
348077
|
+
}
|
|
347658
348078
|
await postResult(opts.serverUrl, headers, run2.runId, {
|
|
347659
348079
|
status: runStatus,
|
|
347660
348080
|
stepResults: result.stepResults,
|
|
@@ -347670,7 +348090,7 @@ ${result2.logs}`);
|
|
|
347670
348090
|
if (result.passed) {
|
|
347671
348091
|
log.ok(`PASSED (${duration}ms)${result.suspiciousPass ? ` \u26A0 suspicious-pass (viewport ${Math.round(result.suspiciousPass.emptyRatio * 100)}% blank)` : ""}`);
|
|
347672
348092
|
} else if (runStatus === "ERROR") {
|
|
347673
|
-
log.warn(`BLOCKED (${duration}ms) - rate-limit
|
|
348093
|
+
log.warn(`BLOCKED (${duration}ms) - environmental block (rate-limit / config-issue), not counted as failure`);
|
|
347674
348094
|
} else {
|
|
347675
348095
|
log.fail(`FAILED (${duration}ms)${result.error ? ` - ${result.error}` : ""}`);
|
|
347676
348096
|
if (result.logs) {
|