@swmansion/argent 0.14.0 → 0.14.1-next.1

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.
Binary file
Binary file
Binary file
@@ -121358,7 +121358,7 @@ init_zod();
121358
121358
 
121359
121359
  // ../tool-server/src/tools/describe/platforms/chromium.ts
121360
121360
  var DESCRIBE_DOM_SCRIPT = `(() => {
121361
- const MAX_DEPTH = 24;
121361
+ const MAX_DEPTH = 60;
121362
121362
  const MAX_NODES = 5000;
121363
121363
  let nodeBudget = MAX_NODES;
121364
121364
  let truncated = false;
@@ -121366,23 +121366,57 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121366
121366
  const h = window.innerHeight;
121367
121367
  if (!w || !h) return JSON.stringify({ tree: null, error: "viewport is zero" });
121368
121368
 
121369
+ // Native prototype accessors, captured once. HTMLFormElement (and HTMLObject/Embed)
121370
+ // is [LegacyOverrideBuiltins]: a control named the same as an inherited member shadows
121371
+ // it on the <form>, so el.children returns the control element (not iterable) and
121372
+ // el.getAttribute returns an element (not a function) \u2014 both throw and abort the whole
121373
+ // walk, the exact crash class this walker must avoid. A prototype accessor is never
121374
+ // shadowed by a form's named properties, so EVERY inherited member read on a possibly-
121375
+ // clobbered element (methods and getters alike) goes through these via .call(el).
121376
+ //
121377
+ // protoGetter reads the accessor's getter defensively: if the descriptor is ever
121378
+ // absent (a page can delete/redefine a DOM prototype member), it falls back to a
121379
+ // direct property read so one missing accessor degrades a single node instead of a
121380
+ // \`.get\` on \`undefined\` throwing at script top and aborting the entire describe.
121381
+ function protoGetter(proto, prop) {
121382
+ const d = Object.getOwnPropertyDescriptor(proto, prop);
121383
+ return d && d.get ? d.get : function () { return this[prop]; };
121384
+ }
121385
+ const getChildNodes = protoGetter(Node.prototype, "childNodes");
121386
+ const getNodeType = protoGetter(Node.prototype, "nodeType");
121387
+ const getNodeValue = protoGetter(Node.prototype, "nodeValue");
121388
+ const getTextContent = protoGetter(Node.prototype, "textContent");
121389
+ const getTagName = protoGetter(Element.prototype, "tagName");
121390
+ const getChildrenEls = protoGetter(Element.prototype, "children");
121391
+ const getShadowRoot = protoGetter(Element.prototype, "shadowRoot");
121392
+ const getScrollHeight = protoGetter(Element.prototype, "scrollHeight");
121393
+ const getClientHeight = protoGetter(Element.prototype, "clientHeight");
121394
+ const getScrollWidth = protoGetter(Element.prototype, "scrollWidth");
121395
+ const getClientWidth = protoGetter(Element.prototype, "clientWidth");
121396
+ const getAttr = Element.prototype.getAttribute;
121397
+ const hasAttr = Element.prototype.hasAttribute;
121398
+ const getBCR = Element.prototype.getBoundingClientRect;
121399
+
121369
121400
  function nodeRole(el) {
121370
- const r = el.getAttribute("role");
121401
+ const r = getAttr.call(el, "role");
121371
121402
  if (r) return r;
121372
- const t = el.tagName.toLowerCase();
121403
+ const t = getTagName.call(el).toLowerCase();
121373
121404
  return t;
121374
121405
  }
121375
121406
 
121376
121407
  function accessibleName(el) {
121377
- const aria = el.getAttribute("aria-label");
121408
+ const aria = getAttr.call(el, "aria-label");
121378
121409
  if (aria) return aria.trim().slice(0, 200);
121379
- const labelledBy = el.getAttribute("aria-labelledby");
121410
+ const labelledBy = getAttr.call(el, "aria-labelledby");
121380
121411
  if (labelledBy) {
121381
121412
  const ids = labelledBy.split(/\\s+/);
121382
121413
  const parts = [];
121383
121414
  for (const id of ids) {
121384
121415
  const ref = document.getElementById(id);
121385
- if (ref) parts.push((ref.textContent || "").trim());
121416
+ // getTextContent via the prototype: an aria-labelledby target can be a <form>
121417
+ // with a control named "textContent", which would shadow the inherited getter
121418
+ // to a control element and crash (.trim() on a non-string).
121419
+ if (ref) parts.push((getTextContent.call(ref) || "").trim());
121386
121420
  }
121387
121421
  if (parts.length) return parts.join(" ").slice(0, 200);
121388
121422
  }
@@ -121391,36 +121425,44 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121391
121425
  if (el.value) return el.value.slice(0, 200);
121392
121426
  }
121393
121427
  if (el instanceof HTMLImageElement && el.alt) return el.alt.slice(0, 200);
121394
- const t = el.title;
121428
+ // getAttribute, not el.title: a <form> with a control named "title" clobbers the
121429
+ // .title property to return that element (not a string), and .slice() then throws,
121430
+ // aborting the whole describe. getAttribute always yields string | null.
121431
+ const t = getAttr.call(el, "title");
121395
121432
  if (t) return t.slice(0, 200);
121396
121433
  return null;
121397
121434
  }
121398
121435
 
121399
121436
  function ownText(el) {
121400
121437
  let s = "";
121401
- for (const child of el.childNodes) {
121402
- if (child.nodeType === 3) s += child.nodeValue;
121438
+ for (const child of getChildNodes.call(el)) {
121439
+ // nodeType/nodeValue via the prototype getters, keeping the "every inherited
121440
+ // read goes through a captured getter" invariant whole: a child can be a
121441
+ // clobbering <form> whose named control shadows these to a control element.
121442
+ // (=== 3 already made a clobbered nodeType safe; this removes the lone
121443
+ // directly-read member so no read's safety rests on the comparison semantics.)
121444
+ if (getNodeType.call(child) === 3) s += getNodeValue.call(child);
121403
121445
  }
121404
121446
  return s.replace(/\\s+/g, " ").trim();
121405
121447
  }
121406
121448
 
121407
121449
  function isInteractive(el) {
121408
- const tag = el.tagName.toLowerCase();
121450
+ const tag = getTagName.call(el).toLowerCase();
121409
121451
  if (tag === "a" && el.href) return true;
121410
121452
  if (tag === "button") return true;
121411
121453
  if (tag === "input" || tag === "textarea" || tag === "select") return true;
121412
121454
  if (tag === "summary" || tag === "details") return true;
121413
- if (el.hasAttribute("onclick")) return true;
121414
- const role = el.getAttribute("role");
121455
+ if (hasAttr.call(el, "onclick")) return true;
121456
+ const role = getAttr.call(el, "role");
121415
121457
  if (role && /^(button|link|tab|menuitem|checkbox|radio|switch|option)$/i.test(role)) return true;
121416
- const tabIndex = el.getAttribute("tabindex");
121458
+ const tabIndex = getAttr.call(el, "tabindex");
121417
121459
  if (tabIndex !== null && tabIndex !== "-1") return true;
121418
121460
  return false;
121419
121461
  }
121420
121462
 
121421
121463
  function isDisabled(el) {
121422
- if (el.hasAttribute("disabled")) return true;
121423
- if (el.getAttribute("aria-disabled") === "true") return true;
121464
+ if (hasAttr.call(el, "disabled")) return true;
121465
+ if (getAttr.call(el, "aria-disabled") === "true") return true;
121424
121466
  return false;
121425
121467
  }
121426
121468
 
@@ -121428,7 +121470,7 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121428
121470
  if (el instanceof HTMLInputElement && (el.type === "checkbox" || el.type === "radio")) {
121429
121471
  return el.checked;
121430
121472
  }
121431
- const v = el.getAttribute("aria-checked");
121473
+ const v = getAttr.call(el, "aria-checked");
121432
121474
  if (v === "true") return true;
121433
121475
  return false;
121434
121476
  }
@@ -121442,13 +121484,16 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121442
121484
  const oy = style.overflowY;
121443
121485
  const ox = style.overflowX;
121444
121486
  if (oy === "auto" || oy === "scroll" || ox === "auto" || ox === "scroll") {
121445
- if (el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth) return true;
121487
+ if (
121488
+ getScrollHeight.call(el) > getClientHeight.call(el) ||
121489
+ getScrollWidth.call(el) > getClientWidth.call(el)
121490
+ )
121491
+ return true;
121446
121492
  }
121447
121493
  return false;
121448
121494
  }
121449
121495
 
121450
- function frame(el) {
121451
- const r = el.getBoundingClientRect();
121496
+ function normRect(r) {
121452
121497
  const x = Math.max(0, Math.min(1, r.left / w));
121453
121498
  const y = Math.max(0, Math.min(1, r.top / h));
121454
121499
  const right = Math.max(0, Math.min(1, r.right / w));
@@ -121456,29 +121501,120 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121456
121501
  return { x, y, width: Math.max(0, right - x), height: Math.max(0, bottom - y) };
121457
121502
  }
121458
121503
 
121459
- function visible(el) {
121460
- const r = el.getBoundingClientRect();
121461
- if (r.width <= 0 || r.height <= 0) return false;
121462
- const style = window.getComputedStyle(el);
121463
- if (style.visibility === "hidden" || style.display === "none" || style.opacity === "0") {
121464
- return false;
121504
+ function frame(el) {
121505
+ return normRect(getBCR.call(el));
121506
+ }
121507
+
121508
+ // Painted extent of an element's own inline TEXT. A box-less element (display:contents,
121509
+ // or a zero-width box whose text overflows) has a 0x0 border box yet its text still
121510
+ // paints; a Range over its own text measures that. Measure ONLY the element's direct
121511
+ // text-node children \u2014 NOT selectNodeContents(el) over the whole subtree, which also
121512
+ // spans the still-laid-out boxes of visibility:hidden / opacity:0 element descendants
121513
+ // (they keep a layout box but paint nothing), oversizing the frame and mis-placing the
121514
+ // tap point. Returns 0x0 when an ancestor transform (e.g. scale(0)) or display:none
121515
+ // collapses the paint. walk() consults this only when the element has its own text.
121516
+ function contentFrame(el) {
121517
+ try {
121518
+ let box = null;
121519
+ for (const child of getChildNodes.call(el)) {
121520
+ // nodeType via the prototype getter (clobber-safe, like ownText); own text only.
121521
+ if (getNodeType.call(child) !== 3) continue;
121522
+ const range = document.createRange();
121523
+ range.selectNodeContents(child);
121524
+ const r = range.getBoundingClientRect();
121525
+ if (r.width <= 0 || r.height <= 0) continue;
121526
+ if (!box) {
121527
+ box = { left: r.left, top: r.top, right: r.right, bottom: r.bottom };
121528
+ } else {
121529
+ if (r.left < box.left) box.left = r.left;
121530
+ if (r.top < box.top) box.top = r.top;
121531
+ if (r.right > box.right) box.right = r.right;
121532
+ if (r.bottom > box.bottom) box.bottom = r.bottom;
121533
+ }
121534
+ }
121535
+ if (!box || box.right <= box.left || box.bottom <= box.top) return null;
121536
+ return normRect(box);
121537
+ } catch (e) {
121538
+ return null;
121465
121539
  }
121466
- return true;
121540
+ }
121541
+
121542
+ function overflowClips(style) {
121543
+ return style.overflowX !== "visible" || style.overflowY !== "visible";
121544
+ }
121545
+
121546
+ // An element has no box of its own when it is display:contents (renders children
121547
+ // but generates no box) or its border box is zero-area. We give such elements a
121548
+ // frame spanning their children rather than their own 0x0 rect.
121549
+ function boxless(el, style) {
121550
+ if (style.display === "contents") return true;
121551
+ const r = getBCR.call(el);
121552
+ return r.width <= 0 || r.height <= 0;
121553
+ }
121554
+
121555
+ function hidden(el, style) {
121556
+ if (style.display === "none") return true;
121557
+ // display:contents has no box, so box-only properties don't apply \u2014 it lays
121558
+ // its children out normally. Never prune it for opacity:0 (opacity affects a
121559
+ // box, of which there is none, so descendants still paint) or for its 0x0
121560
+ // rect; walk() descends and promotes the visible content.
121561
+ if (style.display === "contents") return false;
121562
+ if (style.opacity === "0") return true;
121563
+ // visibility:hidden is deliberately NOT hard-pruned here: visibility inherits but a
121564
+ // descendant can override it back to visible, so cutting the subtree would drop
121565
+ // painted content. walk() descends and suppresses only this element's own paint
121566
+ // (see \`invisibleSelf\`), pruning it only if no visible descendant survives.
121567
+ const r = getBCR.call(el);
121568
+ if (r.width > 0 && r.height > 0) return false;
121569
+ // Zero-area box: prune it only when it clips its overflow (a collapsed
121570
+ // overflow:hidden container genuinely hides its content). With the default
121571
+ // overflow:visible, abs-positioned / overflowing / floated descendants still
121572
+ // paint, so walk() must descend and promote them instead of cutting the subtree.
121573
+ return overflowClips(style);
121574
+ }
121575
+
121576
+ // Smallest normalized box covering every surviving child frame \u2014 the frame we give
121577
+ // a box-less wrapper we still emit, since it has no rect of its own.
121578
+ function unionFrame(children) {
121579
+ let minX = 1;
121580
+ let minY = 1;
121581
+ let maxRight = 0;
121582
+ let maxBottom = 0;
121583
+ for (const c of children) {
121584
+ minX = Math.min(minX, c.frame.x);
121585
+ minY = Math.min(minY, c.frame.y);
121586
+ maxRight = Math.max(maxRight, c.frame.x + c.frame.width);
121587
+ maxBottom = Math.max(maxBottom, c.frame.y + c.frame.height);
121588
+ }
121589
+ if (maxRight <= minX || maxBottom <= minY) {
121590
+ return { x: 0, y: 0, width: 0, height: 0 };
121591
+ }
121592
+ return { x: minX, y: minY, width: maxRight - minX, height: maxBottom - minY };
121467
121593
  }
121468
121594
 
121469
121595
  function walk(el, depth) {
121470
121596
  if (truncated) return null;
121471
121597
  if (depth > MAX_DEPTH) return null;
121472
121598
  if (!(el instanceof Element)) return null;
121473
- if (!visible(el)) return null;
121474
- if (nodeBudget <= 0) {
121475
- truncated = true;
121476
- return null;
121599
+ const style = window.getComputedStyle(el);
121600
+ if (hidden(el, style)) return null;
121601
+ // Charge the node budget only for elements that can actually EMIT. A
121602
+ // visibility:hidden element paints nothing itself and is descended into
121603
+ // purely to catch a descendant that overrides visibility back to visible
121604
+ // (see hidden()); it is otherwise promoted/dropped. Counting it would let a
121605
+ // large fully-hidden subtree (a closed drawer/modal) exhaust the budget and
121606
+ // truncate genuinely visible content elsewhere in the tree. Its visible
121607
+ // descendants, if any, still consume the budget themselves.
121608
+ if (style.visibility !== "hidden") {
121609
+ if (nodeBudget <= 0) {
121610
+ truncated = true;
121611
+ return null;
121612
+ }
121613
+ nodeBudget--;
121477
121614
  }
121478
- nodeBudget--;
121479
121615
 
121480
121616
  const childResults = [];
121481
- for (const child of el.children) {
121617
+ for (const child of getChildrenEls.call(el)) {
121482
121618
  const c = walk(child, depth + 1);
121483
121619
  if (c) childResults.push(c);
121484
121620
  }
@@ -121487,8 +121623,13 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121487
121623
  // Web-components-heavy apps (VS Code, every Lit/Polymer SPA) put their
121488
121624
  // interactive content under .shadowRoot, so without this descent describe
121489
121625
  // returns an empty body.
121490
- if (el.shadowRoot) {
121491
- for (const child of el.shadowRoot.children) {
121626
+ // getShadowRoot via the prototype: a <form> with a control named "shadowRoot"
121627
+ // would otherwise return that control, and we'd re-walk its light-DOM children as
121628
+ // shadow content and duplicate the subtree. A real ShadowRoot is a DocumentFragment
121629
+ // (never a form), so its own .children read is safe.
121630
+ const shadow = getShadowRoot.call(el);
121631
+ if (shadow) {
121632
+ for (const child of shadow.children) {
121492
121633
  const c = walk(child, depth + 1);
121493
121634
  if (c) childResults.push(c);
121494
121635
  }
@@ -121497,7 +121638,7 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121497
121638
  // Same-origin iframes: pierce contentDocument if accessible. Cross-origin
121498
121639
  // contentDocument access throws SecurityError \u2014 swallowed silently so the
121499
121640
  // walker doesn't abort the whole tree.
121500
- if (el.tagName === "IFRAME") {
121641
+ if (getTagName.call(el) === "IFRAME") {
121501
121642
  try {
121502
121643
  const doc = el.contentDocument;
121503
121644
  if (doc && doc.documentElement) {
@@ -121509,30 +121650,89 @@ var DESCRIBE_DOM_SCRIPT = `(() => {
121509
121650
  }
121510
121651
  }
