@browserbridge/bbx 1.11.3 → 1.12.0

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.
@@ -19,19 +19,25 @@ import {
19
19
  DEFAULT_NETWORK_INTERCEPT_ACTION,
20
20
  DEFAULT_NETWORK_LIMIT,
21
21
  DEFAULT_PAGE_TEXT_BUDGET,
22
+ DEFAULT_PERFORM_TIMEOUT_MS,
22
23
  DEFAULT_TEXT_BUDGET,
24
+ DEFAULT_TOUCH_HOLD_MS,
23
25
  DEFAULT_VIEWPORT_HEIGHT,
24
26
  DEFAULT_VIEWPORT_WIDTH,
25
27
  DEFAULT_WAIT_TIMEOUT_MS,
26
28
  MAX_EXTRACT_SETTLE_TIMEOUT_MS,
27
29
  MAX_ARTIFACT_BYTES,
28
30
  MAX_HAR_ENTRIES,
31
+ MAX_INPUT_HOLD_MS,
29
32
  MAX_INTERCEPT_BODY_BYTES,
30
33
  MAX_INTERCEPT_HEADER_BYTES,
31
34
  MAX_INTERCEPT_HEADERS,
32
35
  MAX_INTERCEPT_HEADER_VALUE_BYTES,
33
36
  MAX_INTERCEPT_URL_PATTERN_LENGTH,
37
+ MAX_PERFORM_STEPS,
38
+ MAX_PERFORM_TIMEOUT_MS,
34
39
  MAX_SENSITIVE_VALUE_BYTES,
40
+ MAX_TOUCH_POINTS,
35
41
  } from './defaults.js';
36
42
  import { BridgeError, ERROR_CODES, getErrorRecovery } from './errors.js';
37
43
  import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry.js';
@@ -96,6 +102,8 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
96
102
  /** @typedef {import('./types.js').NormalizedNetworkParams} NormalizedNetworkParams */
97
103
  /** @typedef {import('./types.js').NormalizedPageTextParams} NormalizedPageTextParams */
98
104
  /** @typedef {import('./types.js').NormalizedPatchOperation} NormalizedPatchOperation */
105
+ /** @typedef {import('./types.js').NormalizedPerformParams} NormalizedPerformParams */
106
+ /** @typedef {import('./types.js').NormalizedPerformStep} NormalizedPerformStep */
99
107
  /** @typedef {import('./types.js').NormalizedSelectAction} NormalizedSelectAction */
100
108
  /** @typedef {import('./types.js').NormalizedScreenshotParams} NormalizedScreenshotParams */
101
109
  /** @typedef {import('./types.js').NormalizedStorageParams} NormalizedStorageParams */
@@ -103,11 +111,17 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
103
111
  /** @typedef {import('./types.js').NormalizedStyleQuery} NormalizedStyleQuery */
104
112
  /** @typedef {import('./types.js').NormalizedTabCloseParams} NormalizedTabCloseParams */
105
113
  /** @typedef {import('./types.js').NormalizedTabCreateParams} NormalizedTabCreateParams */
114
+ /** @typedef {import('./types.js').NormalizedTouchParams} NormalizedTouchParams */
115
+ /** @typedef {import('./types.js').NormalizedTouchPoint} NormalizedTouchPoint */
116
+ /** @typedef {import('./types.js').NormalizedTouchPosition} NormalizedTouchPosition */
106
117
  /** @typedef {import('./types.js').NormalizedViewportAction} NormalizedViewportAction */
107
118
  /** @typedef {import('./types.js').NormalizedViewportResizeParams} NormalizedViewportResizeParams */
108
119
  /** @typedef {import('./types.js').NormalizedWaitForLoadStateParams} NormalizedWaitForLoadStateParams */
109
120
  /** @typedef {import('./types.js').NormalizedWaitForParams} NormalizedWaitForParams */
110
121
  /** @typedef {import('./types.js').PageTextParams} PageTextParams */
122
+ /** @typedef {import('./types.js').PerformParams} PerformParams */
123
+ /** @typedef {import('./types.js').PerformStepMethod} PerformStepMethod */
124
+ /** @typedef {import('./types.js').TouchParams} TouchParams */
111
125
  /** @typedef {import('./types.js').PatchOperationParams} PatchOperationParams */
