@validate.qa/runner 1.0.100 → 1.0.102

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 (31) hide show
  1. package/README.md +3 -0
  2. package/dist/{chunk-MBKGTELT.mjs → chunk-3JM6XTSZ.mjs} +1862 -530
  3. package/dist/{chunk-2XD63NU5.mjs → chunk-4WLP3RWN.mjs} +941 -86
  4. package/dist/{chunk-MHFP3QXU.mjs → chunk-5DFBEL72.mjs} +1 -1
  5. package/dist/{chunk-II7QW7MZ.mjs → chunk-BXLEPT4M.mjs} +31 -18
  6. package/dist/{chunk-MAO6ADAO.mjs → chunk-CBVHPYVJ.mjs} +62 -2
  7. package/dist/{chunk-5P4ANVDY.mjs → chunk-DN3O6EYT.mjs} +43073 -23357
  8. package/dist/{chunk-GIIIVO2W.mjs → chunk-E2RRAE27.mjs} +155 -11
  9. package/dist/chunk-E2VEMBQQ.mjs +291 -0
  10. package/dist/{chunk-HMUC3DKS.mjs → chunk-HJ6FKHMM.mjs} +2281 -2
  11. package/dist/{chunk-WDXLAZ2J.mjs → chunk-PWFWMVZU.mjs} +3 -2
  12. package/dist/{chunk-6NIT2CCH.mjs → chunk-ZZYBRIBT.mjs} +18 -12
  13. package/dist/cli.js +139723 -114754
  14. package/dist/cli.mjs +536 -247
  15. package/dist/credential-mcp-server.js +1761 -358
  16. package/dist/credential-mcp-server.mjs +3 -3
  17. package/dist/discovery-mcp-server.js +1874 -379
  18. package/dist/discovery-mcp-server.mjs +4 -4
  19. package/dist/dist-52M5RDZ6.mjs +17 -0
  20. package/dist/inbox-mcp-server.js +1902 -505
  21. package/dist/inbox-mcp-server.mjs +3 -3
  22. package/dist/route-seed-worker.js +3976 -356
  23. package/dist/route-seed-worker.mjs +14 -7
  24. package/dist/run-write-guard-preload.js +2908 -572
  25. package/dist/run-write-guard-preload.mjs +5 -285
  26. package/dist/run-write-guard.js +2908 -572
  27. package/dist/run-write-guard.mjs +3 -3
  28. package/dist/spec-driver.js +1 -1
  29. package/dist/spec-driver.mjs +1 -1
  30. package/package.json +4 -3
  31. package/dist/dist-DFNLMH6L.mjs +0 -16
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  require_test_inbox_broker,
3
3
  require_verification_inbox