121511
121652
 
121512
- const text = ownText(el);
121513
- const name = accessibleName(el);
121514
- const clickable = isInteractive(el);
121653
+ // A visibility:hidden element paints nothing itself, but a descendant can override
121654
+ // visibility back to visible, so walk() descended instead of pruning (see hidden()).
121655
+ // Suppress this element's own paint \u2014 its text / name / interactivity are invisible \u2014
121656
+ // and treat it as box-less so it contributes no box of its own: it survives only
121657
+ // through, and is framed by, whatever visible descendants it has.
121658
+ const invisibleSelf = style.visibility === "hidden";
121659
+ const text = invisibleSelf ? "" : ownText(el);
121660
+ const name = invisibleSelf ? null : accessibleName(el);
121661
+ const clickable = invisibleSelf ? false : isInteractive(el);
121515
121662
  const role = nodeRole(el);
121663
+ // getAttribute, not el.id: like .title, a named form control can clobber the .id
121664
+ // property to a DOM node, which would then break JSON.stringify of the tree.
121665
+ // Computed here (before promotion) because an id / data-testid is agent-visible
121666
+ // targeting info: a wrapper that carries one is not a pure layer, so neither
121667
+ // promotion path below may drop it.
121668
+ const id =
121669
+ getAttr.call(el, "id") ||
121670
+ getAttr.call(el, "data-testid") ||
121671
+ getAttr.call(el, "data-test-id");
121672
+ const bl = invisibleSelf || boxless(el, style);
121673
+
121674
+ // A box-less element has no rect of its own. Its visible extent is whatever its
121675
+ // children span; with no surviving child, fall back to the painted extent of its own
121676
+ // inline TEXT \u2014 but only when it has its own text. A Range over a wrapper whose
121677
+ // element children were all pruned as invisible still measures their
121678
+ // visibility:hidden / opacity:0 layout boxes (non-zero though nothing paints), which
121679
+ // would resurrect an empty wrapper with a real frame; own text is the only inline
121680
+ // content not already represented by childResults. If the frame is still zero-area it
121681
+ // paints nothing \u2014 e.g. a transform: scale(0) subtree, whose descendants all collapse
121682
+ // \u2014 so it is invisible and dropped. A box-less wrapper with one child and nothing of
121683
+ // its own (no clickable / name / text / identifier) is just a layer, so promote the
121684
+ // child. (Clickable / named / identified box-less nodes fall through and are emitted
121685
+ // with this child- or content-spanning frame.)
121686
+ let selfFrame;
121687
+ if (bl) {
121688
+ selfFrame = unionFrame(childResults);
121689
+ if (text && selfFrame.width <= 0 && selfFrame.height <= 0) {
121690
+ const cf = contentFrame(el);
121691
+ if (cf) selfFrame = cf;
121692
+ }
121693
+ if (childResults.length === 0 && selfFrame.width <= 0 && selfFrame.height <= 0) {
121694
+ return null;
121695
+ }
121696
+ if (
121697
+ !clickable &&
121698
+ !name &&
121699
+ !text &&
121700
+ !id &&
121701
+ childResults.length === 1 &&
121702
+ (role === "div" || invisibleSelf)
121703
+ ) {
121704
+ // Only a pure layout wrapper (a plain div, e.g. a display:contents RNW
121705
+ // wrapper) or a visibility:hidden element with no meaningful paint of
121706
+ // its own is promoted away. A box-less element with a SEMANTIC role
121707
+ // (list, nav, section, listitem, ...) is kept so its role isn't lost \u2014
121708
+ // mirroring the role === "div" gate on the boxed structural collapse
121709
+ // below.
121710
+ return childResults[0];
121711
+ }
121712
+ } else {
121713
+ selfFrame = frame(el);
121714
+ }
121715
+
121516
121716
  // Prune structural wrappers with no info that just add a layer.
