@validate.qa/runner 1.0.57 → 1.0.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +135 -38
  2. package/dist/cli.mjs +135 -38
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5276,6 +5276,12 @@ var init_auth_state_freshness = __esm({
5276
5276
  function detectSkippedApiCoverage(steps) {
5277
5277
  const allSkipped = steps.length > 0 && steps.every((s) => s.status === "skipped");
5278
5278
  if (allSkipped) {
5279
+ if (steps.every((s) => s.skipKind === "fixme")) {
5280
+ return {
5281
+ detected: false,
5282
+ logLine: "All @api tests skipped, but EVERY skip is a deliberate fixme (intentional N/A \u2014 e.g. a UI-only redirect login) \u2014 NOT relabelling CONFIG_ISSUE"
5283
+ };
5284
+ }
5279
5285
  return {
5280
5286
  detected: true,
5281
5287
  error: (
@@ -72835,18 +72841,15 @@ var require_verification_inbox = __commonJS({
72835
72841
  return { code: cleaned, confidence: "keyword" };
72836
72842
  }
72837
72843
  }
72838
- const distinct = /* @__PURE__ */ new Set();
72839
72844
  for (const body of bodies) {
72840
72845
  if (!body)
72841
72846
  continue;
72842
72847
  for (const n of body.match(/\b[0-9]{4,8}\b/g) ?? []) {
72843
- if (!looksLikeYearOrDate(n))
72844
- distinct.add(n);
72848
+ if (looksLikeYearOrDate(n))
72849
+ continue;
72850
+ return { code: n, confidence: "fallback" };
72845
72851
  }
72846
72852
  }
72847
- if (distinct.size === 1) {
72848
- return { code: distinct.values().next().value ?? null, confidence: "fallback" };
72849
- }
72850
72853
  return { code: null, confidence: null };
72851
72854
  }
72852
72855
  function matchBestCode(line) {
@@ -73144,9 +73147,10 @@ var require_verification_inbox = __commonJS({
73144
73147
  const satisfies = (m) => {
73145
73148
  if (opts.require === "code")
73146
73149
  return m.code !== null && m.codeConfidence !== "fallback";
73150
+ const linkOk = (l) => l !== null && (!opts.linkAcceptable || opts.linkAcceptable(l));
73147
73151
  if (opts.require === "link")
73148
- return m.link !== null;
73149
- return m.hasVerificationSignal;
73152
+ return linkOk(m.link);
73153
+ return m.code !== null && m.codeConfidence !== "fallback" || linkOk(m.link);
73150
73154
  };
73151
73155
  let weakest = null;
73152
73156
  const check2 = async () => {
@@ -73193,7 +73197,7 @@ var require_verification_inbox = __commonJS({
73193
73197
  const since = new Date(Math.max(0, opts.sinceMs));
73194
73198
  const lock = await client.getMailboxLock(mailbox);
73195
73199
  const results = [];
73196
- const taggedRecipient = isPlusTagged(opts.toAddress);
73200
+ const demoteMismatch = isPlusTagged(opts.toAddress) && Boolean(opts.demoteFilterMismatch);
73197
73201
  try {
73198
73202
  try {
73199
73203
  await client.noop();
@@ -73239,7 +73243,7 @@ var require_verification_inbox = __commonJS({
73239
73243
  continue;
73240
73244
  const filterActive = Boolean(opts.fromMatch || opts.subjectMatch);
73241
73245
  const filterMatched = (!opts.fromMatch || matchField(parsed.from?.text, opts.fromMatch)) && (!opts.subjectMatch || matchField(parsed.subject, opts.subjectMatch));
73242
- if (filterActive && !filterMatched && !taggedRecipient) {
73246
+ if (filterActive && !filterMatched && !demoteMismatch) {
73243
73247
  if (opts.stats)
73244
73248
  opts.stats.filteredOut += 1;
73245
73249
  continue;
@@ -73377,7 +73381,8 @@ var require_inbox_tools = __commonJS({
73377
73381
  // Captured at provision (needs a live reader); 0 = use the sinceMs floor.
73378
73382
  sinceUid: 0,
73379
73383
  sinceUidValidity: 0,
73380
- knownLinks: /* @__PURE__ */ new Set()
73384
+ knownLinks: /* @__PURE__ */ new Set(),
73385
+ demoteFilterMismatch: opts.demoteFilterMismatch ?? false
73381
73386
  };
73382
73387
  }
73383
73388
  exports2.PROVISION_IDENTITY_TOOL = {
@@ -73565,6 +73570,7 @@ var require_inbox_tools = __commonJS({
73565
73570
  sinceUidValidity: ctx.state.sinceUidValidity,
73566
73571
  fromMatch: asStr(args.senderMatch),
73567
73572
  subjectMatch: asStr(args.subjectMatch),
73573
+ demoteFilterMismatch: ctx.state.demoteFilterMismatch,
73568
73574
  limit: 10
73569
73575
  });
73570
73576
  return ok({
@@ -73589,6 +73595,7 @@ var require_inbox_tools = __commonJS({
73589
73595
  sinceUidValidity: ctx.state.sinceUidValidity,
73590
73596
  fromMatch: asStr(args.senderMatch),
73591
73597
  subjectMatch: asStr(args.subjectMatch),
73598
+ demoteFilterMismatch: ctx.state.demoteFilterMismatch,
73592
73599
  maxWaitMs: maxWaitSeconds * 1e3,
73593
73600
  stats
73594
73601
  });
@@ -73735,7 +73742,7 @@ var require_inbox_tools = __commonJS({
73735
73742
  const u = new URL(raw);
73736
73743
  if (u.protocol !== "http:" && u.protocol !== "https:")
73737
73744
  return false;
73738
- host = u.hostname.toLowerCase().replace(/^\[|\]$/g, "");
73745
+ host = u.hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.+$/, "");
73739
73746
  } catch {
73740
73747
  return false;
73741
73748
  }
@@ -73751,7 +73758,7 @@ var require_inbox_tools = __commonJS({
73751
73758
  const dotted = host.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
73752
73759
  if (dotted && !ipv4IsPublic(dotted[1]))
73753
73760
  return false;
73754
- const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
73761
+ const mapped = host.match(/^::(?:ffff:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
73755
73762
  if (mapped) {
73756
73763
  const hi = parseInt(mapped[1], 16);
73757
73764
  const lo = parseInt(mapped[2], 16);
@@ -75514,12 +75521,11 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
75514
75521
  keyboardRecoveryScreenId = currentScreenId ?? null;
75515
75522
  keyboardRecoverySameScreenTurns = 0;
75516
75523
  }
75517
- const keyboardLikelyOpen = (0, mobile_keyboard_js_1.hasFocusedEditableElement)(getLatest()?.screen.elements ?? []);
75518
- if (keyboardRecoverySameScreenTurns >= 4 && keyboardLikelyOpen && iteration - lastKeyboardRecoveryIteration > 4) {
75524
+ if (keyboardRecoverySameScreenTurns >= 4 && iteration - lastKeyboardRecoveryIteration > 4) {
75519
75525
  lastKeyboardRecoveryIteration = iteration;
75520
75526
  try {
75521
75527
  await (0, mobile_keyboard_js_1.dismissKeyboardForRetry)(driver);
75522
- log2(" Auto-dismissed the on-screen keyboard (several turns stalled on this screen with a focused input).");
75528
+ log2(" Auto-dismissed the on-screen keyboard (several turns stalled on this screen).");
75523
75529
  transientDirectives.push("HARNESS NOTE: the on-screen keyboard was just auto-dismissed because multiple turns stalled on this screen \u2014 controls it occluded (e.g. the form submit button) may now be visible. Call mobile_snapshot, then TAP the commit/submit control instead of re-filling fields.");
75524
75530
  } catch (error2) {
75525
75531
  (0, mobile_driver_errors_js_1.rethrowIfTerminalMobileDriverError)(error2);
@@ -84295,6 +84301,7 @@ ${args.directive}`);
84295
84301
  aiResponseCount += 1;
84296
84302
  const parsed = parseHealVerdict(content);
84297
84303
  if (!parsed) {
84304
+ options.onProgress?.(`Phase 1 attempt ${attempt}: response was not a valid verdict \u2014 granting a recovery turn`);
84298
84305
  markRecoverableTurn();
84299
84306
  pendingDirective = "Your last response was not valid JSON matching the verdict schema. Return exactly one JSON verdict object.";
84300
84307
  continue;
@@ -86412,6 +86419,8 @@ var require_inbox_stamp = __commonJS({
86412
86419
  exports2.INBOX_HELPER_TOKEN_RE = exports2.INBOX_HELPER_NAME = exports2.INBOX_REGION_RE = exports2.INBOX_MARKER_VERSION = void 0;
86413
86420
  exports2.codeUsesInboxHelper = codeUsesInboxHelper;
86414
86421
  exports2.stripInboxRegions = stripInboxRegions;
86422
+ exports2.codeUsesTestInboxIdentity = codeUsesTestInboxIdentity;
86423
+ exports2.codeSelfProvisionsRegistration = codeSelfProvisionsRegistration2;
86415
86424
  exports2.renderInboxFn = renderInboxFn;
86416
86425
  exports2.upsertInboxBlock = upsertInboxBlock;
86417
86426
  var auth_recipe_js_1 = require_auth_recipe();
@@ -86426,12 +86435,54 @@ var require_inbox_stamp = __commonJS({
86426
86435
  return false;
86427
86436
  return exports2.INBOX_HELPER_TOKEN_RE.test(code) || /VALIDATE_QA_INBOX/.test(code);
86428
86437
  }
86438
+ var INBOX_REGION_START = "// >>> VALIDATE_QA_INBOX";
86439
+ var INBOX_REGION_END = "// <<< VALIDATE_QA_INBOX";
86429
86440
  function stripInboxRegions(code) {
86430
- return code.replace(exports2.INBOX_REGION_RE, "");
86441
+ if (!code || code.indexOf(INBOX_REGION_START) === -1)
86442
+ return code;
86443
+ let out = "";
86444
+ let i = 0;
86445
+ while (i < code.length) {
86446
+ const start = code.indexOf(INBOX_REGION_START, i);
86447
+ if (start === -1) {
86448
+ out += code.slice(i);
86449
+ break;
86450
+ }
86451
+ const end = code.indexOf(INBOX_REGION_END, start + INBOX_REGION_START.length);
86452
+ if (end === -1) {
86453
+ out += code.slice(i);
86454
+ break;
86455
+ }
86456
+ let lineEnd = code.indexOf("\n", end);
86457
+ lineEnd = lineEnd === -1 ? code.length : lineEnd + 1;
86458
+ out += code.slice(i, start);
86459
+ i = lineEnd;
86460
+ }
86461
+ return out;
86462
+ }
86463
+ var TEST_INBOX_IDENTITY_RE = /VQA_TEST_INBOX_EMAIL|VQA_TEST_ACCOUNT_PASSWORD/;
86464
+ function codeUsesTestInboxIdentity(code) {
86465
+ if (!code)
86466
+ return false;
86467
+ return TEST_INBOX_IDENTITY_RE.test(stripInboxRegions(code));
86468
+ }
86469
+ function codeSelfProvisionsRegistration2(code) {
86470
+ return codeUsesTestInboxIdentity(code);
86431
86471
  }
86432
86472
  function countInboxRegions(code) {
86433
- const m = code.match(exports2.INBOX_REGION_RE);
86434
- return m ? m.length : 0;
86473
+ let count = 0;
86474
+ let i = 0;
86475
+ while (i < code.length) {
86476
+ const start = code.indexOf(INBOX_REGION_START, i);
86477
+ if (start === -1)
86478
+ break;
86479
+ const end = code.indexOf(INBOX_REGION_END, start + INBOX_REGION_START.length);
86480
+ if (end === -1)
86481
+ break;
86482
+ count++;
86483
+ i = end + INBOX_REGION_END.length;
86484
+ }
86485
+ return count;
86435
86486
  }
86436
86487
  function renderInboxFn() {
86437
86488
  const lines = [
@@ -86525,7 +86576,12 @@ var require_test_inbox_broker = __commonJS({
86525
86576
  return { found: true, code, link, subject: msg.subject, from: msg.from };
86526
86577
  }
86527
86578
  async function startTestInboxBroker(opts) {
86528
- const identity = mintTestRunInboxIdentity(opts.config, opts);
86579
+ const executionNonce = (0, node_crypto_1.randomBytes)(4).toString("hex");
86580
+ const identity = mintTestRunInboxIdentity(opts.config, {
86581
+ baseUrl: opts.baseUrl,
86582
+ projectId: opts.projectId,
86583
+ runId: `${opts.runId}#${executionNonce}`
86584
+ });
86529
86585
  const reader = new verification_inbox_js_1.VerificationInboxReader(opts.config);
86530
86586
  const sinceMs = Date.now();
86531
86587
  let sinceUid = 0;
@@ -86565,6 +86621,12 @@ var require_test_inbox_broker = __commonJS({
86565
86621
  sinceUidValidity,
86566
86622
  maxWaitMs: timeoutMs,
86567
86623
  require: require2,
86624
+ // The plus-tag recipient is unique to this run, so an unexpected ESP
86625
+ // sender is still this run's mail — demote, don't drop.
86626
+ demoteFilterMismatch: true,
86627
+ // Don't let a private/internal link end a `require:'link'` wait early —
86628
+ // keep polling for a public one (SSRF parity with the post-wait check).
86629
+ linkAcceptable: inbox_tools_js_1.isPublicHttpUrl,
86568
86630
  stats
86569
86631
  });
86570
86632
  const publicLink = msg && msg.link && (0, inbox_tools_js_1.isPublicHttpUrl)(msg.link) ? msg.link : null;
@@ -86699,7 +86761,8 @@ var require_playwright_native = __commonJS({
86699
86761
  runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
86700
86762
  await (0, promises_1.mkdir)(runDir, { recursive: true });
86701
86763
  const isApiTest2 = playwrightCode.includes("@api") || playwrightCode.includes("{ request }") && !playwrightCode.includes("{ page");
86702
- const usesInboxHelper = !isApiTest2 && (0, inbox_stamp_js_1.codeUsesInboxHelper)(playwrightCode);
86764
+ const usesRealInboxWait = !isApiTest2 && (0, inbox_stamp_js_1.codeUsesInboxHelper)(playwrightCode);
86765
+ const selfProvisionsRegistration = !isApiTest2 && (0, inbox_stamp_js_1.codeSelfProvisionsRegistration)(playwrightCode);
86703
86766
  const effectiveStandingMode = isApiTest2 ? authStandingMode ?? "legacy-if-configured" : "none";
86704
86767
  const resolvedExtraHeaders = (0, api_auth_headers_js_1.resolveApiAuthHeaders)({
86705
86768
  explicit: extraHttpHeaders,
@@ -86946,7 +87009,7 @@ test.afterEach(async ({ page }, testInfo) => {
86946
87009
  module.exports = {
86947
87010
  testDir: ${JSON.stringify(absTestDir)},
86948
87011
  outputDir: ${JSON.stringify(absOutputDir)},
86949
- timeout: ${usesInboxHelper ? 24e4 : 6e4},
87012
+ timeout: ${usesRealInboxWait ? 24e4 : 6e4},
86950
87013
  retries: 0,
86951
87014
  use: {
86952
87015
  baseURL: ${JSON.stringify(baseUrl)},
@@ -86972,7 +87035,7 @@ module.exports = {
86972
87035
  const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
86973
87036
  const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
86974
87037
  const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
86975
- if (usesInboxHelper && validateContext?.runId) {
87038
+ if (selfProvisionsRegistration && validateContext?.runId) {
86976
87039
  const inboxConfig = (0, verification_inbox_js_1.loadVerificationInboxConfig)();
86977
87040
  if (!inboxConfig) {
86978
87041
  logs2.push("\u{1F4EA} Test uses __vqaWaitForEmail but this runner has no verification inbox configured \u2014 the helper will fail as CONFIG_ISSUE");
@@ -86990,8 +87053,8 @@ module.exports = {
86990
87053
  logs2.push(`\u26A0 Inbox broker failed to start: ${error3 instanceof Error ? error3.message : String(error3)}`);
86991
87054
  }
86992
87055
  }
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");
87056
+ } else if (selfProvisionsRegistration) {
87057
+ logs2.push("\u{1F4EA} Test self-provisions a registration identity (VQA_TEST_* / __vqaWaitForEmail) but no run callback context is available \u2014 the broker cannot start, so those values will be undefined at runtime");
86995
87058
  }
86996
87059
  if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
86997
87060
  try {
@@ -87032,16 +87095,20 @@ module.exports = {
87032
87095
  ...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {},
87033
87096
  // Register-and-verify tests: the run's minted registration identity. An
87034
87097
  // 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() : {}
87098
+ // which stays in this parent (test-inbox-broker.ts). Injected whenever the
87099
+ // broker started, NOT gated on the callback proxy: a signup that registers
87100
+ // with this identity but needs no email verification uses the env yet never
87101
+ // touches the proxy — gating on the proxy left it with undefined creds. A
87102
+ // helper test still needs the proxy, and simply fails as CONFIG_ISSUE
87103
+ // without it; leaking the throwaway address/password to that child is inert.
87104
+ ...inboxBroker ? inboxBroker.childEnv() : {}
87038
87105
  };
87039
87106
  delete childTestVariables.API_AUTH_HEADERS;
87040
87107
  logs2.push("\u{1F680} Spawning playwright test...");
87041
87108
  const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
87042
87109
  let playResult;
87043
87110
  try {
87044
- playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv, usesInboxHelper ? 3e5 : void 0);
87111
+ playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv, usesRealInboxWait ? 3e5 : void 0);
87045
87112
  } finally {
87046
87113
  await stopLiveBrowserPolling?.();
87047
87114
  }
@@ -94793,7 +94860,7 @@ var require_discover_generator = __commonJS({
94793
94860
  var test_inbox_broker_js_1 = require_test_inbox_broker();
94794
94861
  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
94862
  function scenarioLooksLikeRegistration(scenario) {
94796
- const hay = `${scenario.name ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")}`;
94863
+ const hay = `${scenario.name ?? ""} ${scenario.goal ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")}`;
94797
94864
  return REGISTER_SCENARIO_RE.test(hay);
94798
94865
  }
94799
94866
  function buildEmailVerifyGenBlock(genEmail, genPassword) {
@@ -97746,7 +97813,7 @@ ${tailBlock}`;
97746
97813
  const genEmail = (0, verification_inbox_js_1.applyPlusTag)(inboxGenConfig.address, genTag);
97747
97814
  const genPassword = (0, verification_inbox_js_1.deriveRegistrationPassword)(genEmail);
97748
97815
  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 });
97816
+ genInboxState = (0, inbox_tools_js_1.createInboxRunState)(inboxGenConfig, { tag: genTag, passwordKey: test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV, demoteFilterMismatch: true });
97750
97817
  genInboxCredentials = { ...credentials, [test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV]: genEmail, [test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV]: genPassword };
97751
97818
  genInboxSecret = genPassword;
97752
97819
  openaiTools.push(...inbox_tools_js_1.INBOX_TOOLS);
@@ -98308,6 +98375,13 @@ Do NOT assert that the button/tab/heading you interacted with is merely visible
98308
98375
  logs2.push(` Strength gate: block persists after revision \u2014 tagged @needs-review (proves no outcome)`);
98309
98376
  }
98310
98377
  }
98378
+ const finalSelfProvisions = (0, inbox_stamp_js_1.codeSelfProvisionsRegistration)(result.playwrightCode);
98379
+ if (finalSelfProvisions) {
98380
+ if (!result.tags.includes("@email-verify"))
98381
+ result.tags.push("@email-verify");
98382
+ } else if (inboxGenConfig) {
98383
+ logs2.push(` \u26A0 Email-verify generation was armed but the emitted test neither calls __vqaWaitForEmail nor uses the VQA_TEST_* runtime identity \u2014 it may have hardcoded values (stale on re-run).`);
98384
+ }
98311
98385
  const elapsed = Date.now() - startTime;
98312
98386
  logs2.push(` Scenario test generated in ${iteration} iterations (${elapsed}ms, ${verifyCallCount} verify calls)`);
98313
98387
  } else {
@@ -349876,9 +349950,9 @@ var require_dist2 = __commonJS({
349876
349950
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports3, p)) __createBinding(exports3, m, p);
349877
349951
  };
349878
349952
  Object.defineProperty(exports2, "__esModule", { value: true });
349879
- exports2.acquireRepoForImport = exports2.runFrameworkImportOnRunner = exports2.getAgenticEngine = exports2.resolveRefFromSnapshot = exports2.replayAuthRecipeIntoBrowser = exports2.runRegisterCaptureRun = exports2.runLoginCaptureRun = exports2.filterLoginCaptureCredentials = exports2.buildLoginCaptureCredentials = exports2.isReservedRunnerEnvVar = exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = exports2.buildHealAuthStamp = exports2.extractAuthRegion = exports2.upsertAuthBlock = exports2.buildAuthRecipe = exports2.commandToReplayStep = exports2.buildReplayPlan = exports2.absoluteRoute = exports2.renderReplaySettleArrow = exports2.renderReplayClickArrow = exports2.renderAuthFn = exports2.filterFeatureGroupsToPathScope = exports2.isAuthFlowRoute = exports2.deriveSurveyPathScope = exports2.canonicalizeRoute = exports2.executeGrokPerBatchHeal = exports2.executeGrokPerTestHeal = exports2.headerVal = exports2.classifyAuthType = exports2.isTextExcerptable = exports2.isStaticAssetByExt = exports2.isApiCall = exports2.buildApiCallSummary = exports2.computeMobileHealOutcome = exports2.detectMobileStepSecretLeak = exports2.redactMobileSecretsFromText = exports2.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports2.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports2.executeMobileLiveHeal = exports2.executeMobileTestWithHealing = exports2.executeMobileScriptDrivenHeal = exports2.prepareMobileScenarioAppState = exports2.runMobileGeneratePhase = exports2.runMobileAIPlanner = exports2.buildMobileSiteMap = exports2.formatMobileEvidenceForPlanner = exports2.MOBILE_SCENARIO_SYSTEM_PROMPT = exports2.MOBILE_SURVEY_SYSTEM_PROMPT = exports2.runMobileExplorePhase = void 0;
349880
- exports2.gracefulCloseWithTreeKill = exports2.liveBrowserRegistry = exports2.REPO_SWITCH_REMOVED_FRACTION = exports2.MAX_MANIFEST_FILES = exports2.toDiffSummary = exports2.isLikelyTestFile = exports2.sanitizeRepoRelPath = exports2.sha256Hex = exports2.hashFiles = exports2.enumerateTestFiles = exports2.buildNextManifest = exports2.detectRepoSwitch = exports2.classifyImportDiff = exports2.parseImportManifest = exports2.validateAuthoredMobileTest = exports2.buildMobileRepoAuthorSystemRules = exports2.buildMobileRepoAuthorKickoff = exports2.buildMobileRepoAuthorContractMd = exports2.REPO_ANALYSIS_SCHEMA_VERSION = exports2.validateSourceEndpoints = exports2.validateAuthoredTest = exports2.validateRepoMapFile = exports2.validateRepoMemoryFile = exports2.buildTestingStandardsSection = exports2.buildRepoAuthorSystemRules = exports2.buildRepoAuthorKickoff = exports2.buildRepoAuthorContractMd = exports2.buildRepoMapSystemRules = exports2.buildRepoMapKickoff = exports2.buildRepoMapContractMd = exports2.buildRepoMemorySystemRules = exports2.buildRepoMemoryKickoff = exports2.buildRepoMemoryContractMd = exports2.runRepoAnalysisOnRunner = exports2.MOBILE_IMPORT_SCHEMA_VERSION = exports2.validateImportedMobileTest = exports2.buildMobileImportSystemRules = exports2.buildMobileImportKickoff = exports2.buildMobileImportContractMd = exports2.IMPORT_SCHEMA_VERSION = exports2.validateImportResultFile = exports2.validateImportedTest = exports2.buildImportSystemRules = exports2.buildImportKickoff = exports2.buildImportContractMd = exports2.MAX_ARCHIVE_BYTES = exports2.isUnsafeEntryName = exports2.extractZipSafely = exports2.downloadArchiveCapped = exports2.detectArchiveKind = void 0;
349881
- exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = exports2.modelSupportsVision = exports2.decideCanvasStrategy = exports2.decideAppRenderType = exports2.detectCanvasApp = exports2.detectRateLimitBlock = exports2.isSafeCanonicalRedirect = exports2.resolveBaseUrlPreflight = exports2.checkBaseUrlRedirect = exports2.stripJsonFences = exports2.scrubCredentials = exports2.withAIRetry = exports2.snapshotMatchingChildren = exports2.findNewChromiumChildPid = exports2.killProcessTree = void 0;
349953
+ exports2.resolveRefFromSnapshot = exports2.replayAuthRecipeIntoBrowser = exports2.runRegisterCaptureRun = exports2.runLoginCaptureRun = exports2.filterLoginCaptureCredentials = exports2.buildLoginCaptureCredentials = exports2.isReservedRunnerEnvVar = exports2.RESERVED_RUNNER_ENV_VARS = exports2.AUTH_MARKER_VERSION = exports2.buildHealAuthStamp = exports2.extractAuthRegion = exports2.upsertAuthBlock = exports2.buildAuthRecipe = exports2.commandToReplayStep = exports2.buildReplayPlan = exports2.absoluteRoute = exports2.renderReplaySettleArrow = exports2.renderReplayClickArrow = exports2.renderAuthFn = exports2.filterFeatureGroupsToPathScope = exports2.isAuthFlowRoute = exports2.deriveSurveyPathScope = exports2.canonicalizeRoute = exports2.codeUsesInboxHelper = exports2.codeUsesTestInboxIdentity = exports2.codeSelfProvisionsRegistration = exports2.executeGrokPerBatchHeal = exports2.executeGrokPerTestHeal = exports2.headerVal = exports2.classifyAuthType = exports2.isTextExcerptable = exports2.isStaticAssetByExt = exports2.isApiCall = exports2.buildApiCallSummary = exports2.computeMobileHealOutcome = exports2.detectMobileStepSecretLeak = exports2.redactMobileSecretsFromText = exports2.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports2.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports2.executeMobileLiveHeal = exports2.executeMobileTestWithHealing = exports2.executeMobileScriptDrivenHeal = exports2.prepareMobileScenarioAppState = exports2.runMobileGeneratePhase = exports2.runMobileAIPlanner = exports2.buildMobileSiteMap = exports2.formatMobileEvidenceForPlanner = exports2.MOBILE_SCENARIO_SYSTEM_PROMPT = exports2.MOBILE_SURVEY_SYSTEM_PROMPT = exports2.runMobileExplorePhase = void 0;
349954
+ exports2.MAX_MANIFEST_FILES = exports2.toDiffSummary = exports2.isLikelyTestFile = exports2.sanitizeRepoRelPath = exports2.sha256Hex = exports2.hashFiles = exports2.enumerateTestFiles = exports2.buildNextManifest = exports2.detectRepoSwitch = exports2.classifyImportDiff = exports2.parseImportManifest = exports2.validateAuthoredMobileTest = exports2.buildMobileRepoAuthorSystemRules = exports2.buildMobileRepoAuthorKickoff = exports2.buildMobileRepoAuthorContractMd = exports2.REPO_ANALYSIS_SCHEMA_VERSION = exports2.validateSourceEndpoints = exports2.validateAuthoredTest = exports2.validateRepoMapFile = exports2.validateRepoMemoryFile = exports2.buildTestingStandardsSection = exports2.buildRepoAuthorSystemRules = exports2.buildRepoAuthorKickoff = exports2.buildRepoAuthorContractMd = exports2.buildRepoMapSystemRules = exports2.buildRepoMapKickoff = exports2.buildRepoMapContractMd = exports2.buildRepoMemorySystemRules = exports2.buildRepoMemoryKickoff = exports2.buildRepoMemoryContractMd = exports2.runRepoAnalysisOnRunner = exports2.MOBILE_IMPORT_SCHEMA_VERSION = exports2.validateImportedMobileTest = exports2.buildMobileImportSystemRules = exports2.buildMobileImportKickoff = exports2.buildMobileImportContractMd = exports2.IMPORT_SCHEMA_VERSION = exports2.validateImportResultFile = exports2.validateImportedTest = exports2.buildImportSystemRules = exports2.buildImportKickoff = exports2.buildImportContractMd = exports2.MAX_ARCHIVE_BYTES = exports2.isUnsafeEntryName = exports2.extractZipSafely = exports2.downloadArchiveCapped = exports2.detectArchiveKind = exports2.acquireRepoForImport = exports2.runFrameworkImportOnRunner = exports2.getAgenticEngine = void 0;
349955
+ exports2.UNSUPPORTED_RENDERER_PREFIX = exports2.UnsupportedRendererError = exports2.buildCanvasModeAuthoringAddendum = exports2.buildUnsupportedRendererMessage = exports2.modelSupportsVision = exports2.decideCanvasStrategy = exports2.decideAppRenderType = exports2.detectCanvasApp = exports2.detectRateLimitBlock = exports2.isSafeCanonicalRedirect = exports2.resolveBaseUrlPreflight = exports2.checkBaseUrlRedirect = exports2.stripJsonFences = exports2.scrubCredentials = exports2.withAIRetry = exports2.snapshotMatchingChildren = exports2.findNewChromiumChildPid = exports2.killProcessTree = exports2.gracefulCloseWithTreeKill = exports2.liveBrowserRegistry = exports2.REPO_SWITCH_REMOVED_FRACTION = void 0;
349882
349956
  __exportStar(require_ai_provider(), exports2);
349883
349957
  __exportStar(require_mobile_types(), exports2);
349884
349958
  __exportStar(require_mobile_source_parser(), exports2);
@@ -349969,6 +350043,16 @@ var require_dist2 = __commonJS({
349969
350043
  return grok_batch_healer_js_1.executeGrokPerBatchHeal;
349970
350044
  } });
349971
350045
  __exportStar(require_playwright_code(), exports2);
350046
+ var inbox_stamp_js_1 = require_inbox_stamp();
350047
+ Object.defineProperty(exports2, "codeSelfProvisionsRegistration", { enumerable: true, get: function() {
350048
+ return inbox_stamp_js_1.codeSelfProvisionsRegistration;
350049
+ } });
350050
+ Object.defineProperty(exports2, "codeUsesTestInboxIdentity", { enumerable: true, get: function() {
350051
+ return inbox_stamp_js_1.codeUsesTestInboxIdentity;
350052
+ } });
350053
+ Object.defineProperty(exports2, "codeUsesInboxHelper", { enumerable: true, get: function() {
350054
+ return inbox_stamp_js_1.codeUsesInboxHelper;
350055
+ } });
349972
350056
  __exportStar(require_playwright_native(), exports2);
349973
350057
  __exportStar(require_regression_runner(), exports2);
349974
350058
  __exportStar(require_snapshot_parser(), exports2);
@@ -350298,7 +350382,7 @@ var require_package4 = __commonJS({
350298
350382
  "package.json"(exports2, module2) {
350299
350383
  module2.exports = {
350300
350384
  name: "@validate.qa/runner",
350301
- version: "1.0.57",
350385
+ version: "1.0.59",
350302
350386
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
350303
350387
  bin: {
350304
350388
  "validate-runner": "dist/cli.js"
@@ -351977,6 +352061,12 @@ function isKeyboardAlreadyHiddenError(err) {
351977
352061
  return /keyboard.*(not|already).*(shown|visible|present|open)|no.*keyboard/i.test(message);
351978
352062
  }
351979
352063
  async function dismissKeyboard(sessionPath, platform3) {
352064
+ try {
352065
+ const shown = await appiumRequest(`${sessionPath}/appium/device/is_keyboard_shown`, { method: "GET" });
352066
+ if (readValue(shown) === false) return;
352067
+ } catch (err) {
352068
+ if (isAppiumDriverDeadError(err)) throw err;
352069
+ }
351980
352070
  if (platform3 === "ANDROID") {
351981
352071
  await pressBack(sessionPath);
351982
352072
  return;
@@ -356560,11 +356650,17 @@ var MobileBridgeDriver = class {
356560
356650
  }
356561
356651
  /** Type `value`; pass `bounds` to tap-to-focus first when nothing is focused. */
356562
356652
  async type(value, bounds) {
356563
- return this.runAction(() => typeText(this.sessionPath, value, bounds));
356653
+ return this.runAction(async () => {
356654
+ await typeText(this.sessionPath, value, bounds);
356655
+ await hideKeyboardIfShown(this.sessionPath);
356656
+ });
356564
356657
  }
356565
356658
  /** Invoke the focused field's Enter/Done action without clearing its value. */
356566
356659
  async submitKeyboard(bounds) {
356567
- return this.runAction(() => submitKeyboard(this.sessionPath, bounds));
356660
+ return this.runAction(async () => {
356661
+ await submitKeyboard(this.sessionPath, bounds);
356662
+ await hideKeyboardIfShown(this.sessionPath);
356663
+ });
356568
356664
  }
356569
356665
  /** Clear the focused element; pass `bounds` to tap-to-focus first. */
356570
356666
  async clear(bounds) {
@@ -357915,7 +358011,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
357915
358011
  const codeUsesCreds = codeProvided && /TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin/.test(code);
357916
358012
  const tags = Array.isArray(run2.tags) ? run2.tags : [];
357917
358013
  const isHallucinationShape = codeProvided && !codeUsesCreds && tags.includes("@inline-login") && !tags.includes("@api");
357918
- const requireCreds = authPlan.needsLoginCreds && !legacyHeaderBypass && !isHallucinationShape;
358014
+ const selfProvisioningRegisterTest = codeProvided && !codeUsesCreds && tags.includes("@email-verify") && (0, import_runner_core13.codeSelfProvisionsRegistration)(code);
358015
+ const requireCreds = authPlan.needsLoginCreds && !legacyHeaderBypass && !isHallucinationShape && !selfProvisioningRegisterTest;
357919
358016
  if (requireCreds && !run2.testVariables?.["TEST_USER_EMAIL"]?.trim()) {
357920
358017
  preflightError = "Pre-flight failed: Missing TEST_USER_EMAIL. Tests that log in (tagged @auth, @needs-auth, @api, @auth-cookie-state, @auth-login-cookie, or @auth-login-bearer) require login credentials.";
357921
358018
  } else if (requireCreds && !run2.testVariables?.["TEST_USER_PASSWORD"]?.trim()) {
package/dist/cli.mjs CHANGED
@@ -5281,6 +5281,12 @@ var init_auth_state_freshness = __esm({
5281
5281
  function detectSkippedApiCoverage(steps) {
5282
5282
  const allSkipped = steps.length > 0 && steps.every((s) => s.status === "skipped");
5283
5283
  if (allSkipped) {
5284
+ if (steps.every((s) => s.skipKind === "fixme")) {
5285
+ return {
5286
+ detected: false,
5287
+ logLine: "All @api tests skipped, but EVERY skip is a deliberate fixme (intentional N/A \u2014 e.g. a UI-only redirect login) \u2014 NOT relabelling CONFIG_ISSUE"
5288
+ };
5289
+ }
5284
5290
  return {
5285
5291
  detected: true,
5286
5292
  error: (
@@ -72840,18 +72846,15 @@ var require_verification_inbox = __commonJS({
72840
72846
  return { code: cleaned, confidence: "keyword" };
72841
72847
  }
72842
72848
  }
72843
- const distinct = /* @__PURE__ */ new Set();
72844
72849
  for (const body of bodies) {
72845
72850
  if (!body)
72846
72851
  continue;
72847
72852
  for (const n of body.match(/\b[0-9]{4,8}\b/g) ?? []) {
72848
- if (!looksLikeYearOrDate(n))
72849
- distinct.add(n);
72853
+ if (looksLikeYearOrDate(n))
72854
+ continue;
72855
+ return { code: n, confidence: "fallback" };
72850
72856
  }
72851
72857
  }
72852
- if (distinct.size === 1) {
72853
- return { code: distinct.values().next().value ?? null, confidence: "fallback" };
72854
- }
72855
72858
  return { code: null, confidence: null };
72856
72859
  }
72857
72860
  function matchBestCode(line) {
@@ -73149,9 +73152,10 @@ var require_verification_inbox = __commonJS({
73149
73152
  const satisfies = (m) => {
73150
73153
  if (opts.require === "code")
73151
73154
  return m.code !== null && m.codeConfidence !== "fallback";
73155
+ const linkOk = (l) => l !== null && (!opts.linkAcceptable || opts.linkAcceptable(l));
73152
73156
  if (opts.require === "link")
73153
- return m.link !== null;
73154
- return m.hasVerificationSignal;
73157
+ return linkOk(m.link);
73158
+ return m.code !== null && m.codeConfidence !== "fallback" || linkOk(m.link);
73155
73159
  };
73156
73160
  let weakest = null;
73157
73161
  const check2 = async () => {
@@ -73198,7 +73202,7 @@ var require_verification_inbox = __commonJS({
73198
73202
  const since = new Date(Math.max(0, opts.sinceMs));
73199
73203
  const lock = await client.getMailboxLock(mailbox);
73200
73204
  const results = [];
73201
- const taggedRecipient = isPlusTagged(opts.toAddress);
73205
+ const demoteMismatch = isPlusTagged(opts.toAddress) && Boolean(opts.demoteFilterMismatch);
73202
73206
  try {
73203
73207
  try {
73204
73208
  await client.noop();
@@ -73244,7 +73248,7 @@ var require_verification_inbox = __commonJS({
73244
73248
  continue;
73245
73249
  const filterActive = Boolean(opts.fromMatch || opts.subjectMatch);
73246
73250
  const filterMatched = (!opts.fromMatch || matchField(parsed.from?.text, opts.fromMatch)) && (!opts.subjectMatch || matchField(parsed.subject, opts.subjectMatch));
73247
- if (filterActive && !filterMatched && !taggedRecipient) {
73251
+ if (filterActive && !filterMatched && !demoteMismatch) {
73248
73252
  if (opts.stats)
73249
73253
  opts.stats.filteredOut += 1;
73250
73254
  continue;
@@ -73382,7 +73386,8 @@ var require_inbox_tools = __commonJS({
73382
73386
  // Captured at provision (needs a live reader); 0 = use the sinceMs floor.
73383
73387
  sinceUid: 0,
73384
73388
  sinceUidValidity: 0,
73385
- knownLinks: /* @__PURE__ */ new Set()
73389
+ knownLinks: /* @__PURE__ */ new Set(),
73390
+ demoteFilterMismatch: opts.demoteFilterMismatch ?? false
73386
73391
  };
73387
73392
  }
73388
73393
  exports.PROVISION_IDENTITY_TOOL = {
@@ -73570,6 +73575,7 @@ var require_inbox_tools = __commonJS({
73570
73575
  sinceUidValidity: ctx.state.sinceUidValidity,
73571
73576
  fromMatch: asStr(args.senderMatch),
73572
73577
  subjectMatch: asStr(args.subjectMatch),
73578
+ demoteFilterMismatch: ctx.state.demoteFilterMismatch,
73573
73579
  limit: 10
73574
73580
  });
73575
73581
  return ok({
@@ -73594,6 +73600,7 @@ var require_inbox_tools = __commonJS({
73594
73600
  sinceUidValidity: ctx.state.sinceUidValidity,
73595
73601
  fromMatch: asStr(args.senderMatch),
73596
73602
  subjectMatch: asStr(args.subjectMatch),
73603
+ demoteFilterMismatch: ctx.state.demoteFilterMismatch,
73597
73604
  maxWaitMs: maxWaitSeconds * 1e3,
73598
73605
  stats
73599
73606
  });
@@ -73740,7 +73747,7 @@ var require_inbox_tools = __commonJS({
73740
73747
  const u = new URL(raw);
73741
73748
  if (u.protocol !== "http:" && u.protocol !== "https:")
73742
73749
  return false;
73743
- host = u.hostname.toLowerCase().replace(/^\[|\]$/g, "");
73750
+ host = u.hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.+$/, "");
73744
73751
  } catch {
73745
73752
  return false;
73746
73753
  }
@@ -73756,7 +73763,7 @@ var require_inbox_tools = __commonJS({
73756
73763
  const dotted = host.match(/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/);
73757
73764
  if (dotted && !ipv4IsPublic(dotted[1]))
73758
73765
  return false;
73759
- const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
73766
+ const mapped = host.match(/^::(?:ffff:)?([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
73760
73767
  if (mapped) {
73761
73768
  const hi = parseInt(mapped[1], 16);
73762
73769
  const lo = parseInt(mapped[2], 16);
@@ -75519,12 +75526,11 @@ ${existingScreens.slice(0, 100).map((s) => `- ${s.screenId}${s.name ? ` "${s.nam
75519
75526
  keyboardRecoveryScreenId = currentScreenId ?? null;
75520
75527
  keyboardRecoverySameScreenTurns = 0;
75521
75528
  }
75522
- const keyboardLikelyOpen = (0, mobile_keyboard_js_1.hasFocusedEditableElement)(getLatest()?.screen.elements ?? []);
75523
- if (keyboardRecoverySameScreenTurns >= 4 && keyboardLikelyOpen && iteration - lastKeyboardRecoveryIteration > 4) {
75529
+ if (keyboardRecoverySameScreenTurns >= 4 && iteration - lastKeyboardRecoveryIteration > 4) {
75524
75530
  lastKeyboardRecoveryIteration = iteration;
75525
75531
  try {
75526
75532
  await (0, mobile_keyboard_js_1.dismissKeyboardForRetry)(driver);
75527
- log2(" Auto-dismissed the on-screen keyboard (several turns stalled on this screen with a focused input).");
75533
+ log2(" Auto-dismissed the on-screen keyboard (several turns stalled on this screen).");
75528
75534
  transientDirectives.push("HARNESS NOTE: the on-screen keyboard was just auto-dismissed because multiple turns stalled on this screen \u2014 controls it occluded (e.g. the form submit button) may now be visible. Call mobile_snapshot, then TAP the commit/submit control instead of re-filling fields.");
75529
75535
  } catch (error2) {
75530
75536
  (0, mobile_driver_errors_js_1.rethrowIfTerminalMobileDriverError)(error2);
@@ -84300,6 +84306,7 @@ ${args.directive}`);
84300
84306
  aiResponseCount += 1;
84301
84307
  const parsed = parseHealVerdict(content);
84302
84308
  if (!parsed) {
84309
+ options.onProgress?.(`Phase 1 attempt ${attempt}: response was not a valid verdict \u2014 granting a recovery turn`);
84303
84310
  markRecoverableTurn();
84304
84311
  pendingDirective = "Your last response was not valid JSON matching the verdict schema. Return exactly one JSON verdict object.";
84305
84312
  continue;
@@ -86417,6 +86424,8 @@ var require_inbox_stamp = __commonJS({
86417
86424
  exports.INBOX_HELPER_TOKEN_RE = exports.INBOX_HELPER_NAME = exports.INBOX_REGION_RE = exports.INBOX_MARKER_VERSION = void 0;
86418
86425
  exports.codeUsesInboxHelper = codeUsesInboxHelper;
86419
86426
  exports.stripInboxRegions = stripInboxRegions;
86427
+ exports.codeUsesTestInboxIdentity = codeUsesTestInboxIdentity;
86428
+ exports.codeSelfProvisionsRegistration = codeSelfProvisionsRegistration2;
86420
86429
  exports.renderInboxFn = renderInboxFn;
86421
86430
  exports.upsertInboxBlock = upsertInboxBlock;
86422
86431
  var auth_recipe_js_1 = require_auth_recipe();
@@ -86431,12 +86440,54 @@ var require_inbox_stamp = __commonJS({
86431
86440
  return false;
86432
86441
  return exports.INBOX_HELPER_TOKEN_RE.test(code) || /VALIDATE_QA_INBOX/.test(code);
86433
86442
  }
86443
+ var INBOX_REGION_START = "// >>> VALIDATE_QA_INBOX";
86444
+ var INBOX_REGION_END = "// <<< VALIDATE_QA_INBOX";
86434
86445
  function stripInboxRegions(code) {
86435
- return code.replace(exports.INBOX_REGION_RE, "");
86446
+ if (!code || code.indexOf(INBOX_REGION_START) === -1)
86447
+ return code;
86448
+ let out = "";
86449
+ let i = 0;
86450
+ while (i < code.length) {
86451
+ const start = code.indexOf(INBOX_REGION_START, i);
86452
+ if (start === -1) {
86453
+ out += code.slice(i);
86454
+ break;
86455
+ }
86456
+ const end = code.indexOf(INBOX_REGION_END, start + INBOX_REGION_START.length);
86457
+ if (end === -1) {
86458
+ out += code.slice(i);
86459
+ break;
86460
+ }
86461
+ let lineEnd = code.indexOf("\n", end);
86462
+ lineEnd = lineEnd === -1 ? code.length : lineEnd + 1;
86463
+ out += code.slice(i, start);
86464
+ i = lineEnd;
86465
+ }
86466
+ return out;
86467
+ }
86468
+ var TEST_INBOX_IDENTITY_RE = /VQA_TEST_INBOX_EMAIL|VQA_TEST_ACCOUNT_PASSWORD/;
86469
+ function codeUsesTestInboxIdentity(code) {
86470
+ if (!code)
86471
+ return false;
86472
+ return TEST_INBOX_IDENTITY_RE.test(stripInboxRegions(code));
86473
+ }
86474
+ function codeSelfProvisionsRegistration2(code) {
86475
+ return codeUsesTestInboxIdentity(code);
86436
86476
  }
86437
86477
  function countInboxRegions(code) {
86438
- const m = code.match(exports.INBOX_REGION_RE);
86439
- return m ? m.length : 0;
86478
+ let count = 0;
86479
+ let i = 0;
86480
+ while (i < code.length) {
86481
+ const start = code.indexOf(INBOX_REGION_START, i);
86482
+ if (start === -1)
86483
+ break;
86484
+ const end = code.indexOf(INBOX_REGION_END, start + INBOX_REGION_START.length);
86485
+ if (end === -1)
86486
+ break;
86487
+ count++;
86488
+ i = end + INBOX_REGION_END.length;
86489
+ }
86490
+ return count;
86440
86491
  }
86441
86492
  function renderInboxFn() {
86442
86493
  const lines = [
@@ -86530,7 +86581,12 @@ var require_test_inbox_broker = __commonJS({
86530
86581
  return { found: true, code, link, subject: msg.subject, from: msg.from };
86531
86582
  }
86532
86583
  async function startTestInboxBroker(opts) {
86533
- const identity = mintTestRunInboxIdentity(opts.config, opts);
86584
+ const executionNonce = (0, node_crypto_1.randomBytes)(4).toString("hex");
86585
+ const identity = mintTestRunInboxIdentity(opts.config, {
86586
+ baseUrl: opts.baseUrl,
86587
+ projectId: opts.projectId,
86588
+ runId: `${opts.runId}#${executionNonce}`
86589
+ });
86534
86590
  const reader = new verification_inbox_js_1.VerificationInboxReader(opts.config);
86535
86591
  const sinceMs = Date.now();
86536
86592
  let sinceUid = 0;
@@ -86570,6 +86626,12 @@ var require_test_inbox_broker = __commonJS({
86570
86626
  sinceUidValidity,
86571
86627
  maxWaitMs: timeoutMs,
86572
86628
  require: require2,
86629
+ // The plus-tag recipient is unique to this run, so an unexpected ESP
86630
+ // sender is still this run's mail — demote, don't drop.
86631
+ demoteFilterMismatch: true,
86632
+ // Don't let a private/internal link end a `require:'link'` wait early —
86633
+ // keep polling for a public one (SSRF parity with the post-wait check).
86634
+ linkAcceptable: inbox_tools_js_1.isPublicHttpUrl,
86573
86635
  stats
86574
86636
  });
86575
86637
  const publicLink = msg && msg.link && (0, inbox_tools_js_1.isPublicHttpUrl)(msg.link) ? msg.link : null;
@@ -86704,7 +86766,8 @@ var require_playwright_native = __commonJS({
86704
86766
  runDir = (0, path_1.join)(RUNS_DIR, (0, crypto_1.randomUUID)());
86705
86767
  await (0, promises_1.mkdir)(runDir, { recursive: true });
86706
86768
  const isApiTest2 = playwrightCode.includes("@api") || playwrightCode.includes("{ request }") && !playwrightCode.includes("{ page");
86707
- const usesInboxHelper = !isApiTest2 && (0, inbox_stamp_js_1.codeUsesInboxHelper)(playwrightCode);
86769
+ const usesRealInboxWait = !isApiTest2 && (0, inbox_stamp_js_1.codeUsesInboxHelper)(playwrightCode);
86770
+ const selfProvisionsRegistration = !isApiTest2 && (0, inbox_stamp_js_1.codeSelfProvisionsRegistration)(playwrightCode);
86708
86771
  const effectiveStandingMode = isApiTest2 ? authStandingMode ?? "legacy-if-configured" : "none";
86709
86772
  const resolvedExtraHeaders = (0, api_auth_headers_js_1.resolveApiAuthHeaders)({
86710
86773
  explicit: extraHttpHeaders,
@@ -86951,7 +87014,7 @@ test.afterEach(async ({ page }, testInfo) => {
86951
87014
  module.exports = {
86952
87015
  testDir: ${JSON.stringify(absTestDir)},
86953
87016
  outputDir: ${JSON.stringify(absOutputDir)},
86954
- timeout: ${usesInboxHelper ? 24e4 : 6e4},
87017
+ timeout: ${usesRealInboxWait ? 24e4 : 6e4},
86955
87018
  retries: 0,
86956
87019
  use: {
86957
87020
  baseURL: ${JSON.stringify(baseUrl)},
@@ -86977,7 +87040,7 @@ module.exports = {
86977
87040
  const isolation = await (0, chromium_isolation_js_1.createChromiumIsolation)(runDir);
86978
87041
  const processClaimEpoch = Number(process.env.CLAIM_EPOCH);
86979
87042
  const callbackClaimEpoch = Number.isSafeInteger(validateContext?.claimEpoch) ? validateContext?.claimEpoch : Number.isSafeInteger(processClaimEpoch) && processClaimEpoch >= 0 ? processClaimEpoch : void 0;
86980
- if (usesInboxHelper && validateContext?.runId) {
87043
+ if (selfProvisionsRegistration && validateContext?.runId) {
86981
87044
  const inboxConfig = (0, verification_inbox_js_1.loadVerificationInboxConfig)();
86982
87045
  if (!inboxConfig) {
86983
87046
  logs2.push("\u{1F4EA} Test uses __vqaWaitForEmail but this runner has no verification inbox configured \u2014 the helper will fail as CONFIG_ISSUE");
@@ -86995,8 +87058,8 @@ module.exports = {
86995
87058
  logs2.push(`\u26A0 Inbox broker failed to start: ${error3 instanceof Error ? error3.message : String(error3)}`);
86996
87059
  }
86997
87060
  }
86998
- } else if (usesInboxHelper) {
86999
- logs2.push("\u{1F4EA} Test uses __vqaWaitForEmail but no run callback context is available \u2014 the helper will fail as CONFIG_ISSUE");
87061
+ } else if (selfProvisionsRegistration) {
87062
+ logs2.push("\u{1F4EA} Test self-provisions a registration identity (VQA_TEST_* / __vqaWaitForEmail) but no run callback context is available \u2014 the broker cannot start, so those values will be undefined at runtime");
87000
87063
  }
87001
87064
  if (validateContext?.runId && validateContext.serverUrl && validateContext.authHeader) {
87002
87065
  try {
@@ -87037,16 +87100,20 @@ module.exports = {
87037
87100
  ...process.env.SHARED_AUTH_INJECT ? { SHARED_AUTH_INJECT: process.env.SHARED_AUTH_INJECT } : {},
87038
87101
  // Register-and-verify tests: the run's minted registration identity. An
87039
87102
  // address + a DERIVED throwaway-app password — never the IMAP secret,
87040
- // which stays in this parent (test-inbox-broker.ts). Useless without the
87041
- // loopback proxy the helper reads mail through, hence the guard.
87042
- ...inboxBroker && callbackProxy ? inboxBroker.childEnv() : {}
87103
+ // which stays in this parent (test-inbox-broker.ts). Injected whenever the
87104
+ // broker started, NOT gated on the callback proxy: a signup that registers
87105
+ // with this identity but needs no email verification uses the env yet never
87106
+ // touches the proxy — gating on the proxy left it with undefined creds. A
87107
+ // helper test still needs the proxy, and simply fails as CONFIG_ISSUE
87108
+ // without it; leaking the throwaway address/password to that child is inert.
87109
+ ...inboxBroker ? inboxBroker.childEnv() : {}
87043
87110
  };
87044
87111
  delete childTestVariables.API_AUTH_HEADERS;
87045
87112
  logs2.push("\u{1F680} Spawning playwright test...");
87046
87113
  const stopLiveBrowserPolling = liveBrowserStatePath && liveBrowserHandle ? startLiveBrowserStatePolling(liveBrowserStatePath, liveBrowserHandle, onLiveBrowserSnapshot) : null;
87047
87114
  let playResult;
87048
87115
  try {
87049
- playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv, usesInboxHelper ? 3e5 : void 0);
87116
+ playResult = await runPlaywright(configPath, childTestVariables, trustedRuntimeEnv, usesRealInboxWait ? 3e5 : void 0);
87050
87117
  } finally {
87051
87118
  await stopLiveBrowserPolling?.();
87052
87119
  }
@@ -94798,7 +94865,7 @@ var require_discover_generator = __commonJS({
94798
94865
  var test_inbox_broker_js_1 = require_test_inbox_broker();
94799
94866
  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;
94800
94867
  function scenarioLooksLikeRegistration(scenario) {
94801
- const hay = `${scenario.name ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")}`;
94868
+ const hay = `${scenario.name ?? ""} ${scenario.goal ?? ""} ${scenario.description ?? ""} ${scenario.entryRoute ?? ""} ${(scenario.targetPages ?? []).join(" ")}`;
94802
94869
  return REGISTER_SCENARIO_RE.test(hay);
94803
94870
  }
94804
94871
  function buildEmailVerifyGenBlock(genEmail, genPassword) {
@@ -97751,7 +97818,7 @@ ${tailBlock}`;
97751
97818
  const genEmail = (0, verification_inbox_js_1.applyPlusTag)(inboxGenConfig.address, genTag);
97752
97819
  const genPassword = (0, verification_inbox_js_1.deriveRegistrationPassword)(genEmail);
97753
97820
  genInboxReader = new verification_inbox_js_1.VerificationInboxReader(inboxGenConfig);
97754
- genInboxState = (0, inbox_tools_js_1.createInboxRunState)(inboxGenConfig, { tag: genTag, passwordKey: test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV });
97821
+ genInboxState = (0, inbox_tools_js_1.createInboxRunState)(inboxGenConfig, { tag: genTag, passwordKey: test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV, demoteFilterMismatch: true });
97755
97822
  genInboxCredentials = { ...credentials, [test_inbox_broker_js_1.TEST_INBOX_EMAIL_ENV]: genEmail, [test_inbox_broker_js_1.TEST_INBOX_PASSWORD_ENV]: genPassword };
97756
97823
  genInboxSecret = genPassword;
97757
97824
  openaiTools.push(...inbox_tools_js_1.INBOX_TOOLS);
@@ -98313,6 +98380,13 @@ Do NOT assert that the button/tab/heading you interacted with is merely visible
98313
98380
  logs2.push(` Strength gate: block persists after revision \u2014 tagged @needs-review (proves no outcome)`);
98314
98381
  }
98315
98382
  }
98383
+ const finalSelfProvisions = (0, inbox_stamp_js_1.codeSelfProvisionsRegistration)(result.playwrightCode);
98384
+ if (finalSelfProvisions) {
98385
+ if (!result.tags.includes("@email-verify"))
98386
+ result.tags.push("@email-verify");
98387
+ } else if (inboxGenConfig) {
98388
+ logs2.push(` \u26A0 Email-verify generation was armed but the emitted test neither calls __vqaWaitForEmail nor uses the VQA_TEST_* runtime identity \u2014 it may have hardcoded values (stale on re-run).`);
98389
+ }
98316
98390
  const elapsed = Date.now() - startTime;
98317
98391
  logs2.push(` Scenario test generated in ${iteration} iterations (${elapsed}ms, ${verifyCallCount} verify calls)`);
98318
98392
  } else {
@@ -349881,9 +349955,9 @@ var require_dist2 = __commonJS({
349881
349955
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
349882
349956
  };
349883
349957
  Object.defineProperty(exports, "__esModule", { value: true });
349884
- exports.acquireRepoForImport = exports.runFrameworkImportOnRunner = exports.getAgenticEngine = exports.resolveRefFromSnapshot = exports.replayAuthRecipeIntoBrowser = exports.runRegisterCaptureRun = exports.runLoginCaptureRun = exports.filterLoginCaptureCredentials = exports.buildLoginCaptureCredentials = exports.isReservedRunnerEnvVar = exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = exports.buildHealAuthStamp = exports.extractAuthRegion = exports.upsertAuthBlock = exports.buildAuthRecipe = exports.commandToReplayStep = exports.buildReplayPlan = exports.absoluteRoute = exports.renderReplaySettleArrow = exports.renderReplayClickArrow = exports.renderAuthFn = exports.filterFeatureGroupsToPathScope = exports.isAuthFlowRoute = exports.deriveSurveyPathScope = exports.canonicalizeRoute = exports.executeGrokPerBatchHeal = exports.executeGrokPerTestHeal = exports.headerVal = exports.classifyAuthType = exports.isTextExcerptable = exports.isStaticAssetByExt = exports.isApiCall = exports.buildApiCallSummary = exports.computeMobileHealOutcome = exports.detectMobileStepSecretLeak = exports.redactMobileSecretsFromText = exports.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports.executeMobileLiveHeal = exports.executeMobileTestWithHealing = exports.executeMobileScriptDrivenHeal = exports.prepareMobileScenarioAppState = exports.runMobileGeneratePhase = exports.runMobileAIPlanner = exports.buildMobileSiteMap = exports.formatMobileEvidenceForPlanner = exports.MOBILE_SCENARIO_SYSTEM_PROMPT = exports.MOBILE_SURVEY_SYSTEM_PROMPT = exports.runMobileExplorePhase = void 0;
349885
- exports.gracefulCloseWithTreeKill = exports.liveBrowserRegistry = exports.REPO_SWITCH_REMOVED_FRACTION = exports.MAX_MANIFEST_FILES = exports.toDiffSummary = exports.isLikelyTestFile = exports.sanitizeRepoRelPath = exports.sha256Hex = exports.hashFiles = exports.enumerateTestFiles = exports.buildNextManifest = exports.detectRepoSwitch = exports.classifyImportDiff = exports.parseImportManifest = exports.validateAuthoredMobileTest = exports.buildMobileRepoAuthorSystemRules = exports.buildMobileRepoAuthorKickoff = exports.buildMobileRepoAuthorContractMd = exports.REPO_ANALYSIS_SCHEMA_VERSION = exports.validateSourceEndpoints = exports.validateAuthoredTest = exports.validateRepoMapFile = exports.validateRepoMemoryFile = exports.buildTestingStandardsSection = exports.buildRepoAuthorSystemRules = exports.buildRepoAuthorKickoff = exports.buildRepoAuthorContractMd = exports.buildRepoMapSystemRules = exports.buildRepoMapKickoff = exports.buildRepoMapContractMd = exports.buildRepoMemorySystemRules = exports.buildRepoMemoryKickoff = exports.buildRepoMemoryContractMd = exports.runRepoAnalysisOnRunner = exports.MOBILE_IMPORT_SCHEMA_VERSION = exports.validateImportedMobileTest = exports.buildMobileImportSystemRules = exports.buildMobileImportKickoff = exports.buildMobileImportContractMd = exports.IMPORT_SCHEMA_VERSION = exports.validateImportResultFile = exports.validateImportedTest = exports.buildImportSystemRules = exports.buildImportKickoff = exports.buildImportContractMd = exports.MAX_ARCHIVE_BYTES = exports.isUnsafeEntryName = exports.extractZipSafely = exports.downloadArchiveCapped = exports.detectArchiveKind = void 0;
349886
- exports.UNSUPPORTED_RENDERER_PREFIX = exports.UnsupportedRendererError = exports.buildCanvasModeAuthoringAddendum = exports.buildUnsupportedRendererMessage = exports.modelSupportsVision = exports.decideCanvasStrategy = exports.decideAppRenderType = exports.detectCanvasApp = exports.detectRateLimitBlock = exports.isSafeCanonicalRedirect = exports.resolveBaseUrlPreflight = exports.checkBaseUrlRedirect = exports.stripJsonFences = exports.scrubCredentials = exports.withAIRetry = exports.snapshotMatchingChildren = exports.findNewChromiumChildPid = exports.killProcessTree = void 0;
349958
+ exports.resolveRefFromSnapshot = exports.replayAuthRecipeIntoBrowser = exports.runRegisterCaptureRun = exports.runLoginCaptureRun = exports.filterLoginCaptureCredentials = exports.buildLoginCaptureCredentials = exports.isReservedRunnerEnvVar = exports.RESERVED_RUNNER_ENV_VARS = exports.AUTH_MARKER_VERSION = exports.buildHealAuthStamp = exports.extractAuthRegion = exports.upsertAuthBlock = exports.buildAuthRecipe = exports.commandToReplayStep = exports.buildReplayPlan = exports.absoluteRoute = exports.renderReplaySettleArrow = exports.renderReplayClickArrow = exports.renderAuthFn = exports.filterFeatureGroupsToPathScope = exports.isAuthFlowRoute = exports.deriveSurveyPathScope = exports.canonicalizeRoute = exports.codeUsesInboxHelper = exports.codeUsesTestInboxIdentity = exports.codeSelfProvisionsRegistration = exports.executeGrokPerBatchHeal = exports.executeGrokPerTestHeal = exports.headerVal = exports.classifyAuthType = exports.isTextExcerptable = exports.isStaticAssetByExt = exports.isApiCall = exports.buildApiCallSummary = exports.computeMobileHealOutcome = exports.detectMobileStepSecretLeak = exports.redactMobileSecretsFromText = exports.MOBILE_LIVE_HEAL_SYSTEM_PROMPT = exports.MOBILE_SCRIPT_HEAL_SYSTEM_PROMPT = exports.executeMobileLiveHeal = exports.executeMobileTestWithHealing = exports.executeMobileScriptDrivenHeal = exports.prepareMobileScenarioAppState = exports.runMobileGeneratePhase = exports.runMobileAIPlanner = exports.buildMobileSiteMap = exports.formatMobileEvidenceForPlanner = exports.MOBILE_SCENARIO_SYSTEM_PROMPT = exports.MOBILE_SURVEY_SYSTEM_PROMPT = exports.runMobileExplorePhase = void 0;
349959
+ exports.MAX_MANIFEST_FILES = exports.toDiffSummary = exports.isLikelyTestFile = exports.sanitizeRepoRelPath = exports.sha256Hex = exports.hashFiles = exports.enumerateTestFiles = exports.buildNextManifest = exports.detectRepoSwitch = exports.classifyImportDiff = exports.parseImportManifest = exports.validateAuthoredMobileTest = exports.buildMobileRepoAuthorSystemRules = exports.buildMobileRepoAuthorKickoff = exports.buildMobileRepoAuthorContractMd = exports.REPO_ANALYSIS_SCHEMA_VERSION = exports.validateSourceEndpoints = exports.validateAuthoredTest = exports.validateRepoMapFile = exports.validateRepoMemoryFile = exports.buildTestingStandardsSection = exports.buildRepoAuthorSystemRules = exports.buildRepoAuthorKickoff = exports.buildRepoAuthorContractMd = exports.buildRepoMapSystemRules = exports.buildRepoMapKickoff = exports.buildRepoMapContractMd = exports.buildRepoMemorySystemRules = exports.buildRepoMemoryKickoff = exports.buildRepoMemoryContractMd = exports.runRepoAnalysisOnRunner = exports.MOBILE_IMPORT_SCHEMA_VERSION = exports.validateImportedMobileTest = exports.buildMobileImportSystemRules = exports.buildMobileImportKickoff = exports.buildMobileImportContractMd = exports.IMPORT_SCHEMA_VERSION = exports.validateImportResultFile = exports.validateImportedTest = exports.buildImportSystemRules = exports.buildImportKickoff = exports.buildImportContractMd = exports.MAX_ARCHIVE_BYTES = exports.isUnsafeEntryName = exports.extractZipSafely = exports.downloadArchiveCapped = exports.detectArchiveKind = exports.acquireRepoForImport = exports.runFrameworkImportOnRunner = exports.getAgenticEngine = void 0;
349960
+ exports.UNSUPPORTED_RENDERER_PREFIX = exports.UnsupportedRendererError = exports.buildCanvasModeAuthoringAddendum = exports.buildUnsupportedRendererMessage = exports.modelSupportsVision = exports.decideCanvasStrategy = exports.decideAppRenderType = exports.detectCanvasApp = exports.detectRateLimitBlock = exports.isSafeCanonicalRedirect = exports.resolveBaseUrlPreflight = exports.checkBaseUrlRedirect = exports.stripJsonFences = exports.scrubCredentials = exports.withAIRetry = exports.snapshotMatchingChildren = exports.findNewChromiumChildPid = exports.killProcessTree = exports.gracefulCloseWithTreeKill = exports.liveBrowserRegistry = exports.REPO_SWITCH_REMOVED_FRACTION = void 0;
349887
349961
  __exportStar(require_ai_provider(), exports);
349888
349962
  __exportStar(require_mobile_types(), exports);
349889
349963
  __exportStar(require_mobile_source_parser(), exports);
@@ -349974,6 +350048,16 @@ var require_dist2 = __commonJS({
349974
350048
  return grok_batch_healer_js_1.executeGrokPerBatchHeal;
349975
350049
  } });
349976
350050
  __exportStar(require_playwright_code(), exports);
350051
+ var inbox_stamp_js_1 = require_inbox_stamp();
350052
+ Object.defineProperty(exports, "codeSelfProvisionsRegistration", { enumerable: true, get: function() {
350053
+ return inbox_stamp_js_1.codeSelfProvisionsRegistration;
350054
+ } });
350055
+ Object.defineProperty(exports, "codeUsesTestInboxIdentity", { enumerable: true, get: function() {
350056
+ return inbox_stamp_js_1.codeUsesTestInboxIdentity;
350057
+ } });
350058
+ Object.defineProperty(exports, "codeUsesInboxHelper", { enumerable: true, get: function() {
350059
+ return inbox_stamp_js_1.codeUsesInboxHelper;
350060
+ } });
349977
350061
  __exportStar(require_playwright_native(), exports);
349978
350062
  __exportStar(require_regression_runner(), exports);
349979
350063
  __exportStar(require_snapshot_parser(), exports);
@@ -350303,7 +350387,7 @@ var require_package4 = __commonJS({
350303
350387
  "package.json"(exports, module) {
350304
350388
  module.exports = {
350305
350389
  name: "@validate.qa/runner",
350306
- version: "1.0.57",
350390
+ version: "1.0.59",
350307
350391
  description: "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
350308
350392
  bin: {
350309
350393
  "validate-runner": "dist/cli.js"
@@ -351982,6 +352066,12 @@ function isKeyboardAlreadyHiddenError(err) {
351982
352066
  return /keyboard.*(not|already).*(shown|visible|present|open)|no.*keyboard/i.test(message);
351983
352067
  }
351984
352068
  async function dismissKeyboard(sessionPath, platform3) {
352069
+ try {
352070
+ const shown = await appiumRequest(`${sessionPath}/appium/device/is_keyboard_shown`, { method: "GET" });
352071
+ if (readValue(shown) === false) return;
352072
+ } catch (err) {
352073
+ if (isAppiumDriverDeadError(err)) throw err;
352074
+ }
351985
352075
  if (platform3 === "ANDROID") {
351986
352076
  await pressBack(sessionPath);
351987
352077
  return;
@@ -356568,11 +356658,17 @@ var MobileBridgeDriver = class {
356568
356658
  }
356569
356659
  /** Type `value`; pass `bounds` to tap-to-focus first when nothing is focused. */
356570
356660
  async type(value, bounds) {
356571
- return this.runAction(() => typeText(this.sessionPath, value, bounds));
356661
+ return this.runAction(async () => {
356662
+ await typeText(this.sessionPath, value, bounds);
356663
+ await hideKeyboardIfShown(this.sessionPath);
356664
+ });
356572
356665
  }
356573
356666
  /** Invoke the focused field's Enter/Done action without clearing its value. */
356574
356667
  async submitKeyboard(bounds) {
356575
- return this.runAction(() => submitKeyboard(this.sessionPath, bounds));
356668
+ return this.runAction(async () => {
356669
+ await submitKeyboard(this.sessionPath, bounds);
356670
+ await hideKeyboardIfShown(this.sessionPath);
356671
+ });
356576
356672
  }
356577
356673
  /** Clear the focused element; pass `bounds` to tap-to-focus first. */
356578
356674
  async clear(bounds) {
@@ -357923,7 +358019,8 @@ async function executeRun(run2, opts, headers, requestLiveBrowserHeartbeat = ()
357923
358019
  const codeUsesCreds = codeProvided && /TEST_USER_EMAIL|TEST_USER_PASSWORD|__vqaLogin/.test(code);
357924
358020
  const tags = Array.isArray(run2.tags) ? run2.tags : [];
357925
358021
  const isHallucinationShape = codeProvided && !codeUsesCreds && tags.includes("@inline-login") && !tags.includes("@api");
357926
- const requireCreds = authPlan.needsLoginCreds && !legacyHeaderBypass && !isHallucinationShape;
358022
+ const selfProvisioningRegisterTest = codeProvided && !codeUsesCreds && tags.includes("@email-verify") && (0, import_runner_core13.codeSelfProvisionsRegistration)(code);
358023
+ const requireCreds = authPlan.needsLoginCreds && !legacyHeaderBypass && !isHallucinationShape && !selfProvisioningRegisterTest;
357927
358024
  if (requireCreds && !run2.testVariables?.["TEST_USER_EMAIL"]?.trim()) {
357928
358025
  preflightError = "Pre-flight failed: Missing TEST_USER_EMAIL. Tests that log in (tagged @auth, @needs-auth, @api, @auth-cookie-state, @auth-login-cookie, or @auth-login-bearer) require login credentials.";
357929
358026
  } else if (requireCreds && !run2.testVariables?.["TEST_USER_PASSWORD"]?.trim()) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@validate.qa/runner",
3
- "version": "1.0.57",
3
+ "version": "1.0.59",
4
4
  "description": "validate local test runner - execute Playwright tests on your machine, connected to validate.qa cloud.",
5
5
  "bin": {
6
6
  "validate-runner": "dist/cli.js"