112
126
  /** @typedef {import('./types.js').SelectActionParams} SelectActionParams */
113
127
  /** @typedef {import('./types.js').ScreenshotParams} ScreenshotParams */
@@ -401,6 +415,11 @@ export function validateBridgeRequest(request) {
401
415
  source: meta.source === 'cli' || meta.source === 'mcp' ? meta.source : undefined,
402
416
  mcp_era: normalizeMcpEra(meta.source, meta.mcp_era),
403
417
  automatic_retry: normalizeAutomaticRetryMeta(meta.source, meta.automatic_retry),
418
+ agent_session:
419
+ typeof meta.agent_session === 'string' &&
420
+ /^[A-Za-z0-9_.:-]{1,128}$/.test(meta.agent_session.trim())
421
+ ? meta.agent_session.trim()
422
+ : undefined,
404
423
  },
405
424
  };
406
425
  }
@@ -442,13 +461,14 @@ function normalizeAutomaticRetryMeta(source, value) {
442
461
 
443
462
  /**
444
463
  * Normalize request params early so malformed payloads fail at the shared
445
- * protocol boundary before daemon or extension dispatch.
464
+ * protocol boundary before daemon or extension dispatch. Exported so later
465
+ * stages re-normalize with the same per-method options (auto-wait, observe).
446
466
  *
447
467
  * @param {BridgeMethod} method
448
468
  * @param {Record<string, unknown>} params
449
469
  * @returns {Record<string, unknown>}
450
470
  */
451
- function normalizeRequestParams(method, params) {
471
+ export function normalizeRequestParams(method, params) {
452
472
  switch (method) {
453
473
  case 'access.request':
454
474
  return normalizeAccessRequestParams(params);
@@ -512,7 +532,7 @@ function normalizeRequestParams(method, params) {
512
532
  case 'input.type':
513
533
  case 'input.fill':
514
534
  case 'input.press_key':
515
- return normalizeInputAction(params);
535
+ return withInputOptions(method, normalizeInputAction(params), params);
516
536
  case 'cdp.dispatch_key_event':
517
537
  return normalizeCdpDispatchKeyEventParams(params);
518
538
  case 'cdp.get_box_model':
@@ -521,13 +541,17 @@ function normalizeRequestParams(method, params) {
521
541
  case 'cdp.get_dom_snapshot':
522
542
  return normalizeCdpDomSnapshotParams(params);
523
543
  case 'input.set_checked':
524
- return normalizeCheckedAction(params);
544
+ return withInputOptions(method, normalizeCheckedAction(params), params);
525
545
  case 'input.select_option':
526
- return normalizeSelectAction(params);
546
+ return withInputOptions(method, normalizeSelectAction(params), params);
527
547
  case 'input.hover':
528
- return normalizeHoverParams(params);
548
+ return withInputOptions(method, normalizeHoverParams(params), params);
529
549
  case 'input.drag':
530
- return normalizeDragParams(params);
550
+ return withInputOptions(method, normalizeDragParams(params), params);
551
+ case 'input.touch':
552
+ return normalizeTouchParams(params);
553
+ case 'input.perform':
554
+ return normalizeInputPerformParams(params);
531
555
  case 'screenshot.capture_region':
532
556
  return { ...params, ...normalizeScreenshotParams(params) };
533
557
  case 'screenshot.capture_element':
@@ -604,11 +628,37 @@ export function getBridgeOperationTimeoutMs(method, params = {}) {
604
628
  }
605
629
  case 'dom.wait_for':
606
630
  return clampInt(params.timeoutMs, 100, 30_000, 5_000);
631
+ case 'input.perform':
632
+ return normalizeInputPerformParams(params).timeoutMs;
633
+ case 'input.click':
634
+ case 'input.press_key':
635
+ case 'input.focus':
636
+ case 'input.type':
637
+ case 'input.fill':
638
+ case 'input.set_checked':
639
+ case 'input.select_option':
640
+ case 'input.hover':
641
+ case 'input.drag': {
642
+ const normalized =
643
+ /** @type {{ timeoutMs?: number, observe?: { settleMs: number } | null }} */ (
644
+ normalizeRequestParams(method, params)
645
+ );
646
+ const holdMs = normalizeHoldMs(params.holdMs, 0);
647
+ const waitMs = normalized.timeoutMs ?? 0;
648
+ const settleMs = normalized.observe?.settleMs ?? 0;
649
+ const total = holdMs + waitMs + settleMs;
650
+ return total > 0 ? total + INPUT_HOLD_TIMEOUT_MARGIN_MS : null;
651
+ }
652
+ case 'input.touch':
653
+ return normalizeHoldMs(params.holdMs, DEFAULT_TOUCH_HOLD_MS) + INPUT_HOLD_TIMEOUT_MARGIN_MS;
607
654
  default:
608
655
  return null;
609
656
  }
610
657
  }
611
658
 
659
+ /** Resolution and dispatch allowance added around an input hold (ms). */
660
+ const INPUT_HOLD_TIMEOUT_MARGIN_MS = 5_000;
661
+
612
662
  /**
613
663
  * @param {DomQueryParams} [params={}]
614
664
  * @returns {NormalizedDomQuery}
@@ -724,15 +774,107 @@ export function normalizeStyleQuery(params = {}) {
724
774
  };
725
775
  }
726
776
 
777
+ const TARGET_LOCATOR_KEYS = /** @type {const} */ ([
778
+ 'role',
779
+ 'name',
780
+ 'text',
781
+ 'label',
782
+ 'placeholder',
783
+ 'testId',
784
+ ]);
785
+ const MAX_LOCATOR_VALUE_LENGTH = 500;
786
+
727
787
  /**
728
- * @param {{ elementRef?: string, selector?: string } | null | undefined} target
788
+ * Normalize an input target. Besides `elementRef` and `selector`, a target can
789
+ * be a semantic locator (`role` + `name`, `text`, `label`, `placeholder`,
790
+ * `testId`) so one action call can find and act on an element. `selector`
791
+ * then narrows locator candidates; `nth` picks one of several matches.
792
+ *
793
+ * @param {Record<string, unknown> | null | undefined} target
729
794
  * @returns {import('./types.js').InputTarget}
730
795
  */
731
796
  function normalizeTarget(target) {
732
- return {
797
+ /** @type {import('./types.js').InputTarget} */
798
+ const normalized = {
733
799
  elementRef: typeof target?.elementRef === 'string' ? target.elementRef : undefined,
734
800
  selector: typeof target?.selector === 'string' ? target.selector : undefined,
735
801
  };
802
+ if (!target) return normalized;
803
+ for (const key of TARGET_LOCATOR_KEYS) {
804
+ const value = target[key];
805
+ if (typeof value !== 'string' || !value.trim()) continue;
806
+ if (value.length > MAX_LOCATOR_VALUE_LENGTH) {
807
+ throw new BridgeError(
808
+ ERROR_CODES.INVALID_REQUEST,
809
+ `target.${key} must not exceed ${MAX_LOCATOR_VALUE_LENGTH} characters.`
810
+ );
811
+ }
812
+ normalized[key] = value.trim();
813
+ }
814
+ if (target.exact === true) normalized.exact = true;
815
+ if (target.nth !== undefined && target.nth !== null) {
816
+ const nth = Number(target.nth);
817
+ if (!Number.isInteger(nth) || nth < 0 || nth > 49) {
818
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'target.nth must be an integer 0-49.');
819
+ }
820
+ normalized.nth = nth;
821
+ }
822
+ return normalized;
823
+ }
824
+
825
+ /** Default bounded wait for a target to exist and become actionable (ms). */
826
+ export const DEFAULT_INPUT_WAIT_MS = 2_500;
827
+ export const MAX_INPUT_WAIT_MS = 15_000;
828
+ /** Default and maximum post-action settle window used by `observe` (ms). */
829
+ export const DEFAULT_OBSERVE_SETTLE_MS = 500;
830
+ export const MAX_OBSERVE_SETTLE_MS = 5_000;
831
+
832
+ /** Actions that report their observed effects unless `observe: false`. */
833
+ const OBSERVE_BY_DEFAULT_METHODS = new Set([
834
+ 'input.click',
835
+ 'input.press_key',
836
+ 'input.select_option',
837
+ 'input.set_checked',
838
+ ]);
839
+
840
+ /**
841
+ * @param {string} method
842
+ * @param {unknown} value
843
+ * @param {{ submit?: boolean }} normalized
844
+ * @returns {{ settleMs: number } | null}
845
+ */
846
+ function normalizeObserveOption(method, value, normalized) {
847
+ if (value === false || value === null) return null;
848
+ if (value && typeof value === 'object') {
849
+ const settleMs = /** @type {{ settleMs?: unknown }} */ (value).settleMs;
850
+ return {
851
+ settleMs: clampInt(settleMs, 0, MAX_OBSERVE_SETTLE_MS, DEFAULT_OBSERVE_SETTLE_MS),
852
+ };
853
+ }
854
+ const observeByDefault =
855
+ OBSERVE_BY_DEFAULT_METHODS.has(method) ||
856
+ ((method === 'input.type' || method === 'input.fill') && normalized.submit === true);
857
+ return value === true || observeByDefault ? { settleMs: DEFAULT_OBSERVE_SETTLE_MS } : null;
858
+ }
859
+
860
+ /**
861
+ * Add the shared auto-wait and observation options to normalized input params.
862
+ *
863
+ * @template {Record<string, unknown>} T
864
+ * @param {string} method
865
+ * @param {T} normalized
866
+ * @param {Record<string, unknown>} raw
867
+ * @returns {T & { timeoutMs: number, observe: { settleMs: number } | null }}
868
+ */
869
+ function withInputOptions(method, normalized, raw) {
870
+ return {
871
+ ...normalized,
872
+ timeoutMs:
873
+ typeof raw.timeoutMs === 'number' && Number.isFinite(raw.timeoutMs)
874
+ ? Math.round(clampNumber(raw.timeoutMs, 0, MAX_INPUT_WAIT_MS, DEFAULT_INPUT_WAIT_MS))
875
+ : DEFAULT_INPUT_WAIT_MS,
876
+ observe: normalizeObserveOption(method, raw.observe, normalized),
877
+ };
736
878
  }
737
879
 
738
880
  /**
@@ -740,10 +882,10 @@ function normalizeTarget(target) {
740
882
  * @returns {import('./types.js').InputExecutionMode}
741
883
  */
742
884
  function normalizeInputExecutionMode(value) {
743
- if (value !== undefined && value !== 'dom' && value !== 'cdp') {
744
- throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'executionMode must be either dom or cdp.');
885
+ if (value !== undefined && value !== 'dom' && value !== 'cdp' && value !== 'auto') {
886
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'executionMode must be dom, cdp, or auto.');
745
887
  }
746
- return value === 'cdp' ? 'cdp' : 'dom';
888
+ return value === 'cdp' || value === 'auto' ? value : 'dom';
747
889
  }
748
890
 
749
891
  /**
@@ -774,11 +916,209 @@ export function normalizeInputAction(params = {}) {
774
916
  modifiers: Array.isArray(params.modifiers)
775
917
  ? params.modifiers.filter((modifier) => typeof modifier === 'string' && modifier.trim())
776
918
  : [],
919
+ holdMs: normalizeHoldMs(params.holdMs, 0),
777
920
  executionMode: normalizeInputExecutionMode(params.executionMode),
778
921
  recoverStale: params.recoverStale === true,
779
922
  };
780
923
  }
781
924
 
925
+ /**
926
+ * Normalize a press-and-hold duration, honoring an explicit zero.
927
+ *
928
+ * @param {unknown} value
929
+ * @param {number} fallback
930
+ * @returns {number}
931
+ */
932
+ function normalizeHoldMs(value, fallback) {
933
+ return Math.round(clampNumber(value, 0, MAX_INPUT_HOLD_MS, fallback));
934
+ }
935
+
936
+ /**
937
+ * @param {unknown} value
938
+ * @param {string} label
939
+ * @returns {NormalizedTouchPosition}
940
+ */
941
+ function normalizeTouchPosition(value, label) {
942
+ if (!isPlainRecord(value)) {
943
+ throw new BridgeError(
944
+ ERROR_CODES.INVALID_REQUEST,
945
+ `${label} must be an object with either target or numeric x and y.`
946
+ );
947
+ }
948
+ const hasTarget = isPlainRecord(value.target);
949
+ const hasPoint =
950
+ typeof value.x === 'number' &&
951
+ Number.isFinite(value.x) &&
952
+ typeof value.y === 'number' &&
953
+ Number.isFinite(value.y);
954
+ if (hasTarget === hasPoint) {
955
+ throw new BridgeError(
956
+ ERROR_CODES.INVALID_REQUEST,
957
+ `${label} needs exactly one of target or numeric x and y.`
958
+ );
959
+ }
960
+ if (hasTarget) {
961
+ return {
962
+ target: normalizeTarget(
963
+ /** @type {{ elementRef?: string, selector?: string }} */ (value.target)
964
+ ),
965
+ x: null,
966
+ y: null,
967
+ };
968
+ }
969
+ return { target: null, x: /** @type {number} */ (value.x), y: /** @type {number} */ (value.y) };
970
+ }
971
+
972
+ /**
973
+ * Normalize a simultaneous touch gesture: one to MAX_TOUCH_POINTS fingers,
974
+ * each placed on a target or viewport point and optionally moved to an end
975
+ * position while all fingers stay down for holdMs.
976
+ *
977
+ * @param {TouchParams} [params={}]
978
+ * @returns {NormalizedTouchParams}
979
+ */
980
+ export function normalizeTouchParams(params = {}) {
981
+ const rawPoints = params.points;
982
+ if (!Array.isArray(rawPoints) || rawPoints.length < 1 || rawPoints.length > MAX_TOUCH_POINTS) {
983
+ throw new BridgeError(
984
+ ERROR_CODES.INVALID_REQUEST,
985
+ `points must be an array of 1 to ${MAX_TOUCH_POINTS} touch points.`
986
+ );
987
+ }
988
+ /** @type {NormalizedTouchPoint[]} */
989
+ const points = rawPoints.map((point, index) => {
990
+ const start = normalizeTouchPosition(point, `points[${index}]`);
991
+ const rawEnd = isPlainRecord(point) ? point.to : undefined;
992
+ return {
993
+ ...start,
994
+ to: rawEnd == null ? null : normalizeTouchPosition(rawEnd, `points[${index}].to`),
995
+ };
996
+ });
997
+ return {
998
+ points,
999
+ holdMs: normalizeHoldMs(params.holdMs, DEFAULT_TOUCH_HOLD_MS),
1000
+ moveSteps: clampInt(params.moveSteps, 1, 60, 10),
1001
+ executionMode: normalizeInputExecutionMode(params.executionMode),
1002
+ recoverStale: params.recoverStale === true,
1003
+ };
1004
+ }
1005
+
1006
+ /** Methods that may appear as input.perform steps. */
1007
+ const PERFORM_STEP_METHODS = new Set([
1008
+ 'input.click',
1009
+ 'input.focus',
1010
+ 'input.type',
1011
+ 'input.fill',
1012
+ 'input.press_key',
1013
+ 'input.set_checked',
1014
+ 'input.select_option',
1015
+ 'input.hover',
1016
+ 'input.drag',
1017
+ 'input.touch',
1018
+ 'input.scroll_into_view',
1019
+ 'viewport.scroll',
1020
+ 'dom.wait_for',
1021
+ 'page.wait_for_load_state',
1022
+ ]);
1023
+
1024
+ /** Step methods that accept executionMode=cdp. */
1025
+ const CDP_CAPABLE_STEP_METHODS = new Set([
1026
+ 'input.click',
1027
+ 'input.hover',
1028
+ 'input.drag',
1029
+ 'input.type',
1030
+ 'input.fill',
1031
+ 'input.press_key',
1032
+ 'input.touch',
1033
+ ]);
1034
+
1035
+ /**
1036
+ * @param {unknown} value
1037
+ * @param {string} label
1038
+ * @param {number} maximum
1039
+ * @returns {number | null}
1040
+ */
1041
+ function normalizeStepTiming(value, label, maximum) {
1042
+ if (value == null) return null;
1043
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > maximum) {
1044
+ throw new BridgeError(
1045
+ ERROR_CODES.INVALID_REQUEST,
1046
+ `${label} must be a number between 0 and the sequence timeoutMs (${maximum}).`
1047
+ );
1048
+ }
1049
+ return Math.round(value);
1050
+ }
1051
+
1052
+ /**
1053
+ * Normalize an ordered, timed input sequence. Each step reuses an existing
1054
+ * bridge method and its own normalizer; the sequence adds only scheduling.
1055
+ *
1056
+ * @param {PerformParams} [params={}]
1057
+ * @returns {NormalizedPerformParams}
1058
+ */
1059
+ export function normalizeInputPerformParams(params = {}) {
1060
+ const timeoutMs = clampInt(
1061
+ params.timeoutMs,
1062
+ 100,
1063
+ MAX_PERFORM_TIMEOUT_MS,
1064
+ DEFAULT_PERFORM_TIMEOUT_MS
1065
+ );
1066
+ const executionMode =
1067
+ params.executionMode === undefined ? null : normalizeInputExecutionMode(params.executionMode);
1068
+ const rawSteps = params.steps;
1069
+ if (!Array.isArray(rawSteps) || rawSteps.length < 1 || rawSteps.length > MAX_PERFORM_STEPS) {
1070
+ throw new BridgeError(
1071
+ ERROR_CODES.INVALID_REQUEST,
1072
+ `steps must be an array of 1 to ${MAX_PERFORM_STEPS} steps.`
1073
+ );
1074
+ }
1075
+ /** @type {NormalizedPerformStep[]} */
1076
+ const steps = rawSteps.map((step, index) => {
1077
+ if (!isPlainRecord(step)) {
1078
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, `steps[${index}] must be an object.`);
1079
+ }
1080
+ const method = step.method;
1081
+ if (typeof method !== 'string' || !PERFORM_STEP_METHODS.has(method)) {
1082
+ throw new BridgeError(
1083
+ ERROR_CODES.INVALID_REQUEST,
1084
+ `steps[${index}].method must be one of ${[...PERFORM_STEP_METHODS].join(', ')}.`
1085
+ );
1086
+ }
1087
+ if (step.params !== undefined && !isPlainRecord(step.params)) {
1088
+ throw new BridgeError(
1089
+ ERROR_CODES.INVALID_REQUEST,
1090
+ `steps[${index}].params must be an object.`
1091
+ );
1092
+ }
1093
+ const rawParams = /** @type {Record<string, unknown>} */ (step.params ?? {});
1094
+ const stepParams =
1095
+ executionMode && CDP_CAPABLE_STEP_METHODS.has(method) && rawParams.executionMode === undefined
1096
+ ? { ...rawParams, executionMode }
1097
+ : rawParams;
1098
+ const delayMs = normalizeStepTiming(step.delayMs, `steps[${index}].delayMs`, timeoutMs);
1099
+ const atMs = normalizeStepTiming(step.atMs, `steps[${index}].atMs`, timeoutMs);
1100
+ if (delayMs && atMs !== null) {
1101
+ throw new BridgeError(
1102
+ ERROR_CODES.INVALID_REQUEST,
1103
+ `steps[${index}] accepts either delayMs or atMs, not both.`
1104
+ );
1105
+ }
1106
+ const stepMethod = /** @type {PerformStepMethod} */ (method);
1107
+ const normalizedStepParams = normalizeRequestParams(stepMethod, stepParams);
1108
+ // Timed sequences keep their schedule: steps observe effects only on request.
1109
+ if (rawParams.observe === undefined && 'observe' in normalizedStepParams) {
1110
+ normalizedStepParams.observe = null;
1111
+ }
1112
+ return {
1113
+ method: stepMethod,
1114
+ params: normalizedStepParams,
1115
+ delayMs: delayMs ?? 0,
1116
+ atMs,
1117
+ };
1118
+ });
1119
+ return { steps, timeoutMs, continueOnError: params.continueOnError === true };
1120
+ }
1121
+
782
1122
  /**
783
1123
  * @param {CdpDispatchKeyEventParams} [params={}]
784
1124
  * @returns {NormalizedCdpDispatchKeyEventParams}
@@ -1119,6 +1459,7 @@ export function normalizeFindByTextParams(params = {}) {
1119
1459
  exact: Boolean(params.exact),
1120
1460
  selector: typeof params.selector === 'string' && params.selector.trim() ? params.selector : '*',
1121
1461
  maxResults: clampInt(params.maxResults, 1, 50, 10),
1462
+ includeHidden: params.includeHidden === true,
1122
1463
  };
1123
1464
  }
1124
1465
 
@@ -1130,8 +1471,10 @@ export function normalizeFindByRoleParams(params = {}) {
1130
1471
  return {
1131
1472
  role: typeof params.role === 'string' ? params.role : '',
1132
1473
  name: typeof params.name === 'string' ? params.name : '',
1474
+ exact: Boolean(params.exact),
1133
1475
  selector: typeof params.selector === 'string' && params.selector.trim() ? params.selector : '*',
1134
1476
  maxResults: clampInt(params.maxResults, 1, 50, 10),
1477
+ includeHidden: params.includeHidden === true,
1135
1478
  };
1136
1479
  }
1137
1480
 
@@ -1381,6 +1724,14 @@ export function normalizeTabCloseParams(params = {}) {
1381
1724
  * @returns {NormalizedAccessibilityTreeParams}
1382
1725
  */
1383
1726
  export function normalizeAccessibilityTreeParams(params = {}) {
1727
+ const source = params.source ?? 'cdp';
1728
+ if (source !== 'cdp' && source !== 'dom') {
1729
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'source must be cdp or dom.');
1730
+ }
1731
+ const format = params.format ?? (source === 'dom' ? 'outline' : 'tree');
1732
+ if (format !== 'tree' && format !== 'outline') {
1733
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'format must be tree or outline.');
1734
+ }
1384
1735
  return {
1385
1736
  selector:
1386
1737
  typeof params.selector === 'string' && params.selector.trim() ? params.selector.trim() : null,
@@ -1388,6 +1739,9 @@ export function normalizeAccessibilityTreeParams(params = {}) {
1388
1739
  maxNodes: clampInt(params.maxNodes, 10, 5000, DEFAULT_A11Y_MAX_NODES),
1389
1740
  compact: params.compact === true,
1390
1741
  interactiveOnly: params.interactiveOnly === true,
1742
+ // Omitted at defaults so existing CDP callers see an unchanged shape.
1743
+ ...(source === 'dom' ? { source } : {}),
1744
+ ...(format === 'outline' ? { format } : {}),
1391
1745
  };
1392
1746
  }
