@validate.qa/runner 1.0.56 → 1.0.57
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 +1232 -530
- package/dist/cli.mjs +1232 -530
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1767,7 +1767,12 @@ var init_constants = __esm({
|
|
|
1767
1767
|
"VALIDATE_TASK_ARN",
|
|
1768
1768
|
"VALIDATE_RUNNER_AUTH",
|
|
1769
1769
|
"VALIDATE_SERVER_URL",
|
|
1770
|
-
"VALIDATE_LIVE_BROWSER_STATE_PATH"
|
|
1770
|
+
"VALIDATE_LIVE_BROWSER_STATE_PATH",
|
|
1771
|
+
// Register-and-verify test identity (parent-minted per run; see runner-core
|
|
1772
|
+
// test-inbox-broker.ts). A customer variable must never spoof the address a
|
|
1773
|
+
// generated test registers with or the derived account password.
|
|
1774
|
+
"VQA_TEST_INBOX_EMAIL",
|
|
1775
|
+
"VQA_TEST_ACCOUNT_PASSWORD"
|
|
1771
1776
|
]);
|
|
1772
1777
|
SPAWNED_ENV_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
1773
1778
|
// Runtime essentials - Node.js and Playwright need these to function
|
|
@@ -3864,8 +3869,9 @@ function inlineLocatorBindings(stmts) {
|
|
|
3864
3869
|
function stripLoginScaffolding(code) {
|
|
3865
3870
|
const out = [];
|
|
3866
3871
|
let inAuth = false;
|
|
3872
|
+
let inInbox = false;
|
|
3867
3873
|
for (const ln of code.split("\n")) {
|
|
3868
|
-
if (!inAuth && AUTH_START.test(ln)) {
|
|
3874
|
+
if (!inAuth && !inInbox && AUTH_START.test(ln)) {
|
|
3869
3875
|
inAuth = true;
|
|
3870
3876
|
continue;
|
|
3871
3877
|
}
|
|
@@ -3874,6 +3880,15 @@ function stripLoginScaffolding(code) {
|
|
|
3874
3880
|
inAuth = false;
|
|
3875
3881
|
continue;
|
|
3876
3882
|
}
|
|
3883
|
+
if (!inInbox && INBOX_START.test(ln)) {
|
|
3884
|
+
inInbox = true;
|
|
3885
|
+
continue;
|
|
3886
|
+
}
|
|
3887
|
+
if (inInbox) {
|
|
3888
|
+
if (INBOX_END.test(ln))
|
|
3889
|
+
inInbox = false;
|
|
3890
|
+
continue;
|
|
3891
|
+
}
|
|
3877
3892
|
if (LOGIN_CALL_LINE.test(ln))
|
|
3878
3893
|
continue;
|
|
3879
3894
|
out.push(ln);
|
|
@@ -4262,7 +4277,7 @@ function validateAssertionStrength(code, opts = {}) {
|
|
|
4262
4277
|
signals
|
|
4263
4278
|
};
|
|
4264
4279
|
}
|
|
4265
|
-
var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, NEGATED_ASSERT, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
|
|
4280
|
+
var ERROR_BANNER, OUTCOME_ALWAYS, FORM_INPUT, ANY_CLICK, NAV_LOCATOR, URL_PROOF, RELOAD, EXPECT_STMT, BLOCK_SCAFFOLD, LOCATOR_BINDING, STATE_MATCHER, CHROME_LOCATOR, CONTENT_ROLE, GET_BY_TEXT, HAS_TEXT_FILTER, OUTCOME_TEXT, MESSAGE_SURFACE, TEXT_CONTENT_MATCHER, VISIBILITY_MATCHER, ADDITIVE_AFFORDANCE, EXCLUDE_AFFORDANCE, EMPTY_STATE_TEXT, AUTH_START, AUTH_END, LOGIN_CALL_LINE, INBOX_START, INBOX_END, NEGATED_ASSERT, TYPED_INPUT, TITLE_LINE, ROLE_NAME_RE, INTERACT_VERB, ROLE_SIG_RE, TRANSIENT_CONTROL_ROLES;
|
|
4266
4281
|
var init_assertion_strength = __esm({
|
|
4267
4282
|
"../shared/dist/assertion-strength.js"() {
|
|
4268
4283
|
"use strict";
|
|
@@ -4312,6 +4327,8 @@ var init_assertion_strength = __esm({
|
|
|
4312
4327
|
AUTH_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_AUTH/;
|
|
4313
4328
|
AUTH_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_AUTH/;
|
|
4314
4329
|
LOGIN_CALL_LINE = /^\s*await\s+__vqaLogin\s*\(\s*page\s*\)\s*;?\s*$/;
|
|
4330
|
+
INBOX_START = /^\s*\/\/\s*>>>\s*VALIDATE_QA_INBOX/;
|
|
4331
|
+
INBOX_END = /^\s*\/\/\s*<<<\s*VALIDATE_QA_INBOX/;
|
|
4315
4332
|
NEGATED_ASSERT = /\.not\.(toBeVisible|toContainText|toHaveText|toBeChecked)\b|\.toBeHidden\s*\(/;
|
|
4316
4333
|
TYPED_INPUT = /\.(fill|type|pressSequentially|setInputFiles)\s*\(/;
|
|
4317
4334
|
TITLE_LINE = /^\s*(?:test|describe)(?:\.\w+)?\s*\(/;
|
|
@@ -4354,10 +4371,7 @@ function isEnvironmentalError(run2) {
|
|
|
4354
4371
|
return false;
|
|
4355
4372
|
if (run2.status !== "ERROR")
|
|
4356
4373
|
return false;
|
|
4357
|
-
|
|
4358
|
-
if (typeof err !== "string")
|
|
4359
|
-
return false;
|
|
4360
|
-
return ENVIRONMENTAL_ERROR_PREFIXES.some((p) => err.startsWith(p));
|
|
4374
|
+
return classifyErrorMessage(run2.error) !== null;
|
|
4361
4375
|
}
|
|
4362
4376
|
function isCountedFailure(run2) {
|
|
4363
4377
|
if (!run2)
|
|
@@ -4376,9 +4390,10 @@ function environmentalReason(run2) {
|
|
|
4376
4390
|
function classifyErrorMessage(error2) {
|
|
4377
4391
|
if (typeof error2 !== "string")
|
|
4378
4392
|
return null;
|
|
4379
|
-
|
|
4393
|
+
const normalized = error2.replace(/^\s+/, "").replace(/^Error:\s*/i, "");
|
|
4394
|
+
if (normalized.startsWith("RATE_LIMIT_BLOCKED:"))
|
|
4380
4395
|
return "rate_limit";
|
|
4381
|
-
if (
|
|
4396
|
+
if (normalized.startsWith("CONFIG_ISSUE:"))
|
|
4382
4397
|
return "config_issue";
|
|
4383
4398
|
return null;
|
|
4384
4399
|
}
|
|
@@ -72600,7 +72615,7 @@ var require_verification_inbox = __commonJS({
|
|
|
72600
72615
|
};
|
|
72601
72616
|
})();
|
|
72602
72617
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
72603
|
-
exports2.VerificationInboxReader = void 0;
|
|
72618
|
+
exports2.VerificationInboxReader = exports2.InboxAuthError = void 0;
|
|
72604
72619
|
exports2.isVerificationInboxConfigured = isVerificationInboxConfigured;
|
|
72605
72620
|
exports2.describeVerificationInboxConfigGap = describeVerificationInboxConfigGap;
|
|
72606
72621
|
exports2.loadVerificationInboxConfig = loadVerificationInboxConfig;
|
|
@@ -72612,8 +72627,10 @@ var require_verification_inbox = __commonJS({
|
|
|
72612
72627
|
exports2.applyPlusTag = applyPlusTag;
|
|
72613
72628
|
exports2.deriveRegistrationPassword = deriveRegistrationPassword;
|
|
72614
72629
|
exports2.extractVerificationCode = extractVerificationCode;
|
|
72630
|
+
exports2.extractVerificationCodeDetailed = extractVerificationCodeDetailed;
|
|
72615
72631
|
exports2.extractVerificationLink = extractVerificationLink;
|
|
72616
72632
|
exports2.collectMessageUrls = collectMessageUrls;
|
|
72633
|
+
exports2.isInboxAuthFailure = isInboxAuthFailure;
|
|
72617
72634
|
var node_crypto_1 = require("crypto");
|
|
72618
72635
|
var DEFAULT_MAILBOX = "INBOX";
|
|
72619
72636
|
var DEFAULT_GMAIL_SPAM_MAILBOX = "[Gmail]/Spam";
|
|
@@ -72786,6 +72803,9 @@ var require_verification_inbox = __commonJS({
|
|
|
72786
72803
|
var CODE_TOKEN = new RegExp(`\\b(${CODE_ALT})\\b`, "gi");
|
|
72787
72804
|
var YEAR_CONTEXT = /(©|\(c\)|copyright|all rights reserved|®)/i;
|
|
72788
72805
|
function extractVerificationCode(text, html, subject) {
|
|
72806
|
+
return extractVerificationCodeDetailed(text, html, subject).code;
|
|
72807
|
+
}
|
|
72808
|
+
function extractVerificationCodeDetailed(text, html, subject) {
|
|
72789
72809
|
const subj = collapseSpacedDigits(subject || "");
|
|
72790
72810
|
const bodies = [text || "", stripHtml(html || "")].map(collapseSpacedDigits);
|
|
72791
72811
|
const withSubject = [subj, ...bodies];
|
|
@@ -72794,7 +72814,7 @@ var require_verification_inbox = __commonJS({
|
|
|
72794
72814
|
if (lone) {
|
|
72795
72815
|
const c = cleanCode(lone[1]);
|
|
72796
72816
|
if (c)
|
|
72797
|
-
return c;
|
|
72817
|
+
return { code: c, confidence: "subject" };
|
|
72798
72818
|
}
|
|
72799
72819
|
}
|
|
72800
72820
|
for (const body of withSubject) {
|
|
@@ -72806,26 +72826,28 @@ var require_verification_inbox = __commonJS({
|
|
|
72806
72826
|
continue;
|
|
72807
72827
|
const m = matchBestCode(line);
|
|
72808
72828
|
if (m)
|
|
72809
|
-
return m;
|
|
72829
|
+
return { code: m, confidence: "keyword" };
|
|
72810
72830
|
}
|
|
72811
72831
|
const windowed = body.match(new RegExp(`${CODE_KEYWORDS.source}[\\s\\S]{0,40}?\\b(${CODE_ALT})\\b`, "i"));
|
|
72812
72832
|
if (windowed && windowed[1]) {
|
|
72813
72833
|
const cleaned = cleanCode(windowed[1]);
|
|
72814
72834
|
if (cleaned && !looksLikeYearOrDate(cleaned))
|
|
72815
|
-
return cleaned;
|
|
72835
|
+
return { code: cleaned, confidence: "keyword" };
|
|
72816
72836
|
}
|
|
72817
72837
|
}
|
|
72838
|
+
const distinct = /* @__PURE__ */ new Set();
|
|
72818
72839
|
for (const body of bodies) {
|
|
72819
72840
|
if (!body)
|
|
72820
72841
|
continue;
|
|
72821
|
-
const
|
|
72822
|
-
|
|
72823
|
-
|
|
72824
|
-
|
|
72825
|
-
if (real)
|
|
72826
|
-
return real;
|
|
72842
|
+
for (const n of body.match(/\b[0-9]{4,8}\b/g) ?? []) {
|
|
72843
|
+
if (!looksLikeYearOrDate(n))
|
|
72844
|
+
distinct.add(n);
|
|
72845
|
+
}
|
|
72827
72846
|
}
|
|
72828
|
-
|
|
72847
|
+
if (distinct.size === 1) {
|
|
72848
|
+
return { code: distinct.values().next().value ?? null, confidence: "fallback" };
|
|
72849
|
+
}
|
|
72850
|
+
return { code: null, confidence: null };
|
|
72829
72851
|
}
|
|
72830
72852
|
function matchBestCode(line) {
|
|
72831
72853
|
const hasYearContext = YEAR_CONTEXT.test(line);
|
|
@@ -72947,6 +72969,28 @@ var require_verification_inbox = __commonJS({
|
|
|
72947
72969
|
}
|
|
72948
72970
|
var MAX_WAIT_CEILING_MS = 18e4;
|
|
72949
72971
|
var MIN_POLL_MS = 2e3;
|
|
72972
|
+
var InboxAuthError = class extends Error {
|
|
72973
|
+
constructor(message) {
|
|
72974
|
+
super(message);
|
|
72975
|
+
this.name = "InboxAuthError";
|
|
72976
|
+
}
|
|
72977
|
+
};
|
|
72978
|
+
exports2.InboxAuthError = InboxAuthError;
|
|
72979
|
+
function isInboxAuthFailure(error2) {
|
|
72980
|
+
if (!error2)
|
|
72981
|
+
return false;
|
|
72982
|
+
if (error2 instanceof InboxAuthError)
|
|
72983
|
+
return true;
|
|
72984
|
+
const e = error2;
|
|
72985
|
+
if (e.authenticationFailed === true)
|
|
72986
|
+
return true;
|
|
72987
|
+
const hay = `${e.responseText ?? ""} ${e.message ?? ""}`;
|
|
72988
|
+
return /\bAUTHENTICATIONFAILED\b|\bInvalid credentials\b/i.test(hay);
|
|
72989
|
+
}
|
|
72990
|
+
function isPlusTagged(address) {
|
|
72991
|
+
const at = address.indexOf("@");
|
|
72992
|
+
return at > 0 && address.slice(0, at).includes("+");
|
|
72993
|
+
}
|
|
72950
72994
|
var VerificationInboxReader = class {
|
|
72951
72995
|
config;
|
|
72952
72996
|
client = null;
|
|
@@ -72976,7 +73020,14 @@ var require_verification_inbox = __commonJS({
|
|
|
72976
73020
|
});
|
|
72977
73021
|
client.on("error", () => {
|
|
72978
73022
|
});
|
|
72979
|
-
|
|
73023
|
+
try {
|
|
73024
|
+
await client.connect();
|
|
73025
|
+
} catch (error2) {
|
|
73026
|
+
if (isInboxAuthFailure(error2)) {
|
|
73027
|
+
throw new InboxAuthError(`IMAP authentication rejected for ${this.config.user} \u2014 the inbox app password is revoked or wrong`);
|
|
73028
|
+
}
|
|
73029
|
+
throw error2;
|
|
73030
|
+
}
|
|
72980
73031
|
this.client = client;
|
|
72981
73032
|
return client;
|
|
72982
73033
|
})();
|
|
@@ -73021,7 +73072,10 @@ var require_verification_inbox = __commonJS({
|
|
|
73021
73072
|
} finally {
|
|
73022
73073
|
lock.release();
|
|
73023
73074
|
}
|
|
73024
|
-
} catch {
|
|
73075
|
+
} catch (error2) {
|
|
73076
|
+
if (isInboxAuthFailure(error2)) {
|
|
73077
|
+
throw error2 instanceof InboxAuthError ? error2 : new InboxAuthError(`IMAP authentication rejected for ${this.config.user} \u2014 the inbox app password is revoked or wrong`);
|
|
73078
|
+
}
|
|
73025
73079
|
return { uid: 0, uidValidity: 0 };
|
|
73026
73080
|
}
|
|
73027
73081
|
}
|
|
@@ -73072,23 +73126,52 @@ var require_verification_inbox = __commonJS({
|
|
|
73072
73126
|
}
|
|
73073
73127
|
}
|
|
73074
73128
|
/**
|
|
73075
|
-
* Poll until a
|
|
73076
|
-
*
|
|
73129
|
+
* Poll until a message with a STRONG verification signal (subject/keyword
|
|
73130
|
+
* code or a positive-scoring link) arrives, or the deadline passes.
|
|
73131
|
+
*
|
|
73132
|
+
* Signal-less mail addressed to the same recipient (a welcome email, a
|
|
73133
|
+
* receipt) is remembered but polled PAST — returning it immediately used to
|
|
73134
|
+
* hand welcome-mail order numbers to the caller as "the code" whenever the
|
|
73135
|
+
* welcome email out-raced the verification email. Only at the deadline is
|
|
73136
|
+
* the newest signal-less message surfaced (better than pretending the
|
|
73137
|
+
* mailbox is empty: the caller can still read it and decide).
|
|
73077
73138
|
*/
|
|
73078
73139
|
async waitForMessage(opts) {
|
|
73079
73140
|
const maxWaitMs = Math.min(opts.maxWaitMs ?? 9e4, MAX_WAIT_CEILING_MS);
|
|
73080
73141
|
const pollIntervalMs = Math.max(opts.pollIntervalMs ?? 5e3, MIN_POLL_MS);
|
|
73081
73142
|
const deadline = startTime() + maxWaitMs;
|
|
73143
|
+
const limit = Math.max(1, Math.min(opts.limit ?? 10, 50));
|
|
73144
|
+
const satisfies = (m) => {
|
|
73145
|
+
if (opts.require === "code")
|
|
73146
|
+
return m.code !== null && m.codeConfidence !== "fallback";
|
|
73147
|
+
if (opts.require === "link")
|
|
73148
|
+
return m.link !== null;
|
|
73149
|
+
return m.hasVerificationSignal;
|
|
73150
|
+
};
|
|
73151
|
+
let weakest = null;
|
|
73152
|
+
const check2 = async () => {
|
|
73153
|
+
if (opts.stats)
|
|
73154
|
+
opts.stats.filteredOut = 0;
|
|
73155
|
+
const messages = await this.fetchMatching({ ...opts, limit }, true);
|
|
73156
|
+
if (!opts.require)
|
|
73157
|
+
return messages[0] ?? null;
|
|
73158
|
+
const strong = messages.find((m) => satisfies(m) && !m.senderMismatch) ?? messages.find(satisfies);
|
|
73159
|
+
if (strong)
|
|
73160
|
+
return strong;
|
|
73161
|
+
if (messages[0])
|
|
73162
|
+
weakest = messages[0];
|
|
73163
|
+
return null;
|
|
73164
|
+
};
|
|
73082
73165
|
do {
|
|
73083
|
-
const
|
|
73084
|
-
if (
|
|
73085
|
-
return
|
|
73166
|
+
const found = await check2();
|
|
73167
|
+
if (found)
|
|
73168
|
+
return found;
|
|
73086
73169
|
const remaining = deadline - startTime();
|
|
73087
73170
|
if (remaining <= 0)
|
|
73088
73171
|
break;
|
|
73089
73172
|
await sleep(Math.min(pollIntervalMs, remaining));
|
|
73090
73173
|
} while (startTime() < deadline);
|
|
73091
|
-
return
|
|
73174
|
+
return await check2() ?? weakest;
|
|
73092
73175
|
}
|
|
73093
73176
|
async fetchMatching(opts, withBody) {
|
|
73094
73177
|
const limit = Math.max(1, Math.min(opts.limit ?? 10, 50));
|
|
@@ -73102,7 +73185,7 @@ var require_verification_inbox = __commonJS({
|
|
|
73102
73185
|
throw e;
|
|
73103
73186
|
}
|
|
73104
73187
|
}
|
|
73105
|
-
results.sort((a, b) => Date.parse(b.date) - Date.parse(a.date));
|
|
73188
|
+
results.sort((a, b) => Number(a.senderMismatch ?? false) - Number(b.senderMismatch ?? false) || Date.parse(b.date) - Date.parse(a.date));
|
|
73106
73189
|
return results.slice(0, limit);
|
|
73107
73190
|
}
|
|
73108
73191
|
async fetchMatchingInMailbox(opts, withBody, mailbox, limit) {
|
|
@@ -73110,6 +73193,7 @@ var require_verification_inbox = __commonJS({
|
|
|
73110
73193
|
const since = new Date(Math.max(0, opts.sinceMs));
|
|
73111
73194
|
const lock = await client.getMailboxLock(mailbox);
|
|
73112
73195
|
const results = [];
|
|
73196
|
+
const taggedRecipient = isPlusTagged(opts.toAddress);
|
|
73113
73197
|
try {
|
|
73114
73198
|
try {
|
|
73115
73199
|
await client.noop();
|
|
@@ -73153,17 +73237,23 @@ var require_verification_inbox = __commonJS({
|
|
|
73153
73237
|
const receivedMs = (parsed.date ?? msg.envelope?.date ?? /* @__PURE__ */ new Date(0)).getTime();
|
|
73154
73238
|
if (!useUid && receivedMs < opts.sinceMs - 6e4)
|
|
73155
73239
|
continue;
|
|
73156
|
-
|
|
73157
|
-
|
|
73158
|
-
if (
|
|
73240
|
+
const filterActive = Boolean(opts.fromMatch || opts.subjectMatch);
|
|
73241
|
+
const filterMatched = (!opts.fromMatch || matchField(parsed.from?.text, opts.fromMatch)) && (!opts.subjectMatch || matchField(parsed.subject, opts.subjectMatch));
|
|
73242
|
+
if (filterActive && !filterMatched && !taggedRecipient) {
|
|
73243
|
+
if (opts.stats)
|
|
73244
|
+
opts.stats.filteredOut += 1;
|
|
73159
73245
|
continue;
|
|
73160
|
-
|
|
73246
|
+
}
|
|
73247
|
+
results.push({ uid: msg.uid, parsed, receivedMs, senderMismatch: filterActive && !filterMatched });
|
|
73161
73248
|
}
|
|
73162
73249
|
} finally {
|
|
73163
73250
|
lock.release();
|
|
73164
73251
|
}
|
|
73165
|
-
results.sort((a, b) => b.receivedMs - a.receivedMs);
|
|
73166
|
-
return results.slice(0, limit).map((r) =>
|
|
73252
|
+
results.sort((a, b) => Number(a.senderMismatch) - Number(b.senderMismatch) || b.receivedMs - a.receivedMs);
|
|
73253
|
+
return results.slice(0, limit).map((r) => ({
|
|
73254
|
+
...toMessage(messageId(mailbox, String(r.uid), this.config.mailbox), r.parsed, withBody, mailbox),
|
|
73255
|
+
...r.senderMismatch ? { senderMismatch: true } : {}
|
|
73256
|
+
}));
|
|
73167
73257
|
}
|
|
73168
73258
|
async close() {
|
|
73169
73259
|
const client = this.client;
|
|
@@ -73194,12 +73284,18 @@ var require_verification_inbox = __commonJS({
|
|
|
73194
73284
|
const text = parsed.text || "";
|
|
73195
73285
|
const html = typeof parsed.html === "string" ? parsed.html : "";
|
|
73196
73286
|
const summary = toSummary({ uid, parsed, mailbox });
|
|
73287
|
+
const { code, confidence } = extractVerificationCodeDetailed(text, html, parsed.subject || "");
|
|
73288
|
+
const link = extractVerificationLink(text, html);
|
|
73197
73289
|
return {
|
|
73198
73290
|
...summary,
|
|
73199
73291
|
text: withBody ? text : "",
|
|
73200
73292
|
html: withBody ? html : "",
|
|
73201
|
-
code
|
|
73202
|
-
link
|
|
73293
|
+
code,
|
|
73294
|
+
link,
|
|
73295
|
+
codeConfidence: confidence,
|
|
73296
|
+
// A fallback-confidence code is NOT a signal: it is exactly the shape a
|
|
73297
|
+
// welcome/receipt email produces (one stray number, no keyword context).
|
|
73298
|
+
hasVerificationSignal: code !== null && confidence !== "fallback" || link !== null
|
|
73203
73299
|
};
|
|
73204
73300
|
}
|
|
73205
73301
|
function toSummary(input) {
|
|
@@ -73397,7 +73493,11 @@ var require_inbox_tools = __commonJS({
|
|
|
73397
73493
|
function err(message) {
|
|
73398
73494
|
return { content: JSON.stringify({ error: message }), didMutate: false };
|
|
73399
73495
|
}
|
|
73496
|
+
var INBOX_AUTH_DEAD_GUIDANCE = "the inbox authentication is PERMANENTLY rejected (revoked/wrong app password) \u2014 STOP calling inbox tools; none of them can succeed this run. Treat email verification as UNAVAILABLE: hard-stop at any emailed code/link wall, NEVER type a guessed code, and report verification-gated flows as BLOCKED (blockedReason: inbox authentication failed). Continue with publicly reachable coverage.";
|
|
73400
73497
|
async function dispatchInboxTool(toolName, toolArgs, ctx) {
|
|
73498
|
+
if (ctx.state.authDead) {
|
|
73499
|
+
return err(`${toolName} skipped: ${INBOX_AUTH_DEAD_GUIDANCE}`);
|
|
73500
|
+
}
|
|
73401
73501
|
try {
|
|
73402
73502
|
switch (toolName) {
|
|
73403
73503
|
case "provision_identity":
|
|
@@ -73419,6 +73519,10 @@ var require_inbox_tools = __commonJS({
|
|
|
73419
73519
|
}
|
|
73420
73520
|
} catch (e) {
|
|
73421
73521
|
const message = e instanceof Error ? e.message : String(e);
|
|
73522
|
+
if ((0, verification_inbox_js_1.isInboxAuthFailure)(e)) {
|
|
73523
|
+
ctx.state.authDead = true;
|
|
73524
|
+
return err(`${toolName} failed: ${INBOX_AUTH_DEAD_GUIDANCE}`);
|
|
73525
|
+
}
|
|
73422
73526
|
return err(`${toolName} failed: ${message}`);
|
|
73423
73527
|
}
|
|
73424
73528
|
}
|
|
@@ -73476,6 +73580,7 @@ var require_inbox_tools = __commonJS({
|
|
|
73476
73580
|
return notReady;
|
|
73477
73581
|
const maxWaitSeconds = clampNumber(args.maxWaitSeconds, 90, 5, 180);
|
|
73478
73582
|
const { toAddress, altAddress } = inboxRecipients(ctx.state);
|
|
73583
|
+
const stats = { filteredOut: 0 };
|
|
73479
73584
|
const msg = await ctx.reader.waitForMessage({
|
|
73480
73585
|
toAddress,
|
|
73481
73586
|
altAddress,
|
|
@@ -73484,13 +73589,16 @@ var require_inbox_tools = __commonJS({
|
|
|
73484
73589
|
sinceUidValidity: ctx.state.sinceUidValidity,
|
|
73485
73590
|
fromMatch: asStr(args.senderMatch),
|
|
73486
73591
|
subjectMatch: asStr(args.subjectMatch),
|
|
73487
|
-
maxWaitMs: maxWaitSeconds * 1e3
|
|
73592
|
+
maxWaitMs: maxWaitSeconds * 1e3,
|
|
73593
|
+
stats
|
|
73488
73594
|
});
|
|
73489
73595
|
if (!msg) {
|
|
73596
|
+
const filterHint = stats.filteredOut > 0 ? ` NOTE: ${stats.filteredOut} email(s) addressed to you WERE found but were excluded by your senderMatch/subjectMatch filter \u2014 transactional mail often comes from a delivery provider (mailgun/sendgrid/amazonses), so call wait_for_verification again WITHOUT senderMatch.` : "";
|
|
73490
73597
|
return ok({
|
|
73491
73598
|
found: false,
|
|
73492
73599
|
waitedSeconds: maxWaitSeconds,
|
|
73493
|
-
|
|
73600
|
+
...stats.filteredOut > 0 ? { filteredOut: stats.filteredOut } : {},
|
|
73601
|
+
hint: `No verification email yet \u2014 usually still in transit, not necessarily a failure.${filterHint} Call wait_for_verification AGAIN (safe to repeat; allow 30-90s total) and you may click an in-app "Resend" control AT MOST ONCE. Do NOT re-submit the signup form, do NOT create another account, and do not immediately assume your credentials are wrong \u2014 your account most likely already exists. BUT if you have already re-polled several times AND resent once and still nothing arrives, the signup may have genuinely failed: check the page for an error/validation message and report it (finish / needs-review) rather than re-polling forever \u2014 and still do not re-register. Use check_inbox to see what has arrived.`
|
|
73494
73602
|
}, ctx.maxResultChars);
|
|
73495
73603
|
}
|
|
73496
73604
|
rememberLink(ctx.state, msg);
|
|
@@ -73502,6 +73610,8 @@ var require_inbox_tools = __commonJS({
|
|
|
73502
73610
|
subject: msg.subject,
|
|
73503
73611
|
from: msg.from,
|
|
73504
73612
|
code: msg.code,
|
|
73613
|
+
...msg.codeConfidence === "fallback" ? { codeConfidence: "low", codeNote: "This code was the only number in the email but had no verification keyword next to it \u2014 verify against read_verification_email if entering it fails." } : {},
|
|
73614
|
+
...msg.senderMismatch ? { senderMismatch: true, senderNote: "This email is addressed to your exact registration address but its sender did not match your senderMatch \u2014 the recipient match is authoritative for your plus-tagged address." } : {},
|
|
73505
73615
|
link,
|
|
73506
73616
|
next: msg.code && link ? "Both a code and a confirmation link were found. Prefer typing the code into the verification field; if the app instead wants you to click, call open_verification_link with the link (and if the page it lands on then asks for the code, enter the code there)." : msg.code ? "Type this code into the verification field." : link ? "Call open_verification_link with this link to confirm. It navigates your current tab to the verify page; after it confirms, return to the app to keep exploring." : msg.link ? "A confirmation link was found but points at a private/internal host and was withheld for safety. Read the email body if you need to inspect it." : "No code/link auto-detected \u2014 call read_verification_email to read the body."
|
|
73507
73617
|
}, ctx.maxResultChars);
|
|
@@ -73635,20 +73745,42 @@ var require_inbox_tools = __commonJS({
|
|
|
73635
73745
|
return false;
|
|
73636
73746
|
if (host === "::1" || host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd"))
|
|
73637
73747
|
return false;
|
|
73638
|
-
|
|
73639
|
-
|
|
73640
|
-
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
73641
|
-
if (a === 127 || a === 0 || a === 10)
|
|
73642
|
-
return false;
|
|
73643
|
-
if (a === 169 && b === 254)
|
|
73644
|
-
return false;
|
|
73645
|
-
if (a === 192 && b === 168)
|
|
73748
|
+
if (host.includes(":")) {
|
|
73749
|
+
if (host === "::" || host === "::ffff:0:0")
|
|
73646
73750
|
return false;
|
|
73647
|
-
|
|
73648
|
-
|
|
73649
|
-
if (a >= 224)
|
|
73751
|
+
const dotted = host.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
|
|
73752
|
+
if (dotted && !ipv4IsPublic(dotted[1]))
|
|
73650
73753
|
return false;
|
|
73754
|
+
const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
73755
|
+
if (mapped) {
|
|
73756
|
+
const hi = parseInt(mapped[1], 16);
|
|
73757
|
+
const lo = parseInt(mapped[2], 16);
|
|
73758
|
+
const ipv4 = `${hi >> 8 & 255}.${hi & 255}.${lo >> 8 & 255}.${lo & 255}`;
|
|
73759
|
+
if (!ipv4IsPublic(ipv4))
|
|
73760
|
+
return false;
|
|
73761
|
+
}
|
|
73651
73762
|
}
|
|
73763
|
+
if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) && !ipv4IsPublic(host))
|
|
73764
|
+
return false;
|
|
73765
|
+
return true;
|
|
73766
|
+
}
|
|
73767
|
+
function ipv4IsPublic(ipv4) {
|
|
73768
|
+
const m = ipv4.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
73769
|
+
if (!m)
|
|
73770
|
+
return true;
|
|
73771
|
+
const [a, b] = [Number(m[1]), Number(m[2])];
|
|
73772
|
+
if (a > 255 || b > 255 || Number(m[3]) > 255 || Number(m[4]) > 255)
|
|
73773
|
+
return false;
|
|
73774
|
+
if (a === 127 || a === 0 || a === 10)
|
|
73775
|
+
return false;
|
|
73776
|
+
if (a === 169 && b === 254)
|
|
73777
|
+
return false;
|
|
73778
|
+
if (a === 192 && b === 168)
|
|
73779
|
+
return false;
|
|
73780
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
73781
|
+
return false;
|
|
73782
|
+
if (a >= 224)
|
|
73783
|
+
return false;
|
|
73652
73784
|
return true;
|
|
73653
73785
|
}
|
|
73654
73786
|
function clampNumber(v, dflt, min, max) {
|
|
@@ -85717,12 +85849,14 @@ var require_run_callback_proxy = __commonJS({
|
|
|
85717
85849
|
}
|
|
85718
85850
|
const runPath = `/api/runner/runs/${encodeURIComponent(input.runId)}`;
|
|
85719
85851
|
const allowedPaths = /* @__PURE__ */ new Set([`${runPath}/credential`, `${runPath}/auth-state`]);
|
|
85852
|
+
const localPaths = new Map(Object.entries(input.localRoutes ?? {}).map(([suffix, handler]) => [`${runPath}/${suffix}`, handler]));
|
|
85720
85853
|
const token = (0, node_crypto_1.randomBytes)(32).toString("base64url");
|
|
85721
85854
|
const authHeader = `Bearer ${token}`;
|
|
85722
85855
|
const server3 = (0, node_http_1.createServer)(async (req, res) => {
|
|
85723
85856
|
try {
|
|
85724
85857
|
const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
85725
|
-
|
|
85858
|
+
const localHandler = localPaths.get(requestUrl.pathname);
|
|
85859
|
+
if (req.method !== "POST" || !allowedPaths.has(requestUrl.pathname) && !localHandler || requestUrl.search) {
|
|
85726
85860
|
sendJson(res, 404, { error: "Callback route not available" });
|
|
85727
85861
|
return;
|
|
85728
85862
|
}
|
|
@@ -85741,6 +85875,11 @@ var require_run_callback_proxy = __commonJS({
|
|
|
85741
85875
|
sendJson(res, 400, { error: "Callback body must be a JSON object" });
|
|
85742
85876
|
return;
|
|
85743
85877
|
}
|
|
85878
|
+
if (localHandler) {
|
|
85879
|
+
const out = await localHandler(parsedBody);
|
|
85880
|
+
sendJson(res, out.status, out.body);
|
|
85881
|
+
return;
|
|
85882
|
+
}
|
|
85744
85883
|
const upstreamBody = JSON.stringify({
|
|
85745
85884
|
...parsedBody,
|
|
85746
85885
|
...input.taskArn ? { taskArn: input.taskArn } : {},
|
|
@@ -85799,6 +85938,668 @@ var require_run_callback_proxy = __commonJS({
|
|
|
85799
85938
|
}
|
|
85800
85939
|
});
|
|
85801
85940
|
|
|
85941
|
+
// ../runner-core/dist/services/discovery-lint-flags.js
|
|
85942
|
+
var require_discovery_lint_flags = __commonJS({
|
|
85943
|
+
"../runner-core/dist/services/discovery-lint-flags.js"(exports2) {
|
|
85944
|
+
"use strict";
|
|
85945
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
85946
|
+
exports2.structuralLocatorLintMode = structuralLocatorLintMode;
|
|
85947
|
+
exports2.isPwMethodPreflightEnabled = isPwMethodPreflightEnabled;
|
|
85948
|
+
exports2.isVqaLoginDedupeEnabled = isVqaLoginDedupeEnabled;
|
|
85949
|
+
exports2.isTestgenEmailVerifyEnabled = isTestgenEmailVerifyEnabled;
|
|
85950
|
+
function strictOn(value) {
|
|
85951
|
+
return value === "true" || value === "1";
|
|
85952
|
+
}
|
|
85953
|
+
function structuralLocatorLintMode() {
|
|
85954
|
+
const v = (process.env.DISCOVERY_STRUCTURAL_LOCATOR_LINT || "").trim().toLowerCase();
|
|
85955
|
+
if (v === "warn")
|
|
85956
|
+
return "warn";
|
|
85957
|
+
if (v === "true" || v === "1" || v === "enforce")
|
|
85958
|
+
return "enforce";
|
|
85959
|
+
return "off";
|
|
85960
|
+
}
|
|
85961
|
+
function isPwMethodPreflightEnabled() {
|
|
85962
|
+
return strictOn(process.env.DISCOVERY_PW_METHOD_PREFLIGHT);
|
|
85963
|
+
}
|
|
85964
|
+
function isVqaLoginDedupeEnabled() {
|
|
85965
|
+
return strictOn(process.env.DISCOVERY_VQALOGIN_DEDUPE);
|
|
85966
|
+
}
|
|
85967
|
+
function isTestgenEmailVerifyEnabled() {
|
|
85968
|
+
return strictOn(process.env.DISCOVERY_TESTGEN_EMAIL_VERIFY);
|
|
85969
|
+
}
|
|
85970
|
+
}
|
|
85971
|
+
});
|
|
85972
|
+
|
|
85973
|
+
// ../runner-core/dist/services/auth-recipe.js
|
|
85974
|
+
var require_auth_recipe = __commonJS({
|
|
85975
|
+
"../runner-core/dist/services/auth-recipe.js"(exports2) {
|
|
85976
|
+
"use strict";
|
|
85977
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
85978
|
+
exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = void 0;
|
|
85979
|
+
exports2.computeRecipeHash = computeRecipeHash;
|
|
85980
|
+
exports2.buildAuthRecipe = buildAuthRecipe;
|
|
85981
|
+
exports2.authProofLocator = authProofLocator;
|
|
85982
|
+
exports2.urlProofPredicateExpr = urlProofPredicateExpr;
|
|
85983
|
+
exports2.renderAuthProofAssertion = renderAuthProofAssertion;
|
|
85984
|
+
exports2.renderProofBaselineCheck = renderProofBaselineCheck;
|
|
85985
|
+
exports2.renderAuthFn = renderAuthFn;
|
|
85986
|
+
exports2.isReservedRunnerEnvVar = isReservedRunnerEnvVar;
|
|
85987
|
+
exports2.absoluteRoute = absoluteRoute;
|
|
85988
|
+
exports2.renderReplayClickArrow = renderReplayClickArrow;
|
|
85989
|
+
exports2.renderReplaySettleArrow = renderReplaySettleArrow;
|
|
85990
|
+
exports2.renderAuthUrlWaitLine = renderAuthUrlWaitLine;
|
|
85991
|
+
exports2.commandToReplayStep = commandToReplayStep;
|
|
85992
|
+
exports2.buildReplayPlan = buildReplayPlan;
|
|
85993
|
+
exports2.extractAuthRegion = extractAuthRegion;
|
|
85994
|
+
exports2.stripAuthRegions = stripAuthRegions;
|
|
85995
|
+
exports2.ensureExpectImported = ensureExpectImported;
|
|
85996
|
+
exports2.insertAfterPwImport = insertAfterPwImport;
|
|
85997
|
+
exports2.ensureLoginCall = ensureLoginCall;
|
|
85998
|
+
exports2.upsertAuthBlock = upsertAuthBlock;
|
|
85999
|
+
exports2.buildHealAuthStamp = buildHealAuthStamp;
|
|
86000
|
+
var crypto_1 = require("crypto");
|
|
86001
|
+
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
86002
|
+
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
86003
|
+
exports2.AUTH_MARKER_VERSION = "v3";
|
|
86004
|
+
var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports2.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
|
|
86005
|
+
var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports2.AUTH_MARKER_VERSION} <<<`;
|
|
86006
|
+
var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
|
|
86007
|
+
var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
|
|
86008
|
+
function computeRecipeHash(input) {
|
|
86009
|
+
const canonical = JSON.stringify({
|
|
86010
|
+
preNavRoute: input.preNavRoute,
|
|
86011
|
+
preNavCmds: input.preNavCmds,
|
|
86012
|
+
route: input.route,
|
|
86013
|
+
cmds: input.cmds,
|
|
86014
|
+
redirectRoute: input.redirectRoute,
|
|
86015
|
+
authProof: input.authProof
|
|
86016
|
+
});
|
|
86017
|
+
return (0, crypto_1.createHash)("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
86018
|
+
}
|
|
86019
|
+
function buildAuthRecipe(login, authProof, verifiedAt) {
|
|
86020
|
+
const preNavRoute = login.preNavRoute ?? "/";
|
|
86021
|
+
const preNavCmds = login.preNavCmds ?? [];
|
|
86022
|
+
const recipeHash = computeRecipeHash({
|
|
86023
|
+
preNavRoute,
|
|
86024
|
+
preNavCmds,
|
|
86025
|
+
route: login.route,
|
|
86026
|
+
cmds: login.cmds,
|
|
86027
|
+
redirectRoute: login.redirectRoute,
|
|
86028
|
+
authProof
|
|
86029
|
+
});
|
|
86030
|
+
return {
|
|
86031
|
+
version: 1,
|
|
86032
|
+
recipeHash,
|
|
86033
|
+
preNavRoute,
|
|
86034
|
+
preNavCmds,
|
|
86035
|
+
route: login.route,
|
|
86036
|
+
cmds: login.cmds,
|
|
86037
|
+
redirectRoute: login.redirectRoute,
|
|
86038
|
+
authProof,
|
|
86039
|
+
replayPlan: login.replayPlan ?? [],
|
|
86040
|
+
verifiedAt
|
|
86041
|
+
};
|
|
86042
|
+
}
|
|
86043
|
+
function authProofLocator(proof) {
|
|
86044
|
+
if (proof.kind === "url" || proof.kind === "endpoint") {
|
|
86045
|
+
throw new Error(`authProofLocator: ${proof.kind} proof has no element locator; callers must branch on proof.kind`);
|
|
86046
|
+
}
|
|
86047
|
+
if (proof.kind === "testid") {
|
|
86048
|
+
return `page.getByTestId(${JSON.stringify(proof.testId)})`;
|
|
86049
|
+
}
|
|
86050
|
+
if (proof.name) {
|
|
86051
|
+
return `page.getByRole(${JSON.stringify(proof.role)}, { name: ${JSON.stringify(proof.name)} })`;
|
|
86052
|
+
}
|
|
86053
|
+
return `page.getByRole(${JSON.stringify(proof.role)}).first()`;
|
|
86054
|
+
}
|
|
86055
|
+
function stripToPathname(route2) {
|
|
86056
|
+
return route2.replace(/[?#].*$/, "").replace(/\/+$/, "") || "/";
|
|
86057
|
+
}
|
|
86058
|
+
function urlProofPredicateExpr(route2, exact = false) {
|
|
86059
|
+
const p = stripToPathname(route2);
|
|
86060
|
+
if (p === "/")
|
|
86061
|
+
return `() => false`;
|
|
86062
|
+
const r = JSON.stringify(p);
|
|
86063
|
+
if (exact)
|
|
86064
|
+
return `(u) => u.pathname === ${r} || u.pathname === ${r} + '/'`;
|
|
86065
|
+
return `(u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/')`;
|
|
86066
|
+
}
|
|
86067
|
+
function renderStableExactUrlProof(pred, tabs) {
|
|
86068
|
+
return [
|
|
86069
|
+
`await page.waitForURL(${pred}, { timeout: 15000 });`,
|
|
86070
|
+
`if (await page.waitForURL((u) => !(${pred})(u), { timeout: 1500 }).then(() => true, () => false)) throw new Error('__vqa auth-proof: authenticated route was transient (rebounded to a logged-out route)');`,
|
|
86071
|
+
`if (await page.locator('input[type="password"]').first().isVisible().catch(() => false)) throw new Error('__vqa auth-proof: a login form is visible on the authenticated route \u2014 logged out');`
|
|
86072
|
+
].join(`
|
|
86073
|
+
${tabs}`);
|
|
86074
|
+
}
|
|
86075
|
+
var ENDPOINT_ORIGIN_EXPR = `(page.url().match(/^https?:\\/\\/[^/]+/) || [''])[0]`;
|
|
86076
|
+
function renderEndpointProbe(url, tabs, mode, originMode) {
|
|
86077
|
+
const u = JSON.stringify(url);
|
|
86078
|
+
const target = originMode === "pageurl" ? `${ENDPOINT_ORIGIN_EXPR} + ${u}` : u;
|
|
86079
|
+
const lines = [
|
|
86080
|
+
`const __vqaResp = await page.request.get(${target});`,
|
|
86081
|
+
`const __vqaJson = __vqaResp.ok() ? await __vqaResp.json().catch(() => null) : null;`,
|
|
86082
|
+
`const __vqaUser = __vqaJson && (__vqaJson.user ?? __vqaJson.data?.user ?? __vqaJson);`,
|
|
86083
|
+
`const __vqaAuthed = !!(__vqaUser && (__vqaUser.id || __vqaUser.email || __vqaUser.sub));`,
|
|
86084
|
+
mode === "authed" ? `if (!__vqaAuthed) throw new Error('__vqa auth-proof: identity endpoint shows no authenticated user');` : `if (__vqaAuthed) throw new Error('__vqa auth-baseline: identity endpoint shows an authenticated user while logged out');`
|
|
86085
|
+
];
|
|
86086
|
+
return lines.join(`
|
|
86087
|
+
${tabs}`);
|
|
86088
|
+
}
|
|
86089
|
+
function renderAuthProofAssertion(proof) {
|
|
86090
|
+
if (proof.kind === "endpoint") {
|
|
86091
|
+
return renderEndpointProbe(proof.url, " ", "authed", "baseurl");
|
|
86092
|
+
}
|
|
86093
|
+
if (proof.kind === "url") {
|
|
86094
|
+
const pred = urlProofPredicateExpr(proof.route, proof.exact);
|
|
86095
|
+
if (proof.exact)
|
|
86096
|
+
return renderStableExactUrlProof(pred, " ");
|
|
86097
|
+
return `await page.waitForURL(${pred}, { timeout: 15000 });`;
|
|
86098
|
+
}
|
|
86099
|
+
return `await expect(${authProofLocator(proof)}).toBeVisible({ timeout: 15000 });`;
|
|
86100
|
+
}
|
|
86101
|
+
var BASELINE_VISIBLE_TIMEOUT_MS = 15e3;
|
|
86102
|
+
function renderProofBaselineCheck(proof, tabs) {
|
|
86103
|
+
if (proof.kind === "endpoint") {
|
|
86104
|
+
return renderEndpointProbe(proof.url, tabs, "anon", "baseurl");
|
|
86105
|
+
}
|
|
86106
|
+
if (proof.kind === "url") {
|
|
86107
|
+
const pred = urlProofPredicateExpr(proof.route, proof.exact);
|
|
86108
|
+
return `if (await page.waitForURL(${pred}, { timeout: 2500 }).then(() => true, () => false)) throw new Error('__vqa auth-baseline: authed route reachable while logged out \u2014 not a discriminating proof');`;
|
|
86109
|
+
}
|
|
86110
|
+
const loc = authProofLocator(proof);
|
|
86111
|
+
return [
|
|
86112
|
+
`if (await ${loc}.waitFor({ state: 'visible', timeout: ${BASELINE_VISIBLE_TIMEOUT_MS} }).then(() => true, () => false)) {`,
|
|
86113
|
+
` throw new Error('__vqa auth-baseline: authed-only proof visible while logged out \u2014 not a discriminating proof');`,
|
|
86114
|
+
`}`
|
|
86115
|
+
].join(`
|
|
86116
|
+
${tabs}`);
|
|
86117
|
+
}
|
|
86118
|
+
function normalizeCmd(cmd) {
|
|
86119
|
+
let c = cmd.trim();
|
|
86120
|
+
if (c.endsWith(";"))
|
|
86121
|
+
c = c.slice(0, -1);
|
|
86122
|
+
return ` ${c};`;
|
|
86123
|
+
}
|
|
86124
|
+
function renderAuthFn(recipe, opts) {
|
|
86125
|
+
const lines = [];
|
|
86126
|
+
lines.push(START_MARKER);
|
|
86127
|
+
lines.push(`async function __vqaLogin(page) {`);
|
|
86128
|
+
if (opts?.warmGuard) {
|
|
86129
|
+
const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
|
|
86130
|
+
const loginRoute = recipe.route ?? recipe.preNavRoute;
|
|
86131
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
|
|
86132
|
+
lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
|
|
86133
|
+
if (recipe.authProof.kind === "endpoint") {
|
|
86134
|
+
lines.push(` try {`);
|
|
86135
|
+
lines.push(` ${renderEndpointProbe(recipe.authProof.url, " ", "authed", "baseurl")}`);
|
|
86136
|
+
lines.push(` return;`);
|
|
86137
|
+
lines.push(` } catch {}`);
|
|
86138
|
+
} else if (recipe.authProof.kind === "url") {
|
|
86139
|
+
const pred = urlProofPredicateExpr(recipe.authProof.route, recipe.authProof.exact);
|
|
86140
|
+
lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
|
|
86141
|
+
lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
|
|
86142
|
+
} else if (loginRoute) {
|
|
86143
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
|
|
86144
|
+
} else {
|
|
86145
|
+
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
|
|
86146
|
+
}
|
|
86147
|
+
lines.push(` }`);
|
|
86148
|
+
}
|
|
86149
|
+
lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
|
|
86150
|
+
for (const cmd of recipe.preNavCmds)
|
|
86151
|
+
lines.push(normalizeCmd(cmd));
|
|
86152
|
+
if (recipe.preNavCmds.length === 0 && recipe.route && recipe.route !== recipe.preNavRoute) {
|
|
86153
|
+
lines.push(` await page.goto(${JSON.stringify(recipe.route)}, { waitUntil: 'domcontentloaded' });`);
|
|
86154
|
+
}
|
|
86155
|
+
for (const cmd of recipe.cmds)
|
|
86156
|
+
lines.push(normalizeCmd(cmd));
|
|
86157
|
+
lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
|
|
86158
|
+
lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
|
|
86159
|
+
if (opts?.warmGuard) {
|
|
86160
|
+
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
86161
|
+
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) {`);
|
|
86162
|
+
lines.push(` try {`);
|
|
86163
|
+
lines.push(` const __vqaState = await page.context().storageState();`);
|
|
86164
|
+
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 });`);
|
|
86165
|
+
lines.push(` } catch {}`);
|
|
86166
|
+
lines.push(` }`);
|
|
86167
|
+
}
|
|
86168
|
+
lines.push(`}`);
|
|
86169
|
+
lines.push(END_MARKER);
|
|
86170
|
+
return lines.join("\n");
|
|
86171
|
+
}
|
|
86172
|
+
exports2.RESERVED_RUNNER_ENV_VARS = /* @__PURE__ */ new Set([
|
|
86173
|
+
"AUTH_FILE",
|
|
86174
|
+
"SHARED_AUTH_INJECT",
|
|
86175
|
+
"VALIDATE_RUN_ID",
|
|
86176
|
+
"VALIDATE_CLAIM_EPOCH",
|
|
86177
|
+
"VALIDATE_AUTH_CONFIG_HASH",
|
|
86178
|
+
"VALIDATE_TASK_ARN",
|
|
86179
|
+
"VALIDATE_RUNNER_AUTH",
|
|
86180
|
+
"VALIDATE_SERVER_URL",
|
|
86181
|
+
// Register-and-verify tests (VALIDATE_QA_INBOX region, inbox-stamp.ts): the
|
|
86182
|
+
// per-run minted registration address + its DERIVED throwaway-app password,
|
|
86183
|
+
// injected by the parent's test-inbox-broker — never project variables.
|
|
86184
|
+
"VQA_TEST_INBOX_EMAIL",
|
|
86185
|
+
"VQA_TEST_ACCOUNT_PASSWORD"
|
|
86186
|
+
]);
|
|
86187
|
+
function isReservedRunnerEnvVar(key) {
|
|
86188
|
+
return exports2.RESERVED_RUNNER_ENV_VARS.has(key);
|
|
86189
|
+
}
|
|
86190
|
+
function absoluteRoute(route2, baseUrl) {
|
|
86191
|
+
try {
|
|
86192
|
+
return new URL(route2, baseUrl).href;
|
|
86193
|
+
} catch {
|
|
86194
|
+
return route2;
|
|
86195
|
+
}
|
|
86196
|
+
}
|
|
86197
|
+
function renderReplayClickArrow(step) {
|
|
86198
|
+
const loc = step.locator.trim();
|
|
86199
|
+
if (step.kind === "wait") {
|
|
86200
|
+
return `async (page) => {
|
|
86201
|
+
await ${loc}.waitFor({ state: 'visible', timeout: 15000 });
|
|
86202
|
+
}`;
|
|
86203
|
+
}
|
|
86204
|
+
if (step.kind === "press") {
|
|
86205
|
+
return `async (page) => {
|
|
86206
|
+
await ${loc}.press(${JSON.stringify(step.key ?? "Enter")});
|
|
86207
|
+
}`;
|
|
86208
|
+
}
|
|
86209
|
+
return `async (page) => {
|
|
86210
|
+
await ${loc}.click();
|
|
86211
|
+
}`;
|
|
86212
|
+
}
|
|
86213
|
+
function renderReplaySettleArrow(proof) {
|
|
86214
|
+
if (proof.kind === "endpoint") {
|
|
86215
|
+
return `async (page) => {
|
|
86216
|
+
${renderEndpointProbe(proof.url, " ", "authed", "pageurl")}
|
|
86217
|
+
}`;
|
|
86218
|
+
}
|
|
86219
|
+
if (proof.kind === "url") {
|
|
86220
|
+
const pred = urlProofPredicateExpr(proof.route, proof.exact);
|
|
86221
|
+
const body = proof.exact ? renderStableExactUrlProof(pred, " ") : `await page.waitForURL(${pred}, { timeout: 15000 });`;
|
|
86222
|
+
return `async (page) => {
|
|
86223
|
+
${body}
|
|
86224
|
+
}`;
|
|
86225
|
+
}
|
|
86226
|
+
return `async (page) => {
|
|
86227
|
+
await ${authProofLocator(proof)}.waitFor({ state: 'visible', timeout: 15000 });
|
|
86228
|
+
}`;
|
|
86229
|
+
}
|
|
86230
|
+
function renderAuthUrlWaitLine(redirectRoute) {
|
|
86231
|
+
const opts = `{ waitUntil: 'domcontentloaded', timeout: 8000 }`;
|
|
86232
|
+
const tail = `.catch(() => { /* URL drift \u2014 the auth-proof assertion below is the gate */ });`;
|
|
86233
|
+
const rr = redirectRoute ? stripToPathname(redirectRoute) : null;
|
|
86234
|
+
if (rr && rr !== "/") {
|
|
86235
|
+
const r = JSON.stringify(rr);
|
|
86236
|
+
return ` await page.waitForURL((u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/'), ${opts})${tail}`;
|
|
86237
|
+
}
|
|
86238
|
+
if (rr === "/") {
|
|
86239
|
+
return ` await page.waitForURL((u) => !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
|
|
86240
|
+
}
|
|
86241
|
+
return ` await page.waitForURL((u) => u.pathname !== '/' && !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
|
|
86242
|
+
}
|
|
86243
|
+
function locatorChainOf(cmd) {
|
|
86244
|
+
const m = cmd.match(/\bpage\.[\s\S]*?(?=\.(?:fill|click|check|press|selectOption|setChecked|waitFor)\s*\()/);
|
|
86245
|
+
return m ? m[0].trim() : null;
|
|
86246
|
+
}
|
|
86247
|
+
function parseRoleName(locator) {
|
|
86248
|
+
const role = locator.match(/getByRole\(\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
86249
|
+
const name = locator.match(/getByRole\([^)]*name:\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByLabel\(\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByPlaceholder\(\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
86250
|
+
return { role, name };
|
|
86251
|
+
}
|
|
86252
|
+
function credentialKeyOf(cmd) {
|
|
86253
|
+
return cmd.match(/\.fill\(\s*process\.env\.([A-Za-z_$][\w$]*)!?\s*\)/)?.[1] ?? null;
|
|
86254
|
+
}
|
|
86255
|
+
function commandToReplayStep(cmd) {
|
|
86256
|
+
const chain = locatorChainOf(cmd);
|
|
86257
|
+
if (!chain)
|
|
86258
|
+
return null;
|
|
86259
|
+
const credKey = credentialKeyOf(cmd);
|
|
86260
|
+
if (cmd.includes(".fill(")) {
|
|
86261
|
+
if (!credKey)
|
|
86262
|
+
return null;
|
|
86263
|
+
const { role, name } = parseRoleName(chain);
|
|
86264
|
+
return { kind: "fill", credentialKey: credKey, locator: chain, role, name, element: name ?? role ?? "credential field" };
|
|
86265
|
+
}
|
|
86266
|
+
if (/\.waitFor\s*\(/.test(cmd))
|
|
86267
|
+
return { kind: "wait", locator: chain };
|
|
86268
|
+
const pressKey = cmd.match(/\.press\(\s*['"`]([^'"`]+)['"`]\s*\)/)?.[1];
|
|
86269
|
+
if (pressKey)
|
|
86270
|
+
return { kind: "press", locator: chain, key: pressKey };
|
|
86271
|
+
if (/\.(?:click|check|setChecked|selectOption)\s*\(/.test(cmd))
|
|
86272
|
+
return { kind: "click", locator: chain };
|
|
86273
|
+
return null;
|
|
86274
|
+
}
|
|
86275
|
+
function buildReplayPlan(login) {
|
|
86276
|
+
const plan = [];
|
|
86277
|
+
const preNavRoute = login.preNavRoute ?? "/";
|
|
86278
|
+
plan.push({ kind: "nav", route: preNavRoute });
|
|
86279
|
+
for (const c of login.preNavCmds ?? []) {
|
|
86280
|
+
const step = commandToReplayStep(c);
|
|
86281
|
+
if (step)
|
|
86282
|
+
plan.push(step);
|
|
86283
|
+
}
|
|
86284
|
+
if ((login.preNavCmds ?? []).length === 0 && login.route && login.route !== preNavRoute) {
|
|
86285
|
+
plan.push({ kind: "nav", route: login.route });
|
|
86286
|
+
}
|
|
86287
|
+
for (const c of login.cmds) {
|
|
86288
|
+
const step = commandToReplayStep(c);
|
|
86289
|
+
if (step)
|
|
86290
|
+
plan.push(step);
|
|
86291
|
+
}
|
|
86292
|
+
return plan;
|
|
86293
|
+
}
|
|
86294
|
+
function extractAuthRegion(code) {
|
|
86295
|
+
const m = code.match(new RegExp(AUTH_REGION_RE.source));
|
|
86296
|
+
return m ? m[0] : null;
|
|
86297
|
+
}
|
|
86298
|
+
function countAuthRegions(code) {
|
|
86299
|
+
const m = code.match(AUTH_REGION_RE);
|
|
86300
|
+
return m ? m.length : 0;
|
|
86301
|
+
}
|
|
86302
|
+
function stripAuthRegions(code) {
|
|
86303
|
+
return code.replace(AUTH_REGION_RE, "");
|
|
86304
|
+
}
|
|
86305
|
+
var PW_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]@playwright\/test['"]\s*;?/;
|
|
86306
|
+
function ensureExpectImported(code) {
|
|
86307
|
+
const m = code.match(PW_IMPORT_RE);
|
|
86308
|
+
if (!m)
|
|
86309
|
+
return code;
|
|
86310
|
+
const names = m[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
86311
|
+
if (names.includes("expect"))
|
|
86312
|
+
return code;
|
|
86313
|
+
const next = [...names, "expect"];
|
|
86314
|
+
return code.replace(PW_IMPORT_RE, `import { ${next.join(", ")} } from '@playwright/test';`);
|
|
86315
|
+
}
|
|
86316
|
+
function insertAfterPwImport(code, block) {
|
|
86317
|
+
const m = code.match(PW_IMPORT_RE);
|
|
86318
|
+
if (!m || m.index === void 0) {
|
|
86319
|
+
return `${block}
|
|
86320
|
+
|
|
86321
|
+
${code.replace(/^\r?\n+/, "")}`;
|
|
86322
|
+
}
|
|
86323
|
+
const end = m.index + m[0].length;
|
|
86324
|
+
const head = code.slice(0, end);
|
|
86325
|
+
const tail = code.slice(end).replace(/^\r?\n+/, "");
|
|
86326
|
+
return `${head}
|
|
86327
|
+
|
|
86328
|
+
${block}
|
|
86329
|
+
|
|
86330
|
+
${tail}`;
|
|
86331
|
+
}
|
|
86332
|
+
var TEST_OPENER_RE = /test(?:\.(?:only|skip|fixme))?\s*\(\s*(['"`])(?:[^\\]|\\.)*?\1\s*,\s*(?:\{[\s\S]*?\}\s*,\s*)?async\s*\(\s*\{[^}]*\}[^)]*\)\s*=>\s*\{/;
|
|
86333
|
+
var LOGIN_CALL_RE = /await\s+__vqaLogin\s*\(\s*page\s*\)/;
|
|
86334
|
+
function ensureLoginCall(code) {
|
|
86335
|
+
if (code.match(new RegExp(TEST_OPENER_RE.source, "g")) === null)
|
|
86336
|
+
return code;
|
|
86337
|
+
let result = code;
|
|
86338
|
+
if ((0, discovery_lint_flags_js_1.isVqaLoginDedupeEnabled)()) {
|
|
86339
|
+
result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
|
|
86340
|
+
}
|
|
86341
|
+
const matches = [...result.matchAll(new RegExp(TEST_OPENER_RE.source, "g"))];
|
|
86342
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
86343
|
+
const m = matches[i];
|
|
86344
|
+
if (m.index === void 0)
|
|
86345
|
+
continue;
|
|
86346
|
+
const insertAt = m.index + m[0].length;
|
|
86347
|
+
const following = result.slice(insertAt, insertAt + 60);
|
|
86348
|
+
if (/^\s*await\s+__vqaLogin\s*\(\s*page\s*\)/.test(following))
|
|
86349
|
+
continue;
|
|
86350
|
+
result = `${result.slice(0, insertAt)}
|
|
86351
|
+
await __vqaLogin(page);${result.slice(insertAt)}`;
|
|
86352
|
+
}
|
|
86353
|
+
return result;
|
|
86354
|
+
}
|
|
86355
|
+
function upsertAuthBlock(code, ctx) {
|
|
86356
|
+
if (ctx.mode === "generate") {
|
|
86357
|
+
if (!ctx.recipe || !ctx.isAuthNeeded)
|
|
86358
|
+
return code;
|
|
86359
|
+
let result = stripAuthRegions(code);
|
|
86360
|
+
result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
|
|
86361
|
+
result = ensureExpectImported(result);
|
|
86362
|
+
const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
|
|
86363
|
+
result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
|
|
86364
|
+
result = ensureLoginCall(result);
|
|
86365
|
+
const n = countAuthRegions(result);
|
|
86366
|
+
if (n !== 1) {
|
|
86367
|
+
throw new Error(`upsertAuthBlock(generate): expected exactly 1 auth region after stamp, got ${n}`);
|
|
86368
|
+
}
|
|
86369
|
+
if (!LOGIN_CALL_RE.test(result)) {
|
|
86370
|
+
throw new Error("upsertAuthBlock(generate): stamped __vqaLogin but found no test() opener to inject the call");
|
|
86371
|
+
}
|
|
86372
|
+
return result;
|
|
86373
|
+
}
|
|
86374
|
+
try {
|
|
86375
|
+
if (!ctx.preservedAuthRegion)
|
|
86376
|
+
return code;
|
|
86377
|
+
let result = ensureExpectImported(code);
|
|
86378
|
+
if (countAuthRegions(result) >= 1) {
|
|
86379
|
+
result = result.replace(AUTH_REGION_RE, "");
|
|
86380
|
+
result = insertAfterPwImport(result, ctx.preservedAuthRegion);
|
|
86381
|
+
} else {
|
|
86382
|
+
result = insertAfterPwImport(result, ctx.preservedAuthRegion);
|
|
86383
|
+
}
|
|
86384
|
+
result = ensureLoginCall(result);
|
|
86385
|
+
if (countAuthRegions(result) !== 1) {
|
|
86386
|
+
throw new Error("heal re-stamp produced non-unique auth region");
|
|
86387
|
+
}
|
|
86388
|
+
return result;
|
|
86389
|
+
} catch (err) {
|
|
86390
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
86391
|
+
ctx.onSignal?.({ type: "auth-stamp-degraded", reason });
|
|
86392
|
+
return code;
|
|
86393
|
+
}
|
|
86394
|
+
}
|
|
86395
|
+
function buildHealAuthStamp(originalCode, failureError, logs2) {
|
|
86396
|
+
const region = extractAuthRegion(originalCode);
|
|
86397
|
+
const loginFailed = !!failureError && /__vqaLogin/.test(failureError);
|
|
86398
|
+
return {
|
|
86399
|
+
mode: "heal",
|
|
86400
|
+
preservedAuthRegion: region && !loginFailed ? region : null,
|
|
86401
|
+
onSignal: logs2 ? (s) => logs2.push(`\u26A0 auth-stamp ${s.type}: ${s.reason}`) : void 0
|
|
86402
|
+
};
|
|
86403
|
+
}
|
|
86404
|
+
}
|
|
86405
|
+
});
|
|
86406
|
+
|
|
86407
|
+
// ../runner-core/dist/services/inbox-stamp.js
|
|
86408
|
+
var require_inbox_stamp = __commonJS({
|
|
86409
|
+
"../runner-core/dist/services/inbox-stamp.js"(exports2) {
|
|
86410
|
+
"use strict";
|
|
86411
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
86412
|
+
exports2.INBOX_HELPER_TOKEN_RE = exports2.INBOX_HELPER_NAME = exports2.INBOX_REGION_RE = exports2.INBOX_MARKER_VERSION = void 0;
|
|
86413
|
+
exports2.codeUsesInboxHelper = codeUsesInboxHelper;
|
|
86414
|
+
exports2.stripInboxRegions = stripInboxRegions;
|
|
86415
|
+
exports2.renderInboxFn = renderInboxFn;
|
|
86416
|
+
exports2.upsertInboxBlock = upsertInboxBlock;
|
|
86417
|
+
var auth_recipe_js_1 = require_auth_recipe();
|
|
86418
|
+
exports2.INBOX_MARKER_VERSION = "v1";
|
|
86419
|
+
var START_MARKER = `// >>> VALIDATE_QA_INBOX ${exports2.INBOX_MARKER_VERSION} \u2014 generated, do not edit >>>`;
|
|
86420
|
+
var END_MARKER = `// <<< VALIDATE_QA_INBOX ${exports2.INBOX_MARKER_VERSION} <<<`;
|
|
86421
|
+
exports2.INBOX_REGION_RE = /\/\/ >>> VALIDATE_QA_INBOX[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_INBOX[^\n]*\r?\n?/g;
|
|
86422
|
+
exports2.INBOX_HELPER_NAME = "__vqaWaitForEmail";
|
|
86423
|
+
exports2.INBOX_HELPER_TOKEN_RE = /__vqaWaitForEmail\s*\(/;
|
|
86424
|
+
function codeUsesInboxHelper(code) {
|
|
86425
|
+
if (!code)
|
|
86426
|
+
return false;
|
|
86427
|
+
return exports2.INBOX_HELPER_TOKEN_RE.test(code) || /VALIDATE_QA_INBOX/.test(code);
|
|
86428
|
+
}
|
|
86429
|
+
function stripInboxRegions(code) {
|
|
86430
|
+
return code.replace(exports2.INBOX_REGION_RE, "");
|
|
86431
|
+
}
|
|
86432
|
+
function countInboxRegions(code) {
|
|
86433
|
+
const m = code.match(exports2.INBOX_REGION_RE);
|
|
86434
|
+
return m ? m.length : 0;
|
|
86435
|
+
}
|
|
86436
|
+
function renderInboxFn() {
|
|
86437
|
+
const lines = [
|
|
86438
|
+
START_MARKER,
|
|
86439
|
+
`// Reads THIS run's verification email via the runner's inbox broker. The`,
|
|
86440
|
+
`// IMAP credential stays in the runner parent; this helper can only see mail`,
|
|
86441
|
+
`// addressed to the run's own registration address (VQA_TEST_INBOX_EMAIL).`,
|
|
86442
|
+
`// kind: 'code' \u2192 the one-time code, 'link' \u2192 the verification URL,`,
|
|
86443
|
+
`// 'either' / omitted \u2192 { code, link, subject, from } for flexible flows.`,
|
|
86444
|
+
`async function __vqaWaitForEmail(kind, opts) {`,
|
|
86445
|
+
` const want = kind === 'code' || kind === 'link' ? kind : 'either';`,
|
|
86446
|
+
` if (!process.env.VQA_TEST_INBOX_EMAIL || !process.env.VALIDATE_SERVER_URL || !process.env.VALIDATE_RUN_ID) {`,
|
|
86447
|
+
` throw new Error('CONFIG_ISSUE: email verification unavailable - this runner has no verification inbox, so a register-and-verify test cannot run here');`,
|
|
86448
|
+
` }`,
|
|
86449
|
+
` const timeoutMs = Math.min(Math.max((opts && opts.timeoutMs) || 150000, 5000), 170000);`,
|
|
86450
|
+
` const res = await fetch(process.env.VALIDATE_SERVER_URL + '/api/runner/runs/' + encodeURIComponent(process.env.VALIDATE_RUN_ID) + '/inbox-wait', {`,
|
|
86451
|
+
` method: 'POST',`,
|
|
86452
|
+
` headers: { authorization: process.env.VALIDATE_RUNNER_AUTH || '', 'content-type': 'application/json' },`,
|
|
86453
|
+
` body: JSON.stringify({ kind: want, timeoutMs: timeoutMs }),`,
|
|
86454
|
+
` });`,
|
|
86455
|
+
` if (res.status === 404) {`,
|
|
86456
|
+
` throw new Error('CONFIG_ISSUE: email verification unavailable - the runner inbox broker is not active for this run');`,
|
|
86457
|
+
` }`,
|
|
86458
|
+
` let data = null;`,
|
|
86459
|
+
` try { data = await res.json(); } catch (e) { data = null; }`,
|
|
86460
|
+
` if (!res.ok || !data) { throw new Error('inbox broker request failed (status ' + res.status + ')'); }`,
|
|
86461
|
+
` if (!data.found) {`,
|
|
86462
|
+
` if (data.error) { throw new Error('CONFIG_ISSUE: verification inbox failed on this runner - ' + data.error); }`,
|
|
86463
|
+
` throw new Error('VERIFICATION_EMAIL_NOT_RECEIVED: no verification email arrived for this test account within ' + Math.round(timeoutMs / 1000) + 's - the app may not have sent it');`,
|
|
86464
|
+
` }`,
|
|
86465
|
+
` if (want === 'code' && !data.code) {`,
|
|
86466
|
+
` throw new Error('VERIFICATION_EMAIL_HAS_NO_CODE: an email arrived (subject: ' + JSON.stringify(data.subject || '') + ') but no one-time code could be extracted');`,
|
|
86467
|
+
` }`,
|
|
86468
|
+
` if (want === 'link' && !data.link) {`,
|
|
86469
|
+
` throw new Error('VERIFICATION_EMAIL_HAS_NO_LINK: an email arrived (subject: ' + JSON.stringify(data.subject || '') + ') but no verification link could be extracted');`,
|
|
86470
|
+
` }`,
|
|
86471
|
+
` return want === 'code' ? data.code : want === 'link' ? data.link : data;`,
|
|
86472
|
+
`}`,
|
|
86473
|
+
END_MARKER
|
|
86474
|
+
];
|
|
86475
|
+
return lines.join("\n");
|
|
86476
|
+
}
|
|
86477
|
+
function upsertInboxBlock(code) {
|
|
86478
|
+
const stripped = stripInboxRegions(code);
|
|
86479
|
+
if (!exports2.INBOX_HELPER_TOKEN_RE.test(stripped))
|
|
86480
|
+
return stripped;
|
|
86481
|
+
const result = (0, auth_recipe_js_1.insertAfterPwImport)(stripped, renderInboxFn());
|
|
86482
|
+
const n = countInboxRegions(result);
|
|
86483
|
+
if (n !== 1) {
|
|
86484
|
+
throw new Error(`upsertInboxBlock: expected exactly 1 inbox region after stamp, got ${n}`);
|
|
86485
|
+
}
|
|
86486
|
+
return result;
|
|
86487
|
+
}
|
|
86488
|
+
}
|
|
86489
|
+
});
|
|
86490
|
+
|
|
86491
|
+
// ../runner-core/dist/services/test-inbox-broker.js
|
|
86492
|
+
var require_test_inbox_broker = __commonJS({
|
|
86493
|
+
"../runner-core/dist/services/test-inbox-broker.js"(exports2) {
|
|
86494
|
+
"use strict";
|
|
86495
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
86496
|
+
exports2.INBOX_WAIT_ROUTE = exports2.TEST_INBOX_PASSWORD_ENV = exports2.TEST_INBOX_EMAIL_ENV = void 0;
|
|
86497
|
+
exports2.deriveTestRunInboxTag = deriveTestRunInboxTag;
|
|
86498
|
+
exports2.mintTestRunInboxIdentity = mintTestRunInboxIdentity;
|
|
86499
|
+
exports2.decideInboxWaitResponse = decideInboxWaitResponse;
|
|
86500
|
+
exports2.startTestInboxBroker = startTestInboxBroker;
|
|
86501
|
+
var node_crypto_1 = require("crypto");
|
|
86502
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
86503
|
+
var inbox_tools_js_1 = require_inbox_tools();
|
|
86504
|
+
exports2.TEST_INBOX_EMAIL_ENV = "VQA_TEST_INBOX_EMAIL";
|
|
86505
|
+
exports2.TEST_INBOX_PASSWORD_ENV = "VQA_TEST_ACCOUNT_PASSWORD";
|
|
86506
|
+
exports2.INBOX_WAIT_ROUTE = "inbox-wait";
|
|
86507
|
+
var MIN_WAIT_MS = 5e3;
|
|
86508
|
+
var MAX_WAIT_MS = 17e4;
|
|
86509
|
+
var DEFAULT_WAIT_MS = 15e4;
|
|
86510
|
+
function deriveTestRunInboxTag(seed, projectId, runId) {
|
|
86511
|
+
const hostKey = (0, verification_inbox_js_1.seedToHostKey)(seed || "");
|
|
86512
|
+
const hash = (0, node_crypto_1.createHash)("sha256").update(`testrun|${hostKey}|${projectId ?? ""}|${runId}`).digest("hex").slice(0, 10);
|
|
86513
|
+
return `vqat${hash}`;
|
|
86514
|
+
}
|
|
86515
|
+
function mintTestRunInboxIdentity(config, input) {
|
|
86516
|
+
const tag = deriveTestRunInboxTag(input.baseUrl, input.projectId, input.runId);
|
|
86517
|
+
const email = (0, verification_inbox_js_1.applyPlusTag)(config.address, tag);
|
|
86518
|
+
return { email, password: (0, verification_inbox_js_1.deriveRegistrationPassword)(email), tag };
|
|
86519
|
+
}
|
|
86520
|
+
function decideInboxWaitResponse(msg, publicLink) {
|
|
86521
|
+
const code = msg && msg.code && msg.codeConfidence !== "fallback" ? msg.code : null;
|
|
86522
|
+
const link = publicLink;
|
|
86523
|
+
if (!msg || !code && !link)
|
|
86524
|
+
return { found: false };
|
|
86525
|
+
return { found: true, code, link, subject: msg.subject, from: msg.from };
|
|
86526
|
+
}
|
|
86527
|
+
async function startTestInboxBroker(opts) {
|
|
86528
|
+
const identity = mintTestRunInboxIdentity(opts.config, opts);
|
|
86529
|
+
const reader = new verification_inbox_js_1.VerificationInboxReader(opts.config);
|
|
86530
|
+
const sinceMs = Date.now();
|
|
86531
|
+
let sinceUid = 0;
|
|
86532
|
+
let sinceUidValidity = 0;
|
|
86533
|
+
let authDeadReason = null;
|
|
86534
|
+
try {
|
|
86535
|
+
const wm = await reader.getUidWatermark();
|
|
86536
|
+
sinceUid = wm.uid;
|
|
86537
|
+
sinceUidValidity = wm.uidValidity;
|
|
86538
|
+
} catch (error2) {
|
|
86539
|
+
if ((0, verification_inbox_js_1.isInboxAuthFailure)(error2)) {
|
|
86540
|
+
authDeadReason = error2 instanceof Error ? error2.message : String(error2);
|
|
86541
|
+
opts.log?.(`\u26A0 Inbox broker: IMAP authentication rejected at watermark probe \u2014 email waits will fail as CONFIG_ISSUE (${authDeadReason})`);
|
|
86542
|
+
} else {
|
|
86543
|
+
opts.log?.("\u26A0 Inbox broker: UID watermark unavailable, falling back to the time floor");
|
|
86544
|
+
}
|
|
86545
|
+
}
|
|
86546
|
+
opts.log?.(`\u{1F4EC} Inbox broker active for ${identity.email} (uid watermark ${sinceUid || "none"})`);
|
|
86547
|
+
let waiting = false;
|
|
86548
|
+
const handleWait = async (body) => {
|
|
86549
|
+
if (authDeadReason) {
|
|
86550
|
+
return { status: 200, body: { found: false, error: "inbox authentication failed (revoked/wrong app password)" } };
|
|
86551
|
+
}
|
|
86552
|
+
if (waiting) {
|
|
86553
|
+
return { status: 200, body: { found: false, error: "another verification wait is already in progress for this run" } };
|
|
86554
|
+
}
|
|
86555
|
+
waiting = true;
|
|
86556
|
+
const rawTimeout = typeof body.timeoutMs === "number" && Number.isFinite(body.timeoutMs) ? body.timeoutMs : DEFAULT_WAIT_MS;
|
|
86557
|
+
const timeoutMs = Math.min(Math.max(rawTimeout, MIN_WAIT_MS), MAX_WAIT_MS);
|
|
86558
|
+
const require2 = body.kind === "code" ? "code" : body.kind === "link" ? "link" : "signal";
|
|
86559
|
+
try {
|
|
86560
|
+
const stats = { filteredOut: 0 };
|
|
86561
|
+
const msg = await reader.waitForMessage({
|
|
86562
|
+
toAddress: identity.email,
|
|
86563
|
+
sinceMs,
|
|
86564
|
+
sinceUid,
|
|
86565
|
+
sinceUidValidity,
|
|
86566
|
+
maxWaitMs: timeoutMs,
|
|
86567
|
+
require: require2,
|
|
86568
|
+
stats
|
|
86569
|
+
});
|
|
86570
|
+
const publicLink = msg && msg.link && (0, inbox_tools_js_1.isPublicHttpUrl)(msg.link) ? msg.link : null;
|
|
86571
|
+
const decision = decideInboxWaitResponse(msg, publicLink);
|
|
86572
|
+
if (!decision.found) {
|
|
86573
|
+
opts.log?.(`\u{1F4ED} Inbox broker: no usable verification email for ${identity.tag} within ${Math.round(timeoutMs / 1e3)}s`);
|
|
86574
|
+
return { status: 200, body: { found: false } };
|
|
86575
|
+
}
|
|
86576
|
+
opts.log?.(`\u{1F4EC} Inbox broker: email for ${identity.tag} (code=${decision.code ? "yes" : "no"}, link=${decision.link ? "yes" : "no"})`);
|
|
86577
|
+
return { status: 200, body: { ...decision } };
|
|
86578
|
+
} catch (error2) {
|
|
86579
|
+
if ((0, verification_inbox_js_1.isInboxAuthFailure)(error2)) {
|
|
86580
|
+
authDeadReason = error2 instanceof Error ? error2.message : String(error2);
|
|
86581
|
+
return { status: 200, body: { found: false, error: "inbox authentication failed (revoked/wrong app password)" } };
|
|
86582
|
+
}
|
|
86583
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
86584
|
+
opts.log?.(`\u26A0 Inbox broker wait failed: ${message}`);
|
|
86585
|
+
return { status: 200, body: { found: false, error: `inbox read failed: ${message}` } };
|
|
86586
|
+
} finally {
|
|
86587
|
+
waiting = false;
|
|
86588
|
+
}
|
|
86589
|
+
};
|
|
86590
|
+
return {
|
|
86591
|
+
identity,
|
|
86592
|
+
childEnv: () => ({
|
|
86593
|
+
[exports2.TEST_INBOX_EMAIL_ENV]: identity.email,
|
|
86594
|
+
[exports2.TEST_INBOX_PASSWORD_ENV]: identity.password
|
|
86595
|
+
}),
|
|
86596
|
+
handleWait,
|
|
86597
|
+
close: () => reader.close()
|
|
86598
|
+
};
|
|
86599
|
+
}
|
|
86600
|
+
}
|
|
86601
|
+
});
|
|
86602
|
+
|
|
85802
86603
|
// ../runner-core/dist/services/playwright-native.js
|
|
85803
86604
|
var require_playwright_native = __commonJS({
|
|
85804
86605
|
"../runner-core/dist/services/playwright-native.js"(exports2) {
|
|
@@ -85856,6 +86657,9 @@ var require_playwright_native = __commonJS({
|
|
|
85856
86657
|
var har_stream_js_1 = require_har_stream();
|
|
85857
86658
|
var api_auth_headers_js_1 = require_api_auth_headers();
|
|
85858
86659
|
var run_callback_proxy_js_1 = require_run_callback_proxy();
|
|
86660
|
+
var inbox_stamp_js_1 = require_inbox_stamp();
|
|
86661
|
+
var test_inbox_broker_js_1 = require_test_inbox_broker();
|
|
86662
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
85859
86663
|
var api_auth_headers_js_2 = require_api_auth_headers();
|
|
85860
86664
|
Object.defineProperty(exports2, "parseApiAuthHeaders", { enumerable: true, get: function() {
|
|
85861
86665
|
return api_auth_headers_js_2.parseApiAuthHeaders;
|
|
@@ -85890,17 +86694,20 @@ var require_playwright_native = __commonJS({
|
|
|
85890
86694
|
let runDir = null;
|
|
85891
86695
|
let standingNeedleMatcher = null;
|
|
85892
86696
|
let callbackProxy;
|
|
86697
|
+
let inboxBroker;
|
|
85893
86698
|
try {
|
|
85894
86699
|
runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
|
|
85895
86700
|
await (0, promises_1.mkdir)(runDir, { recursive: true });
|
|
85896
86701
|
const isApiTest2 = playwrightCode.includes("@api") || playwrightCode.includes("{ request }") && !playwrightCode.includes("{ page");
|
|
86702
|
+
const usesInboxHelper = !isApiTest2 && (0, inbox_stamp_js_1.codeUsesInboxHelper)(playwrightCode);
|
|
85897
86703
|
const effectiveStandingMode = isApiTest2 ? authStandingMode ?? "legacy-if-configured" : "none";
|
|
85898
86704
|
const resolvedExtraHeaders = (0, api_auth_headers_js_1.resolveApiAuthHeaders)({
|
|
85899
86705
|
explicit: extraHttpHeaders,
|
|
85900
86706
|
standingHeadersMode: effectiveStandingMode,
|
|
85901
86707
|
apiAuthHeadersVar: testVariables?.API_AUTH_HEADERS
|
|
85902
86708
|
});
|
|
85903
|
-
|
|
86709
|
+
const needleValues = resolvedExtraHeaders ? Object.values(resolvedExtraHeaders) : [];
|
|
86710
|
+
standingNeedleMatcher = (0, shared_1.buildNeedleMatcher)(needleValues);
|
|
85904
86711
|
const cssDisableSetup = `
|
|
85905
86712
|
const __qaLiveBrowserStateFile = process.env.VALIDATE_LIVE_BROWSER_STATE_PATH;
|
|
85906
86713
|
let __qaLiveBrowserInterval: ReturnType<typeof setInterval> | null = null;
|
|
@@ -86139,7 +86946,7 @@ test.afterEach(async ({ page }, testInfo) => {
|
|
|
86139
86946
|
module.exports = {
|
|
86140
86947
|
testDir: ${JSON.stringify(absTestDir)},
|
|
86141
86948
|
outputDir: ${JSON.stringify(absOutputDir)},
|
|
86142
|
-
timeout:
|
|
86949
|
+
timeout: ${usesInboxHelper ? 24e4 : 6e4},
|
|
86143
86950
|
retries: 0,
|
|
86144
86951
|
use: {
|
|
86145
86952
|
baseURL: ${JSON.stringify(baseUrl)},
|
|
@@ -86165,6 +86972,27 @@ module.exports = {
|
|
|
86165
86972
|
const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
|
|
86166
86973
|
const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
|
|
86167
86974
|
const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
|
|
86975
|
+
if (usesInboxHelper && validateContext?.runId) {
|
|
86976
|
+
const inboxConfig = (0, verification_inbox_js_1.loadVerificationInboxConfig)();
|
|
86977
|
+
if (!inboxConfig) {
|
|
86978
|
+
logs2.push("\u{1F4EA} Test uses __vqaWaitForEmail but this runner has no verification inbox configured \u2014 the helper will fail as CONFIG_ISSUE");
|
|
86979
|
+
} else {
|
|
86980
|
+
try {
|
|
86981
|
+
inboxBroker = await (0, test_inbox_broker_js_1.startTestInboxBroker)({
|
|
86982
|
+
config: inboxConfig,
|
|
86983
|
+
baseUrl,
|
|
86984
|
+
projectId: options.projectId ?? null,
|
|
86985
|
+
runId: validateContext.runId,
|
|
86986
|
+
log: (line) => logs2.push(line)
|
|
86987
|
+
});
|
|
86988
|
+
standingNeedleMatcher = (0, shared_1.buildNeedleMatcher)([...needleValues, inboxBroker.identity.password]);
|
|
86989
|
+
} catch (error3) {
|
|
86990
|
+
logs2.push(`\u26A0 Inbox broker failed to start: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
86991
|
+
}
|
|
86992
|
+
}
|
|
86993
|
+
} else if (usesInboxHelper) {
|
|
86994
|
+
logs2.push("\u{1F4EA} Test uses __vqaWaitForEmail but no run callback context is available \u2014 the helper will fail as CONFIG_ISSUE");
|
|
86995
|
+
}
|
|
86168
86996
|
if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
|
|
86169
86997
|
try {
|
|
86170
86998
|
callbackProxy = await (0, run_callback_proxy_js_1.startRunCallbackProxy)({
|
|
@@ -86173,7 +87001,8 @@ module.exports = {
|
|
|
86173
87001
|
upstreamAuthHeader: validateContext.authHeader,
|
|
86174
87002
|
taskArn: process.env.VALIDATE_TASK_ARN,
|
|
86175
87003
|
claimEpoch: callbackClaimEpoch,
|
|
86176
|
-
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH
|
|
87004
|
+
authConfigHash: process.env.VALIDATE_AUTH_CONFIG_HASH,
|
|
87005
|
+
...inboxBroker ? { localRoutes: { [test_inbox_broker_js_1.INBOX_WAIT_ROUTE]: inboxBroker.handleWait } } : {}
|
|
86177
87006
|
});
|
|
86178
87007
|
} catch (error3) {
|
|
86179
87008
|
logs2.push(`Callback proxy unavailable: ${error3 instanceof Error ? error3.message : String(error3)}`);
|
|
@@ -86200,14 +87029,19 @@ module.exports = {
|
|
|
86200
87029
|
// Preserve an explicit shared-auth kill-switch in the child. Unset defaults on in
|
|
86201
87030
|
// both parent and generated __vqaLogin code; "false" must still cross this process
|
|
86202
87031
|
// boundary to disable the early publish POST. It remains a per-run toggle.
|
|
86203
|
-
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {}
|
|
87032
|
+
...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {},
|
|
87033
|
+
// Register-and-verify tests: the run's minted registration identity. An
|
|
87034
|
+
// address + a DERIVED throwaway-app password — never the IMAP secret,
|
|
87035
|
+
// which stays in this parent (test-inbox-broker.ts). Useless without the
|
|
87036
|
+
// loopback proxy the helper reads mail through, hence the guard.
|
|
87037
|
+
...inboxBroker && callbackProxy ? inboxBroker.childEnv() : {}
|
|
86204
87038
|
};
|
|
86205
87039
|
delete childTestVariables.API_AUTH_HEADERS;
|
|
86206
87040
|
logs2.push("\u{1F680} Spawning playwright test...");
|
|
86207
87041
|
const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
|
|
86208
87042
|
let playResult;
|
|
86209
87043
|
try {
|
|
86210
|
-
playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv);
|
|
87044
|
+
playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv, usesInboxHelper ? 3e5 : void 0);
|
|
86211
87045
|
} finally {
|
|
86212
87046
|
await stopLiveBrowserPolling?.();
|
|
86213
87047
|
}
|
|
@@ -86298,6 +87132,8 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
86298
87132
|
} finally {
|
|
86299
87133
|
await callbackProxy?.close().catch(() => {
|
|
86300
87134
|
});
|
|
87135
|
+
await inboxBroker?.close().catch(() => {
|
|
87136
|
+
});
|
|
86301
87137
|
if (runDir) {
|
|
86302
87138
|
await (0, promises_1.rm)(runDir, { recursive: true, force: true }).catch(() => {
|
|
86303
87139
|
});
|
|
@@ -86349,7 +87185,7 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
86349
87185
|
stopped = true;
|
|
86350
87186
|
};
|
|
86351
87187
|
}
|
|
86352
|
-
function runPlaywright(configPath, testVariables, trustedRuntimeEnv) {
|
|
87188
|
+
function runPlaywright(configPath, testVariables, trustedRuntimeEnv, killAfterMs) {
|
|
86353
87189
|
return new Promise((resolvePromise) => {
|
|
86354
87190
|
const sandboxedEnv = buildNativePlaywrightChildEnv(process.env, testVariables, trustedRuntimeEnv);
|
|
86355
87191
|
const proc = (0, spawn_with_tree_kill_js_1.spawnDetachedGroup)(process.execPath, [PW_CLI, "test", "--config", configPath], {
|
|
@@ -86367,11 +87203,13 @@ ${passed ? "\u2705" : "\u274C"} Test ${passed ? "PASSED" : "FAILED"}${error2 ? `
|
|
|
86367
87203
|
proc.stderr?.on("data", (chunk) => {
|
|
86368
87204
|
stderr += chunk.toString();
|
|
86369
87205
|
});
|
|
86370
|
-
const
|
|
87206
|
+
const killMs = killAfterMs ?? 12e4;
|
|
87207
|
+
const cancelKill = (0, spawn_with_tree_kill_js_1.scheduleTreeKill)(proc, killMs, {
|
|
86371
87208
|
graceMs: 1e4,
|
|
86372
87209
|
onTimeout: () => {
|
|
86373
87210
|
timedOut = true;
|
|
86374
|
-
stderr +=
|
|
87211
|
+
stderr += `
|
|
87212
|
+
[playwright killed after ${Math.round(killMs / 1e3)}s safety timeout]`;
|
|
86375
87213
|
}
|
|
86376
87214
|
});
|
|
86377
87215
|
const settle = (result) => {
|
|
@@ -90070,402 +90908,6 @@ var require_anchor_harvester = __commonJS({
|
|
|
90070
90908
|
}
|
|
90071
90909
|
});
|
|
90072
90910
|
|
|
90073
|
-
// ../runner-core/dist/services/discovery-lint-flags.js
|
|
90074
|
-
var require_discovery_lint_flags = __commonJS({
|
|
90075
|
-
"../runner-core/dist/services/discovery-lint-flags.js"(exports2) {
|
|
90076
|
-
"use strict";
|
|
90077
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
90078
|
-
exports2.structuralLocatorLintMode = structuralLocatorLintMode;
|
|
90079
|
-
exports2.isPwMethodPreflightEnabled = isPwMethodPreflightEnabled;
|
|
90080
|
-
exports2.isVqaLoginDedupeEnabled = isVqaLoginDedupeEnabled;
|
|
90081
|
-
function strictOn(value) {
|
|
90082
|
-
return value === "true" || value === "1";
|
|
90083
|
-
}
|
|
90084
|
-
function structuralLocatorLintMode() {
|
|
90085
|
-
const v = (process.env.DISCOVERY_STRUCTURAL_LOCATOR_LINT || "").trim().toLowerCase();
|
|
90086
|
-
if (v === "warn")
|
|
90087
|
-
return "warn";
|
|
90088
|
-
if (v === "true" || v === "1" || v === "enforce")
|
|
90089
|
-
return "enforce";
|
|
90090
|
-
return "off";
|
|
90091
|
-
}
|
|
90092
|
-
function isPwMethodPreflightEnabled() {
|
|
90093
|
-
return strictOn(process.env.DISCOVERY_PW_METHOD_PREFLIGHT);
|
|
90094
|
-
}
|
|
90095
|
-
function isVqaLoginDedupeEnabled() {
|
|
90096
|
-
return strictOn(process.env.DISCOVERY_VQALOGIN_DEDUPE);
|
|
90097
|
-
}
|
|
90098
|
-
}
|
|
90099
|
-
});
|
|
90100
|
-
|
|
90101
|
-
// ../runner-core/dist/services/auth-recipe.js
|
|
90102
|
-
var require_auth_recipe = __commonJS({
|
|
90103
|
-
"../runner-core/dist/services/auth-recipe.js"(exports2) {
|
|
90104
|
-
"use strict";
|
|
90105
|
-
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
90106
|
-
exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = void 0;
|
|
90107
|
-
exports2.computeRecipeHash = computeRecipeHash;
|
|
90108
|
-
exports2.buildAuthRecipe = buildAuthRecipe;
|
|
90109
|
-
exports2.authProofLocator = authProofLocator;
|
|
90110
|
-
exports2.urlProofPredicateExpr = urlProofPredicateExpr;
|
|
90111
|
-
exports2.renderAuthProofAssertion = renderAuthProofAssertion;
|
|
90112
|
-
exports2.renderAuthFn = renderAuthFn;
|
|
90113
|
-
exports2.isReservedRunnerEnvVar = isReservedRunnerEnvVar;
|
|
90114
|
-
exports2.absoluteRoute = absoluteRoute;
|
|
90115
|
-
exports2.renderReplayClickArrow = renderReplayClickArrow;
|
|
90116
|
-
exports2.renderReplaySettleArrow = renderReplaySettleArrow;
|
|
90117
|
-
exports2.renderAuthUrlWaitLine = renderAuthUrlWaitLine;
|
|
90118
|
-
exports2.commandToReplayStep = commandToReplayStep;
|
|
90119
|
-
exports2.buildReplayPlan = buildReplayPlan;
|
|
90120
|
-
exports2.extractAuthRegion = extractAuthRegion;
|
|
90121
|
-
exports2.stripAuthRegions = stripAuthRegions;
|
|
90122
|
-
exports2.ensureExpectImported = ensureExpectImported;
|
|
90123
|
-
exports2.ensureLoginCall = ensureLoginCall;
|
|
90124
|
-
exports2.upsertAuthBlock = upsertAuthBlock;
|
|
90125
|
-
exports2.buildHealAuthStamp = buildHealAuthStamp;
|
|
90126
|
-
var crypto_1 = require("crypto");
|
|
90127
|
-
var shared_1 = (init_dist(), __toCommonJS(dist_exports));
|
|
90128
|
-
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
90129
|
-
exports2.AUTH_MARKER_VERSION = "v3";
|
|
90130
|
-
var START_MARKER = `// >>> VALIDATE_QA_AUTH ${exports2.AUTH_MARKER_VERSION} \u2014 generated, do not edit >>>`;
|
|
90131
|
-
var END_MARKER = `// <<< VALIDATE_QA_AUTH ${exports2.AUTH_MARKER_VERSION} <<<`;
|
|
90132
|
-
var SHARED_AUTH_RUNTIME_ENABLED = `!/^false$/i.test((process.env.SHARED_AUTH_INJECT ?? '').trim())`;
|
|
90133
|
-
var AUTH_REGION_RE = /\/\/ >>> VALIDATE_QA_AUTH[^\n]*\r?\n[\s\S]*?\/\/ <<< VALIDATE_QA_AUTH[^\n]*\r?\n?/g;
|
|
90134
|
-
function computeRecipeHash(input) {
|
|
90135
|
-
const canonical = JSON.stringify({
|
|
90136
|
-
preNavRoute: input.preNavRoute,
|
|
90137
|
-
preNavCmds: input.preNavCmds,
|
|
90138
|
-
route: input.route,
|
|
90139
|
-
cmds: input.cmds,
|
|
90140
|
-
redirectRoute: input.redirectRoute,
|
|
90141
|
-
authProof: input.authProof
|
|
90142
|
-
});
|
|
90143
|
-
return (0, crypto_1.createHash)("sha256").update(canonical).digest("hex").slice(0, 12);
|
|
90144
|
-
}
|
|
90145
|
-
function buildAuthRecipe(login, authProof, verifiedAt) {
|
|
90146
|
-
const preNavRoute = login.preNavRoute ?? "/";
|
|
90147
|
-
const preNavCmds = login.preNavCmds ?? [];
|
|
90148
|
-
const recipeHash = computeRecipeHash({
|
|
90149
|
-
preNavRoute,
|
|
90150
|
-
preNavCmds,
|
|
90151
|
-
route: login.route,
|
|
90152
|
-
cmds: login.cmds,
|
|
90153
|
-
redirectRoute: login.redirectRoute,
|
|
90154
|
-
authProof
|
|
90155
|
-
});
|
|
90156
|
-
return {
|
|
90157
|
-
version: 1,
|
|
90158
|
-
recipeHash,
|
|
90159
|
-
preNavRoute,
|
|
90160
|
-
preNavCmds,
|
|
90161
|
-
route: login.route,
|
|
90162
|
-
cmds: login.cmds,
|
|
90163
|
-
redirectRoute: login.redirectRoute,
|
|
90164
|
-
authProof,
|
|
90165
|
-
replayPlan: login.replayPlan ?? [],
|
|
90166
|
-
verifiedAt
|
|
90167
|
-
};
|
|
90168
|
-
}
|
|
90169
|
-
function authProofLocator(proof) {
|
|
90170
|
-
if (proof.kind === "url") {
|
|
90171
|
-
throw new Error("authProofLocator: url proof has no element locator; callers must branch on proof.kind");
|
|
90172
|
-
}
|
|
90173
|
-
if (proof.kind === "testid") {
|
|
90174
|
-
return `page.getByTestId(${JSON.stringify(proof.testId)})`;
|
|
90175
|
-
}
|
|
90176
|
-
if (proof.name) {
|
|
90177
|
-
return `page.getByRole(${JSON.stringify(proof.role)}, { name: ${JSON.stringify(proof.name)} })`;
|
|
90178
|
-
}
|
|
90179
|
-
return `page.getByRole(${JSON.stringify(proof.role)}).first()`;
|
|
90180
|
-
}
|
|
90181
|
-
function stripToPathname(route2) {
|
|
90182
|
-
return route2.replace(/[?#].*$/, "").replace(/\/+$/, "") || "/";
|
|
90183
|
-
}
|
|
90184
|
-
function urlProofPredicateExpr(route2) {
|
|
90185
|
-
const p = stripToPathname(route2);
|
|
90186
|
-
if (p === "/")
|
|
90187
|
-
return `() => false`;
|
|
90188
|
-
const r = JSON.stringify(p);
|
|
90189
|
-
return `(u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/')`;
|
|
90190
|
-
}
|
|
90191
|
-
function renderAuthProofAssertion(proof) {
|
|
90192
|
-
if (proof.kind === "url") {
|
|
90193
|
-
return `await page.waitForURL(${urlProofPredicateExpr(proof.route)}, { timeout: 15000 });`;
|
|
90194
|
-
}
|
|
90195
|
-
return `await expect(${authProofLocator(proof)}).toBeVisible({ timeout: 15000 });`;
|
|
90196
|
-
}
|
|
90197
|
-
function normalizeCmd(cmd) {
|
|
90198
|
-
let c = cmd.trim();
|
|
90199
|
-
if (c.endsWith(";"))
|
|
90200
|
-
c = c.slice(0, -1);
|
|
90201
|
-
return ` ${c};`;
|
|
90202
|
-
}
|
|
90203
|
-
function renderAuthFn(recipe, opts) {
|
|
90204
|
-
const lines = [];
|
|
90205
|
-
lines.push(START_MARKER);
|
|
90206
|
-
lines.push(`async function __vqaLogin(page) {`);
|
|
90207
|
-
if (opts?.warmGuard) {
|
|
90208
|
-
const warmLanding = recipe.redirectRoute ?? (recipe.authProof.kind === "url" ? recipe.authProof.route : recipe.preNavRoute);
|
|
90209
|
-
const loginRoute = recipe.route ?? recipe.preNavRoute;
|
|
90210
|
-
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED}) {`);
|
|
90211
|
-
lines.push(` await page.goto(${JSON.stringify(warmLanding)}, { waitUntil: 'domcontentloaded' }).catch(() => {});`);
|
|
90212
|
-
if (recipe.authProof.kind === "url") {
|
|
90213
|
-
const pred = urlProofPredicateExpr(recipe.authProof.route);
|
|
90214
|
-
lines.push(` await page.waitForURL((u) => !(${pred})(u), { timeout: 2000 }).catch(() => {});`);
|
|
90215
|
-
lines.push(` if ((${pred})(new URL(page.url())) && !(await page.locator('input[type="password"]').first().isVisible().catch(() => false))) return;`);
|
|
90216
|
-
} else if (loginRoute) {
|
|
90217
|
-
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false) && new URL(page.url()).pathname !== new URL(${JSON.stringify(loginRoute)}, page.url()).pathname) return;`);
|
|
90218
|
-
} else {
|
|
90219
|
-
lines.push(` if (await ${authProofLocator(recipe.authProof)}.isVisible().catch(() => false)) return;`);
|
|
90220
|
-
}
|
|
90221
|
-
lines.push(` }`);
|
|
90222
|
-
}
|
|
90223
|
-
lines.push(` await page.goto(${JSON.stringify(recipe.preNavRoute)}, { waitUntil: 'domcontentloaded' });`);
|
|
90224
|
-
for (const cmd of recipe.preNavCmds)
|
|
90225
|
-
lines.push(normalizeCmd(cmd));
|
|
90226
|
-
if (recipe.preNavCmds.length === 0 && recipe.route && recipe.route !== recipe.preNavRoute) {
|
|
90227
|
-
lines.push(` await page.goto(${JSON.stringify(recipe.route)}, { waitUntil: 'domcontentloaded' });`);
|
|
90228
|
-
}
|
|
90229
|
-
for (const cmd of recipe.cmds)
|
|
90230
|
-
lines.push(normalizeCmd(cmd));
|
|
90231
|
-
lines.push(renderAuthUrlWaitLine(recipe.redirectRoute));
|
|
90232
|
-
lines.push(` ${renderAuthProofAssertion(recipe.authProof)}`);
|
|
90233
|
-
if (opts?.warmGuard) {
|
|
90234
|
-
lines.push(` if (${SHARED_AUTH_RUNTIME_ENABLED} && process.env.AUTH_FILE) await page.context().storageState({ path: process.env.AUTH_FILE });`);
|
|
90235
|
-
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) {`);
|
|
90236
|
-
lines.push(` try {`);
|
|
90237
|
-
lines.push(` const __vqaState = await page.context().storageState();`);
|
|
90238
|
-
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 });`);
|
|
90239
|
-
lines.push(` } catch {}`);
|
|
90240
|
-
lines.push(` }`);
|
|
90241
|
-
}
|
|
90242
|
-
lines.push(`}`);
|
|
90243
|
-
lines.push(END_MARKER);
|
|
90244
|
-
return lines.join("\n");
|
|
90245
|
-
}
|
|
90246
|
-
exports2.RESERVED_RUNNER_ENV_VARS = /* @__PURE__ */ new Set([
|
|
90247
|
-
"AUTH_FILE",
|
|
90248
|
-
"SHARED_AUTH_INJECT",
|
|
90249
|
-
"VALIDATE_RUN_ID",
|
|
90250
|
-
"VALIDATE_CLAIM_EPOCH",
|
|
90251
|
-
"VALIDATE_AUTH_CONFIG_HASH",
|
|
90252
|
-
"VALIDATE_TASK_ARN",
|
|
90253
|
-
"VALIDATE_RUNNER_AUTH",
|
|
90254
|
-
"VALIDATE_SERVER_URL"
|
|
90255
|
-
]);
|
|
90256
|
-
function isReservedRunnerEnvVar(key) {
|
|
90257
|
-
return exports2.RESERVED_RUNNER_ENV_VARS.has(key);
|
|
90258
|
-
}
|
|
90259
|
-
function absoluteRoute(route2, baseUrl) {
|
|
90260
|
-
try {
|
|
90261
|
-
return new URL(route2, baseUrl).href;
|
|
90262
|
-
} catch {
|
|
90263
|
-
return route2;
|
|
90264
|
-
}
|
|
90265
|
-
}
|
|
90266
|
-
function renderReplayClickArrow(step) {
|
|
90267
|
-
const loc = step.locator.trim();
|
|
90268
|
-
if (step.kind === "wait") {
|
|
90269
|
-
return `async (page) => {
|
|
90270
|
-
await ${loc}.waitFor({ state: 'visible', timeout: 15000 });
|
|
90271
|
-
}`;
|
|
90272
|
-
}
|
|
90273
|
-
if (step.kind === "press") {
|
|
90274
|
-
return `async (page) => {
|
|
90275
|
-
await ${loc}.press(${JSON.stringify(step.key ?? "Enter")});
|
|
90276
|
-
}`;
|
|
90277
|
-
}
|
|
90278
|
-
return `async (page) => {
|
|
90279
|
-
await ${loc}.click();
|
|
90280
|
-
}`;
|
|
90281
|
-
}
|
|
90282
|
-
function renderReplaySettleArrow(proof) {
|
|
90283
|
-
if (proof.kind === "url") {
|
|
90284
|
-
return `async (page) => {
|
|
90285
|
-
await page.waitForURL(${urlProofPredicateExpr(proof.route)}, { timeout: 15000 });
|
|
90286
|
-
}`;
|
|
90287
|
-
}
|
|
90288
|
-
return `async (page) => {
|
|
90289
|
-
await ${authProofLocator(proof)}.waitFor({ state: 'visible', timeout: 15000 });
|
|
90290
|
-
}`;
|
|
90291
|
-
}
|
|
90292
|
-
function renderAuthUrlWaitLine(redirectRoute) {
|
|
90293
|
-
const opts = `{ waitUntil: 'domcontentloaded', timeout: 8000 }`;
|
|
90294
|
-
const tail = `.catch(() => { /* URL drift \u2014 the auth-proof assertion below is the gate */ });`;
|
|
90295
|
-
const rr = redirectRoute ? stripToPathname(redirectRoute) : null;
|
|
90296
|
-
if (rr && rr !== "/") {
|
|
90297
|
-
const r = JSON.stringify(rr);
|
|
90298
|
-
return ` await page.waitForURL((u) => u.pathname === ${r} || u.pathname.startsWith(${r} + '/'), ${opts})${tail}`;
|
|
90299
|
-
}
|
|
90300
|
-
if (rr === "/") {
|
|
90301
|
-
return ` await page.waitForURL((u) => !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
|
|
90302
|
-
}
|
|
90303
|
-
return ` await page.waitForURL((u) => u.pathname !== '/' && !/\\/(login|sign-?in)/i.test(u.pathname), ${opts})${tail}`;
|
|
90304
|
-
}
|
|
90305
|
-
function locatorChainOf(cmd) {
|
|
90306
|
-
const m = cmd.match(/\bpage\.[\s\S]*?(?=\.(?:fill|click|check|press|selectOption|setChecked|waitFor)\s*\()/);
|
|
90307
|
-
return m ? m[0].trim() : null;
|
|
90308
|
-
}
|
|
90309
|
-
function parseRoleName(locator) {
|
|
90310
|
-
const role = locator.match(/getByRole\(\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
90311
|
-
const name = locator.match(/getByRole\([^)]*name:\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByLabel\(\s*['"`]([^'"`]+)['"`]/)?.[1] ?? locator.match(/getByPlaceholder\(\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
90312
|
-
return { role, name };
|
|
90313
|
-
}
|
|
90314
|
-
function credentialKeyOf(cmd) {
|
|
90315
|
-
return cmd.match(/\.fill\(\s*process\.env\.([A-Za-z_$][\w$]*)!?\s*\)/)?.[1] ?? null;
|
|
90316
|
-
}
|
|
90317
|
-
function commandToReplayStep(cmd) {
|
|
90318
|
-
const chain = locatorChainOf(cmd);
|
|
90319
|
-
if (!chain)
|
|
90320
|
-
return null;
|
|
90321
|
-
const credKey = credentialKeyOf(cmd);
|
|
90322
|
-
if (cmd.includes(".fill(")) {
|
|
90323
|
-
if (!credKey)
|
|
90324
|
-
return null;
|
|
90325
|
-
const { role, name } = parseRoleName(chain);
|
|
90326
|
-
return { kind: "fill", credentialKey: credKey, locator: chain, role, name, element: name ?? role ?? "credential field" };
|
|
90327
|
-
}
|
|
90328
|
-
if (/\.waitFor\s*\(/.test(cmd))
|
|
90329
|
-
return { kind: "wait", locator: chain };
|
|
90330
|
-
const pressKey = cmd.match(/\.press\(\s*['"`]([^'"`]+)['"`]\s*\)/)?.[1];
|
|
90331
|
-
if (pressKey)
|
|
90332
|
-
return { kind: "press", locator: chain, key: pressKey };
|
|
90333
|
-
if (/\.(?:click|check|setChecked|selectOption)\s*\(/.test(cmd))
|
|
90334
|
-
return { kind: "click", locator: chain };
|
|
90335
|
-
return null;
|
|
90336
|
-
}
|
|
90337
|
-
function buildReplayPlan(login) {
|
|
90338
|
-
const plan = [];
|
|
90339
|
-
const preNavRoute = login.preNavRoute ?? "/";
|
|
90340
|
-
plan.push({ kind: "nav", route: preNavRoute });
|
|
90341
|
-
for (const c of login.preNavCmds ?? []) {
|
|
90342
|
-
const step = commandToReplayStep(c);
|
|
90343
|
-
if (step)
|
|
90344
|
-
plan.push(step);
|
|
90345
|
-
}
|
|
90346
|
-
if ((login.preNavCmds ?? []).length === 0 && login.route && login.route !== preNavRoute) {
|
|
90347
|
-
plan.push({ kind: "nav", route: login.route });
|
|
90348
|
-
}
|
|
90349
|
-
for (const c of login.cmds) {
|
|
90350
|
-
const step = commandToReplayStep(c);
|
|
90351
|
-
if (step)
|
|
90352
|
-
plan.push(step);
|
|
90353
|
-
}
|
|
90354
|
-
return plan;
|
|
90355
|
-
}
|
|
90356
|
-
function extractAuthRegion(code) {
|
|
90357
|
-
const m = code.match(new RegExp(AUTH_REGION_RE.source));
|
|
90358
|
-
return m ? m[0] : null;
|
|
90359
|
-
}
|
|
90360
|
-
function countAuthRegions(code) {
|
|
90361
|
-
const m = code.match(AUTH_REGION_RE);
|
|
90362
|
-
return m ? m.length : 0;
|
|
90363
|
-
}
|
|
90364
|
-
function stripAuthRegions(code) {
|
|
90365
|
-
return code.replace(AUTH_REGION_RE, "");
|
|
90366
|
-
}
|
|
90367
|
-
var PW_IMPORT_RE = /import\s*\{([^}]*)\}\s*from\s*['"]@playwright\/test['"]\s*;?/;
|
|
90368
|
-
function ensureExpectImported(code) {
|
|
90369
|
-
const m = code.match(PW_IMPORT_RE);
|
|
90370
|
-
if (!m)
|
|
90371
|
-
return code;
|
|
90372
|
-
const names = m[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
90373
|
-
if (names.includes("expect"))
|
|
90374
|
-
return code;
|
|
90375
|
-
const next = [...names, "expect"];
|
|
90376
|
-
return code.replace(PW_IMPORT_RE, `import { ${next.join(", ")} } from '@playwright/test';`);
|
|
90377
|
-
}
|
|
90378
|
-
function insertAfterPwImport(code, block) {
|
|
90379
|
-
const m = code.match(PW_IMPORT_RE);
|
|
90380
|
-
if (!m || m.index === void 0) {
|
|
90381
|
-
return `${block}
|
|
90382
|
-
|
|
90383
|
-
${code.replace(/^\r?\n+/, "")}`;
|
|
90384
|
-
}
|
|
90385
|
-
const end = m.index + m[0].length;
|
|
90386
|
-
const head = code.slice(0, end);
|
|
90387
|
-
const tail = code.slice(end).replace(/^\r?\n+/, "");
|
|
90388
|
-
return `${head}
|
|
90389
|
-
|
|
90390
|
-
${block}
|
|
90391
|
-
|
|
90392
|
-
${tail}`;
|
|
90393
|
-
}
|
|
90394
|
-
var TEST_OPENER_RE = /test(?:\.(?:only|skip|fixme))?\s*\(\s*(['"`])(?:[^\\]|\\.)*?\1\s*,\s*(?:\{[\s\S]*?\}\s*,\s*)?async\s*\(\s*\{[^}]*\}[^)]*\)\s*=>\s*\{/;
|
|
90395
|
-
var LOGIN_CALL_RE = /await\s+__vqaLogin\s*\(\s*page\s*\)/;
|
|
90396
|
-
function ensureLoginCall(code) {
|
|
90397
|
-
if (code.match(new RegExp(TEST_OPENER_RE.source, "g")) === null)
|
|
90398
|
-
return code;
|
|
90399
|
-
let result = code;
|
|
90400
|
-
if ((0, discovery_lint_flags_js_1.isVqaLoginDedupeEnabled)()) {
|
|
90401
|
-
result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
|
|
90402
|
-
}
|
|
90403
|
-
const matches = [...result.matchAll(new RegExp(TEST_OPENER_RE.source, "g"))];
|
|
90404
|
-
for (let i = matches.length - 1; i >= 0; i--) {
|
|
90405
|
-
const m = matches[i];
|
|
90406
|
-
if (m.index === void 0)
|
|
90407
|
-
continue;
|
|
90408
|
-
const insertAt = m.index + m[0].length;
|
|
90409
|
-
const following = result.slice(insertAt, insertAt + 60);
|
|
90410
|
-
if (/^\s*await\s+__vqaLogin\s*\(\s*page\s*\)/.test(following))
|
|
90411
|
-
continue;
|
|
90412
|
-
result = `${result.slice(0, insertAt)}
|
|
90413
|
-
await __vqaLogin(page);${result.slice(insertAt)}`;
|
|
90414
|
-
}
|
|
90415
|
-
return result;
|
|
90416
|
-
}
|
|
90417
|
-
function upsertAuthBlock(code, ctx) {
|
|
90418
|
-
if (ctx.mode === "generate") {
|
|
90419
|
-
if (!ctx.recipe || !ctx.isAuthNeeded)
|
|
90420
|
-
return code;
|
|
90421
|
-
let result = stripAuthRegions(code);
|
|
90422
|
-
result = result.replace(/^[ \t]*await[ \t]+__vqaLogin[ \t]*\([ \t]*page[ \t]*\)[ \t]*;?[ \t]*\r?\n/gm, "");
|
|
90423
|
-
result = ensureExpectImported(result);
|
|
90424
|
-
const warmGuard = (0, shared_1.isSharedAuthInjectEnabled)();
|
|
90425
|
-
result = insertAfterPwImport(result, renderAuthFn(ctx.recipe, { warmGuard }));
|
|
90426
|
-
result = ensureLoginCall(result);
|
|
90427
|
-
const n = countAuthRegions(result);
|
|
90428
|
-
if (n !== 1) {
|
|
90429
|
-
throw new Error(`upsertAuthBlock(generate): expected exactly 1 auth region after stamp, got ${n}`);
|
|
90430
|
-
}
|
|
90431
|
-
if (!LOGIN_CALL_RE.test(result)) {
|
|
90432
|
-
throw new Error("upsertAuthBlock(generate): stamped __vqaLogin but found no test() opener to inject the call");
|
|
90433
|
-
}
|
|
90434
|
-
return result;
|
|
90435
|
-
}
|
|
90436
|
-
try {
|
|
90437
|
-
if (!ctx.preservedAuthRegion)
|
|
90438
|
-
return code;
|
|
90439
|
-
let result = ensureExpectImported(code);
|
|
90440
|
-
if (countAuthRegions(result) >= 1) {
|
|
90441
|
-
result = result.replace(AUTH_REGION_RE, "");
|
|
90442
|
-
result = insertAfterPwImport(result, ctx.preservedAuthRegion);
|
|
90443
|
-
} else {
|
|
90444
|
-
result = insertAfterPwImport(result, ctx.preservedAuthRegion);
|
|
90445
|
-
}
|
|
90446
|
-
result = ensureLoginCall(result);
|
|
90447
|
-
if (countAuthRegions(result) !== 1) {
|
|
90448
|
-
throw new Error("heal re-stamp produced non-unique auth region");
|
|
90449
|
-
}
|
|
90450
|
-
return result;
|
|
90451
|
-
} catch (err) {
|
|
90452
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
90453
|
-
ctx.onSignal?.({ type: "auth-stamp-degraded", reason });
|
|
90454
|
-
return code;
|
|
90455
|
-
}
|
|
90456
|
-
}
|
|
90457
|
-
function buildHealAuthStamp(originalCode, failureError, logs2) {
|
|
90458
|
-
const region = extractAuthRegion(originalCode);
|
|
90459
|
-
const loginFailed = !!failureError && /__vqaLogin/.test(failureError);
|
|
90460
|
-
return {
|
|
90461
|
-
mode: "heal",
|
|
90462
|
-
preservedAuthRegion: region && !loginFailed ? region : null,
|
|
90463
|
-
onSignal: logs2 ? (s) => logs2.push(`\u26A0 auth-stamp ${s.type}: ${s.reason}`) : void 0
|
|
90464
|
-
};
|
|
90465
|
-
}
|
|
90466
|
-
}
|
|
90467
|
-
});
|
|
90468
|
-
|
|
90469
90911
|
// ../runner-core/dist/services/auth-replay.js
|
|
90470
90912
|
var require_auth_replay = __commonJS({
|
|
90471
90913
|
"../runner-core/dist/services/auth-replay.js"(exports2) {
|
|
@@ -94342,9 +94784,41 @@ var require_discover_generator = __commonJS({
|
|
|
94342
94784
|
var reveal_diff_js_1 = require_reveal_diff();
|
|
94343
94785
|
var anchor_harvester_js_1 = require_anchor_harvester();
|
|
94344
94786
|
var auth_recipe_js_1 = require_auth_recipe();
|
|
94787
|
+
var inbox_stamp_js_1 = require_inbox_stamp();
|
|
94345
94788
|
var auth_replay_js_1 = require_auth_replay();
|
|
94346
94789
|
var agentic_discovery_prompts_js_1 = require_agentic_discovery_prompts();
|
|
94347
94790
|
var discovery_lint_flags_js_1 = require_discovery_lint_flags();
|
|
94791
|
+
var inbox_tools_js_1 = require_inbox_tools();
|
|
94792
|
+
var verification_inbox_js_1 = require_verification_inbox();
|
|
94793
|
+
var test_inbox_broker_js_1 = require_test_inbox_broker();
|
|
94794
|
+
var REGISTER_SCENARIO_RE = /\b(register|registration|sign[\s-]?up|create[\s-]?(a[n]?[\s-]?)?(new[\s-]?)?account|verify[\s-]?(your[\s-]?)?email|email[\s-]?verification|confirm[\s-]?(your[\s-]?)?(email|account)|activate[\s-]?(your[\s-]?)?account|new[\s-]?account)\b/i;
|
|
94795
|
+
function scenarioLooksLikeRegistration(scenario) {
|
|
94796
|
+
const hay = `${scenario.name ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")}`;
|
|
94797
|
+
return REGISTER_SCENARIO_RE.test(hay);
|
|
94798
|
+
}
|
|
94799
|
+
function buildEmailVerifyGenBlock(genEmail, genPassword) {
|
|
94800
|
+
return `
|
|
94801
|
+
|
|
94802
|
+
## EMAIL VERIFICATION TEST \u2014 register a fresh account and verify it (LIVE, then EMIT)
|
|
94803
|
+
This is a signup / email-verification scenario and this runner CAN receive verification email. Prove the full flow live, then emit a test that re-does it on every run.
|
|
94804
|
+
|
|
94805
|
+
LIVE (to prove the flow while generating):
|
|
94806
|
+
- Register on the app's signup form using this throwaway account:
|
|
94807
|
+
- email: \`${genEmail}\`
|
|
94808
|
+
- password: \`${genPassword}\`
|
|
94809
|
+
Type them with browser_type and submit the form ONCE.
|
|
94810
|
+
- If the app then asks for an emailed code or a "verify your email" link, call \`wait_for_verification\` (poll it again if the first call returns found:false \u2014 mail can take 30\u201390s). If it returns a \`code\`, type it into the verification field. If it returns a \`link\`, use \`open_verification_link\`.
|
|
94811
|
+
- Reach the verified / signed-in state and confirm it with a browser_verify_* call, so your final assertion is proven.
|
|
94812
|
+
|
|
94813
|
+
EMIT (the code you put in finish_generate) \u2014 do NOT hardcode the throwaway values above:
|
|
94814
|
+
- Use \`process.env.${test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV}!\` for the email and \`process.env.${test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV}!\` for the password. The runner mints a FRESH real account for each execution and injects these \u2014 a hardcoded email would collide with "already registered" on re-runs.
|
|
94815
|
+
- Read the verification email at runtime with the injected helper (it is provided for you \u2014 do NOT define it, do NOT import it):
|
|
94816
|
+
- code flow: \`const code = await __vqaWaitForEmail('code');\` then type \`code\` into the verification field.
|
|
94817
|
+
- link flow: \`const link = await __vqaWaitForEmail('link');\` then \`await page.goto(link);\`.
|
|
94818
|
+
- if unsure: \`const email = await __vqaWaitForEmail();\` returns \`{ code, link, subject, from }\`.
|
|
94819
|
+
- Tag the test \`@email-verify\`. Emit EXACTLY ONE test() in this file (one registration per run).
|
|
94820
|
+
- End with a DURABLE assertion of the post-verification signed-in state (e.g. a Logout control, an account/dashboard landmark) in your OWN test body \u2014 the helper call is not itself a proof.`;
|
|
94821
|
+
}
|
|
94348
94822
|
exports2.BLOCKED_MCP_TOOLS_SCENARIO_GEN = /* @__PURE__ */ new Set([
|
|
94349
94823
|
"browser_install",
|
|
94350
94824
|
"browser_close",
|
|
@@ -95531,6 +96005,11 @@ ${testOpen}`;
|
|
|
95531
96005
|
if (options?.authStamp) {
|
|
95532
96006
|
result = (0, auth_recipe_js_1.upsertAuthBlock)(result, options.authStamp);
|
|
95533
96007
|
}
|
|
96008
|
+
try {
|
|
96009
|
+
result = (0, inbox_stamp_js_1.upsertInboxBlock)(result);
|
|
96010
|
+
} catch {
|
|
96011
|
+
result = (0, inbox_stamp_js_1.stripInboxRegions)(result);
|
|
96012
|
+
}
|
|
95534
96013
|
return result;
|
|
95535
96014
|
}
|
|
95536
96015
|
function ensureDiscoveredTag(tags) {
|
|
@@ -95617,6 +96096,16 @@ ${testOpen}`;
|
|
|
95617
96096
|
logs2?.push(` \u2717 Rejected "${t.name}": JS parse error \u2014 ${msg}`);
|
|
95618
96097
|
return false;
|
|
95619
96098
|
}
|
|
96099
|
+
{
|
|
96100
|
+
const modelBody = (0, inbox_stamp_js_1.stripInboxRegions)(code);
|
|
96101
|
+
if (inbox_stamp_js_1.INBOX_HELPER_TOKEN_RE.test(modelBody)) {
|
|
96102
|
+
const openerCount = (stripStringsAndComments(modelBody).match(/\btest(?:\.(?:only|skip|fixme))?\s*\(/g) ?? []).length;
|
|
96103
|
+
if (openerCount > 1) {
|
|
96104
|
+
logs2?.push(` \u2717 Rejected "${t.name}": ${openerCount} test() blocks in a register-and-verify spec \u2014 the per-run registration email supports exactly one registration; emit ONE test() per spec`);
|
|
96105
|
+
return false;
|
|
96106
|
+
}
|
|
96107
|
+
}
|
|
96108
|
+
}
|
|
95620
96109
|
const codeForArgOrderScan = stripStringsAndComments(code);
|
|
95621
96110
|
if (/test\s*\(\s*,\s*(?:async\s*)?(?:\([^)]*\)|function\s*\([^)]*\))\s*(?:=>)?\s*\{[\s\S]*?\}\s*,\s*\{\s*(?:tags?|annotations)\s*:/.test(codeForArgOrderScan)) {
|
|
95622
96111
|
logs2?.push(` \u2717 Rejected "${t.name}": test() arg order is wrong \u2014 Playwright signature is test(name, options, fn), AI emitted test(name, fn, options)`);
|
|
@@ -96437,6 +96926,23 @@ ${testOpen}`;
|
|
|
96437
96926
|
return { offendingLines: offending };
|
|
96438
96927
|
}
|
|
96439
96928
|
var ACTION_METHODS = ["click", "dblclick", "tap", "fill", "type", "pressSequentially", "press", "selectOption", "hover", "check", "uncheck", "setInputFiles", "dragTo", "focus", "goto", "waitForURL"];
|
|
96929
|
+
var DYNAMIC_VERIFICATION_ARG = "<verification-dynamic>";
|
|
96930
|
+
function collectVerificationHelperVars(code) {
|
|
96931
|
+
const vars = /* @__PURE__ */ new Set();
|
|
96932
|
+
const assignRe = /(?:(?:const|let|var)\s+)?([A-Za-z_$][\w$]*)\s*=\s*await\s+__vqaWaitForEmail\s*\(/g;
|
|
96933
|
+
let m;
|
|
96934
|
+
while ((m = assignRe.exec(code)) !== null)
|
|
96935
|
+
vars.add(m[1]);
|
|
96936
|
+
const destructRe = /(?:const|let|var)\s*\{([^}]*)\}\s*=\s*await\s+__vqaWaitForEmail\s*\(/g;
|
|
96937
|
+
while ((m = destructRe.exec(code)) !== null) {
|
|
96938
|
+
for (const part of m[1].split(",")) {
|
|
96939
|
+
const name = part.split(":").pop()?.trim().replace(/\s.*$/, "");
|
|
96940
|
+
if (name && /^[A-Za-z_$][\w$]*$/.test(name))
|
|
96941
|
+
vars.add(name);
|
|
96942
|
+
}
|
|
96943
|
+
}
|
|
96944
|
+
return vars;
|
|
96945
|
+
}
|
|
96440
96946
|
function extractStringArg(text) {
|
|
96441
96947
|
const m = text.match(/^\s*(['"`])((?:\\.|(?!\1).)*)\1/);
|
|
96442
96948
|
if (!m)
|
|
@@ -96706,6 +97212,8 @@ ${testOpen}`;
|
|
|
96706
97212
|
if (!locatorsMatch(ledger.locator, submitted.locator))
|
|
96707
97213
|
return false;
|
|
96708
97214
|
if (ledger.method === "fill" || ledger.method === "type") {
|
|
97215
|
+
if (submitted.arg === DYNAMIC_VERIFICATION_ARG)
|
|
97216
|
+
return true;
|
|
96709
97217
|
if (ledger.arg === "<env>" || ledger.arg === "<test-value>") {
|
|
96710
97218
|
return submitted.arg === "<env>" || submitted.arg === "<test-value>";
|
|
96711
97219
|
}
|
|
@@ -96800,6 +97308,25 @@ ${testOpen}`;
|
|
|
96800
97308
|
}
|
|
96801
97309
|
}
|
|
96802
97310
|
const submittedSigs = extractActionSignatures(playwrightCode, credentials);
|
|
97311
|
+
const helperVars = collectVerificationHelperVars(playwrightCode);
|
|
97312
|
+
if (helperVars.size > 0) {
|
|
97313
|
+
const usesHelperVar = (text) => {
|
|
97314
|
+
for (const v of helperVars) {
|
|
97315
|
+
if (new RegExp(`\\b${v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(text))
|
|
97316
|
+
return true;
|
|
97317
|
+
}
|
|
97318
|
+
return false;
|
|
97319
|
+
};
|
|
97320
|
+
for (const sub of submittedSigs) {
|
|
97321
|
+
if ((sub.method === "fill" || sub.method === "type") && sub.arg && usesHelperVar(sub.arg)) {
|
|
97322
|
+
sub.arg = DYNAMIC_VERIFICATION_ARG;
|
|
97323
|
+
} else if (sub.method === "goto") {
|
|
97324
|
+
const gotoArg = sub.raw.match(/\.goto\s*\(\s*([^),]+)/);
|
|
97325
|
+
if (gotoArg && usesHelperVar(gotoArg[1]))
|
|
97326
|
+
sub.arg = DYNAMIC_VERIFICATION_ARG;
|
|
97327
|
+
}
|
|
97328
|
+
}
|
|
97329
|
+
}
|
|
96803
97330
|
const offending = [];
|
|
96804
97331
|
let firstGotoConsumed = false;
|
|
96805
97332
|
let cursor = 0;
|
|
@@ -96807,6 +97334,8 @@ ${testOpen}`;
|
|
|
96807
97334
|
for (const sub of submittedSigs) {
|
|
96808
97335
|
if (sub.method === "waitForURL")
|
|
96809
97336
|
continue;
|
|
97337
|
+
if (sub.method === "goto" && sub.arg === DYNAMIC_VERIFICATION_ARG)
|
|
97338
|
+
continue;
|
|
96810
97339
|
if (sub.method === "goto" && !firstGotoConsumed) {
|
|
96811
97340
|
firstGotoConsumed = true;
|
|
96812
97341
|
if (expectedEntry !== null && sub.arg !== expectedEntry) {
|
|
@@ -97132,7 +97661,7 @@ expect(secondTitle).toBeTruthy();
|
|
|
97132
97661
|
${tailBlock}`;
|
|
97133
97662
|
}
|
|
97134
97663
|
function evaluateAmbiguityGate(finalCode, alreadyAttempted, hasRevisionBudget) {
|
|
97135
|
-
const findings = (0, agentic_discovery_prompts_js_1.scanAmbiguousLocators)((0, auth_recipe_js_1.stripAuthRegions)(finalCode));
|
|
97664
|
+
const findings = (0, agentic_discovery_prompts_js_1.scanAmbiguousLocators)((0, auth_recipe_js_1.stripAuthRegions)((0, inbox_stamp_js_1.stripInboxRegions)(finalCode)));
|
|
97136
97665
|
return { fire: findings.length > 0 && !alreadyAttempted && hasRevisionBudget, findings };
|
|
97137
97666
|
}
|
|
97138
97667
|
async function generateTestFromScenario(mcpClient, scenario, siteMap, baseUrl, credentials, aiClient, agentModel, logs2, onAICall, opts) {
|
|
@@ -97156,6 +97685,7 @@ ${tailBlock}`;
|
|
|
97156
97685
|
}
|
|
97157
97686
|
}
|
|
97158
97687
|
}
|
|
97688
|
+
let genInboxReader = null;
|
|
97159
97689
|
try {
|
|
97160
97690
|
const startTime = Date.now();
|
|
97161
97691
|
const groupId = `scenario-gen-${(0, crypto_1.randomUUID)().slice(0, 8)}`;
|
|
@@ -97206,12 +97736,28 @@ ${tailBlock}`;
|
|
|
97206
97736
|
});
|
|
97207
97737
|
const baseSkeletonHadRegression = tags.includes("@regression");
|
|
97208
97738
|
const hasVerifiedRecipe = !!opts?.recipe && tags.includes("@needs-auth");
|
|
97209
|
-
const
|
|
97739
|
+
const projectHasRegisterIntent = credentials?.DISCOVERY_SHOULD_REGISTER === "true";
|
|
97740
|
+
const inboxGenConfig = (0, discovery_lint_flags_js_1.isTestgenEmailVerifyEnabled)() && projectHasRegisterIntent && !hasVerifiedRecipe && scenarioLooksLikeRegistration(scenario) ? (0, verification_inbox_js_1.loadVerificationInboxConfig)() : null;
|
|
97741
|
+
let genInboxState = null;
|
|
97742
|
+
let genInboxCredentials = credentials;
|
|
97743
|
+
let genInboxSecret = null;
|
|
97744
|
+
if (inboxGenConfig) {
|
|
97745
|
+
const genTag = `vqag${(0, crypto_1.createHash)("sha256").update(`testgen|${(0, verification_inbox_js_1.seedToHostKey)(baseUrl)}|${groupId}`).digest("hex").slice(0, 10)}`;
|
|
97746
|
+
const genEmail = (0, verification_inbox_js_1.applyPlusTag)(inboxGenConfig.address, genTag);
|
|
97747
|
+
const genPassword = (0, verification_inbox_js_1.deriveRegistrationPassword)(genEmail);
|
|
97748
|
+
genInboxReader = new verification_inbox_js_1.VerificationInboxReader(inboxGenConfig);
|
|
97749
|
+
genInboxState = (0, inbox_tools_js_1.createInboxRunState)(inboxGenConfig, { tag: genTag, passwordKey: test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV });
|
|
97750
|
+
genInboxCredentials = { ...credentials, [test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV]: genEmail, [test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV]: genPassword };
|
|
97751
|
+
genInboxSecret = genPassword;
|
|
97752
|
+
openaiTools.push(...inbox_tools_js_1.INBOX_TOOLS);
|
|
97753
|
+
logs2.push(` \u{1F4EC} Email-verification generation ON for "${scenario.name}" \u2014 registering throwaway ${genEmail}`);
|
|
97754
|
+
}
|
|
97755
|
+
const systemPrompt = buildScenarioSystemPrompt(scenario, baseUrl, credentials, tags, siteMap.pages, { appBrief: opts?.appBrief, availableVariableKeys: opts?.availableVariableKeys, hasVerifiedRecipe, knowledgeContext: opts?.knowledgeContext }) + (genInboxState ? buildEmailVerifyGenBlock(genInboxCredentials[test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV], genInboxCredentials[test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV]) : "");
|
|
97210
97756
|
const messages = [
|
|
97211
97757
|
{ role: "system", content: systemPrompt },
|
|
97212
97758
|
{
|
|
97213
97759
|
role: "user",
|
|
97214
|
-
content: hasVerifiedRecipe ? `Start generating the test for scenario: "${scenario.name}". You are ALREADY logged in \u2014 \`await __vqaLogin(page);\` is injected as the first line of your test body, so do NOT author any login steps. Begin from the authenticated app and reach ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"} via visible navigation or a direct goto. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : tags.includes("@needs-auth") ? `Start generating the test for scenario: "${scenario.name}". Begin at ${baseUrl}${scenario.entryRoute}. Complete the AUTH SETUP from the app's own visible flow (do NOT deep-link directly to auth unless the start route exposes no usable entry), then continue to ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"}. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : `Start generating the test for scenario: "${scenario.name}". Begin by navigating to ${baseUrl}${scenario.entryRoute} using browser_navigate. From there, use the app's own navigation to reach the scenario flow. Do NOT deep-link directly to ${baseUrl}${scenario.targetPages[0] || "/"} unless the start route immediately redirects there or no visible navigation path exists. Then execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.`
|
|
97760
|
+
content: genInboxState ? `Start generating the register-and-verify test for scenario: "${scenario.name}". Begin at ${baseUrl}${scenario.entryRoute}. Follow the "EMAIL VERIFICATION TEST" section: register the throwaway account LIVE, complete email verification via wait_for_verification, reach the signed-in state and verify it \u2014 then emit a test that uses process.env.${test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV}!/process.env.${test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV}! and __vqaWaitForEmail(...) as instructed.` : hasVerifiedRecipe ? `Start generating the test for scenario: "${scenario.name}". You are ALREADY logged in \u2014 \`await __vqaLogin(page);\` is injected as the first line of your test body, so do NOT author any login steps. Begin from the authenticated app and reach ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"} via visible navigation or a direct goto. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : tags.includes("@needs-auth") ? `Start generating the test for scenario: "${scenario.name}". Begin at ${baseUrl}${scenario.entryRoute}. Complete the AUTH SETUP from the app's own visible flow (do NOT deep-link directly to auth unless the start route exposes no usable entry), then continue to ${baseUrl}${findFirstNonAuthRoute(scenario.targetPages) || scenario.targetPages[0] || "/"}. Execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.` : `Start generating the test for scenario: "${scenario.name}". Begin by navigating to ${baseUrl}${scenario.entryRoute} using browser_navigate. From there, use the app's own navigation to reach the scenario flow. Do NOT deep-link directly to ${baseUrl}${scenario.targetPages[0] || "/"} unless the start route immediately redirects there or no visible navigation path exists. Then execute each step from the scenario plan, verifying with browser_verify_* tools at transition points.`
|
|
97215
97761
|
}
|
|
97216
97762
|
];
|
|
97217
97763
|
let iteration = 0;
|
|
@@ -97339,6 +97885,15 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
97339
97885
|
const iterStart = Date.now();
|
|
97340
97886
|
const requestMessageCount = messages.length;
|
|
97341
97887
|
const requestMessages = messages.slice();
|
|
97888
|
+
const scrubAudit = (payload) => {
|
|
97889
|
+
if (!genInboxSecret)
|
|
97890
|
+
return payload;
|
|
97891
|
+
try {
|
|
97892
|
+
return JSON.parse(JSON.stringify(payload).split(genInboxSecret).join("[REDACTED:VQA_TEST_ACCOUNT_PASSWORD]"));
|
|
97893
|
+
} catch {
|
|
97894
|
+
return payload;
|
|
97895
|
+
}
|
|
97896
|
+
};
|
|
97342
97897
|
let response;
|
|
97343
97898
|
try {
|
|
97344
97899
|
response = await (0, ai_retry_js_1.resilientAICall)((signal, arm) => (0, ai_queue_js_1.queuedAICall)(() => {
|
|
@@ -97380,7 +97935,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
97380
97935
|
messages.push(assistantMessage);
|
|
97381
97936
|
if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
|
|
97382
97937
|
try {
|
|
97383
|
-
onAICall?.({
|
|
97938
|
+
onAICall?.(scrubAudit({
|
|
97384
97939
|
iteration,
|
|
97385
97940
|
groupId,
|
|
97386
97941
|
callType: "scenarioGenerate",
|
|
@@ -97394,7 +97949,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
97394
97949
|
requestMessageCount,
|
|
97395
97950
|
requestMessages,
|
|
97396
97951
|
ledger: actionLedger.slice()
|
|
97397
|
-
});
|
|
97952
|
+
}));
|
|
97398
97953
|
} catch {
|
|
97399
97954
|
}
|
|
97400
97955
|
logs2.push(` No tool calls \u2014 agent stopped`);
|
|
@@ -97421,7 +97976,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
97421
97976
|
const suspectedBugDeclared = toolArgs.suspectedBug === true;
|
|
97422
97977
|
const bugDescription = String(toolArgs.bugDescription || "").slice(0, 500).replace(/[\u0000-\u001F\u007F]+/g, " ").replace(/\s+/g, " ").trim();
|
|
97423
97978
|
const testObj = { name, description, playwrightCode: code, tags };
|
|
97424
|
-
const validated = validateTests([testObj], logs2, { sensitiveEnvMap:
|
|
97979
|
+
const validated = validateTests([testObj], logs2, { sensitiveEnvMap: genInboxCredentials, authRecipe: opts?.recipe ?? null, basePath: gotoBasePathFromBaseUrl(baseUrl) });
|
|
97425
97980
|
if (validated.length > 0) {
|
|
97426
97981
|
if (actionLedger.length > lastRenderedLedgerEntryCount && actionLedger.length > 0) {
|
|
97427
97982
|
const renderedLedger = renderSessionRecording(actionLedger, credentials);
|
|
@@ -97473,7 +98028,7 @@ Do NOT add steps you did not execute. Do NOT skip steps from the recording.${led
|
|
|
97473
98028
|
});
|
|
97474
98029
|
continue;
|
|
97475
98030
|
}
|
|
97476
|
-
const actionCheck = validateFinalActionFidelity(code, actionLedger,
|
|
98031
|
+
const actionCheck = validateFinalActionFidelity(code, actionLedger, genInboxCredentials, { entryRoute: scenario.entryRoute });
|
|
97477
98032
|
if (actionCheck.offendingActions.length > 0 && !actionRevisionAttempted) {
|
|
97478
98033
|
actionRevisionAttempted = true;
|
|
97479
98034
|
const renderedLedger = actionLedger.length > 0 ? renderSessionRecording(actionLedger, credentials) : "";
|
|
@@ -97786,6 +98341,15 @@ Do NOT assert that the button/tab/heading you interacted with is merely visible
|
|
|
97786
98341
|
});
|
|
97787
98342
|
continue;
|
|
97788
98343
|
}
|
|
98344
|
+
if (genInboxReader && genInboxState && inbox_tools_js_1.INBOX_TOOL_NAMES.has(toolName)) {
|
|
98345
|
+
const inboxResult = await (0, inbox_tools_js_1.dispatchInboxTool)(toolName, toolArgs, {
|
|
98346
|
+
reader: genInboxReader,
|
|
98347
|
+
state: genInboxState,
|
|
98348
|
+
mcpClient
|
|
98349
|
+
});
|
|
98350
|
+
toolResults.push({ role: "tool", tool_call_id: toolCall.id, content: inboxResult.content });
|
|
98351
|
+
continue;
|
|
98352
|
+
}
|
|
97789
98353
|
let toolResultContent = "";
|
|
97790
98354
|
try {
|
|
97791
98355
|
const mcpResult = await mcpClient.callTool({ name: toolName, arguments: toolArgs });
|
|
@@ -97948,7 +98512,7 @@ WARNING: index-based locator (.nth/.first) \u2014 order-dependent and fragile on
|
|
|
97948
98512
|
tool: assistantMessage.tool_calls?.find((tc) => tc.id === tr.tool_call_id)?.function.name ?? "unknown",
|
|
97949
98513
|
result: typeof tr.content === "string" ? tr.content.slice(0, 5e4) : String(tr.content).slice(0, 5e4)
|
|
97950
98514
|
}));
|
|
97951
|
-
onAICall?.({
|
|
98515
|
+
onAICall?.(scrubAudit({
|
|
97952
98516
|
iteration,
|
|
97953
98517
|
groupId,
|
|
97954
98518
|
callType: "scenarioGenerate",
|
|
@@ -97964,7 +98528,7 @@ WARNING: index-based locator (.nth/.first) \u2014 order-dependent and fragile on
|
|
|
97964
98528
|
requestMessageCount,
|
|
97965
98529
|
requestMessages,
|
|
97966
98530
|
ledger: actionLedger.slice()
|
|
97967
|
-
});
|
|
98531
|
+
}));
|
|
97968
98532
|
} catch {
|
|
97969
98533
|
}
|
|
97970
98534
|
messages.push(...toolResults);
|
|
@@ -97972,8 +98536,12 @@ WARNING: index-based locator (.nth/.first) \u2014 order-dependent and fragile on
|
|
|
97972
98536
|
if (!result) {
|
|
97973
98537
|
logs2.push(` Scenario generation did not produce a test after ${iteration} iterations`);
|
|
97974
98538
|
}
|
|
98539
|
+
await genInboxReader?.close().catch(() => {
|
|
98540
|
+
});
|
|
97975
98541
|
return result;
|
|
97976
98542
|
} catch (err) {
|
|
98543
|
+
await genInboxReader?.close().catch(() => {
|
|
98544
|
+
});
|
|
97977
98545
|
const msg = err instanceof Error ? err.message : String(err);
|
|
97978
98546
|
logs2.push(` Scenario generation failed: ${msg}`);
|
|
97979
98547
|
if ((0, ai_retry_js_1.isAIProviderError)(err)) {
|
|
@@ -324908,7 +325476,7 @@ ${currentSummary}`;
|
|
|
324908
325476
|
const distinctPathsVisited = new Set([...state2.visitedRoutes].map((r) => (0, exploration_parser_js_1.normalizeRouteForFocusScope)(r))).size;
|
|
324909
325477
|
const fb = (0, survey_link_frontier_js_1.evaluateFinishBounce)(finishBounce, distinctPathsVisited);
|
|
324910
325478
|
finishBounce = fb.state;
|
|
324911
|
-
const forceAcceptFinish = fb.escape;
|
|
325479
|
+
const forceAcceptFinish = fb.escape && !finishStreakCountedThisBatch;
|
|
324912
325480
|
if (forceAcceptFinish) {
|
|
324913
325481
|
const stuck = adaptiveActive ? adaptiveDebtRoutes() : (0, survey_link_frontier_js_1.getFinishBlockingFrontier)(state2).map((i) => i.route);
|
|
324914
325482
|
const reasonText = String(toolArgs.summary || "").replace(/\s+/g, " ").trim().slice(0, 400);
|
|
@@ -325030,7 +325598,7 @@ Exploration complete: ${summary}`);
|
|
|
325030
325598
|
continue;
|
|
325031
325599
|
}
|
|
325032
325600
|
if (toolName === "fill_credential" || toolName === "fill_credential_form") {
|
|
325033
|
-
if (emailVerificationUnavailable) {
|
|
325601
|
+
if (emailVerificationUnavailable || inboxState?.authDead) {
|
|
325034
325602
|
const codeGuard = (0, inbox_tools_js_1.guardVerificationCodeFabrication)(toolName, toolArgs);
|
|
325035
325603
|
if (codeGuard.blocked) {
|
|
325036
325604
|
logs2.push(` BLOCKED ${toolName}: verification-code field in email-verification-unavailable mode (field="${codeGuard.matchedField}")`);
|
|
@@ -325124,7 +325692,7 @@ Exploration complete: ${summary}`);
|
|
|
325124
325692
|
continue;
|
|
325125
325693
|
}
|
|
325126
325694
|
}
|
|
325127
|
-
if (emailVerificationUnavailable) {
|
|
325695
|
+
if (emailVerificationUnavailable || inboxState?.authDead) {
|
|
325128
325696
|
const codeGuard = (0, inbox_tools_js_1.guardVerificationCodeFabrication)(toolName, toolArgs);
|
|
325129
325697
|
if (codeGuard.blocked) {
|
|
325130
325698
|
logs2.push(` BLOCKED ${toolName}: verification-code field in email-verification-unavailable mode (field="${codeGuard.matchedField}")`);
|
|
@@ -326539,7 +327107,10 @@ var require_auth_capture_run = __commonJS({
|
|
|
326539
327107
|
exports2.getAuthCaptureTimeoutMs = getAuthCaptureTimeoutMs;
|
|
326540
327108
|
exports2.getAuthCaptureRunRetryMaxAttempts = getAuthCaptureRunRetryMaxAttempts;
|
|
326541
327109
|
exports2.classifyCaptureMiss = classifyCaptureMiss;
|
|
327110
|
+
exports2.harvestCollapsedAuthProof = harvestCollapsedAuthProof;
|
|
327111
|
+
exports2.selectAuthLandingRoute = selectAuthLandingRoute;
|
|
326542
327112
|
exports2.harvestAuthProofLive = harvestAuthProofLive;
|
|
327113
|
+
exports2.detectRedirectBackCollapse = detectRedirectBackCollapse;
|
|
326543
327114
|
exports2.findCredentialLiteralLeaks = findCredentialLiteralLeaks;
|
|
326544
327115
|
exports2.runLoginCaptureRunWithOutcome = runLoginCaptureRunWithOutcome;
|
|
326545
327116
|
exports2.runLoginCaptureRun = runLoginCaptureRun;
|
|
@@ -326660,35 +327231,26 @@ ${keyLines || "- (none provided)"}
|
|
|
326660
327231
|
return false;
|
|
326661
327232
|
return true;
|
|
326662
327233
|
}
|
|
326663
|
-
|
|
326664
|
-
|
|
326665
|
-
|
|
326666
|
-
|
|
326667
|
-
|
|
326668
|
-
|
|
326669
|
-
|
|
326670
|
-
|
|
326671
|
-
|
|
326672
|
-
|
|
326673
|
-
|
|
326674
|
-
|
|
326675
|
-
|
|
326676
|
-
|
|
326677
|
-
|
|
326678
|
-
|
|
326679
|
-
continue;
|
|
326680
|
-
for (const el of p.elements ?? []) {
|
|
326681
|
-
const nm = (el.label ?? "").trim().toLowerCase();
|
|
326682
|
-
if (el.role && nm)
|
|
326683
|
-
loginRoleNames.add(`${el.role}|${nm}`);
|
|
326684
|
-
if (el.testId)
|
|
326685
|
-
loginTestIds.add(el.testId);
|
|
326686
|
-
}
|
|
326687
|
-
}
|
|
327234
|
+
var LOGOUT_RE = /\b(log\s?out|sign\s?out)\b/i;
|
|
327235
|
+
var ACCOUNT_RE = /\b(account|profile|settings|avatar|notifications?|dashboard|inbox|my\s+account|user\s+menu)\b/i;
|
|
327236
|
+
var GREETING_RE = /\b(welcome|hello|hey|good\s+(morning|afternoon|evening|day))\b/i;
|
|
327237
|
+
function describeAuthProof(proof) {
|
|
327238
|
+
switch (proof.kind) {
|
|
327239
|
+
case "role":
|
|
327240
|
+
return `role:${proof.role}${proof.name ? `(${proof.name})` : ""}`;
|
|
327241
|
+
case "testid":
|
|
327242
|
+
return `testid:${proof.testId}`;
|
|
327243
|
+
case "endpoint":
|
|
327244
|
+
return `endpoint:${proof.url}`;
|
|
327245
|
+
case "url":
|
|
327246
|
+
return `url:${proof.route}`;
|
|
327247
|
+
}
|
|
327248
|
+
}
|
|
327249
|
+
function rankDurableAuthProof(landingElements, loginRoleNames, loginTestIds, credentials, logs2) {
|
|
326688
327250
|
const haveLoginDiff = loginRoleNames.size > 0 || loginTestIds.size > 0;
|
|
326689
327251
|
const roleNameFreq = /* @__PURE__ */ new Map();
|
|
326690
327252
|
const testIdFreq = /* @__PURE__ */ new Map();
|
|
326691
|
-
for (const el of
|
|
327253
|
+
for (const el of landingElements) {
|
|
326692
327254
|
const nm = (el.label ?? "").trim().toLowerCase();
|
|
326693
327255
|
if (el.role && nm)
|
|
326694
327256
|
roleNameFreq.set(`${el.role}|${nm}`, (roleNameFreq.get(`${el.role}|${nm}`) ?? 0) + 1);
|
|
@@ -326728,7 +327290,7 @@ ${keyLines || "- (none provided)"}
|
|
|
326728
327290
|
return true;
|
|
326729
327291
|
};
|
|
326730
327292
|
const ranked = [];
|
|
326731
|
-
for (const el of
|
|
327293
|
+
for (const el of landingElements) {
|
|
326732
327294
|
const label = (el.label ?? "").trim();
|
|
326733
327295
|
const role = el.role ?? "";
|
|
326734
327296
|
const testId = el.testId ?? "";
|
|
@@ -326740,7 +327302,7 @@ ${keyLines || "- (none provided)"}
|
|
|
326740
327302
|
const r = role === "link" ? "link" : "button";
|
|
326741
327303
|
ranked.push({ tier: 1, proof: { kind: "role", role: r, name: label }, desc: `${r} "${label}" (logout control)` });
|
|
326742
327304
|
}
|
|
326743
|
-
if (label && ACCOUNT_RE.test(label) && !personalized && uniqueRoleName && (role === "button" || role === "link") && absentOnLogin(roleNameKey, "")) {
|
|
327305
|
+
if (label && ACCOUNT_RE.test(label) && !personalized && uniqueRoleName && haveLoginDiff && (role === "button" || role === "link") && absentOnLogin(roleNameKey, "")) {
|
|
326744
327306
|
ranked.push({ tier: 2, proof: { kind: "role", role, name: label }, desc: `${role} "${label}" (account control)` });
|
|
326745
327307
|
}
|
|
326746
327308
|
if (testId && isSemanticTestId(testId) && uniqueTestId && haveLoginDiff && absentOnLogin("", testId)) {
|
|
@@ -326754,25 +327316,110 @@ ${keyLines || "- (none provided)"}
|
|
|
326754
327316
|
}
|
|
326755
327317
|
}
|
|
326756
327318
|
if (ranked.length === 0) {
|
|
326757
|
-
|
|
326758
|
-
const toProofPathname = (r) => {
|
|
326759
|
-
if (!r)
|
|
326760
|
-
return null;
|
|
326761
|
-
const p = r.replace(/[?#].*$/, "").replace(/\/+$/, "");
|
|
326762
|
-
return p && p !== "/" && !isLoginPage(p) && p !== loginPathname ? p : null;
|
|
326763
|
-
};
|
|
326764
|
-
const urlRoute = toProofPathname(redirectRoute) ?? toProofPathname(landing.route);
|
|
326765
|
-
if (urlRoute) {
|
|
326766
|
-
logs2(` \u2713 url auth-proof: authenticated landing route ${urlRoute} (no durable element proof on the default-state page)`);
|
|
326767
|
-
return { kind: "url", route: urlRoute };
|
|
326768
|
-
}
|
|
326769
|
-
logs2(" \u26A0 auth-proof: no durable element proof and no concrete authed landing route \u2014 falling back to model-authored login (no blind navigation default)");
|
|
327319
|
+
logs2(" \u26A0 auth-proof: no durable login-absent element proof on the authenticated page (no url fallback \u2014 coverage deferred to a positive proof)");
|
|
326770
327320
|
return null;
|
|
326771
327321
|
}
|
|
326772
327322
|
ranked.sort((a, b) => a.tier - b.tier);
|
|
326773
327323
|
logs2(` \u2713 live auth-proof: ${ranked[0].desc}`);
|
|
326774
327324
|
return ranked[0].proof;
|
|
326775
327325
|
}
|
|
327326
|
+
function harvestCollapsedAuthProof(landingElements, loginAbsentElements, credentials, logs2) {
|
|
327327
|
+
const loginRoleNames = /* @__PURE__ */ new Set();
|
|
327328
|
+
const loginTestIds = /* @__PURE__ */ new Set();
|
|
327329
|
+
for (const el of loginAbsentElements) {
|
|
327330
|
+
const nm = (el.label ?? "").trim().toLowerCase();
|
|
327331
|
+
if (el.role && nm)
|
|
327332
|
+
loginRoleNames.add(`${el.role}|${nm}`);
|
|
327333
|
+
if (el.testId)
|
|
327334
|
+
loginTestIds.add(el.testId);
|
|
327335
|
+
}
|
|
327336
|
+
return rankDurableAuthProof(landingElements, loginRoleNames, loginTestIds, credentials, logs2);
|
|
327337
|
+
}
|
|
327338
|
+
function pickAuthLandingPage(siteMap, redirectRoute, loginRoute) {
|
|
327339
|
+
const isLoginPage = (route2) => loginRoute != null && route2 === loginRoute || AUTH_ROUTE_RE.test(route2 ?? "");
|
|
327340
|
+
const authedPages = siteMap.pages.filter((p) => (p.requiresAuth || redirectRoute !== null && p.route === redirectRoute) && !isLoginPage(p.route));
|
|
327341
|
+
return (redirectRoute !== null ? authedPages.find((p) => p.route === redirectRoute) : void 0) ?? authedPages[0] ?? null;
|
|
327342
|
+
}
|
|
327343
|
+
function selectAuthLandingRoute(siteMap, redirectRoute, loginRoute) {
|
|
327344
|
+
return pickAuthLandingPage(siteMap, redirectRoute, loginRoute)?.route ?? null;
|
|
327345
|
+
}
|
|
327346
|
+
function harvestAuthProofLive(siteMap, redirectRoute, loginRoute, credentials, logs2) {
|
|
327347
|
+
const isLoginPage = (route2) => loginRoute != null && route2 === loginRoute || AUTH_ROUTE_RE.test(route2 ?? "");
|
|
327348
|
+
const landing = pickAuthLandingPage(siteMap, redirectRoute, loginRoute);
|
|
327349
|
+
if (!landing) {
|
|
327350
|
+
logs2(" \u26A0 auth-proof: no authenticated landing page captured \u2014 cannot derive a durable proof");
|
|
327351
|
+
return null;
|
|
327352
|
+
}
|
|
327353
|
+
const loginRoleNames = /* @__PURE__ */ new Set();
|
|
327354
|
+
const loginTestIds = /* @__PURE__ */ new Set();
|
|
327355
|
+
for (const p of siteMap.pages) {
|
|
327356
|
+
if (!isLoginPage(p.route) || p.route === landing.route)
|
|
327357
|
+
continue;
|
|
327358
|
+
for (const el of p.elements ?? []) {
|
|
327359
|
+
const nm = (el.label ?? "").trim().toLowerCase();
|
|
327360
|
+
if (el.role && nm)
|
|
327361
|
+
loginRoleNames.add(`${el.role}|${nm}`);
|
|
327362
|
+
if (el.testId)
|
|
327363
|
+
loginTestIds.add(el.testId);
|
|
327364
|
+
}
|
|
327365
|
+
}
|
|
327366
|
+
return rankDurableAuthProof(landing.elements ?? [], loginRoleNames, loginTestIds, credentials, logs2);
|
|
327367
|
+
}
|
|
327368
|
+
var AUTH_ROUTE_RE = /\/(log[\s_-]?in|sign[\s_-]?in|sign[\s_-]?up|register|auth|forgot|reset)\b/i;
|
|
327369
|
+
var AUTO_NAV_TRIGGERS = /* @__PURE__ */ new Set(["AUTO", "REDIRECT"]);
|
|
327370
|
+
var USER_ACTION_TRIGGERS = /* @__PURE__ */ new Set(["CLICK", "SUBMIT", "NAVIGATION"]);
|
|
327371
|
+
var stripToBarePath = (route2) => {
|
|
327372
|
+
if (!route2)
|
|
327373
|
+
return null;
|
|
327374
|
+
let path = route2;
|
|
327375
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(route2)) {
|
|
327376
|
+
try {
|
|
327377
|
+
path = new URL(route2).pathname;
|
|
327378
|
+
} catch {
|
|
327379
|
+
}
|
|
327380
|
+
}
|
|
327381
|
+
const p = path.replace(/[?#].*$/, "").replace(/\/+$/, "");
|
|
327382
|
+
return p || "/";
|
|
327383
|
+
};
|
|
327384
|
+
function detectRedirectBackCollapse(baseUrl, transitions) {
|
|
327385
|
+
let basePath;
|
|
327386
|
+
try {
|
|
327387
|
+
basePath = stripToBarePath(new URL(baseUrl).pathname);
|
|
327388
|
+
} catch {
|
|
327389
|
+
return null;
|
|
327390
|
+
}
|
|
327391
|
+
if (!basePath || basePath === "/" || AUTH_ROUTE_RE.test(basePath))
|
|
327392
|
+
return null;
|
|
327393
|
+
const userActionEdges = new Set(transitions.filter((t) => USER_ACTION_TRIGGERS.has(t.triggerType)).map((t) => `${stripToBarePath(t.fromRoute) ?? ""} ${stripToBarePath(t.toRoute) ?? ""}`));
|
|
327394
|
+
const bounceIdx = transitions.findIndex((t) => AUTO_NAV_TRIGGERS.has(t.triggerType) && stripToBarePath(t.fromRoute) === basePath && AUTH_ROUTE_RE.test(stripToBarePath(t.toRoute) ?? "") && !userActionEdges.has(`${basePath} ${stripToBarePath(t.toRoute) ?? ""}`));
|
|
327395
|
+
if (bounceIdx < 0)
|
|
327396
|
+
return null;
|
|
327397
|
+
const authRoute = stripToBarePath(transitions[bounceIdx].toRoute);
|
|
327398
|
+
if (!authRoute)
|
|
327399
|
+
return null;
|
|
327400
|
+
const returned = transitions.slice(bounceIdx + 1).some((t) => AUTH_ROUTE_RE.test(stripToBarePath(t.fromRoute) ?? "") && stripToBarePath(t.toRoute) === basePath);
|
|
327401
|
+
if (!returned)
|
|
327402
|
+
return null;
|
|
327403
|
+
return { basePath, authRoute };
|
|
327404
|
+
}
|
|
327405
|
+
function pickRouteElements(routeElements, wantRoute) {
|
|
327406
|
+
const want = stripToBarePath(wantRoute);
|
|
327407
|
+
for (const [route2, els] of routeElements) {
|
|
327408
|
+
if (stripToBarePath(route2) === want && (els?.length ?? 0) > 0)
|
|
327409
|
+
return els;
|
|
327410
|
+
}
|
|
327411
|
+
return [];
|
|
327412
|
+
}
|
|
327413
|
+
function pickLoginFormElements(routeElements, authRoute) {
|
|
327414
|
+
const exact = pickRouteElements(routeElements, authRoute);
|
|
327415
|
+
if (exact.length > 0)
|
|
327416
|
+
return exact;
|
|
327417
|
+
for (const [route2, els] of routeElements) {
|
|
327418
|
+
if (AUTH_ROUTE_RE.test(stripToBarePath(route2) ?? "") && (els?.length ?? 0) > 0)
|
|
327419
|
+
return els;
|
|
327420
|
+
}
|
|
327421
|
+
return [];
|
|
327422
|
+
}
|
|
326776
327423
|
function findCredentialLiteralLeaks(rendered, credentials) {
|
|
326777
327424
|
const leaked = [];
|
|
326778
327425
|
for (const [key, value] of Object.entries(credentials)) {
|
|
@@ -326785,12 +327432,22 @@ ${keyLines || "- (none provided)"}
|
|
|
326785
327432
|
return leaked;
|
|
326786
327433
|
}
|
|
326787
327434
|
async function verifyAuthRecipe(recipe, baseUrl, credentials, logs2) {
|
|
327435
|
+
const baselineRoute = recipe.redirectRoute ?? baseUrl;
|
|
326788
327436
|
const spec = [
|
|
326789
327437
|
`import { test, expect } from '@playwright/test';`,
|
|
326790
327438
|
``,
|
|
326791
327439
|
(0, auth_recipe_js_1.renderAuthFn)(recipe),
|
|
326792
327440
|
``,
|
|
326793
327441
|
`test('__vqa_auth_verify', async ({ page }) => {`,
|
|
327442
|
+
` // DIFFERENTIAL BASELINE (logged out): the durable proof must NOT already hold here, else it`,
|
|
327443
|
+
` // does not discriminate an authenticated session from an anonymous one \u2014 reject rather than`,
|
|
327444
|
+
` // publish a proof that passes while logged out (the cardinal invariant). The goto is NOT`,
|
|
327445
|
+
` // .catch-swallowed: a genuine navigation failure must fail the verify (fail-closed) rather than`,
|
|
327446
|
+
` // leave the baseline running on about:blank where the proof is vacuously "absent". An auth-gated`,
|
|
327447
|
+
` // route resolves fine (it follows the redirect to /login).`,
|
|
327448
|
+
` await page.goto(${JSON.stringify(baselineRoute)}, { waitUntil: 'domcontentloaded' });`,
|
|
327449
|
+
` ${(0, auth_recipe_js_1.renderProofBaselineCheck)(recipe.authProof, " ")}`,
|
|
327450
|
+
` // AUTHED: real login + the durable proof assertion (inside __vqaLogin).`,
|
|
326794
327451
|
` await __vqaLogin(page);`,
|
|
326795
327452
|
`});`,
|
|
326796
327453
|
``
|
|
@@ -326892,21 +327549,50 @@ ${keyLines || "- (none provided)"}
|
|
|
326892
327549
|
cmds: login.cmds.map((c) => (0, scrub_credentials_js_1.scrubCredentials)(c, credentials)),
|
|
326893
327550
|
preNavCmds: login.preNavCmds?.map((c) => (0, scrub_credentials_js_1.scrubCredentials)(c, credentials))
|
|
326894
327551
|
};
|
|
326895
|
-
|
|
327552
|
+
let authProof = harvestAuthProofLive(loginSiteMap, scrubbedLogin.redirectRoute, scrubbedLogin.route, credentials, logs2);
|
|
327553
|
+
let effectiveLogin = scrubbedLogin;
|
|
327554
|
+
if (authProof && (authProof.kind === "role" || authProof.kind === "testid")) {
|
|
327555
|
+
const landingRoute = selectAuthLandingRoute(loginSiteMap, scrubbedLogin.redirectRoute, scrubbedLogin.route);
|
|
327556
|
+
if (landingRoute)
|
|
327557
|
+
effectiveLogin = { ...effectiveLogin, redirectRoute: landingRoute };
|
|
327558
|
+
}
|
|
326896
327559
|
if (!authProof) {
|
|
326897
|
-
|
|
326898
|
-
|
|
327560
|
+
const collapse = detectRedirectBackCollapse(baseUrl, exploreResult.collectedTransitions);
|
|
327561
|
+
if (collapse) {
|
|
327562
|
+
const landingEls = pickRouteElements(handle.routeElements, collapse.basePath);
|
|
327563
|
+
const loginEls = pickLoginFormElements(handle.routeElements, collapse.authRoute);
|
|
327564
|
+
const collapsedProof = harvestCollapsedAuthProof(landingEls, loginEls, credentials, logs2);
|
|
327565
|
+
if (collapsedProof) {
|
|
327566
|
+
logs2(` \u2713 collapsed-entry element auth-proof (redirect-back-to-entry): ${JSON.stringify(collapsedProof)} on ${collapse.basePath} (${landingEls.length} authed / ${loginEls.length} login-form elements differenced)`);
|
|
327567
|
+
authProof = collapsedProof;
|
|
327568
|
+
effectiveLogin = { ...effectiveLogin, redirectRoute: collapse.basePath };
|
|
327569
|
+
} else {
|
|
327570
|
+
logs2(" \u26A0 redirect-back-to-entry collapse detected but no durable authed-only element differenced (no url fallback) \u2014 deferring to model-authored login");
|
|
327571
|
+
}
|
|
327572
|
+
}
|
|
326899
327573
|
}
|
|
326900
|
-
|
|
326901
|
-
|
|
326902
|
-
|
|
326903
|
-
|
|
326904
|
-
|
|
326905
|
-
|
|
327574
|
+
let identityEndpointProof = null;
|
|
327575
|
+
try {
|
|
327576
|
+
const cookies = await handle.page.context().cookies();
|
|
327577
|
+
if (cookies.some((c) => /(next-auth|authjs)\.session-token/.test(c.name))) {
|
|
327578
|
+
identityEndpointProof = { kind: "endpoint", url: "/api/auth/session" };
|
|
327579
|
+
logs2(" \u2139 NextAuth/Auth.js session cookie detected \u2014 trying an identity-endpoint proof (/api/auth/session) before the element proof");
|
|
327580
|
+
}
|
|
327581
|
+
} catch {
|
|
327582
|
+
}
|
|
327583
|
+
const proofCandidates = [];
|
|
327584
|
+
if (identityEndpointProof)
|
|
327585
|
+
proofCandidates.push(identityEndpointProof);
|
|
327586
|
+
if (authProof)
|
|
327587
|
+
proofCandidates.push(authProof);
|
|
327588
|
+
if (proofCandidates.length === 0) {
|
|
327589
|
+
logs2(" \u26A0 auth unverified (no positive proof \u2014 no identity endpoint, no login-absent element) \u2014 falling back to model-authored login");
|
|
327590
|
+
return { recipe: null, outcome: exploreMiss("verify_failed") };
|
|
326906
327591
|
}
|
|
326907
|
-
const
|
|
326908
|
-
|
|
326909
|
-
|
|
327592
|
+
const loginText = [...effectiveLogin.cmds, ...effectiveLogin.preNavCmds ?? []].join("\n") + "\n" + JSON.stringify(effectiveLogin.replayPlan ?? []);
|
|
327593
|
+
const loginLeaks = findCredentialLiteralLeaks(loginText, credentials);
|
|
327594
|
+
if (loginLeaks.length > 0) {
|
|
327595
|
+
logs2(` \u26A0 auth recipe rejected: login sequence contains literal credential value(s) for ${loginLeaks.join(", ")} \u2014 falling back to model-authored login`);
|
|
326910
327596
|
return { recipe: null, outcome: "credential_leak" };
|
|
326911
327597
|
}
|
|
326912
327598
|
try {
|
|
@@ -326914,14 +327600,25 @@ ${keyLines || "- (none provided)"}
|
|
|
326914
327600
|
} catch {
|
|
326915
327601
|
}
|
|
326916
327602
|
handle = null;
|
|
326917
|
-
|
|
326918
|
-
|
|
326919
|
-
|
|
326920
|
-
|
|
327603
|
+
let lastReject = "no candidate verified";
|
|
327604
|
+
for (const proof of proofCandidates) {
|
|
327605
|
+
const candidate = { ...(0, auth_recipe_js_1.buildAuthRecipe)(effectiveLogin, proof, (/* @__PURE__ */ new Date()).toISOString()), appOrigin: loginRedirectAppOrigin };
|
|
327606
|
+
const renderLeaks = findCredentialLiteralLeaks((0, auth_recipe_js_1.renderAuthFn)(candidate), credentials);
|
|
327607
|
+
if (renderLeaks.length > 0) {
|
|
327608
|
+
lastReject = `${describeAuthProof(proof)} render coincidentally matched credential value(s) ${renderLeaks.join(", ")}`;
|
|
327609
|
+
logs2(` \u26A0 auth-proof candidate skipped (${lastReject}) \u2014 trying the next candidate`);
|
|
327610
|
+
continue;
|
|
327611
|
+
}
|
|
327612
|
+
const ok = await verifyAuthRecipe(candidate, baseUrl, credentials, logs2);
|
|
327613
|
+
if (ok) {
|
|
327614
|
+
logs2(` \u2713 auth recipe verified (hash ${candidate.recipeHash}, proof ${describeAuthProof(proof)}, ${(candidate.replayPlan ?? []).length} replay steps) \u2014 stamping into @needs-auth tests`);
|
|
327615
|
+
return { recipe: candidate, outcome: "captured" };
|
|
327616
|
+
}
|
|
327617
|
+
lastReject = `${describeAuthProof(proof)} failed the differential verify`;
|
|
327618
|
+
logs2(` \u26A0 auth-proof candidate rejected (${lastReject}) \u2014 trying the next candidate`);
|
|
326921
327619
|
}
|
|
326922
|
-
|
|
326923
|
-
|
|
326924
|
-
return { recipe: candidate, outcome: "captured" };
|
|
327620
|
+
logs2(` \u26A0 auth unverified (all proof candidates failed: ${lastReject}) \u2014 falling back to model-authored login`);
|
|
327621
|
+
return { recipe: null, outcome: exploreMiss("verify_failed") };
|
|
326925
327622
|
} catch (err) {
|
|
326926
327623
|
logs2(` \u26A0 auth-capture run failed: ${err instanceof Error ? err.message : String(err)} \u2014 falling back to model-authored login`);
|
|
326927
327624
|
return { recipe: null, outcome: (0, ai_retry_js_1.isTransientRateError)(err) ? "rate_limited" : "error" };
|
|
@@ -334048,8 +334745,13 @@ Respond with ONLY valid JSON:
|
|
|
334048
334745
|
if (!(0, shared_1.parseBoolFlag)(process.env.AUTH_RECIPE_STAMP) && ctx.mode !== "capture")
|
|
334049
334746
|
return null;
|
|
334050
334747
|
if (ctx.authRecipe) {
|
|
334051
|
-
|
|
334052
|
-
|
|
334748
|
+
if (ctx.authRecipe.authProof?.kind === "url") {
|
|
334749
|
+
pushLog(`auth-prologue: persisted recipe ${ctx.authRecipe.recipeHash} uses a demoted url proof \u2014 re-capturing with the differential pipeline (legacy invalidation)`);
|
|
334750
|
+
ctx.authRecipe = void 0;
|
|
334751
|
+
} else {
|
|
334752
|
+
pushLog(`auth-prologue: reusing persisted login recipe ${ctx.authRecipe.recipeHash} \u2014 skipping capture`);
|
|
334753
|
+
return Promise.resolve(ctx.authRecipe);
|
|
334754
|
+
}
|
|
334053
334755
|
}
|
|
334054
334756
|
const exploreModel = ctx.exploreModel ?? "";
|
|
334055
334757
|
const authCaptureModel = ctx.authCaptureModel ?? "";
|
|
@@ -349596,7 +350298,7 @@ var require_package4 = __commonJS({
|
|
|
349596
350298
|
"package.json"(exports2, module2) {
|
|
349597
350299
|
module2.exports = {
|
|
349598
350300
|
name: "@validate.qa/runner",
|
|
349599
|
-
version: "1.0.
|
|
350301
|
+
version: "1.0.57",
|
|
349600
350302
|
description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
|
|
349601
350303
|
bin: {
|
|
349602
350304
|
"validate-runner": "dist/cli.js"
|