@riddledc/riddle-proof 0.7.128 → 0.7.130

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.
@@ -607,6 +607,17 @@ function normalizeSetupActionCoordinateMode(value, index) {
607
607
  if (normalized === "ratio" || normalized === "relative" || normalized === "fraction") return "ratio";
608
608
  throw new Error(`target.setup_actions[${index}].coordinate_mode ${String(value)} is not supported. Supported coordinate modes: pixels, ratio.`);
609
609
  }
610
+ function normalizeSetupActionPointerType(value, type, index) {
611
+ if (value === void 0 || value === null || value === "") return void 0;
612
+ if (type !== "drag") {
613
+ throw new Error(`target.setup_actions[${index}].pointer_type is only supported for drag actions.`);
614
+ }
615
+ const normalized = String(value).trim().replace(/-/g, "_").toLowerCase();
616
+ if (normalized === "mouse") return "mouse";
617
+ if (normalized === "touch" || normalized === "finger") return "touch";
618
+ if (normalized === "pen" || normalized === "stylus") return "pen";
619
+ throw new Error(`target.setup_actions[${index}].pointer_type ${String(value)} is not supported. Supported pointer types: mouse, touch, pen.`);
620
+ }
610
621
  function normalizeSetupAction(input, index) {
611
622
  if (!isRecord(input)) throw new Error(`target.setup_actions[${index}] must be an object.`);
612
623
  const type = normalizeSetupActionType(stringValue(input.type), index);
@@ -625,6 +636,7 @@ function normalizeSetupAction(input, index) {
625
636
  const toX = numberValue(valueFromOwn(input, "to_x", "toX", "end_x", "endX", "x2"));
626
637
  const toY = numberValue(valueFromOwn(input, "to_y", "toY", "end_y", "endY", "y2"));
627
638
  const coordinateMode = normalizeSetupActionCoordinateMode(valueFromOwn(input, "coordinate_mode", "coordinateMode", "coords", "units"), index);
639
+ const pointerType = normalizeSetupActionPointerType(valueFromOwn(input, "pointer_type", "pointerType", "input_type", "inputType"), type, index);
628
640
  if (type === "drag") {
629
641
  if (fromX === void 0 || fromY === void 0 || toX === void 0 || toY === void 0) {
630
642
  throw new Error(`target.setup_actions[${index}] drag requires from_x, from_y, to_x, and to_y.`);
@@ -709,6 +721,7 @@ function normalizeSetupAction(input, index) {
709
721
  force: type === "click" && (input.force === true || input.force_click === true || input.forceClick === true),
710
722
  click_count: normalizeSetupActionClickCount(input, type, index),
711
723
  coordinate_mode: coordinateMode,
724
+ pointer_type: pointerType,
712
725
  from_x: fromX,
713
726
  from_y: fromY,
714
727
  to_x: toX,
@@ -1854,6 +1867,49 @@ function matchText(sample, check) {
1854
1867
  }
1855
1868
  return sample.includes(check.text || "");
1856
1869
  }
1870
+ function compactTextEvidenceSample(value) {
1871
+ return String(value || "").replace(/\s+/g, " ").trim();
1872
+ }
1873
+ function textSampleAroundMatch(sample, index, length) {
1874
+ if (index < 0) return void 0;
1875
+ const source = String(sample || "");
1876
+ const context = 120;
1877
+ const start = Math.max(0, index - context);
1878
+ const end = Math.min(source.length, index + Math.max(length, 1) + context);
1879
+ const prefix = start > 0 ? "..." : "";
1880
+ const suffix = end < source.length ? "..." : "";
1881
+ const compacted = compactTextEvidenceSample(`${prefix}${source.slice(start, end)}${suffix}`);
1882
+ return compacted ? compacted.slice(0, 240) : void 0;
1883
+ }
1884
+ function textMatchSamples(sample, check) {
1885
+ const source = String(sample || "");
1886
+ if (!source) return [];
1887
+ if (check.pattern) {
1888
+ try {
1889
+ const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
1890
+ const match = new RegExp(check.pattern, flags).exec(source);
1891
+ const sampleText2 = match ? textSampleAroundMatch(source, match.index, match[0]?.length || 1) : void 0;
1892
+ return sampleText2 ? [sampleText2] : [];
1893
+ } catch {
1894
+ return [];
1895
+ }
1896
+ }
1897
+ const text = check.text || "";
1898
+ if (!text) return [];
1899
+ const index = source.indexOf(text);
1900
+ const sampleText = textSampleAroundMatch(source, index, text.length);
1901
+ return sampleText ? [sampleText] : [];
1902
+ }
1903
+ function textCheckFailureSamples(viewport, check) {
1904
+ const key = textKey(check);
1905
+ const captured = viewport.text_match_samples?.[key] || [];
1906
+ const capturedSamples = captured.map((sample) => compactTextEvidenceSample(sample).slice(0, 240)).filter(Boolean);
1907
+ if (capturedSamples.length) return capturedSamples.slice(0, 3);
1908
+ const matchedSamples = textMatchSamples(viewport.body_text_sample || "", check);
1909
+ if (matchedSamples.length) return matchedSamples.slice(0, 3);
1910
+ const fallback = compactTextEvidenceSample(viewport.body_text_sample || "").slice(0, 240);
1911
+ return fallback ? [fallback] : [];
1912
+ }
1857
1913
  function allowedMessageSample(input) {
1858
1914
  if (!isRecord(input)) return String(input || "");
1859
1915
  const parts = [
@@ -2298,10 +2354,17 @@ function assessCheckFromEvidence(check, evidence) {
2298
2354
  if (check.type === "text_visible" || check.type === "text_absent") {
2299
2355
  const key = textKey(check);
2300
2356
  const expectedVisible = check.type === "text_visible";
2301
- const matches = viewports.map((viewport) => {
2357
+ const results = viewports.map((viewport) => {
2302
2358
  const fromEvidence = viewport.text_matches?.[key];
2303
- return typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
2359
+ const matched = typeof fromEvidence === "boolean" ? fromEvidence : matchText(viewport.body_text_sample || "", check);
2360
+ const failedAgainstExpectation = matched !== expectedVisible;
2361
+ return {
2362
+ viewport: viewport.name,
2363
+ matched,
2364
+ samples: failedAgainstExpectation ? textCheckFailureSamples(viewport, check) : []
2365
+ };
2304
2366
  });
2367
+ const matches = results.map((result) => result.matched);
2305
2368
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
2306
2369
  return {
2307
2370
  type: check.type,
@@ -2310,7 +2373,8 @@ function assessCheckFromEvidence(check, evidence) {
2310
2373
  evidence: {
2311
2374
  text: check.text || null,
2312
2375
  pattern: check.pattern || null,
2313
- matches
2376
+ matches,
2377
+ viewports: results.map((result) => toJsonValue(result))
2314
2378
  },
2315
2379
  message: failed ? `Text assertion failed in ${failed} viewport(s).` : void 0
2316
2380
  };
@@ -2897,6 +2961,48 @@ function textMatches(sample, check) {
2897
2961
  }
2898
2962
  return String(sample || "").includes(check.text || "");
2899
2963
  }
2964
+ function compactTextEvidenceSample(value) {
2965
+ return String(value || "").replace(/\s+/g, " ").trim();
2966
+ }
2967
+ function textSampleAroundMatch(sample, index, length) {
2968
+ if (index < 0) return undefined;
2969
+ const source = String(sample || "");
2970
+ const context = 120;
2971
+ const start = Math.max(0, index - context);
2972
+ const end = Math.min(source.length, index + Math.max(length, 1) + context);
2973
+ const prefix = start > 0 ? "..." : "";
2974
+ const suffix = end < source.length ? "..." : "";
2975
+ const compacted = compactTextEvidenceSample(prefix + source.slice(start, end) + suffix);
2976
+ return compacted ? compacted.slice(0, 240) : undefined;
2977
+ }
2978
+ function textMatchSamples(sample, check) {
2979
+ const source = String(sample || "");
2980
+ if (!source) return [];
2981
+ if (check.pattern) {
2982
+ try {
2983
+ const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
2984
+ const match = new RegExp(check.pattern, flags).exec(source);
2985
+ const sampleText = match ? textSampleAroundMatch(source, match.index, match[0] ? match[0].length : 1) : undefined;
2986
+ return sampleText ? [sampleText] : [];
2987
+ } catch { return []; }
2988
+ }
2989
+ const text = check.text || "";
2990
+ if (!text) return [];
2991
+ const sampleText = textSampleAroundMatch(source, source.indexOf(text), text.length);
2992
+ return sampleText ? [sampleText] : [];
2993
+ }
2994
+ function textCheckFailureSamples(viewport, check) {
2995
+ const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
2996
+ const captured = viewport && viewport.text_match_samples && Array.isArray(viewport.text_match_samples[key]) ? viewport.text_match_samples[key] : [];
2997
+ const capturedSamples = captured
2998
+ .map((sample) => compactTextEvidenceSample(sample).slice(0, 240))
2999
+ .filter(Boolean);
3000
+ if (capturedSamples.length) return capturedSamples.slice(0, 3);
3001
+ const matchedSamples = textMatchSamples(viewport && viewport.body_text_sample || "", check);
3002
+ if (matchedSamples.length) return matchedSamples.slice(0, 3);
3003
+ const fallback = compactTextEvidenceSample(viewport && viewport.body_text_sample || "").slice(0, 240);
3004
+ return fallback ? [fallback] : [];
3005
+ }
2900
3006
  function allowedMessageSample(input) {
2901
3007
  if (!input || typeof input !== "object" || Array.isArray(input)) return String(input || "");
2902
3008
  const parts = [
@@ -3974,13 +4080,22 @@ function assessProfile(profile, evidence) {
3974
4080
  if (check.type === "text_visible" || check.type === "text_absent") {
3975
4081
  const key = check.pattern ? "pattern:" + check.pattern + "/" + (check.flags || "") : "text:" + (check.text || "");
3976
4082
  const expectedVisible = check.type === "text_visible";
3977
- const matches = checkViewports.map((viewport) => viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check));
4083
+ const results = checkViewports.map((viewport) => {
4084
+ const matched = viewport.text_matches && typeof viewport.text_matches[key] === "boolean" ? viewport.text_matches[key] : textMatches(viewport.body_text_sample || "", check);
4085
+ const failedAgainstExpectation = matched !== expectedVisible;
4086
+ return {
4087
+ viewport: viewport.name,
4088
+ matched,
4089
+ samples: failedAgainstExpectation ? textCheckFailureSamples(viewport, check) : [],
4090
+ };
4091
+ });
4092
+ const matches = results.map((result) => result.matched);
3978
4093
  const failed = matches.filter((matched) => matched !== expectedVisible).length;
3979
4094
  checks.push({
3980
4095
  type: check.type,
3981
4096
  label: check.label || check.type,
3982
4097
  status: failed ? "failed" : "passed",
3983
- evidence: { text: check.text, pattern: check.pattern, matches },
4098
+ evidence: { text: check.text, pattern: check.pattern, matches, viewports: results },
3984
4099
  message: failed ? "Text assertion failed in " + failed + " viewport(s)." : undefined,
3985
4100
  });
3986
4101
  continue;
@@ -4300,6 +4415,36 @@ function textMatches(sample, check) {
4300
4415
  }
4301
4416
  return String(sample || "").includes(check.text || "");
4302
4417
  }
4418
+ function compactTextEvidenceSample(value) {
4419
+ return String(value || "").replace(/\s+/g, " ").trim();
4420
+ }
4421
+ function textSampleAroundMatch(sample, index, length) {
4422
+ if (index < 0) return undefined;
4423
+ const source = String(sample || "");
4424
+ const context = 120;
4425
+ const start = Math.max(0, index - context);
4426
+ const end = Math.min(source.length, index + Math.max(length, 1) + context);
4427
+ const prefix = start > 0 ? "..." : "";
4428
+ const suffix = end < source.length ? "..." : "";
4429
+ const compacted = compactTextEvidenceSample(prefix + source.slice(start, end) + suffix);
4430
+ return compacted ? compacted.slice(0, 240) : undefined;
4431
+ }
4432
+ function textMatchSamples(sample, check) {
4433
+ const source = String(sample || "");
4434
+ if (!source) return [];
4435
+ if (check.pattern) {
4436
+ try {
4437
+ const flags = Array.from(new Set(String(check.flags || "").replace(/[gy]/g, "").split(""))).join("");
4438
+ const match = new RegExp(check.pattern, flags).exec(source);
4439
+ const sampleText = match ? textSampleAroundMatch(source, match.index, match[0] ? match[0].length : 1) : undefined;
4440
+ return sampleText ? [sampleText] : [];
4441
+ } catch { return []; }
4442
+ }
4443
+ const text = check.text || "";
4444
+ if (!text) return [];
4445
+ const sampleText = textSampleAroundMatch(source, source.indexOf(text), text.length);
4446
+ return sampleText ? [sampleText] : [];
4447
+ }
4303
4448
  function profileCheckAppliesToViewport(check, viewport) {
4304
4449
  if (!Array.isArray(check.viewports) || !check.viewports.length) return true;
4305
4450
  return Boolean(viewport && viewport.name && check.viewports.includes(viewport.name));
@@ -4777,23 +4922,75 @@ async function executeSetupAction(action, ordinal, viewport) {
4777
4922
  const requestedSteps = setupNumber(action.steps, 8);
4778
4923
  const steps = Math.min(100, Math.max(1, Math.floor(requestedSteps || 8)));
4779
4924
  const durationMs = setupNumber(action.duration_ms ?? action.durationMs, 0);
4780
- await page.mouse.move(start.x, start.y);
4781
- await page.mouse.down();
4782
- try {
4783
- if (durationMs && steps > 1) {
4784
- for (let step = 1; step <= steps; step += 1) {
4785
- const progress = step / steps;
4786
- await page.mouse.move(
4787
- start.x + (end.x - start.x) * progress,
4788
- start.y + (end.y - start.y) * progress,
4789
- );
4790
- await page.waitForTimeout(durationMs / steps);
4925
+ const pointerType = String(action.pointer_type || action.pointerType || "mouse").trim().toLowerCase();
4926
+ if (pointerType === "touch" || pointerType === "pen") {
4927
+ const localStart = {
4928
+ x: coordinate(fromX, box.width),
4929
+ y: coordinate(fromY, box.height),
4930
+ };
4931
+ const localEnd = {
4932
+ x: coordinate(toX, box.width),
4933
+ y: coordinate(toY, box.height),
4934
+ };
4935
+ await target.evaluate(async (element, payload) => {
4936
+ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4937
+ const rect = element.getBoundingClientRect();
4938
+ const pointerId = payload.pointerType === "touch" ? 11 : 12;
4939
+ const point = (progress) => ({
4940
+ clientX: rect.left + payload.start.x + (payload.end.x - payload.start.x) * progress,
4941
+ clientY: rect.top + payload.start.y + (payload.end.y - payload.start.y) * progress,
4942
+ });
4943
+ const dispatch = (type, progress) => {
4944
+ const coords = point(progress);
4945
+ element.dispatchEvent(new PointerEvent(type, {
4946
+ bubbles: true,
4947
+ cancelable: true,
4948
+ composed: true,
4949
+ pointerId,
4950
+ pointerType: payload.pointerType,
4951
+ isPrimary: true,
4952
+ buttons: type === "pointerup" ? 0 : 1,
4953
+ button: type === "pointerup" ? 0 : 0,
4954
+ clientX: coords.clientX,
4955
+ clientY: coords.clientY,
4956
+ }));
4957
+ };
4958
+ dispatch("pointerover", 0);
4959
+ dispatch("pointerenter", 0);
4960
+ dispatch("pointerdown", 0);
4961
+ for (let step = 1; step <= payload.steps; step += 1) {
4962
+ dispatch("pointermove", step / payload.steps);
4963
+ if (payload.durationMs && payload.steps > 1) await wait(payload.durationMs / payload.steps);
4791
4964
  }
4792
- } else {
4793
- await page.mouse.move(end.x, end.y, { steps });
4965
+ dispatch("pointerup", 1);
4966
+ dispatch("pointerout", 1);
4967
+ dispatch("pointerleave", 1);
4968
+ }, {
4969
+ pointerType,
4970
+ start: localStart,
4971
+ end: localEnd,
4972
+ steps,
4973
+ durationMs,
4974
+ });
4975
+ } else {
4976
+ await page.mouse.move(start.x, start.y);
4977
+ await page.mouse.down();
4978
+ try {
4979
+ if (durationMs && steps > 1) {
4980
+ for (let step = 1; step <= steps; step += 1) {
4981
+ const progress = step / steps;
4982
+ await page.mouse.move(
4983
+ start.x + (end.x - start.x) * progress,
4984
+ start.y + (end.y - start.y) * progress,
4985
+ );
4986
+ await page.waitForTimeout(durationMs / steps);
4987
+ }
4988
+ } else {
4989
+ await page.mouse.move(end.x, end.y, { steps });
4990
+ }
4991
+ } finally {
4992
+ await page.mouse.up().catch(() => {});
4794
4993
  }
4795
- } finally {
4796
- await page.mouse.up().catch(() => {});
4797
4994
  }
4798
4995
  return {
4799
4996
  ...base,
@@ -4806,6 +5003,7 @@ async function executeSetupAction(action, ordinal, viewport) {
4806
5003
  from_y: fromY,
4807
5004
  to_x: toX,
4808
5005
  to_y: toY,
5006
+ pointer_type: pointerType,
4809
5007
  steps,
4810
5008
  duration_ms: durationMs || undefined,
4811
5009
  };
@@ -6251,6 +6449,7 @@ async function captureViewport(viewport) {
6251
6449
  const frames = {};
6252
6450
  const text_sequences = {};
6253
6451
  const text_matches = {};
6452
+ const text_match_samples = {};
6254
6453
  const http_statuses = {};
6255
6454
  const link_statuses = {};
6256
6455
  for (const check of profile.checks || []) {
@@ -6272,7 +6471,10 @@ async function captureViewport(viewport) {
6272
6471
  text_sequences[check.selector] = await selectorTextSequence(check.selector);
6273
6472
  }
6274
6473
  if ((check.type === "text_visible" || check.type === "text_absent") && (check.text || check.pattern)) {
6275
- text_matches[textKey(check)] = textMatches(dom.body_text || dom.body_text_sample || "", check);
6474
+ const key = textKey(check);
6475
+ const sample = dom.body_text || dom.body_text_sample || "";
6476
+ text_matches[key] = textMatches(sample, check);
6477
+ text_match_samples[key] = textMatchSamples(sample, check);
6276
6478
  }
6277
6479
  if ((check.type === "frame_text_visible" || check.type === "frame_url_equals" || check.type === "frame_url_matches" || check.type === "frame_no_horizontal_overflow") && check.selector) {
6278
6480
  selectors[check.selector] = selectors[check.selector] || await selectorStats(check.selector);
@@ -6349,6 +6551,7 @@ async function captureViewport(viewport) {
6349
6551
  frames,
6350
6552
  text_sequences,
6351
6553
  text_matches,
6554
+ text_match_samples,
6352
6555
  http_statuses,
6353
6556
  link_statuses,
6354
6557
  route_inventory: routeInventory,