4
- } from "./chunk-MAO6ADAO.mjs";
4
+ } from "./chunk-CBVHPYVJ.mjs";
5
5
  import {
6
6
  __commonJS,
7
7
  __require
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  require_route_manifest
3
- } from "./chunk-WDXLAZ2J.mjs";
3
+ } from "./chunk-PWFWMVZU.mjs";
4
4
  import {
5
5
  require_survey_network_guard
6
- } from "./chunk-GIIIVO2W.mjs";
6
+ } from "./chunk-E2RRAE27.mjs";
7
7
  import {
8
8
  dist_exports,
9
9
  init_dist
10
- } from "./chunk-MBKGTELT.mjs";
10
+ } from "./chunk-3JM6XTSZ.mjs";
11
11
  import {
12
12
  __commonJS,
13
13
  __toCommonJS
@@ -20,6 +20,7 @@ var require_exploration_parser = __commonJS({
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.extractCodeLines = extractCodeLines;
22
22
  exports.extractProvenLocator = extractProvenLocator;
23
+ exports.maskTypedValues = maskTypedValues;
23
24
  exports.sanitizePromptCodeLine = sanitizePromptCodeLine;
24
25
  exports.extractPageUrl = extractPageUrl;
25
26
  exports.extractPageTitle = extractPageTitle;
@@ -31,9 +32,10 @@ var require_exploration_parser = __commonJS({
31
32
  exports.normalizeRouteForFocusScope = normalizeRouteForFocusScope;
32
33
  exports.routeIsWithinFocusAreas = routeIsWithinFocusAreas;
33
34
  exports.detectRouteFromResponse = detectRouteFromResponse;
35
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
34
36
  function extractCodeLines(toolResultContent) {
35
37
  const isPlaywrightLine = (l) => l.startsWith("await page.") || l.startsWith("await expect(");
36
- const codeSection = toolResultContent.match(/###\s*Ran Playwright code\s*\n([\s\S]*?)(?=\n###|\n$|$)/);
38
+ const codeSection = toolResultContent.match(/###\s*Ran Playwright code\s*\n(?![^\S\n]*\n)([\s\S]*?)(?=\n###|\n$|$)/);
37
39
  if (codeSection) {
38
40
  return codeSection[1].split("\n").map((l) => l.trim()).filter(isPlaywrightLine).map((l) => l.replace(/;$/, ""));
39
41
  }
@@ -77,7 +79,7 @@ var require_exploration_parser = __commonJS({
77
79
  cut = idx;
78
80
  }
79
81
  let locator = cut > start ? codeLine.slice(start, cut) : codeLine.slice(start);
80
- locator = locator.trim().replace(/;+\s*$/, "").trim();
82
+ locator = locator.trim().replace(/(?<!;);+\s*$/, "").trim();
81
83
  if (!locator.includes("("))
82
84
  return null;
83
85
  if (!/^(getBy\w+|locator)\(/.test(locator))
@@ -157,13 +159,16 @@ var require_exploration_parser = __commonJS({
157
159
  return false;
158
160
  return RECORD_TITLE_WITH_COMPANY.test(name);
159
161
  }
162
+ var TYPED_VALUE_CALL_RE = /\.(fill|type|pressSequentially|insertText)\(\s*(['"])(?:\\.|(?!\2)[^\\])*\2(\s*,\s*\{[^{}]*\})?\s*\)/g;
163
+ function maskTypedValues(codeLine, mask) {
164
+ return codeLine.replace(TYPED_VALUE_CALL_RE, (_all, method, _quote, options) => `.${method}(${mask}${options ?? ""})`);
165
+ }
160
166
  function sanitizePromptCodeLine(codeLine) {
161
167
  let sanitized = codeLine;
162
168
  sanitized = sanitized.replace(/getByRole\('(textbox|searchbox|combobox|spinbutton)',\s*\{\s*name:\s*['"]([^'"]*)['"]\s*\}\)/g, (match, role, name, _offset, line) => looksDynamicFieldLabel(name, line) ? `getByRole('${role}')` : match);
163
169
  sanitized = sanitized.replace(/getByLabel\(\s*(['"])([^'"]*)\1\s*\)(?=\)?\.(?:fill|type|toHaveValue|inputValue|press)\()/g, (match, _quote, label, _offset, line) => looksDynamicFieldLabel(label, line) ? "getByRole('textbox')" : match);
164
170
  sanitized = sanitized.replace(/getByLabel\(\s*(['"])([^'"]*)\1\s*\)(?=\)?\.selectOption\()/g, (match, _quote, label, _offset, line) => looksDynamicFieldLabel(label, line) ? "getByRole('combobox')" : match);
165
- sanitized = sanitized.replace(/\.fill\(\s*(['"]).*?\1\s*\)/g, ".fill('<test-value>')");
166
- sanitized = sanitized.replace(/\.type\(\s*(['"]).*?\1\s*\)/g, ".type('<test-value>')");
171
+ sanitized = maskTypedValues(sanitized, "'<test-value>'");
167
172
  sanitized = sanitized.replace(/\.toHaveValue\(\s*(['"]).*?\1\s*\)/g, ".toHaveValue(/./)");
168
173
  sanitized = sanitized.replace(/getByRole\('heading',\s*\{\s*name:\s*'([^']*)'\s*\}\)/g, (match, name) => looksDynamicRecordTitle(name) ? "getByRole('heading')" : match);
169
174
  sanitized = sanitized.replace(/getByRole\('heading',\s*\{\s*name:\s*"([^"]*)"\s*\}\)/g, (match, name) => looksDynamicRecordTitle(name) ? "getByRole('heading')" : match);
@@ -187,7 +192,7 @@ var require_exploration_parser = __commonJS({
187
192
  return match ? match[1].trim() : null;
188
193
  }
189
194
  function extractSnapshotSection(content) {
190
- const match = content.match(/###\s*Snapshot\s*\n([\s\S]*?)(?=\n###|$)/);
195
+ const match = content.match(/###\s*Snapshot\s*\n(?![^\S\n]*\n)([\s\S]*?)(?=\n###|$)/);
191
196
  if (!match)
192
197
  return null;
193
198
  if (/^\s*- \[Snapshot\]\([^)\n]*\)\s*$/.test(match[1]))
@@ -255,7 +260,7 @@ var require_exploration_parser = __commonJS({
255
260
  return hints;
256
261
  }
257
262
  function extractFirstAlert(snapshotYaml) {
258
- const match = snapshotYaml.match(/^\s*-\s+(?:alert|status)\s+"([^"]+)"/m);
263
+ const match = snapshotYaml.match(/^[^\S\n\r\u2028\u2029]*-\s+(?:alert|status)\s+"([^"]+)"/m);
259
264
  return match ? match[1] : null;
260
265
  }
261
266
  function buildInteractionLogEntry(toolName, toolArgs, codeLines, pageUrl, pageTitle, snapshotSection) {
@@ -296,6 +301,12 @@ var require_exploration_parser = __commonJS({
296
301
  return /^\d{10,}$/.test(value);
297
302
  return false;
298
303
  }
304
+ function hashRouterPathWithoutTrailingSlash(hash) {
305
+ const q = hash.indexOf("?");
306
+ const path = q < 0 ? hash : hash.slice(0, q);
307
+ const stripped = (0, shared_1.stripTrailingSlashes)(path);
308
+ return stripped === "#" || stripped === "" ? hash : stripped + (q < 0 ? "" : hash.slice(q));
309
+ }
299
310
  function normalizeRoute(url) {
300
311
  let route = url.pathname;
301
312
  const encodeQueryPart = (part) => part.replace(/[%&=#+?]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
@@ -314,7 +325,7 @@ var require_exploration_parser = __commonJS({
314
325
  }
315
326
  }
316
327
  if (url.hash && url.hash !== "#" && url.hash !== "#/") {
317
- route += url.hash;
328
+ route += url.hash.startsWith("#/") ? hashRouterPathWithoutTrailingSlash(url.hash) : url.hash;
318
329
  }
319
330
  return route;
320
331
  }
@@ -324,7 +335,7 @@ var require_exploration_parser = __commonJS({
324
335
  const decoded = raw.replace(/&amp;/g, "&").replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(Number(code)));
325
336
  try {
326
337
  const url = new URL(decoded, "http://canonical.invalid");
327
- url.pathname = url.pathname.replace(/\/+$/, "") || "/";
338
+ url.pathname = (0, shared_1.stripTrailingSlashes)(url.pathname) || "/";
328
339
  return normalizeRoute(url);
329
340
  } catch {
330
341
  return decoded;
@@ -356,9 +367,9 @@ var require_exploration_parser = __commonJS({
356
367
  const text = String(route ?? "").trim();
357
368
  const at = text.indexOf("#/");
358
369
  if (at < 0)
359
- return text.startsWith("#") ? text.split("?")[0].toLowerCase().replace(/\/+$/, "") : null;
370
+ return text.startsWith("#") ? (0, shared_1.stripTrailingSlashes)(text.split("?")[0].toLowerCase()) : null;
360
371
  const hashPath = text.slice(at).split("?")[0].toLowerCase();
361
- return hashPath.length > 2 ? hashPath.replace(/\/+$/, "") || "#/" : hashPath;
372
+ return hashPath.length > 2 ? (0, shared_1.stripTrailingSlashes)(hashPath) || "#/" : hashPath;
362
373
  }
363
374
  function routeIsWithinFocusAreas(route, focusAreas, otherRoutes) {
364
375
  if (!focusAreas || focusAreas.length === 0)
@@ -839,6 +850,7 @@ var require_survey_repo_route_frontier = __commonJS({
839
850
  exports.buildRepoRouteCandidates = buildRepoRouteCandidates;
840
851
  exports.routeIdsHiddenFromChecklist = routeIdsHiddenFromChecklist;
841
852
  exports.parseRepoRouteCoverage = parseRepoRouteCoverage;
853
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
842
854
  var route_manifest_js_1 = require_route_manifest();
843
855
  var survey_scope_js_1 = require_survey_scope();
844
856
  var repo_route_safety_js_1 = require_repo_route_safety();
@@ -900,7 +912,7 @@ var require_survey_repo_route_frontier = __commonJS({
900
912
  return route;
901
913
  try {
902
914
  const base = new URL(baseUrl);
903
- return `${base.pathname.replace(/\/+$/, "") || "/"}${route}`;
915
+ return `${(0, shared_1.stripTrailingSlashes)(base.pathname) || "/"}${route}`;
904
916
  } catch {
905
917
  return `/${route}`;
906
918
  }
@@ -1131,7 +1143,7 @@ var require_survey_repo_route_frontier = __commonJS({
1131
1143
  try {
1132
1144
  const base = new URL(input.baseUrl);
1133
1145
  origin = base.origin;
1134
- basePathname = base.pathname.replace(/\/+$/, "") || "/";
1146
+ basePathname = (0, shared_1.stripTrailingSlashes)(base.pathname) || "/";
1135
1147
  } catch {
1136
1148
  origin = null;
1137
1149
  }
@@ -1164,7 +1176,7 @@ var require_survey_repo_route_frontier = __commonJS({
1164
1176
  return null;
1165
1177
  if (parsed.origin !== this.appOrigin)
1166
1178
  return null;
1167
- const pathname = parsed.pathname.replace(/\/+$/, "") || "/";
1179
+ const pathname = (0, shared_1.stripTrailingSlashes)(parsed.pathname) || "/";
1168
1180
  const key = manifestRouteKey(`${parsed.pathname}${parsed.search}${parsed.hash}`);
1169
1181
  if (!key)
1170
1182
  return null;
@@ -1629,6 +1641,7 @@ var require_repo_feature_verification = __commonJS({
1629
1641
  exports.parseBrowserVerificationRequirement = parseBrowserVerificationRequirement;
1630
1642
  exports.parseBrowserVerificationResult = parseBrowserVerificationResult;
1631
1643
  exports.renderBrowserVerificationForPrompt = renderBrowserVerificationForPrompt;
1644
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
1632
1645
  var survey_repo_route_frontier_js_1 = require_survey_repo_route_frontier();
1633
1646
  var ABSOLUTE_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
1634
1647
  function originOf(url) {
@@ -1685,7 +1698,7 @@ var require_repo_feature_verification = __commonJS({
1685
1698
  before = before.split("?")[0];
1686
1699
  if (!before.startsWith("/"))
1687
1700
  return null;
1688
- return before.replace(/\/+$/, "");
1701
+ return (0, shared_1.stripTrailingSlashes)(before);
1689
1702
  }
1690
1703
  function sameHashMount(route, entry) {
1691
1704
  const entryMount = hashMount(entry);
@@ -1741,7 +1754,7 @@ var require_repo_feature_verification = __commonJS({
1741
1754
  function compileEntryPattern(pattern) {
1742
1755
  const trimmed = String(pattern ?? "").trim();
1743
1756
  const hashAt = trimmed.indexOf("#/");
1744
- const raw = (hashAt >= 0 || trimmed.startsWith("#") ? trimmed.slice(Math.max(hashAt, 0)).split("?")[0] : trimmed.split(/[?#]/)[0]).replace(/\/+$/, "");
1757
+ const raw = (0, shared_1.stripTrailingSlashes)(hashAt >= 0 || trimmed.startsWith("#") ? trimmed.slice(Math.max(hashAt, 0)).split("?")[0] : trimmed.split(/[?#]/)[0]);
1745
1758
  if (!isEntrySpelling(raw) || raw.length > 500)
1746
1759
  return null;
1747
1760
  let out = "";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  dist_exports,
3
3
  init_dist
4
- } from "./chunk-MBKGTELT.mjs";
4
+ } from "./chunk-3JM6XTSZ.mjs";
5
5
  import {
6
6
  __commonJS,
7
7
  __esm,
@@ -67455,23 +67455,41 @@ var require_harness_init_script = __commonJS({
67455
67455
  exports.isHarnessInitConsoleLine = isHarnessInitConsoleLine;
67456
67456
  exports.stripHarnessConsoleEntries = stripHarnessConsoleEntries;
67457
67457
  var node_crypto_1 = __require("crypto");
67458
+ var shared_1 = (init_dist(), __toCommonJS(dist_exports));
67458
67459
  exports.HARNESS_INIT_SOURCE_URL = `vqa-harness-init-${(0, node_crypto_1.randomBytes)(4).toString("hex")}.js`;
67460
+ var TRANSITION_LOOP_LIMIT = 8;
67461
+ var TRANSITION_LOOP_WINDOW_MS = 1e3;
67459
67462
  function withHarnessSourceUrl(content) {
67460
67463
  return `${content}
67461
67464
  //# sourceURL=${exports.HARNESS_INIT_SOURCE_URL}`;
67462
67465
  }
67463
67466
  function buildAnimationDisablingSource(opts) {
67467
+ const finishTransitions = opts.finishTransitions ?? (0, shared_1.parseBoolFlag)(process.env.HARNESS_FINISH_CSS_TRANSITIONS);
67468
+ const css = finishTransitions ? "*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; }" : "*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }";
67464
67469
  return withHarnessSourceUrl(`(() => {
67465
67470
  // Layer 1: CSS transitions and animations - instant, not removed
67466
67471
  // (duration 0s preserves final state; 'none' would prevent keyframes from completing)
67472
+ // The rule goes in a constructed stylesheet on document.adoptedStyleSheets, which adds no node to the DOM:
67473
+ // a <style> element changed what the test sees (locator('style') and 'head > *' counts, document.styleSheets,
67474
+ // an iframe's empty head), so a spec stock Playwright passes failed. The <style> is the fallback for a
67475
+ // browser with no constructable stylesheets.
67476
+ let adopted = false;
67467
67477
  try {
67478
+ if (typeof CSSStyleSheet === 'function' && typeof Document === 'function' && 'adoptedStyleSheets' in Document.prototype) {
67479
+ const sheet = new CSSStyleSheet();
67480
+ sheet.replaceSync(${JSON.stringify(css)});
67481
+ document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
67482
+ adopted = true;
67483
+ }
67484
+ } catch (e) {}
67485
+ if (!adopted) try {
67468
67486
  let applied = false;
67469
67487
  const apply = () => {
67470
67488
  if (applied) return true;
67471
67489
  const root = document.head || document.documentElement;
67472
67490
  if (!root) return false;
67473
67491
  const style = document.createElement('style');
67474
- style.textContent = '*, *::before, *::after { transition-duration: 0s !important; animation-duration: 0s !important; transition-delay: 0s !important; animation-delay: 0s !important; }';
67492
+ style.textContent = ${JSON.stringify(css)};
67475
67493
  root.appendChild(style);
67476
67494
  applied = true;
67477
67495
  return true;
@@ -67493,6 +67511,48 @@ ${opts.hideWebdriver ? `
67493
67511
  try {
67494
67512
  Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
67495
67513
  } catch (e) {}
67514
+ ` : ""}
67515
+ ${finishTransitions ? `
67516
+ // Layer 1b: CSS transitions run, and are finished as they start, so transitionend fires.
67517
+ // - finish() is deferred to a task: called inside the transitionrun dispatch (or a microtask of it) after a
67518
+ // real input event, Chromium finishes the transition but never fires its transitionstart/transitionend.
67519
+ // - setTimeout and performance.now are the ones present when this script runs, before the page's own
67520
+ // scripts: fake timers the page installs later cannot hold a finish back.
67521
+ // - The subtree is walked only for a pseudo-element's transition (its animations hang off the element).
67522
+ // - An element whose same transition restarts more than ${TRANSITION_LOOP_LIMIT} times within ${TRANSITION_LOOP_WINDOW_MS} ms (an app that starts it again on
67523
+ // each transitionend: carousel, pulse) is left to run at its own pace from then on, instead of cycling at
67524
+ // frame rate for the rest of the run.
67525
+ try {
67526
+ const st = setTimeout;
67527
+ const perf = typeof performance === 'object' && performance && typeof performance.now === 'function' ? performance : null;
67528
+ const now = perf ? perf.now.bind(perf) : Date.now;
67529
+ const seen = new WeakMap();
67530
+ const looping = (el, key) => {
67531
+ let byKey = seen.get(el);
67532
+ if (!byKey) { byKey = new Map(); seen.set(el, byKey); }
67533
+ const t = now();
67534
+ let s = byKey.get(key);
67535
+ if (s && s.loop) return true;
67536
+ if (!s || t - s.since > ${TRANSITION_LOOP_WINDOW_MS}) { s = { n: 0, since: t, loop: false }; byKey.set(key, s); }
67537
+ s.n += 1;
67538
+ if (s.n > ${TRANSITION_LOOP_LIMIT}) s.loop = true;
67539
+ return s.loop;
67540
+ };
67541
+ window.addEventListener('transitionrun', (ev) => {
67542
+ try {
67543
+ const el = ev && ev.target;
67544
+ if (!el || typeof el.getAnimations !== 'function' || typeof CSSTransition !== 'function') return;
67545
+ const pseudo = ev.pseudoElement || '';
67546
+ if (looping(el, ev.propertyName + '|' + pseudo)) return;
67547
+ for (const a of (pseudo ? el.getAnimations({ subtree: true }) : el.getAnimations())) {
67548
+ if (a instanceof CSSTransition && a.transitionProperty === ev.propertyName && a.effect
67549
+ && a.effect.target === el && (a.effect.pseudoElement || '') === pseudo) {
67550
+ st(() => { try { a.finish(); } catch (e) {} }, 0);
67551
+ }
67552
+ }
67553
+ } catch (e) {}
67554
+ }, true);
67555
+ } catch (e) {}
67496
67556
  ` : ""}
67497
67557
  // Layer 2: Web Animations API - make instant instead of no-op
67498
67558
  // (preserves animation completion callbacks that app code may depend on)