1393
1747
 
@@ -140,11 +140,13 @@ const BRIDGE_METHOD_DESCRIPTIONS = Object.freeze({
140
140
  'dom.get_text': 'Read bounded text for one element.',
141
141
  'dom.get_attributes': 'Read selected attributes for one element.',
142
142
  'dom.wait_for': 'Wait for a selector or text condition in the DOM.',
143
- 'dom.find_by_text': 'Find elements by visible text.',
144
- 'dom.find_by_role': 'Find elements by ARIA role and optional name.',
143
+ 'dom.find_by_text':
144
+ 'Find the innermost visible elements by text, including open/closed shadow DOM.',
145
+ 'dom.find_by_role':
146
+ 'Find visible elements by ARIA role and accessible name (labels, aria-*, alt, content).',
145
147
  'dom.get_html': 'Read inner or outer HTML for one element.',
146
148
  'dom.get_accessibility_tree':
147
- 'Read a depth-limited accessibility tree with optional compact or interactive filtering.',
149
+ 'Read an accessibility tree; source=dom returns a compact actionable outline with elementRefs and no debugger.',
148
150
  'layout.get_box_model': 'Read the box model for one element.',
149
151
  'layout.hit_test': 'Resolve the topmost element at a viewport point.',
150
152
  'styles.get_computed':
@@ -153,16 +155,22 @@ const BRIDGE_METHOD_DESCRIPTIONS = Object.freeze({
153
155
  'Read element classes and inline style context (not stylesheet cascade data).',
154
156
  'viewport.scroll': 'Scroll the viewport or a scrollable element.',
155
157
  'viewport.resize': 'Resize or reset the tab viewport.',
156
- 'input.click': 'Actionability-check and click an element through DOM or optional CDP input.',
158
+ 'input.click':
159
+ 'Actionability-check and click an element through DOM or optional CDP input; holdMs keeps the button pressed.',
157
160
  'input.focus': 'Actionability-check and focus an element through DOM input.',
158
161
  'input.type': 'Actionability-check and type through DOM or optional CDP text input.',
159
162
  'input.fill':
160
163
  'Fill an editable target using a DOM strategy or optional CDP text input; verify afterward.',
161
- 'input.press_key': 'Send a key press to the page or an element.',
164
+ 'input.press_key':
165
+ 'Send a key press to the page or an element through DOM or optional CDP input; holdMs keeps the key down.',
162
166
  'input.set_checked': 'Set checkbox or radio checked state.',
163
167
  'input.select_option': 'Select options in a select element.',
164
168
  'input.hover': 'Actionability-check and hover through DOM or optional CDP pointer input.',
165
169
  'input.drag': 'Actionability-check and drag through DOM or optional CDP pointer input.',
170
+ 'input.touch':
171
+ 'Press one or more touch points at the same time (chords, multi-finger taps, swipes, pinches) through DOM or optional CDP touch input.',
172
+ 'input.perform':
173
+ 'Run an ordered, timed sequence of input steps inside the browser in one call; use for rhythm, games, gestures, and multi-step flows.',
166
174
  'input.scroll_into_view': 'Scroll an element into the visible viewport.',
167
175
  'screenshot.capture_region': 'Capture a screenshot of a viewport region.',
168
176
  'screenshot.capture_element': 'Capture a screenshot of one element.',
@@ -516,7 +524,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
516
524
  'dom.find_by_text',
517
525
  'inspect',
518
526
  true,
519
- ['text', 'exact', 'selector', 'maxResults'],
527
+ ['text', 'exact', 'selector', 'maxResults', 'includeHidden'],
520
528
  'low',
521
529
  METHOD_POLICIES.domRead
522
530
  ),
@@ -524,7 +532,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
524
532
  'dom.find_by_role',
525
533
  'inspect',
526
534
  true,
527
- ['role', 'name', 'selector', 'maxResults'],
535
+ ['role', 'name', 'exact', 'selector', 'maxResults', 'includeHidden'],
528
536
  'low',
529
537
  METHOD_POLICIES.domRead
530
538
  ),
@@ -540,7 +548,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
540
548
  'dom.get_accessibility_tree',
541
549
  'inspect',
542
550
  true,
543
- ['selector', 'maxNodes', 'maxDepth', 'compact', 'interactiveOnly'],
551
+ ['selector', 'maxNodes', 'maxDepth', 'compact', 'interactiveOnly', 'source', 'format'],
544
552
  'moderate',
545
553
  METHOD_POLICIES.domReadDebugger
546
554
  ),
@@ -600,7 +608,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
600
608
  'input.click',
601
609
  'interact',
602
610
  true,
603
- ['target', 'button', 'clickCount', 'modifiers', 'executionMode', 'recoverStale'],
611
+ ['target', 'button', 'clickCount', 'modifiers', 'holdMs', 'executionMode', 'recoverStale'],
604
612
  'low',
605
613
  METHOD_POLICIES.automationInput
606
614
  ),