121517
- // Keep them if they're roots/clickable/named/have text.
121717
+ // Keep them if they're roots/clickable/named/identified/have text.
121518
121718
  if (
121519
121719
  depth > 0 &&
121520
121720
  childResults.length === 1 &&
121521
121721
  !clickable &&
121522
121722
  !name &&
121523
121723
  !text &&
121724
+ !id &&
121524
121725
  role === "div"
121525
121726
  ) {
121526
121727
  return childResults[0];
121527
121728
  }
121528
121729
  const node = {
121529
121730
  role,
121530
- frame: frame(el),
121731
+ frame: selfFrame,
121531
121732
  children: childResults,
121532
121733
  };
121533
121734
  if (name) node.label = name;
121534
121735
  if (text && text !== name) node.value = text.slice(0, 200);
121535
- const id = el.id || el.getAttribute("data-testid") || el.getAttribute("data-test-id");
121536
121736
  if (id) node.identifier = id;
121537
121737
  if (clickable) node.clickable = true;
121538
121738
  if (isDisabled(el)) node.disabled = true;
@@ -121579,13 +121779,15 @@ async function describeChromium(api) {
121579
121779
  if (!parsed.tree) {
121580
121780
  throw new Error("Chromium describe: empty tree");
121581
121781
  }
121782
+ const data = { tree: parsed.tree, source: "cdp-dom" };
121582
121783
  if (parsed.truncated) {
121583
121784
  process.stderr.write(
121584
121785
  `[chromium-describe] tree truncated at MAX_NODES \u2014 page exceeds the walker's budget; consider scoping the inspection.
121585
121786
  `
121586
121787
  );
121788
+ data.hint = "describe hit the node budget (MAX_NODES) and returned a PARTIAL tree \u2014 some on-screen content is missing. Scope the inspection to a smaller region (scroll to or focus the relevant view) and describe again.";
121587
121789
  }
121588
- return { tree: parsed.tree, source: "cdp-dom" };
121790
+ return data;
121589
121791
  }
121590
121792
 
121591
121793
  // ../tool-server/src/tools/await-ui-element/index.ts
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/argent",
3
- "version": "0.14.0",
3
+ "version": "0.14.1-next.1",
4
4
  "description": "MCP server for iOS Simulator and Android Emulator control",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {