@crouton-kit/capture 1.4.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/capture CHANGED
@@ -9080,7 +9080,7 @@ function assertScrollDestination(value) {
9080
9080
  function parseDoAction(action) {
9081
9081
  if (action.startsWith("click:")) {
9082
9082
  const target = action.slice("click:".length);
9083
- if (!target) throw invalidInput("Invalid --do action: click requires a target \u2014 a css selector, ax:<name>, axid:<id>, or backend:<id>.");
9083
+ if (!target) throw invalidInput("Invalid --do action: click requires a target \u2014 a css selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, or backend:<id>.");
9084
9084
  return { verb: "click", target };
9085
9085
  }
9086
9086
  if (action.startsWith("scroll:")) {
@@ -9091,7 +9091,7 @@ function parseDoAction(action) {
9091
9091
  if (!isScrollDestination(to)) throw invalidInput("Invalid --do action: scroll requires `scroll:<target>,to=<top|bottom|px>`.");
9092
9092
  return { verb: "scroll", target: spec.slice(0, comma), to };
9093
9093
  }
9094
- throw invalidInput("Unsupported --do action. Supported actions: click:<target>; scroll:<target>,to=<top|bottom|px> \u2014 target is a css selector, ax:<name>, axid:<id>, or backend:<id>.");
9094
+ throw invalidInput("Unsupported --do action. Supported actions: click:<target>; scroll:<target>,to=<top|bottom|px> \u2014 target is a css selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, or backend:<id>.");
9095
9095
  }
9096
9096
  var SWEEP_NUMERIC_AXES, SWEEP_ENUM_VALUES;
9097
9097
  var init_leaf_grammar = __esm({
@@ -10316,7 +10316,7 @@ function validateHarAppendBatch(value, source) {
10316
10316
  return { entries, incompleteLifecycles };
10317
10317
  }
10318
10318
  function captureVersion() {
10319
- const version = "1.4.1";
10319
+ const version = "1.4.2";
10320
10320
  return typeof version === "string" && version.length > 0 ? version : "development";
10321
10321
  }
10322
10322
  function token() {
@@ -12107,7 +12107,7 @@ function wallTime(value) {
12107
12107
  }
12108
12108
  }
12109
12109
  function captureVersion2() {
12110
- const version = "1.4.1";
12110
+ const version = "1.4.2";
12111
12111
  return typeof version === "string" && version.length > 0 ? version : "development";
12112
12112
  }
12113
12113
  function deepFreeze(value) {
@@ -16048,7 +16048,7 @@ async function resolveLiveTarget(client, input) {
16048
16048
  return settleAxMatches("axid", input, matches);
16049
16049
  }
16050
16050
  case "css":
16051
- return resolveLiveCss(client, input, parsed.value);
16051
+ return resolveBareTarget(client, input, parsed.value);
16052
16052
  }
16053
16053
  }
16054
16054
  function preferActionable(matches) {
@@ -16081,6 +16081,17 @@ function settleAxMatches(kind, input, rawMatches) {
16081
16081
  }))
16082
16082
  };
16083
16083
  }