@@ -632,7 +640,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
632
640
  'input.press_key',
633
641
  'interact',
634
642
  true,
635
- ['target', 'key', 'modifiers', 'executionMode', 'recoverStale'],
643
+ ['target', 'key', 'modifiers', 'holdMs', 'executionMode', 'recoverStale'],
636
644
  'low',
637
645
  METHOD_POLICIES.automationInput
638
646
  ),
@@ -668,6 +676,22 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
668
676
  'moderate',
669
677
  METHOD_POLICIES.automationInput
670
678
  ),
679
+ 'input.touch': createRegistryEntry(
680
+ 'input.touch',
681
+ 'interact',
682
+ true,
683
+ ['points', 'holdMs', 'moveSteps', 'executionMode', 'recoverStale'],
684
+ 'moderate',
685
+ METHOD_POLICIES.automationInput
686
+ ),
687
+ 'input.perform': createRegistryEntry(
688
+ 'input.perform',
689
+ 'interact',
690
+ true,
691
+ ['steps', 'executionMode', 'timeoutMs', 'continueOnError'],
692
+ 'moderate',
693
+ METHOD_POLICIES.automationInput
694
+ ),
671
695
  'input.scroll_into_view': createRegistryEntry(
672
696
  'input.scroll_into_view',
673
697
  'interact',