16084
+ async function resolveBareTarget(client, input, selector) {
16085
+ const css = await resolveLiveCss(client, input, selector);
16086
+ if (css.ok || css.code === "ambiguous") return css;
16087
+ const normalized2 = normalizeAccessibleName(input);
16088
+ if (!normalized2) return css;
16089
+ const matches = (await readFullAXTree(client)).filter(
16090
+ (node) => node.backendDOMNodeId !== void 0 && node.name?.value !== void 0 && normalizeAccessibleName(node.name.value) === normalized2
16091
+ );
16092
+ const ax = settleAxMatches("ax", input, matches);
16093
+ return ax.ok || ax.code === "ambiguous" ? ax : css;
16094
+ }
16084
16095
  async function resolveLiveCss(client, input, selector) {
16085
16096
  await client.send("DOM.enable");
16086
16097
  const { root } = await client.send("DOM.getDocument", { depth: 0 });
@@ -16308,7 +16319,7 @@ function emitResolutionError(parsed, command, failure) {
16308
16319
  {
16309
16320
  tag: "error",
16310
16321
  attrs: { command, code: "unsupported_prefix" },
16311
- summary: fact`received: target \`${failure.input}\` — the \`text:\` prefix is not accepted by live driving verbs; expected one of: bare CSS selector, ax:<name>, axid:<id>, backend:<id>.`,
16322
+ summary: fact`received: target \`${failure.input}\` — the \`text:\` prefix is not accepted by live driving verbs; expected one of: bare CSS selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, backend:<id>.`,
16312
16323
  followUp: fact`Retry \`${command}\` with \`ax:<name>\` (accessible-name substring) or a CSS selector; \`text:\` matching exists only on the query leaves.`
16313
16324
  },
16314
16325
  { json: parsed.json }
@@ -16360,7 +16371,7 @@ async function cmdPageClick(parsed, _args) {
16360
16371
  return emitInvalidInput(
16361
16372
  parsed,
16362
16373
  "page click",
16363
- fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector, ax:<name>, axid:<id>, or backend:<id>).`
16374
+ fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector or exact accessible name, ax:<name>, axid:<id>, or backend:<id>).`
16364
16375
  );
16365
16376
  }
16366
16377
  const settle = effectiveSettle(parsed, { standalone: 1e3, session: 2500 });
@@ -16411,7 +16422,7 @@ var init_click = __esm({
16411
16422
  USAGE = `capture page click <target> \u2014 dispatch one real click on exactly one resolved element
16412
16423
 
16413
16424
  input:
16414
- <target> resolved against the LIVE page: bare CSS selector, ax:<name> (case-insensitive substring over accessible names), axid:<id>, backend:<id>. text: is not accepted. Exactly one match required \u2014 zero or many matches is a structured error listing candidates with backend:<id> retry keys.
16425
+ <target> resolved against the LIVE page: bare CSS selector (takes precedence) or exact accessible name when CSS finds none, ax:<name> (case-insensitive substring over accessible names), axid:<id>, backend:<id>. text: is not accepted. Exactly one match required \u2014 zero or many matches is a structured error listing candidates with backend:<id> retry keys.
16415
16426
  --settle <ms> network-settle window applied after the click (default: 1000; 2500 with an active session; 0 disables)
16416
16427
  --no-screenshot skip the auto-screenshot
16417
16428
  output:
@@ -16487,7 +16498,7 @@ var init_type = __esm({
16487
16498
 
16488
16499
  input:
16489
16500
  <text> the text to insert (agent-supplied; echoed back in the result block)
16490
- --into <target> focus this field first \u2014 resolved against the LIVE page: bare CSS selector, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required
16501
+ --into <target> focus this field first \u2014 resolved against the LIVE page: bare CSS selector (takes precedence) or exact accessible name when CSS finds none, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required
16491
16502
  --settle <ms> network-settle window applied after typing (default: 500; 1500 with an active session; 0 disables)
16492
16503
  --no-screenshot skip the auto-screenshot
16493
16504
  output:
@@ -16508,7 +16519,7 @@ async function cmdPageScroll(parsed, _args) {
16508
16519
  return emitInvalidInput(
16509
16520
  parsed,
16510
16521
  "page scroll",
16511
- fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector, ax:<name>, axid:<id>, or backend:<id>) plus --to <top|bottom|px>.`
16522
+ fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector or exact accessible name, ax:<name>, axid:<id>, or backend:<id>) plus --to <top|bottom|px>.`
16512
16523
  );
16513
16524
  }
16514
16525
  if (parsed.to === void 0) {
@@ -16575,7 +16586,7 @@ var init_scroll = __esm({
16575
16586
  USAGE3 = `capture page scroll <target> --to <top|bottom|px> \u2014 scroll one resolved container to a position
16576
16587
 
16577
16588
  input:
16578
- <target> resolved against the LIVE page: bare CSS selector, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required \u2014 zero or many matches is a structured error listing candidates
16589
+ <target> resolved against the LIVE page: bare CSS selector (takes precedence) or exact accessible name when CSS finds none, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required \u2014 zero or many matches is a structured error listing candidates
16579
16590
  --to <dest> required destination: top, bottom, or a pixel offset (scrollTop value)
16580
16591
  --settle <ms> network-settle window applied after the scroll (default: 1000; 2500 with an active session; 0 disables)
16581
16592
  --no-screenshot skip the auto-screenshot
@@ -228112,7 +228123,7 @@ async function cmdPageShot(parsed, _args) {
228112
228123
  return;
228113
228124
  }
228114
228125
  if (parsed.cropSelector !== void 0 && !parsed.cropSelector.trim()) {
228115
- emitResult({ tag: "error", attrs: { command: "page shot", code: "invalid_crop_selector" }, summary: text`received an empty --crop-selector; expected a bare CSS selector, ax:<name>, axid:<id>, or backend:<id>.` }, { json: parsed.json });
228126
+ emitResult({ tag: "error", attrs: { command: "page shot", code: "invalid_crop_selector" }, summary: text`received an empty --crop-selector; expected a bare CSS selector or exact accessible name, ax:<name>, axid:<id>, or backend:<id>.` }, { json: parsed.json });
228116
228127
  process.exitCode = 1;
228117
228128
  return;
228118
228129
  }
@@ -228223,7 +228234,7 @@ input:
228223
228234
  --full-page transient override to the full scrollable content height for this capture
228224
228235
  --color-scheme <dark|light> transient prefers-color-scheme override for this capture; cleared afterward
228225
228236
  --crop <x,y,w,h> capture this page-coordinate CSS-pixel rect, intersected with the live visual viewport; x/y may be fractional, w/h must be positive
228226
- --crop-selector <sel> resolve exactly one live target (bare CSS, ax:<name>, axid:<id>, or backend:<id>), scroll it into view, then crop its border box and surroundings
228237
+ --crop-selector <sel> resolve exactly one live target (bare CSS takes precedence; exact accessible name applies when CSS finds none; ax:<name>, axid:<id>, or backend:<id>), scroll it into view, then crop its border box and surroundings
228227
228238
  --pad <px> nonnegative integer CSS pixels added on every side of --crop-selector (default: 0; not accepted with --crop)
228228
228239
  --zoom <factor> positive decimal CSS-to-image scale requested for a CSS crop; the 1600px image cap may produce a smaller reported scale
228229
228240
  --out <path> destination file; default: the active session's shots/ sequence, or a fresh oneshot-*/page/ dir under the capture root when no session is active
@@ -228364,7 +228375,7 @@ use when driving or inspecting the tab a session opened or adopted; all verbs au
228364
228375
 
228365
228376
  All verbs auto-target the active session tab; --target/--url override. The
228366
228377
  driving verbs (click, type, scroll) resolve exactly one element via the
228367
- unified target grammar \u2014 bare CSS selector, ax:<name>, axid:<id>,
228378
+ unified target grammar \u2014 bare CSS selector (which takes precedence) or exact accessible name when CSS finds none, ax:<name>, axid:<id>,
228368
228379
  backend:<id> \u2014 and reject an ambiguous target with the candidate list.
228369
228380
  During a live composed recording every verb routes through the recorder.
228370
228381
 
@@ -241508,7 +241519,7 @@ function recorderLiveClient(recorder) {
241508
241519
  function resolutionError(failure) {
241509
241520
  if (failure.code === "unsupported-prefix") {
241510
241521
  return new DoActionError(
241511
- `Unsupported --do target prefix in "${failure.input}": text: is query-leaf-only. Accepted prefixes: bare css selector, ax:<name>, axid:<id>, backend:<id>.`,
241522
+ `Unsupported --do target prefix in "${failure.input}": text: is query-leaf-only. Accepted prefixes: bare css selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, backend:<id>.`,
241512
241523
  "unsupported_target_prefix"
241513
241524
  );
241514
241525
  }
@@ -241814,7 +241825,7 @@ var init_rec = __esm({
241814
241825
  input:
241815
241826
  [url] --do <action> one-shot: open <url> (or record the active session tab when <url> is omitted), record one action, finalize
241816
241827
  action click:<target> | scroll:<target>,to=<top|bottom|px>
241817
- target css selector (bare string) | ax:<name> (case-insensitive substring) | axid:<id> | backend:<id>
241828
+ target css selector (takes precedence) or exact accessible name when CSS finds none (bare string) | ax:<name> (case-insensitive substring) | axid:<id> | backend:<id>
241818
241829
  must resolve to exactly one live element; text: is not accepted by driving actions
241819
241830
  --duration <seconds> keep recording after the action (default: 0)
241820
241831
  --viewport <WxH> emulate a viewport for the recording window (restored after); exact <positive-safe-int>x<positive-safe-int> grammar with lowercase x and no whitespace
@@ -243333,7 +243344,7 @@ precedence: explicit flag > active session > env. \`session start\` adopts only
243333
243344
  explicit --target.`;
243334
243345
  }
243335
243346
  function printVersion() {
243336
- const declared = "1.4.1";
243347
+ const declared = "1.4.2";
243337
243348
  if (declared) {
243338
243349
  console.log(declared);
243339
243350
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/capture",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "Browser automation and UI validation via CDP — session-based screenshots, HAR, a11y, click/type, and JS exec",
5
5
  "bin": {
6
6
  "capture": "./bin/capture"
@@ -72,7 +72,7 @@ const USAGE = `capture motion rec — record the page over time: composed (whate
72
72
  input:
73
73
  [url] --do <action> one-shot: open <url> (or record the active session tab when <url> is omitted), record one action, finalize
74
74
  action click:<target> | scroll:<target>,to=<top|bottom|px>
75
- target css selector (bare string) | ax:<name> (case-insensitive substring) | axid:<id> | backend:<id>
75
+ target css selector (takes precedence) or exact accessible name when CSS finds none (bare string) | ax:<name> (case-insensitive substring) | axid:<id> | backend:<id>
76
76
  must resolve to exactly one live element; text: is not accepted by driving actions
77
77
  --duration <seconds> keep recording after the action (default: 0)
78
78
  --viewport <WxH> emulate a viewport for the recording window (restored after); exact <positive-safe-int>x<positive-safe-int> grammar with lowercase x and no whitespace
@@ -212,7 +212,7 @@ function recorderLiveClient(recorder: Pick<RecorderSession, 'handleCdp'>): LiveC
212
212
  function resolutionError(failure: ResolutionFailure): DoActionError {
213
213
  if (failure.code === 'unsupported-prefix') {
214
214
  return new DoActionError(
215
- `Unsupported --do target prefix in "${failure.input}": text: is query-leaf-only. Accepted prefixes: bare css selector, ax:<name>, axid:<id>, backend:<id>.`,
215
+ `Unsupported --do target prefix in "${failure.input}": text: is query-leaf-only. Accepted prefixes: bare css selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, backend:<id>.`,
216
216
  'unsupported_target_prefix',
217
217
  );
218
218
  }
@@ -112,7 +112,7 @@ export function emitResolutionError(parsed: ParsedArgs, command: string, failure
112
112
  {
113
113
  tag: 'error',
114
114
  attrs: { command, code: 'unsupported_prefix' },
115
- summary: fact`received: target \`${failure.input}\` — the \`text:\` prefix is not accepted by live driving verbs; expected one of: bare CSS selector, ax:<name>, axid:<id>, backend:<id>.`,
115
+ summary: fact`received: target \`${failure.input}\` — the \`text:\` prefix is not accepted by live driving verbs; expected one of: bare CSS selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, backend:<id>.`,
116
116
  followUp: fact`Retry \`${command}\` with \`ax:<name>\` (accessible-name substring) or a CSS selector; \`text:\` matching exists only on the query leaves.`,
117
117
  },
118
118
  { json: parsed.json },
@@ -170,7 +170,7 @@ export function emitResolutionError(parsed: ParsedArgs, command: string, failure
170
170
  const USAGE = `capture page click <target> — dispatch one real click on exactly one resolved element
171
171
 
172
172
  input:
173
- <target> resolved against the LIVE page: bare CSS selector, ax:<name> (case-insensitive substring over accessible names), axid:<id>, backend:<id>. text: is not accepted. Exactly one match required — zero or many matches is a structured error listing candidates with backend:<id> retry keys.
173
+ <target> resolved against the LIVE page: bare CSS selector (takes precedence) or exact accessible name when CSS finds none, ax:<name> (case-insensitive substring over accessible names), axid:<id>, backend:<id>. text: is not accepted. Exactly one match required — zero or many matches is a structured error listing candidates with backend:<id> retry keys.
174
174
  --settle <ms> network-settle window applied after the click (default: 1000; 2500 with an active session; 0 disables)
175
175
  --no-screenshot skip the auto-screenshot
176
176
  output:
@@ -188,7 +188,7 @@ export async function cmdPageClick(parsed: ParsedArgs, _args: string[]): Promise
188
188
  return emitInvalidInput(
189
189
  parsed,
190
190
  'page click',
191
- fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector, ax:<name>, axid:<id>, or backend:<id>).`,
191
+ fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector or exact accessible name, ax:<name>, axid:<id>, or backend:<id>).`,
192
192
  );
193
193
  }
194
194
 
@@ -30,7 +30,7 @@ export const PAGE_USAGE = `capture page — verbs against the live session tab:
30
30
 
31
31
  All verbs auto-target the active session tab; --target/--url override. The
32
32
  driving verbs (click, type, scroll) resolve exactly one element via the
33
- unified target grammar — bare CSS selector, ax:<name>, axid:<id>,
33
+ unified target grammar — bare CSS selector (which takes precedence) or exact accessible name when CSS finds none, ax:<name>, axid:<id>,
34
34
  backend:<id> — and reject an ambiguous target with the candidate list.
35
35
  During a live composed recording every verb routes through the recorder.
36
36
 
@@ -22,7 +22,7 @@ import {
22
22
  const USAGE = `capture page scroll <target> --to <top|bottom|px> — scroll one resolved container to a position
23
23
 
24
24
  input:
25
- <target> resolved against the LIVE page: bare CSS selector, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required — zero or many matches is a structured error listing candidates
25
+ <target> resolved against the LIVE page: bare CSS selector (takes precedence) or exact accessible name when CSS finds none, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required — zero or many matches is a structured error listing candidates
26
26
  --to <dest> required destination: top, bottom, or a pixel offset (scrollTop value)
27
27
  --settle <ms> network-settle window applied after the scroll (default: 1000; 2500 with an active session; 0 disables)
28
28
  --no-screenshot skip the auto-screenshot
@@ -41,7 +41,7 @@ export async function cmdPageScroll(parsed: ParsedArgs, _args: string[]): Promis
41
41
  return emitInvalidInput(
42
42
  parsed,
43
43
  'page scroll',
44
- fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector, ax:<name>, axid:<id>, or backend:<id>) plus --to <top|bottom|px>.`,
44
+ fact`received: ${parsed.positional.length} positional arguments; expected exactly one target (CSS selector or exact accessible name, ax:<name>, axid:<id>, or backend:<id>) plus --to <top|bottom|px>.`,
45
45
  );
46
46
  }
47
47
  if (parsed.to === undefined) {
@@ -42,7 +42,7 @@ input:
42
42
  --full-page transient override to the full scrollable content height for this capture
43
43
  --color-scheme <dark|light> transient prefers-color-scheme override for this capture; cleared afterward
44
44
  --crop <x,y,w,h> capture this page-coordinate CSS-pixel rect, intersected with the live visual viewport; x/y may be fractional, w/h must be positive
45
- --crop-selector <sel> resolve exactly one live target (bare CSS, ax:<name>, axid:<id>, or backend:<id>), scroll it into view, then crop its border box and surroundings
45
+ --crop-selector <sel> resolve exactly one live target (bare CSS takes precedence; exact accessible name applies when CSS finds none; ax:<name>, axid:<id>, or backend:<id>), scroll it into view, then crop its border box and surroundings
46
46
  --pad <px> nonnegative integer CSS pixels added on every side of --crop-selector (default: 0; not accepted with --crop)
47
47
  --zoom <factor> positive decimal CSS-to-image scale requested for a CSS crop; the 1600px image cap may produce a smaller reported scale
48
48
  --out <path> destination file; default: the active session's shots/ sequence, or a fresh oneshot-*/page/ dir under the capture root when no session is active
@@ -266,7 +266,7 @@ export async function cmdPageShot(parsed: ParsedArgs, _args: string[]): Promise<
266
266
  return;
267
267
  }
268
268
  if (parsed.cropSelector !== undefined && !parsed.cropSelector.trim()) {
269
- emitResult({ tag: 'error', attrs: { command: 'page shot', code: 'invalid_crop_selector' }, summary: text`received an empty --crop-selector; expected a bare CSS selector, ax:<name>, axid:<id>, or backend:<id>.` }, { json: parsed.json });
269
+ emitResult({ tag: 'error', attrs: { command: 'page shot', code: 'invalid_crop_selector' }, summary: text`received an empty --crop-selector; expected a bare CSS selector or exact accessible name, ax:<name>, axid:<id>, or backend:<id>.` }, { json: parsed.json });
270
270
  process.exitCode = 1;
271
271
  return;
272
272
  }
@@ -28,7 +28,7 @@ const USAGE = `capture page type <text> [--into <target>] — type text into the
28
28
 
29
29
  input:
30
30
  <text> the text to insert (agent-supplied; echoed back in the result block)
31
- --into <target> focus this field first — resolved against the LIVE page: bare CSS selector, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required
31
+ --into <target> focus this field first — resolved against the LIVE page: bare CSS selector (takes precedence) or exact accessible name when CSS finds none, ax:<name> (case-insensitive substring), axid:<id>, backend:<id>; text: is not accepted; exactly one match required
32
32
  --settle <ms> network-settle window applied after typing (default: 500; 1500 with an active session; 0 disables)
33
33
  --no-screenshot skip the auto-screenshot
34
34
  output:
@@ -84,7 +84,7 @@ export type DoAction =
84
84
  export function parseDoAction(action: string): DoAction {
85
85
  if (action.startsWith('click:')) {
86
86
  const target = action.slice('click:'.length);
87
- if (!target) throw invalidInput('Invalid --do action: click requires a target — a css selector, ax:<name>, axid:<id>, or backend:<id>.');
87
+ if (!target) throw invalidInput('Invalid --do action: click requires a target — a css selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, or backend:<id>.');
88
88
  return { verb: 'click', target };
89
89
  }
90
90
  if (action.startsWith('scroll:')) {
@@ -95,5 +95,5 @@ export function parseDoAction(action: string): DoAction {
95
95
  if (!isScrollDestination(to)) throw invalidInput('Invalid --do action: scroll requires `scroll:<target>,to=<top|bottom|px>`.');
96
96
  return { verb: 'scroll', target: spec.slice(0, comma), to };
97
97
  }
98
- throw invalidInput('Unsupported --do action. Supported actions: click:<target>; scroll:<target>,to=<top|bottom|px> — target is a css selector, ax:<name>, axid:<id>, or backend:<id>.');
98
+ throw invalidInput('Unsupported --do action. Supported actions: click:<target>; scroll:<target>,to=<top|bottom|px> — target is a css selector (or exact accessible name when CSS finds none), ax:<name>, axid:<id>, or backend:<id>.');
99
99
  }
package/src/interact.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * (`src/output/selector.ts`'s `parseSelectorInput`) and resolves it against
7
7
  * the LIVE page (not collector-stored records), per prefix:
8
8
  *
9
- * bare string live CSS query — `DOM.getDocument` + `DOM.querySelectorAll`
9
+ * bare string live CSS query, or an exact accessible-name match when CSS finds none
10
10
  * backend:<id> identity — the canonical retry key in every resolution failure
11
11
  * ax:<name> case-insensitive SUBSTRING over live `Accessibility.getFullAXTree` names
12
12
  * axid:<id> AX node id from the same live fetch
@@ -172,7 +172,7 @@ export async function resolveLiveTarget(
172
172
  return settleAxMatches('axid', input, matches);
173
173
  }
174
174
  case 'css':
175
- return resolveLiveCss(client, input, parsed.value);
175
+ return resolveBareTarget(client, input, parsed.value);
176
176
  }
177
177
  }
178
178
 
@@ -220,6 +220,32 @@ function settleAxMatches(
220
220
  };
221
221
  }
222
222
 
223
+ /**
224
+ * Bare targets preserve CSS behavior when they select one or many elements;
225
+ * when CSS selects none (or rejects the string), an exact accessible-name
226
+ * lookup lets an element name emitted by `page elements` drive directly.
227
+ * `ax:` remains the explicit case-insensitive substring query.
228
+ */
229
+ async function resolveBareTarget(
230
+ client: LiveClient,
231
+ input: string,
232
+ selector: string,
233
+ ): Promise<ResolvedTarget | ResolutionFailure> {
234
+ const css = await resolveLiveCss(client, input, selector);
235
+ if (css.ok || css.code === 'ambiguous') return css;
236
+
237
+ const normalized = normalizeAccessibleName(input);
238
+ if (!normalized) return css;
239
+ const matches = (await readFullAXTree(client)).filter(
240
+ (node) =>
241
+ node.backendDOMNodeId !== undefined &&
242
+ node.name?.value !== undefined &&
243
+ normalizeAccessibleName(node.name.value) === normalized,
244
+ );
245
+ const ax = settleAxMatches('ax', input, matches);
246
+ return ax.ok || ax.code === 'ambiguous' ? ax : css;
247
+ }
248
+
223
249
  async function resolveLiveCss(
224
250
  client: LiveClient,
225
251
  input: string,