rnxsim 0.1.366 → 0.1.367
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/README.md +14 -2
- package/cli/bin.ts +18 -0
- package/cli/bridge-flow-runner.ts +14 -151
- package/cli/commands/assert.ts +1 -1
- package/cli/commands/box/checkout-plane.ts +126 -0
- package/cli/commands/box/rnx-command.ts +68 -0
- package/cli/commands/box.ts +200 -0
- package/cli/commands/control.ts +12 -32
- package/cli/commands/do-chain.ts +412 -0
- package/cli/commands/flow.ts +0 -10
- package/cli/commands/inspect/core.ts +302 -725
- package/cli/commands/inspect/describe.ts +3 -76
- package/cli/commands/inspect/find.ts +3 -6
- package/cli/commands/inspect/get-layout.ts +14 -1
- package/cli/commands/inspect/resolve-target.ts +41 -47
- package/cli/commands/inspect/shared.ts +28 -123
- package/cli/commands/inspect/tree.ts +6 -15
- package/cli/commands/inspect/wait-ready.ts +2 -2
- package/cli/commands/inspect/wait-selector.ts +4 -8
- package/cli/commands/inspect.ts +23 -17
- package/cli/commands/install-desktop.ts +44 -7
- package/cli/commands/maestro-generate.ts +0 -2
- package/cli/commands/platform.ts +68 -0
- package/cli/commands/state.ts +5 -2
- package/cli/outbound-endpoints.ts +6 -1
- package/cli/parse-args.ts +13 -2
- package/cli/ws-bridge.ts +30 -2
- package/detox/index.ts +34 -17
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-SO6IOPRK.js → agent-EKKIAKP5.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-PMYCB2KO.js → agent-wrapper-MHLQUGA6.js} +2 -2
- package/dist-cli/chunks/{app-fonts-LULQGFY7.js → app-fonts-HN6ADRTQ.js} +2 -2
- package/dist-cli/chunks/{assert-WGNCD4ZU.js → assert-IMR6QCN6.js} +3 -3
- package/dist-cli/chunks/{auth-G7CCHH3M.js → auth-27LPMQNH.js} +2 -2
- package/dist-cli/chunks/{beta-N4LXL5TO.js → beta-6RXBB5AI.js} +2 -2
- package/dist-cli/chunks/box-DORLF3GP.js +1709 -0
- package/dist-cli/chunks/{camera-4JHCYBQS.js → camera-2G7JZ5DL.js} +2 -2
- package/dist-cli/chunks/{chunk-2UXU53MG.js → chunk-3QZJLNYY.js} +1 -1
- package/dist-cli/chunks/{chunk-YXXKSL6R.js → chunk-3VBVEDDA.js} +1 -1
- package/dist-cli/chunks/{chunk-ZJIS3CTW.js → chunk-4BWGLTKU.js} +2 -2
- package/dist-cli/chunks/chunk-4XSNO374.js +423 -0
- package/dist-cli/chunks/{chunk-RYIMSKDO.js → chunk-5DGMFUZG.js} +2 -2
- package/dist-cli/chunks/{chunk-M3BNCF7E.js → chunk-6DBS5EGX.js} +2 -2
- package/dist-cli/chunks/{chunk-ONORMKWX.js → chunk-6SO6I3D5.js} +2 -2
- package/dist-cli/chunks/chunk-75EFOPXE.js +32 -0
- package/dist-cli/chunks/{chunk-5NMCF5VF.js → chunk-A5AJFVOF.js} +1 -1
- package/dist-cli/chunks/{chunk-N3UC7HIX.js → chunk-AM2ODYAN.js} +1 -1
- package/dist-cli/chunks/{chunk-I6KIM6PN.js → chunk-AVOROGA3.js} +1 -1
- package/dist-cli/chunks/chunk-AXMMLO7D.js +130 -0
- package/dist-cli/chunks/{chunk-ORWP2E7R.js → chunk-BSEXEYNX.js} +2 -2
- package/dist-cli/chunks/{chunk-NQFJOCSF.js → chunk-CM6ZKZW7.js} +2 -2
- package/dist-cli/chunks/{chunk-ZMJ75EDG.js → chunk-DNVABCEC.js} +2 -2
- package/dist-cli/chunks/{chunk-2S7BKUP2.js → chunk-E33A4AIR.js} +2 -2
- package/dist-cli/chunks/{chunk-GXAB52K7.js → chunk-EFMOXYDD.js} +2 -2
- package/dist-cli/chunks/{chunk-TIAEYO6I.js → chunk-EV4P5UBP.js} +2 -2
- package/dist-cli/chunks/{chunk-JHXETDVC.js → chunk-G5IUIGGC.js} +1 -1
- package/dist-cli/chunks/chunk-GMLOCV3E.js +312 -0
- package/dist-cli/chunks/{chunk-ZLVRYXZ7.js → chunk-GRN2FHVW.js} +1 -1
- package/dist-cli/chunks/{chunk-A5FXX5PJ.js → chunk-HFFXPUEP.js} +1 -1
- package/dist-cli/chunks/chunk-HG6T4OOR.js +4 -0
- package/dist-cli/chunks/{chunk-OW5XHAA4.js → chunk-HNT2OXPZ.js} +1 -1
- package/dist-cli/chunks/chunk-IPEURNLI.js +24 -0
- package/dist-cli/chunks/chunk-IR44SFBA.js +5 -0
- package/dist-cli/chunks/chunk-JMSLXZFW.js +4 -0
- package/dist-cli/chunks/{chunk-EFR5DVNP.js → chunk-JPXQKLNH.js} +1 -1
- package/dist-cli/chunks/chunk-K3EC6JDE.js +4 -0
- package/dist-cli/chunks/{chunk-YGMEDJKJ.js → chunk-KHX65HAU.js} +2 -2
- package/dist-cli/chunks/{chunk-EQK5MJCU.js → chunk-LWOVKHRC.js} +2 -2
- package/dist-cli/chunks/{chunk-5OK3LCEJ.js → chunk-MQN5DWTI.js} +2 -2
- package/dist-cli/chunks/{chunk-TTASWLKM.js → chunk-N3BXQEUF.js} +1 -1
- package/dist-cli/chunks/{chunk-UCHW7ZHR.js → chunk-N5OSV26B.js} +2 -2
- package/dist-cli/chunks/chunk-PHNURIH3.js +4 -0
- package/dist-cli/chunks/{chunk-AFV32WZT.js → chunk-PP2KNVXP.js} +3 -3
- package/dist-cli/chunks/{chunk-XJU2QIRY.js → chunk-PROKNJVY.js} +2 -2
- package/dist-cli/chunks/chunk-QXLLY4IM.js +7 -0
- package/dist-cli/chunks/{chunk-HT6GFZOZ.js → chunk-RNQTOGM7.js} +1 -1
- package/dist-cli/chunks/{chunk-CN6XYYEA.js → chunk-RPNKKVBN.js} +27 -27
- package/dist-cli/chunks/{chunk-LN3RNW6X.js → chunk-RSNLI2NH.js} +2 -2
- package/dist-cli/chunks/{chunk-YCCCPFWT.js → chunk-RZGHV2WI.js} +2 -2
- package/dist-cli/chunks/chunk-SGGL3AFV.js +497 -0
- package/dist-cli/chunks/chunk-SH5ETJKG.js +4 -0
- package/dist-cli/chunks/{chunk-77ZHQJZR.js → chunk-T6J4YP56.js} +2 -2
- package/dist-cli/chunks/{chunk-MGDSJZ37.js → chunk-TWUE7WF5.js} +2 -2
- package/dist-cli/chunks/{chunk-SA6XVI55.js → chunk-U44VEQRN.js} +1 -1
- package/dist-cli/chunks/{chunk-IZKHOM37.js → chunk-URLVQHC3.js} +1 -1
- package/dist-cli/chunks/{chunk-GAJQSITE.js → chunk-VANZFTUE.js} +1 -1
- package/dist-cli/chunks/{chunk-EGAGZHO6.js → chunk-VB2YBH46.js} +1 -1
- package/dist-cli/chunks/{chunk-H23KWIKA.js → chunk-VFU2SJ7M.js} +2 -2
- package/dist-cli/chunks/{chunk-34IQM22N.js → chunk-VUBJ6FXU.js} +15 -15
- package/dist-cli/chunks/{chunk-NM2L2QV4.js → chunk-W5PATILQ.js} +2 -2
- package/dist-cli/chunks/chunk-WI5V4TYG.js +38 -0
- package/dist-cli/chunks/{chunk-6PL2CCGP.js → chunk-XTRHQAOV.js} +2 -2
- package/dist-cli/chunks/chunk-XWUTJLBM.js +27 -0
- package/dist-cli/chunks/{chunk-5DAIA77C.js → chunk-XYRUQQUR.js} +2 -2
- package/dist-cli/chunks/chunk-YBFB3RM7.js +4 -0
- package/dist-cli/chunks/chunk-YGQVQ2VG.js +4 -0
- package/dist-cli/chunks/{chunk-CTG7HBCA.js → chunk-YINACV32.js} +1 -1
- package/dist-cli/chunks/chunk-YV7ERO2V.js +5 -0
- package/dist-cli/chunks/{cleanup-KRIATKFH.js → cleanup-GVFHQ3KH.js} +2 -2
- package/dist-cli/chunks/cli-version-DPX5MA2M.js +4 -0
- package/dist-cli/chunks/{compat-6PTQ3QEA.js → compat-AYRTXYAM.js} +2 -2
- package/dist-cli/chunks/{config-ETGDUANO.js → config-K7LOOV62.js} +2 -2
- package/dist-cli/chunks/connect-H3CNY4IO.js +4 -0
- package/dist-cli/chunks/control-SMHZ2CF5.js +4 -0
- package/dist-cli/chunks/daemon-TEZUNI73.js +4 -0
- package/dist-cli/chunks/{debug-WIIB6ZKC.js → debug-ELGHG57T.js} +3 -3
- package/dist-cli/chunks/{desktop-TD7Q5LNB.js → desktop-4G7FK3MP.js} +3 -3
- package/dist-cli/chunks/{detox-7GNR33VM.js → detox-JKXGEJIZ.js} +2 -2
- package/dist-cli/chunks/{device-QOR3PGUF.js → device-IV3AR6KG.js} +2 -2
- package/dist-cli/chunks/{diagnose-RRCBLP6E.js → diagnose-3ERGF7EW.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-MJUGJPXT.js → disk-cleanup-ALFEVZ4B.js} +2 -2
- package/dist-cli/chunks/do-chain-K2B4OHM7.js +5 -0
- package/dist-cli/chunks/drivers-KJYXA3M3.js +4 -0
- package/dist-cli/chunks/env-2KF6IYZE.js +4 -0
- package/dist-cli/chunks/{film-D7Y64LID.js → film-UQXJFAD5.js} +3 -3
- package/dist-cli/chunks/flow-ASTUH56E.js +4 -0
- package/dist-cli/chunks/help-SNNYFELA.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-JGURVR2Z.js → hidden-runtime-alias-VJCWCFVO.js} +2 -2
- package/dist-cli/chunks/{home-paths-4ICFIGXO.js → home-paths-EGUZZ6J6.js} +2 -2
- package/dist-cli/chunks/inspect-XANAADQT.js +4 -0
- package/dist-cli/chunks/install-desktop-JKQA7UY6.js +4 -0
- package/dist-cli/chunks/{login-RU33QR2G.js → login-6KQGD7HK.js} +2 -2
- package/dist-cli/chunks/{logout-2IUG3Y5J.js → logout-2FCOVLBC.js} +2 -2
- package/dist-cli/chunks/{maestro-2MXTGG7I.js → maestro-HPSSEVJD.js} +3 -3
- package/dist-cli/chunks/{maestro-generate-2PYIFGLR.js → maestro-generate-KQQ6JHMR.js} +5 -5
- package/dist-cli/chunks/{mode-3DAPG3HF.js → mode-SH7KRZET.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-BWSDJF2Z.js → optional-demo-registry-NAMCAVFX.js} +2 -2
- package/dist-cli/chunks/{perf-2TKPWB4N.js → perf-LWKAHXUL.js} +2 -2
- package/dist-cli/chunks/{permissions-5CSMHYNA.js → permissions-EZTKWTXV.js} +2 -2
- package/dist-cli/chunks/platform-KSOJIVN5.js +6 -0
- package/dist-cli/chunks/privacy-EJULWP7L.js +4 -0
- package/dist-cli/chunks/{record-I2HJCFP3.js → record-2IC7BAQN.js} +3 -3
- package/dist-cli/chunks/{report-issue-A4FZWBOC.js → report-issue-MMX6HOQO.js} +2 -2
- package/dist-cli/chunks/reset-PKFZA4NC.js +4 -0
- package/dist-cli/chunks/resolve-target-AFKU3BRE.js +4 -0
- package/dist-cli/chunks/runtime-XHWWPOIN.js +4 -0
- package/dist-cli/chunks/{screenshot-command-UFW5BFHP.js → screenshot-command-JYUXDVSA.js} +3 -3
- package/dist-cli/chunks/{screenshot-layers-5BIAL4YN.js → screenshot-layers-OGRY6AA3.js} +2 -2
- package/dist-cli/chunks/{screenshots-capture-EHYS3BV2.js → screenshots-capture-QHDN65XS.js} +2 -2
- package/dist-cli/chunks/{serve-O4FDM3AC.js → serve-W4BKAG4J.js} +3 -3
- package/dist-cli/chunks/setup-WJPMPSAC.js +4 -0
- package/dist-cli/chunks/sim-client-UOQGFBPO.js +4 -0
- package/dist-cli/chunks/{skills-ZK55SY32.js → skills-OPPXOD63.js} +2 -2
- package/dist-cli/chunks/state-TDAN7BOM.js +14 -0
- package/dist-cli/chunks/{storage-WP33ISEM.js → storage-CFB2YGLY.js} +2 -2
- package/dist-cli/chunks/store-4TEED4ZU.js +4 -0
- package/dist-cli/chunks/telemetry-S6N7CV6G.js +4 -0
- package/dist-cli/chunks/{timeline-DKGW5S4I.js → timeline-ASOOVP4C.js} +2 -2
- package/dist-cli/chunks/{upgrade-LL7G4B7G.js → upgrade-6MVJISHG.js} +2 -2
- package/dist-cli/chunks/upload-CBNIRFEW.js +4 -0
- package/dist-cli/chunks/{version-YLWR5G4F.js → version-5GS7CWRL.js} +2 -2
- package/dist-cli/chunks/{web-QELZ4CP6.js → web-QRRMYMV4.js} +2 -2
- package/dist-cli/chunks/welcome-33FXMAMB.js +5 -0
- package/dist-cli/chunks/{what-happened-MIOMZWM5.js → what-happened-GAZJGKXX.js} +2 -2
- package/dist-cli/chunks/ws-bridge-DIXUAPLQ.js +4 -0
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +336 -0
- package/dist-lib/bridge-contract-input.mjs +303 -0
- package/dist-lib/bridge-contract.cjs +29 -1
- package/dist-lib/bridge-contract.mjs +19 -0
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +32 -18
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +5 -1
- package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
- package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
- package/dist-lib/host/replacement-module-handler.cjs +1 -1
- package/dist-lib/host/websocket-proxy.cjs +1 -1
- package/dist-lib/index.cjs +214 -594
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +273 -657
- package/dist-lib/sdk.mjs +271 -652
- package/dist-lib/skills.cjs +190 -287
- package/dist-lib/vite.cjs +1 -1
- package/package.json +9 -6
- package/skills/contrast/SKILL.md +3 -0
- package/skills/rnx-debug/SKILL.md +8 -0
- package/skills/rnx-test/SKILL.md +21 -0
- package/src/bridge-contract-input.ts +359 -0
- package/src/bridge-contract.ts +121 -1
- package/src/host/bridge-host.ts +7 -12
- package/src/index.ts +7 -0
- package/src/sim-client.ts +12 -3
- package/dist-cli/chunks/chunk-3X6IBUBX.js +0 -38
- package/dist-cli/chunks/chunk-7B37IZBN.js +0 -15
- package/dist-cli/chunks/chunk-DMPF2D24.js +0 -4
- package/dist-cli/chunks/chunk-DW37EVX5.js +0 -767
- package/dist-cli/chunks/chunk-DWFIH7CB.js +0 -32
- package/dist-cli/chunks/chunk-GUI3PDH2.js +0 -27
- package/dist-cli/chunks/chunk-KLF3K6ZZ.js +0 -4
- package/dist-cli/chunks/chunk-M5QCGLXR.js +0 -944
- package/dist-cli/chunks/chunk-M6SKQGAN.js +0 -5
- package/dist-cli/chunks/chunk-NAYLPOC5.js +0 -24
- package/dist-cli/chunks/chunk-VIND4SP3.js +0 -124
- package/dist-cli/chunks/chunk-XXNLYR64.js +0 -5
- package/dist-cli/chunks/chunk-ZFM24DOX.js +0 -4
- package/dist-cli/chunks/cli-version-SYGNHGFR.js +0 -4
- package/dist-cli/chunks/control-RBTNOLQM.js +0 -4
- package/dist-cli/chunks/daemon-N3SC5DK6.js +0 -4
- package/dist-cli/chunks/drivers-W6E3BZH4.js +0 -4
- package/dist-cli/chunks/flow-7QAE2V5Y.js +0 -4
- package/dist-cli/chunks/help-NLD6KWVG.js +0 -4
- package/dist-cli/chunks/inspect-C347BG52.js +0 -4
- package/dist-cli/chunks/install-desktop-YR6TWZYK.js +0 -4
- package/dist-cli/chunks/privacy-PWWC2BXO.js +0 -4
- package/dist-cli/chunks/reset-Y3PBPFYA.js +0 -4
- package/dist-cli/chunks/runtime-IC6NVVGA.js +0 -4
- package/dist-cli/chunks/setup-Y4QGSJBM.js +0 -4
- package/dist-cli/chunks/state-KFLAZNEY.js +0 -14
- package/dist-cli/chunks/store-VEE22CAY.js +0 -4
- package/dist-cli/chunks/telemetry-PZV4BISJ.js +0 -4
- package/dist-cli/chunks/upload-Y4C3YZDK.js +0 -4
- package/dist-cli/chunks/welcome-ZPKA5PZJ.js +0 -5
- package/dist-lib/mcp.cjs +0 -673
- package/dist-lib/mcp.mjs +0 -641
- package/src/mcp.ts +0 -791
package/dist-lib/sdk.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.367 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
@@ -653,6 +653,15 @@ async function tapBest(bridge, query, opts = {}) {
|
|
|
653
653
|
});
|
|
654
654
|
}
|
|
655
655
|
|
|
656
|
+
// src/bridge-contract.ts
|
|
657
|
+
var SEMANTIC_READY_CONTENT_NODE_FLOOR = 100;
|
|
658
|
+
function readyProbeHasContent(probe) {
|
|
659
|
+
return probe.liveFrameActive || probe.targets > 0 || probe.nodes >= SEMANTIC_READY_CONTENT_NODE_FLOOR;
|
|
660
|
+
}
|
|
661
|
+
function readyProbeHasTargetContent(probe) {
|
|
662
|
+
return probe.liveFrameActive || probe.targets > 0;
|
|
663
|
+
}
|
|
664
|
+
|
|
656
665
|
// cli/commands/inspect/core.ts
|
|
657
666
|
function resolveMaxMsFlag(args, fallbackMs) {
|
|
658
667
|
const aliases = ["--max-ms", "--maxMs", "--maxms", "--max_ms"];
|
|
@@ -692,121 +701,116 @@ async function shouldSkipAutoSettleForInspectPick(bridge, subcommand) {
|
|
|
692
701
|
}
|
|
693
702
|
}
|
|
694
703
|
async function inspectNodeCount(bridge) {
|
|
695
|
-
const
|
|
696
|
-
type: "
|
|
697
|
-
|
|
704
|
+
const result = await bridge.send({
|
|
705
|
+
type: "query",
|
|
706
|
+
query: { kind: "count" }
|
|
698
707
|
});
|
|
699
|
-
return { nodes:
|
|
708
|
+
return { nodes: isSemanticQueryResult(result) ? result.total : 0 };
|
|
700
709
|
}
|
|
701
|
-
async function inspectTree(bridge, depth = 5
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
code: `(async () => await window.__sootsimTest.dumpTree(${depth}${optsArg}))()`
|
|
710
|
+
async function inspectTree(bridge, depth = 5) {
|
|
711
|
+
const result = await bridge.send({
|
|
712
|
+
type: "query",
|
|
713
|
+
query: { kind: "tree", depth }
|
|
706
714
|
});
|
|
707
|
-
|
|
708
|
-
try {
|
|
709
|
-
return { depth, tree: JSON.parse(tree) };
|
|
710
|
-
} catch {
|
|
711
|
-
return { depth, tree };
|
|
712
|
-
}
|
|
713
|
-
}
|
|
715
|
+
const tree = isSemanticQueryResult(result) ? result.nodes : [];
|
|
714
716
|
return { depth, tree };
|
|
715
717
|
}
|
|
716
718
|
async function inspectUrl(bridge) {
|
|
717
719
|
const url = await bridge.send({ type: "evaluate", code: "window.location.href" });
|
|
718
720
|
return { url: typeof url === "string" ? url : "" };
|
|
719
721
|
}
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
722
|
+
function isSemanticGeometry(value) {
|
|
723
|
+
return value !== null && typeof value === "object" && [
|
|
724
|
+
Reflect.get(value, "x"),
|
|
725
|
+
Reflect.get(value, "y"),
|
|
726
|
+
Reflect.get(value, "width"),
|
|
727
|
+
Reflect.get(value, "height")
|
|
728
|
+
].every(
|
|
729
|
+
(coordinate) => typeof coordinate === "number" ? Number.isFinite(coordinate) : false
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
function isSimSemanticTree(value) {
|
|
733
|
+
if (!Array.isArray(value)) return false;
|
|
734
|
+
return value.every((node) => {
|
|
735
|
+
if (node === null || typeof node !== "object" || !Number.isInteger(Reflect.get(node, "nodeId")) || typeof Reflect.get(node, "type") !== "string" || !isSemanticGeometry(Reflect.get(node, "geometry"))) {
|
|
736
|
+
return false;
|
|
737
|
+
}
|
|
738
|
+
for (const key of ["testID", "text", "role", "label"]) {
|
|
739
|
+
const value2 = Reflect.get(node, key);
|
|
740
|
+
if (value2 !== void 0 && typeof value2 !== "string") return false;
|
|
741
|
+
}
|
|
742
|
+
const pressable = Reflect.get(node, "pressable");
|
|
743
|
+
if (pressable !== void 0 && pressable !== true) return false;
|
|
744
|
+
const children = Reflect.get(node, "children");
|
|
745
|
+
return children === void 0 || isSimSemanticTree(children);
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
function isSemanticQueryResult(value) {
|
|
749
|
+
const total = value !== null && typeof value === "object" ? Reflect.get(value, "total") : void 0;
|
|
750
|
+
return value !== null && typeof value === "object" && typeof total === "number" && Number.isInteger(total) && total >= 0 && isSimSemanticTree(Reflect.get(value, "nodes"));
|
|
751
|
+
}
|
|
752
|
+
function globRegex(glob) {
|
|
753
|
+
const escaped = glob.replace(/[.+^$(){}|[\]\\]/g, "\\$&");
|
|
754
|
+
return new RegExp(`^${escaped.replace(/\*/g, ".*").replace(/\?/g, ".")}$`, "i");
|
|
755
|
+
}
|
|
756
|
+
function formatSemanticTree(nodes, opts = {}) {
|
|
757
|
+
const filter = opts.filter?.toLowerCase() ?? "";
|
|
758
|
+
const testIDRegex = opts.testIdLike ? globRegex(opts.testIdLike) : null;
|
|
759
|
+
const onlyRegex = opts.onlyGlob ? globRegex(opts.onlyGlob) : null;
|
|
760
|
+
const lines = [];
|
|
761
|
+
const visit = (node, indent) => {
|
|
762
|
+
const joined = [node.role, node.label, node.text, node.testID].filter((value) => typeof value === "string").join(" ");
|
|
763
|
+
const matches = (!filter || joined.toLowerCase().includes(filter)) && (!testIDRegex || testIDRegex.test(node.testID ?? "")) && (!onlyRegex || onlyRegex.test(joined));
|
|
764
|
+
if (matches) {
|
|
765
|
+
const parts = [
|
|
766
|
+
node.role ? `[${node.role}]` : node.pressable ? "[tap]" : `<${node.type}>`
|
|
767
|
+
];
|
|
768
|
+
if (node.text) {
|
|
769
|
+
const text = opts.fullText || node.text.length <= 50 ? node.text : `${node.text.slice(0, 49).trimEnd()}\u2026`;
|
|
770
|
+
parts.push(JSON.stringify(text));
|
|
749
771
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (!Array.isArray(list)) return null
|
|
754
|
-
const filter = typeof opts.filter === 'string' ? opts.filter.toLowerCase() : ''
|
|
755
|
-
const testIdRegex = opts.testIdLike ? globToRegex(opts.testIdLike) : null
|
|
756
|
-
const onlyRegex = opts.onlyGlob ? globToRegex(opts.onlyGlob) : null
|
|
757
|
-
const lines = []
|
|
758
|
-
let matched = 0
|
|
759
|
-
for (const node of list) {
|
|
760
|
-
const testID = node?.testID || node?.testId || ''
|
|
761
|
-
const role = node?.role || ''
|
|
762
|
-
const label = node?.accessibilityLabel || node?.label || ''
|
|
763
|
-
const text = node?.text || ''
|
|
764
|
-
if (testIdRegex && !testIdRegex.test(testID)) continue
|
|
765
|
-
const joined = [role, label, text, testID].filter(Boolean).join(' ')
|
|
766
|
-
if (onlyRegex && !onlyRegex.test(joined)) continue
|
|
767
|
-
if (filter && !joined.toLowerCase().includes(filter)) continue
|
|
768
|
-
matched++
|
|
769
|
-
|
|
770
|
-
const type = String(node?.type || 'node').toLowerCase()
|
|
771
|
-
const handlers = Array.isArray(node?.handlers) ? node.handlers : []
|
|
772
|
-
const parts = []
|
|
773
|
-
if (role) parts.push('[' + role + ']')
|
|
774
|
-
else if (handlers.length > 0) parts.push('[tap]')
|
|
775
|
-
else parts.push('<' + type + '>')
|
|
776
|
-
const visibleText = clipForDisplay(text, 50)
|
|
777
|
-
if (visibleText) parts.push(JSON.stringify(visibleText))
|
|
778
|
-
if (testID) parts.push('#' + testID)
|
|
779
|
-
if (label && label !== text) {
|
|
780
|
-
parts.push('label=' + JSON.stringify(clipForDisplay(label, 50)))
|
|
781
|
-
}
|
|
782
|
-
if (!opts.hideXy) {
|
|
783
|
-
const abs = node?.absolute || {}
|
|
784
|
-
const layout = node?.layout || {}
|
|
785
|
-
const x = Math.round(Number(abs.x) || 0)
|
|
786
|
-
const y = Math.round(Number(abs.y) || 0)
|
|
787
|
-
const w = Math.round(Number(layout.width) || 0)
|
|
788
|
-
const h = Math.round(Number(layout.height) || 0)
|
|
789
|
-
parts.push('@(' + x + ',' + y + ') ' + w + 'x' + h)
|
|
790
|
-
}
|
|
791
|
-
if (handlers.length > 0) parts.push('(tap)')
|
|
792
|
-
lines.push(' ' + parts.join(' '))
|
|
793
|
-
}
|
|
794
|
-
return { tree: lines.join('\\n'), matched }
|
|
772
|
+
if (node.testID) parts.push(`#${node.testID}`);
|
|
773
|
+
if (node.label && node.label !== node.text) {
|
|
774
|
+
parts.push(`label=${JSON.stringify(node.label)}`);
|
|
795
775
|
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
} else {
|
|
802
|
-
tree = await t.dumpTree(${dumpDepth}, opts)
|
|
776
|
+
if (!opts.hideXy) {
|
|
777
|
+
const geometry = node.geometry;
|
|
778
|
+
parts.push(
|
|
779
|
+
`@(${Math.round(geometry.x)},${Math.round(geometry.y)}) ${Math.round(geometry.width)}x${Math.round(geometry.height)}`
|
|
780
|
+
);
|
|
803
781
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
782
|
+
if (node.pressable) parts.push("(tap)");
|
|
783
|
+
lines.push(`${" ".repeat(indent + 1)}${parts.join(" ")}`);
|
|
784
|
+
}
|
|
785
|
+
for (const child of node.children ?? []) visit(child, indent + 1);
|
|
786
|
+
};
|
|
787
|
+
let roots = nodes;
|
|
788
|
+
if (opts.subtreeRoot) {
|
|
789
|
+
const pending = [...nodes];
|
|
790
|
+
let found;
|
|
791
|
+
while (pending.length > 0 && !found) {
|
|
792
|
+
const candidate = pending.shift();
|
|
793
|
+
if (!candidate) continue;
|
|
794
|
+
if (candidate.testID === opts.subtreeRoot) found = candidate;
|
|
795
|
+
else pending.push(...candidate.children ?? []);
|
|
796
|
+
}
|
|
797
|
+
if (!found) return `__SUBTREE_NOT_FOUND__:${opts.subtreeRoot}`;
|
|
798
|
+
roots = [found];
|
|
799
|
+
}
|
|
800
|
+
for (const root of roots) visit(root, 0);
|
|
801
|
+
return lines.join("\n");
|
|
802
|
+
}
|
|
803
|
+
async function inspectDescribe(bridge, dumpOpts) {
|
|
804
|
+
const dumpDepth = dumpOpts.maxDepth ?? 12;
|
|
805
|
+
const result = await bridge.send({
|
|
806
|
+
type: "query",
|
|
807
|
+
query: { kind: "tree", depth: dumpDepth }
|
|
808
|
+
});
|
|
809
|
+
if (!isSemanticQueryResult(result)) return {};
|
|
810
|
+
return {
|
|
811
|
+
tree: formatSemanticTree(result.nodes, dumpOpts),
|
|
812
|
+
nodeCount: result.total
|
|
813
|
+
};
|
|
810
814
|
}
|
|
811
815
|
function scoreLayoutTextContrast(input) {
|
|
812
816
|
const required = input.fontSize >= 18 || input.fontSize >= 14 && (input.fontWeight ?? 400) >= 700 ? 3 : 4.5;
|
|
@@ -1021,9 +1025,6 @@ function layoutGetNativeEval(opts = {}) {
|
|
|
1021
1025
|
type: typeof n.type === 'string' ? n.type : 'node',
|
|
1022
1026
|
text,
|
|
1023
1027
|
box: { x: round(abs.x), y: round(abs.y), w: round(layout.width), h: round(layout.height) },
|
|
1024
|
-
borderRadius: 0,
|
|
1025
|
-
padding: { t: 0, r: 0, b: 0, l: 0 },
|
|
1026
|
-
fontSize: 0,
|
|
1027
1028
|
}
|
|
1028
1029
|
if (!includeStyle) return base
|
|
1029
1030
|
// computedStyle has the resolved values; style is the curated subset \u2014
|
|
@@ -1094,6 +1095,32 @@ function layoutGetNativeEval(opts = {}) {
|
|
|
1094
1095
|
}
|
|
1095
1096
|
var LAYOUT_GET_NATIVE_EVAL = layoutGetNativeEval();
|
|
1096
1097
|
async function inspectGetLayout(bridge, opts = {}) {
|
|
1098
|
+
if (!opts.styling) {
|
|
1099
|
+
const result = await bridge.send({
|
|
1100
|
+
type: "query",
|
|
1101
|
+
query: { kind: "find", selector: { visible: true } }
|
|
1102
|
+
});
|
|
1103
|
+
if (!isSemanticQueryResult(result)) return [];
|
|
1104
|
+
const filter = opts.filter?.trim().toLowerCase() ?? "";
|
|
1105
|
+
return result.nodes.filter((node) => {
|
|
1106
|
+
if (!filter) return true;
|
|
1107
|
+
return [node.text, node.testID, node.role, node.type].some(
|
|
1108
|
+
(value) => typeof value === "string" && value.toLowerCase().includes(filter)
|
|
1109
|
+
);
|
|
1110
|
+
}).map((node) => ({
|
|
1111
|
+
nodeId: node.nodeId,
|
|
1112
|
+
...node.testID ? { testID: node.testID } : {},
|
|
1113
|
+
...node.role ? { role: node.role } : {},
|
|
1114
|
+
type: node.type,
|
|
1115
|
+
...node.text ? { text: node.text } : {},
|
|
1116
|
+
box: {
|
|
1117
|
+
x: Math.round(node.geometry.x),
|
|
1118
|
+
y: Math.round(node.geometry.y),
|
|
1119
|
+
w: Math.round(node.geometry.width),
|
|
1120
|
+
h: Math.round(node.geometry.height)
|
|
1121
|
+
}
|
|
1122
|
+
}));
|
|
1123
|
+
}
|
|
1097
1124
|
const raw = await bridge.send({
|
|
1098
1125
|
type: "evaluate",
|
|
1099
1126
|
code: layoutGetNativeEval(opts)
|
|
@@ -1105,13 +1132,17 @@ function formatLayoutElements(elements, opts) {
|
|
|
1105
1132
|
const lines = elements.map((el) => {
|
|
1106
1133
|
const ident = el.testID ? `#${el.testID}` : el.selector ? el.selector : el.role ? `[${el.role}]` : `<${el.type}>`;
|
|
1107
1134
|
const parts = [ident, `@(${el.box.x},${el.box.y})`, `${el.box.w}x${el.box.h}`];
|
|
1108
|
-
if (el.borderRadius
|
|
1135
|
+
if (el.borderRadius !== void 0 && el.borderRadius > 0) {
|
|
1136
|
+
parts.push(`radius:${el.borderRadius}`);
|
|
1137
|
+
}
|
|
1109
1138
|
const p = el.padding;
|
|
1110
|
-
if (p.t || p.r || p.b || p.l) {
|
|
1139
|
+
if (p && (p.t || p.r || p.b || p.l)) {
|
|
1111
1140
|
const uniform = p.t === p.r && p.r === p.b && p.b === p.l;
|
|
1112
1141
|
parts.push(uniform ? `pad:${p.t}` : `pad:${p.t},${p.r},${p.b},${p.l}`);
|
|
1113
1142
|
}
|
|
1114
|
-
if (el.fontSize
|
|
1143
|
+
if (el.fontSize !== void 0 && el.fontSize > 0) {
|
|
1144
|
+
parts.push(`font:${el.fontSize}`);
|
|
1145
|
+
}
|
|
1115
1146
|
if (opts?.styling) {
|
|
1116
1147
|
if (el.fontWeight) parts.push(`weight:${el.fontWeight}`);
|
|
1117
1148
|
if (el.color) parts.push(`color:${el.color}`);
|
|
@@ -1138,246 +1169,80 @@ function formatLayoutElements(elements, opts) {
|
|
|
1138
1169
|
});
|
|
1139
1170
|
return lines.join("\n");
|
|
1140
1171
|
}
|
|
1141
|
-
var INSPECT_A11Y_CODE = `(async () => {
|
|
1142
|
-
const t = window.__sootsimTest
|
|
1143
|
-
if (!t) return []
|
|
1144
|
-
|
|
1145
|
-
const readLayout = (n) => n?.layout || null
|
|
1146
|
-
const readAbs = (n) => n?.absolutePosition || n?.absolute || null
|
|
1147
|
-
const readRole = (n) => n?.accessibilityRole || n?.role || null
|
|
1148
|
-
const readLabel = (n) => n?.accessibilityLabel || n?.label || n?.text || null
|
|
1149
|
-
const readHint = (n) => n?.accessibilityHint || n?.hint || null
|
|
1150
|
-
const readState = (n) => n?.accessibilityState || n?.state || null
|
|
1151
|
-
const readTestId = (n) => n?.testID || n?.testId || null
|
|
1152
|
-
const readType = (n) => typeof n?.type === 'string' ? n.type.toLowerCase() : n?.type
|
|
1153
|
-
const readPressable = (n) => {
|
|
1154
|
-
if (n?.pressable) return true
|
|
1155
|
-
const handlers = Array.isArray(n?.handlers) ? n.handlers : []
|
|
1156
|
-
const role = readRole(n)
|
|
1157
|
-
return handlers.length > 0 || role === 'button' || role === 'link' || role === 'tab'
|
|
1158
|
-
}
|
|
1159
|
-
const isTextInput = (n) => n?.isTextInput === true
|
|
1160
|
-
const isTextNode = (n) => readType(n) === 'text'
|
|
1161
|
-
const isVisibleTarget = (n) => {
|
|
1162
|
-
const layout = readLayout(n)
|
|
1163
|
-
if (!layout || layout.width <= 0 || layout.height <= 0) return false
|
|
1164
|
-
const abs = readAbs(n)
|
|
1165
|
-
if (!abs) return true
|
|
1166
|
-
const device = n?.device || {}
|
|
1167
|
-
const screenW = Number(device.width) || window.innerWidth || 0
|
|
1168
|
-
const screenH = Number(device.height) || window.innerHeight || 0
|
|
1169
|
-
return (
|
|
1170
|
-
abs.x + layout.width > 0 &&
|
|
1171
|
-
abs.y + layout.height > 0 &&
|
|
1172
|
-
abs.x < screenW &&
|
|
1173
|
-
abs.y < screenH
|
|
1174
|
-
)
|
|
1175
|
-
}
|
|
1176
|
-
const hasAccessibleSignal = (n) => {
|
|
1177
|
-
const role = readRole(n)
|
|
1178
|
-
const label = readLabel(n)
|
|
1179
|
-
const hint = readHint(n)
|
|
1180
|
-
if (role) return true
|
|
1181
|
-
if (hint) return true
|
|
1182
|
-
if (isTextInput(n)) return true
|
|
1183
|
-
if (readPressable(n)) return true
|
|
1184
|
-
if (isTextNode(n) && n?.text) return true
|
|
1185
|
-
if (typeof label === 'string' && label.length <= 30 && label !== n?.text) return true
|
|
1186
|
-
return false
|
|
1187
|
-
}
|
|
1188
|
-
const normalize = (n) => {
|
|
1189
|
-
const layout = readLayout(n)
|
|
1190
|
-
const abs = readAbs(n)
|
|
1191
|
-
const role = readRole(n) || (readPressable(n) ? 'button' : isTextInput(n) ? 'textfield' : isTextNode(n) ? 'statictext' : 'none')
|
|
1192
|
-
return {
|
|
1193
|
-
role,
|
|
1194
|
-
label: readLabel(n),
|
|
1195
|
-
hint: readHint(n),
|
|
1196
|
-
state: readState(n),
|
|
1197
|
-
testID: readTestId(n),
|
|
1198
|
-
position: abs ? { x: Math.round(abs.x), y: Math.round(abs.y) } : null,
|
|
1199
|
-
size: layout ? { w: Math.round(layout.width), h: Math.round(layout.height) } : null,
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
if (typeof t.listInspectable === 'function') {
|
|
1204
|
-
const list = await t.listInspectable({})
|
|
1205
|
-
if (Array.isArray(list)) {
|
|
1206
|
-
return list.filter(n => isVisibleTarget(n) && hasAccessibleSignal(n)).map(normalize)
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
const all = await t.queryAll({ pruneHidden: true })
|
|
1211
|
-
return all
|
|
1212
|
-
.filter(n => isVisibleTarget(n) && hasAccessibleSignal(n))
|
|
1213
|
-
.map(normalize)
|
|
1214
|
-
})()`;
|
|
1215
1172
|
async function inspectAccessibilityTree(bridge) {
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
Math.max(0, absolutePosition.x),
|
|
1239
|
-
),
|
|
1240
|
-
height: Math.max(
|
|
1241
|
-
0,
|
|
1242
|
-
Math.min(screenH, absolutePosition.y + layout.height) -
|
|
1243
|
-
Math.max(0, absolutePosition.y),
|
|
1244
|
-
),
|
|
1245
|
-
}
|
|
1246
|
-
: null
|
|
1247
|
-
const handlers = Array.isArray(n.handlers) ? n.handlers : []
|
|
1248
|
-
const pressable =
|
|
1249
|
-
handlers.length > 0 ||
|
|
1250
|
-
role === 'button' ||
|
|
1251
|
-
role === 'link' ||
|
|
1252
|
-
role === 'tab'
|
|
1253
|
-
return {
|
|
1254
|
-
type: typeof n.type === 'string' ? n.type.toLowerCase() : n.type,
|
|
1255
|
-
id: n.id ?? n.nodeId,
|
|
1256
|
-
nodeId: n.nodeId,
|
|
1257
|
-
testID: n.testID ?? n.testId,
|
|
1258
|
-
text: n.text,
|
|
1259
|
-
layout,
|
|
1260
|
-
absolutePosition,
|
|
1261
|
-
visibleFrame,
|
|
1262
|
-
style: n.computedStyle ?? n.style ?? {},
|
|
1263
|
-
childCount: n.childCount ?? 0,
|
|
1264
|
-
pressed: n.pressed ?? false,
|
|
1265
|
-
pressable,
|
|
1266
|
-
isTextInput: n.isTextInput ?? false,
|
|
1267
|
-
accessible: n.accessible ?? true,
|
|
1268
|
-
accessibilityLabel: label,
|
|
1269
|
-
accessibilityRole: role,
|
|
1270
|
-
accessibilityHint: n.accessibilityHint,
|
|
1271
|
-
accessibilityState: n.accessibilityState,
|
|
1272
|
-
accessibilityValue: n.accessibilityValue,
|
|
1273
|
-
}
|
|
1274
|
-
})
|
|
1275
|
-
}
|
|
1276
|
-
const isVisibleTarget = (n) => {
|
|
1277
|
-
if (!n?.layout) return false
|
|
1278
|
-
const frame = n.visibleFrame
|
|
1279
|
-
if (frame) return frame.width > 0 && frame.height > 0
|
|
1280
|
-
const abs = n.absolutePosition
|
|
1281
|
-
if (!abs) return n.layout.width > 0 && n.layout.height > 0
|
|
1282
|
-
const screenW = window.innerWidth || 0
|
|
1283
|
-
const screenH = window.innerHeight || 0
|
|
1284
|
-
return (
|
|
1285
|
-
abs.x + n.layout.width > 0 &&
|
|
1286
|
-
abs.y + n.layout.height > 0 &&
|
|
1287
|
-
abs.x < screenW &&
|
|
1288
|
-
abs.y < screenH
|
|
1289
|
-
)
|
|
1173
|
+
const result = await bridge.send({
|
|
1174
|
+
type: "query",
|
|
1175
|
+
query: { kind: "find", selector: { visible: true } }
|
|
1176
|
+
});
|
|
1177
|
+
if (!isSemanticQueryResult(result)) return [];
|
|
1178
|
+
return result.nodes.flatMap((node) => {
|
|
1179
|
+
if (!node.role && !node.label && !node.text && !node.pressable) return [];
|
|
1180
|
+
return [
|
|
1181
|
+
{
|
|
1182
|
+
role: node.role ?? (node.pressable ? "button" : node.type === "text" ? "statictext" : "none"),
|
|
1183
|
+
label: node.label ?? node.text ?? null,
|
|
1184
|
+
hint: null,
|
|
1185
|
+
state: null,
|
|
1186
|
+
testID: node.testID ?? null,
|
|
1187
|
+
position: {
|
|
1188
|
+
x: Math.round(node.geometry.x),
|
|
1189
|
+
y: Math.round(node.geometry.y)
|
|
1190
|
+
},
|
|
1191
|
+
size: {
|
|
1192
|
+
w: Math.round(node.geometry.width),
|
|
1193
|
+
h: Math.round(node.geometry.height)
|
|
1194
|
+
}
|
|
1290
1195
|
}
|
|
1291
|
-
|
|
1196
|
+
];
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1292
1199
|
function resolveFindMode(q) {
|
|
1293
1200
|
if (q.testId) {
|
|
1294
|
-
return {
|
|
1295
|
-
mode: "testid",
|
|
1296
|
-
code: `(async () => {
|
|
1297
|
-
const t = window.__sootsimTest
|
|
1298
|
-
if (!t) return null
|
|
1299
|
-
return (await t.findByTestId(${JSON.stringify(q.testId)})) || (await t.findById(${JSON.stringify(q.testId)}))
|
|
1300
|
-
})()`
|
|
1301
|
-
};
|
|
1201
|
+
return { mode: "testid", selector: { testID: q.testId } };
|
|
1302
1202
|
}
|
|
1303
1203
|
if (q.role) {
|
|
1304
|
-
return {
|
|
1305
|
-
mode: "role",
|
|
1306
|
-
code: `(async () => {
|
|
1307
|
-
const t = window.__sootsimTest
|
|
1308
|
-
if (!t) return []
|
|
1309
|
-
return await t.queryAll({ hasRole: ${JSON.stringify(q.role)}, pruneHidden: true })
|
|
1310
|
-
})()`
|
|
1311
|
-
};
|
|
1204
|
+
return { mode: "role", selector: { role: q.role } };
|
|
1312
1205
|
}
|
|
1313
1206
|
if (q.type) {
|
|
1314
|
-
return {
|
|
1315
|
-
mode: "type",
|
|
1316
|
-
code: `(async () => {
|
|
1317
|
-
const t = window.__sootsimTest
|
|
1318
|
-
if (!t) return []
|
|
1319
|
-
return await t.queryAll({ type: ${JSON.stringify(q.type)}, pruneHidden: true })
|
|
1320
|
-
})()`
|
|
1321
|
-
};
|
|
1207
|
+
return { mode: "type", selector: { type: q.type } };
|
|
1322
1208
|
}
|
|
1323
1209
|
if (q.pressable) {
|
|
1324
|
-
return {
|
|
1325
|
-
mode: "pressable",
|
|
1326
|
-
code: `(async () => {
|
|
1327
|
-
const t = window.__sootsimTest
|
|
1328
|
-
if (!t) return []
|
|
1329
|
-
${FIND_INSPECTABLE_TARGETS}
|
|
1330
|
-
const inspectable = await fromInspectable()
|
|
1331
|
-
if (inspectable) return inspectable.filter(n => n.pressable && isVisibleTarget(n))
|
|
1332
|
-
const all = await t.queryAll({ pruneHidden: true })
|
|
1333
|
-
return all.filter(n => n.pressable && isVisibleTarget(n))
|
|
1334
|
-
})()`
|
|
1335
|
-
};
|
|
1210
|
+
return { mode: "pressable", selector: { pressable: true } };
|
|
1336
1211
|
}
|
|
1337
1212
|
if (q.interactive) {
|
|
1338
|
-
return {
|
|
1339
|
-
mode: "interactive-targets",
|
|
1340
|
-
code: `(async () => {
|
|
1341
|
-
const t = window.__sootsimTest
|
|
1342
|
-
if (!t) return []
|
|
1343
|
-
${FIND_INSPECTABLE_TARGETS}
|
|
1344
|
-
const inspectable = await fromInspectable()
|
|
1345
|
-
if (inspectable) {
|
|
1346
|
-
return inspectable.filter(n => n.pressable && isVisibleTarget(n))
|
|
1347
|
-
}
|
|
1348
|
-
const all = await t.queryAll({ pruneHidden: true })
|
|
1349
|
-
return all.filter(n => n.pressable && isVisibleTarget(n))
|
|
1350
|
-
})()`
|
|
1351
|
-
};
|
|
1213
|
+
return { mode: "interactive-targets", selector: { pressable: true } };
|
|
1352
1214
|
}
|
|
1353
1215
|
if (q.visible) {
|
|
1354
|
-
return {
|
|
1355
|
-
mode: "visible",
|
|
1356
|
-
code: `(async () => {
|
|
1357
|
-
const t = window.__sootsimTest
|
|
1358
|
-
if (!t) return []
|
|
1359
|
-
const all = await t.queryAll({ pruneHidden: true })
|
|
1360
|
-
return all.filter(n => n.layout && n.layout.width > 0 && n.layout.height > 0)
|
|
1361
|
-
})()`
|
|
1362
|
-
};
|
|
1216
|
+
return { mode: "visible", selector: { visible: true } };
|
|
1363
1217
|
}
|
|
1364
1218
|
if (q.text) {
|
|
1365
|
-
return {
|
|
1366
|
-
mode: "text",
|
|
1367
|
-
code: `(async () => {
|
|
1368
|
-
const t = window.__sootsimTest
|
|
1369
|
-
if (!t) return null
|
|
1370
|
-
return await t.findByText(${JSON.stringify(q.text)})
|
|
1371
|
-
})()`
|
|
1372
|
-
};
|
|
1219
|
+
return { mode: "text", selector: { text: q.text } };
|
|
1373
1220
|
}
|
|
1374
1221
|
return null;
|
|
1375
1222
|
}
|
|
1376
1223
|
async function inspectFind(bridge, q) {
|
|
1377
1224
|
const resolved = resolveFindMode(q);
|
|
1378
1225
|
if (!resolved) return null;
|
|
1379
|
-
const result = await bridge.send({
|
|
1380
|
-
|
|
1226
|
+
const result = await bridge.send({
|
|
1227
|
+
type: "query",
|
|
1228
|
+
query: { kind: "find", selector: resolved.selector }
|
|
1229
|
+
});
|
|
1230
|
+
if (!isSemanticQueryResult(result)) return { mode: resolved.mode, result: [] };
|
|
1231
|
+
const nodes = result.nodes.map(
|
|
1232
|
+
(node) => ({
|
|
1233
|
+
type: node.type,
|
|
1234
|
+
nodeId: node.nodeId,
|
|
1235
|
+
...node.testID ? { testID: node.testID } : {},
|
|
1236
|
+
...node.text ? { text: node.text } : {},
|
|
1237
|
+
...node.pressable ? { pressable: true } : {},
|
|
1238
|
+
...node.role ? { accessibilityRole: node.role } : {},
|
|
1239
|
+
...node.label ? { accessibilityLabel: node.label } : {},
|
|
1240
|
+
absolutePosition: { x: node.geometry.x, y: node.geometry.y },
|
|
1241
|
+
layout: { width: node.geometry.width, height: node.geometry.height }
|
|
1242
|
+
})
|
|
1243
|
+
);
|
|
1244
|
+
const resultValue = resolved.mode === "testid" || resolved.mode === "text" ? nodes[0] ?? null : nodes;
|
|
1245
|
+
return { mode: resolved.mode, result: resultValue };
|
|
1381
1246
|
}
|
|
1382
1247
|
function rankInteractive(nodes) {
|
|
1383
1248
|
return [...nodes].sort((a, b) => scoreInteractive(b) - scoreInteractive(a));
|
|
@@ -1411,158 +1276,6 @@ function shellEscape(value) {
|
|
|
1411
1276
|
if (/^[A-Za-z0-9_./@:-]+$/.test(value)) return value;
|
|
1412
1277
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1413
1278
|
}
|
|
1414
|
-
var WAIT_READY_PROBE = `(async () => {
|
|
1415
|
-
const t = window.__sootsimTest
|
|
1416
|
-
let nodes = 0
|
|
1417
|
-
try { nodes = (await t?.getNodeCount?.()) || 0 } catch {}
|
|
1418
|
-
let targets = 0
|
|
1419
|
-
let liveFrameActive = false
|
|
1420
|
-
let liveFrameChannels = 0
|
|
1421
|
-
let liveFramePublishes = 0
|
|
1422
|
-
let loadingText = ''
|
|
1423
|
-
const cleanText = (value) =>
|
|
1424
|
-
typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : ''
|
|
1425
|
-
const loadingSignal = (value) => {
|
|
1426
|
-
const text = cleanText(value)
|
|
1427
|
-
if (!text) return ''
|
|
1428
|
-
return /^(opening app\\b|looking for your app\\b|building bundle\\b|preparing rnx runtime\\b|loading native bundle\\b|loading rnx stubs\\b|loading bundle loader\\b|loading native stubs\\b|loading compat bridge\\b|loading react-native shim\\b|loading react bridge\\b|loading reconciler\\b|loading replacement modules\\b|injecting rnx modules\\b|warming sqlite engine\\b|capturing root component\\b|reloading app\\b|loading(?:[.!\u2026]*)?$|connecting(?:[.!\u2026]*)?$)/i.test(text)
|
|
1429
|
-
? text
|
|
1430
|
-
: ''
|
|
1431
|
-
}
|
|
1432
|
-
try {
|
|
1433
|
-
const loading = window.__sootsimConnectRNLoading
|
|
1434
|
-
if (loading && loading.active === true) {
|
|
1435
|
-
loadingText = [loading.title, loading.detail].map(cleanText).filter(Boolean).join(' ')
|
|
1436
|
-
}
|
|
1437
|
-
} catch {}
|
|
1438
|
-
try {
|
|
1439
|
-
if (typeof t?.listInspectable === 'function') {
|
|
1440
|
-
const list = await t.listInspectable({})
|
|
1441
|
-
if (Array.isArray(list)) {
|
|
1442
|
-
const screenW = window.innerWidth || 0
|
|
1443
|
-
const screenH = window.innerHeight || 0
|
|
1444
|
-
const isVisible = (n) => {
|
|
1445
|
-
const layout = n?.layout
|
|
1446
|
-
if (!layout || layout.width <= 0 || layout.height <= 0) return false
|
|
1447
|
-
const abs = n?.absolutePosition || n?.absolute
|
|
1448
|
-
if (!abs) return true
|
|
1449
|
-
const device = n?.device || {}
|
|
1450
|
-
const w = Number(device.width) || screenW
|
|
1451
|
-
const h = Number(device.height) || screenH
|
|
1452
|
-
return (
|
|
1453
|
-
abs.x + layout.width > 0 &&
|
|
1454
|
-
abs.y + layout.height > 0 &&
|
|
1455
|
-
abs.x < w &&
|
|
1456
|
-
abs.y < h
|
|
1457
|
-
)
|
|
1458
|
-
}
|
|
1459
|
-
const hasContentSignal = (n) => {
|
|
1460
|
-
const role = n?.accessibilityRole || n?.role
|
|
1461
|
-
const label = cleanText(n?.accessibilityLabel || n?.label)
|
|
1462
|
-
const handlers = Array.isArray(n?.handlers) ? n.handlers : []
|
|
1463
|
-
const text = cleanText(n?.text)
|
|
1464
|
-
const testID = cleanText(n?.testID || n?.testId)
|
|
1465
|
-
const loading = loadingSignal(text) || loadingSignal(label) || loadingSignal(testID)
|
|
1466
|
-
if (!loadingText && loading) {
|
|
1467
|
-
loadingText = loading
|
|
1468
|
-
}
|
|
1469
|
-
if (handlers.length > 0) return true
|
|
1470
|
-
if (role) return true
|
|
1471
|
-
if (n?.isTextInput) return true
|
|
1472
|
-
if (text) return true
|
|
1473
|
-
if (label) return true
|
|
1474
|
-
if (testID && !/^splash|loading/i.test(testID)) return true
|
|
1475
|
-
return false
|
|
1476
|
-
}
|
|
1477
|
-
targets = list.filter(n => isVisible(n) && hasContentSignal(n)).length
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
} catch {}
|
|
1481
|
-
try {
|
|
1482
|
-
const stats = await t?.getImageLoaderStats?.()
|
|
1483
|
-
const presentation = stats?.liveFramePresentation
|
|
1484
|
-
liveFrameChannels = Number(presentation?.activeChannels) || 0
|
|
1485
|
-
liveFramePublishes = Number(presentation?.publishes) || 0
|
|
1486
|
-
const previous = window.__sootsimWaitReadyLiveFramePublishes
|
|
1487
|
-
liveFrameActive =
|
|
1488
|
-
liveFrameChannels > 0 &&
|
|
1489
|
-
typeof previous === 'number' &&
|
|
1490
|
-
liveFramePublishes > previous
|
|
1491
|
-
window.__sootsimWaitReadyLiveFramePublishes = liveFramePublishes
|
|
1492
|
-
} catch {}
|
|
1493
|
-
let externalReady = null
|
|
1494
|
-
let externalStatus = ''
|
|
1495
|
-
let externalError = ''
|
|
1496
|
-
let suppressedEntryError = ''
|
|
1497
|
-
try {
|
|
1498
|
-
const getExternalAppState = t?.getExternalAppState
|
|
1499
|
-
const external = typeof getExternalAppState === 'function'
|
|
1500
|
-
? await Promise.race([
|
|
1501
|
-
getExternalAppState(),
|
|
1502
|
-
new Promise((resolve) => setTimeout(
|
|
1503
|
-
() => resolve({
|
|
1504
|
-
state: {
|
|
1505
|
-
ready: false,
|
|
1506
|
-
loading: true,
|
|
1507
|
-
status: 'waiting for tenant worker...',
|
|
1508
|
-
},
|
|
1509
|
-
}),
|
|
1510
|
-
1000,
|
|
1511
|
-
)),
|
|
1512
|
-
])
|
|
1513
|
-
: null
|
|
1514
|
-
const state = external?.state
|
|
1515
|
-
if (state && typeof state === 'object') {
|
|
1516
|
-
externalReady = state.ready === true
|
|
1517
|
-
if (typeof state.status === 'string') externalStatus = state.status
|
|
1518
|
-
if (typeof state.error === 'string') externalError = state.error
|
|
1519
|
-
}
|
|
1520
|
-
if (!externalError && Array.isArray(external?.entryErrors)) {
|
|
1521
|
-
// suppressed-and-continued entry errors (bundle-loader's patched __r
|
|
1522
|
-
// swallows entry throws and moves on) must NOT gate readiness \u2014 the
|
|
1523
|
-
// app frequently runs fine after one (e.g. metro's "Unknown named
|
|
1524
|
-
// module" probing noise). they surface separately as a diagnostic
|
|
1525
|
-
// for the timeout path.
|
|
1526
|
-
const isReal = (item) => item && typeof item.message === 'string' && item.message.trim()
|
|
1527
|
-
const fatal = external.entryErrors.find((item) => isReal(item) && item.suppressed !== true)
|
|
1528
|
-
if (fatal) externalError = fatal.message
|
|
1529
|
-
const suppressed = external.entryErrors.find((item) => isReal(item) && item.suppressed === true)
|
|
1530
|
-
if (suppressed) suppressedEntryError = suppressed.message
|
|
1531
|
-
}
|
|
1532
|
-
// once the app has errored its last status ("injecting sootsim
|
|
1533
|
-
// modules...") is stale \u2014 reporting it makes a dead app read as one that
|
|
1534
|
-
// is still booting.
|
|
1535
|
-
if (externalError) loadingText = ''
|
|
1536
|
-
else if (externalReady === false && externalStatus) loadingText = externalStatus
|
|
1537
|
-
} catch {}
|
|
1538
|
-
let errors = 0
|
|
1539
|
-
try { errors = window.__sootsimConsole?.count?.()?.errors ?? 0 } catch {}
|
|
1540
|
-
return {
|
|
1541
|
-
flag: (window).__sootsimExternalAppReady,
|
|
1542
|
-
at: (window).__sootsimExternalAppReadyAt || 0,
|
|
1543
|
-
nodes,
|
|
1544
|
-
targets,
|
|
1545
|
-
liveFrameActive,
|
|
1546
|
-
liveFrameChannels,
|
|
1547
|
-
liveFramePublishes,
|
|
1548
|
-
errors,
|
|
1549
|
-
loadingText,
|
|
1550
|
-
externalReady,
|
|
1551
|
-
externalStatus,
|
|
1552
|
-
externalError,
|
|
1553
|
-
suppressedEntryError,
|
|
1554
|
-
}
|
|
1555
|
-
})()`;
|
|
1556
|
-
var READY_CONTENT_NODE_FLOOR = 100;
|
|
1557
|
-
var READY_NODE_STABLE_MS = 750;
|
|
1558
|
-
var WAIT_READY_PROGRESS_INTERVAL_MS = 2e3;
|
|
1559
|
-
var READY_PROBE_POLL_MS = 150;
|
|
1560
|
-
function readyProbeHasContent(probe) {
|
|
1561
|
-
return probe.liveFrameActive || probe.targets > 0 || probe.nodes >= READY_CONTENT_NODE_FLOOR;
|
|
1562
|
-
}
|
|
1563
|
-
function readyProbeHasTargetContent(probe) {
|
|
1564
|
-
return probe.liveFrameActive || probe.targets > 0;
|
|
1565
|
-
}
|
|
1566
1279
|
function waitReadyReason(status) {
|
|
1567
1280
|
if (status.bridgeError) {
|
|
1568
1281
|
return `sim unreachable: ${status.bridgeError} \u2014 the target tab is closed or no sim is connected (check \`rnx list\`, reopen with \`rnx open\`)`;
|
|
@@ -1572,155 +1285,69 @@ function waitReadyReason(status) {
|
|
|
1572
1285
|
}
|
|
1573
1286
|
async function inspectWaitReady(bridge, timeoutMs = 2e4, options = {}) {
|
|
1574
1287
|
const start = Date.now();
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
liveFramePublishes: 0,
|
|
1589
|
-
errors: 0,
|
|
1590
|
-
loadingText: "",
|
|
1591
|
-
externalReady: null,
|
|
1592
|
-
externalStatus: "",
|
|
1593
|
-
externalError: "",
|
|
1594
|
-
suppressedEntryError: ""
|
|
1595
|
-
};
|
|
1596
|
-
while (Date.now() < deadline) {
|
|
1597
|
-
try {
|
|
1598
|
-
last = await bridge.send({ type: "evaluate", code: WAIT_READY_PROBE }) ?? last;
|
|
1599
|
-
bridgeError = "";
|
|
1600
|
-
} catch (error) {
|
|
1601
|
-
bridgeError = error instanceof Error ? error.message : String(error);
|
|
1602
|
-
if (bridgeError.startsWith("multiple sims are connected:") || bridgeError.startsWith("saved sim ")) {
|
|
1603
|
-
throw error;
|
|
1604
|
-
}
|
|
1288
|
+
let result;
|
|
1289
|
+
try {
|
|
1290
|
+
result = await bridge.send(
|
|
1291
|
+
{
|
|
1292
|
+
type: "waitFor",
|
|
1293
|
+
waitForOptions: { condition: { type: "ready" }, timeoutMs }
|
|
1294
|
+
},
|
|
1295
|
+
{ timeoutMs: timeoutMs + 1e3 }
|
|
1296
|
+
);
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
const bridgeError = error instanceof Error ? error.message : String(error);
|
|
1299
|
+
if (bridgeError.startsWith("multiple sims are connected:") || bridgeError.startsWith("saved sim ")) {
|
|
1300
|
+
throw error;
|
|
1605
1301
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
targets: last.targets,
|
|
1612
|
-
liveFrameActive: last.liveFrameActive,
|
|
1613
|
-
liveFrameChannels: last.liveFrameChannels,
|
|
1614
|
-
liveFramePublishes: last.liveFramePublishes,
|
|
1615
|
-
flag: last.flag,
|
|
1616
|
-
loadingText: last.loadingText,
|
|
1617
|
-
externalReady: last.externalReady,
|
|
1618
|
-
externalStatus: last.externalStatus,
|
|
1619
|
-
externalError: last.externalError,
|
|
1620
|
-
suppressedEntryError: last.suppressedEntryError,
|
|
1621
|
-
errors: last.errors,
|
|
1622
|
-
bridgeError
|
|
1302
|
+
result = {
|
|
1303
|
+
matched: false,
|
|
1304
|
+
elapsedMs: Date.now() - start,
|
|
1305
|
+
polls: 0,
|
|
1306
|
+
error: bridgeError
|
|
1623
1307
|
};
|
|
1624
|
-
if (last.nodes !== lastNodes) {
|
|
1625
|
-
lastNodes = last.nodes;
|
|
1626
|
-
nodeStableSince = now;
|
|
1627
|
-
}
|
|
1628
|
-
const hasReadySignal = last.flag === true && readyProbeHasContent(last);
|
|
1629
|
-
const hasRenderedContentWithoutReadySignal = last.flag !== true && readyProbeHasTargetContent(last);
|
|
1630
|
-
if ((hasReadySignal || hasRenderedContentWithoutReadySignal) && last.externalReady !== false && !last.externalError && !last.loadingText) {
|
|
1631
|
-
if (now - nodeStableSince >= READY_NODE_STABLE_MS) {
|
|
1632
|
-
return {
|
|
1633
|
-
...status2,
|
|
1634
|
-
ready: true
|
|
1635
|
-
};
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
if (last.externalError) return status2;
|
|
1639
|
-
if (options.onProgress && progressIntervalMs > 0 && now >= nextProgressAt) {
|
|
1640
|
-
options.onProgress(status2);
|
|
1641
|
-
do {
|
|
1642
|
-
nextProgressAt += progressIntervalMs;
|
|
1643
|
-
} while (now >= nextProgressAt);
|
|
1644
|
-
}
|
|
1645
|
-
await new Promise((r) => setTimeout(r, READY_PROBE_POLL_MS));
|
|
1646
1308
|
}
|
|
1309
|
+
const probe = result.probe;
|
|
1647
1310
|
const status = {
|
|
1648
|
-
ready:
|
|
1649
|
-
elapsedMs:
|
|
1650
|
-
nodes:
|
|
1651
|
-
targets:
|
|
1652
|
-
liveFrameActive:
|
|
1653
|
-
liveFrameChannels:
|
|
1654
|
-
liveFramePublishes:
|
|
1655
|
-
flag:
|
|
1656
|
-
loadingText:
|
|
1657
|
-
externalReady:
|
|
1658
|
-
externalStatus:
|
|
1659
|
-
externalError:
|
|
1660
|
-
suppressedEntryError:
|
|
1661
|
-
errors:
|
|
1662
|
-
bridgeError
|
|
1311
|
+
ready: result.matched,
|
|
1312
|
+
elapsedMs: result.elapsedMs,
|
|
1313
|
+
nodes: probe?.nodes ?? 0,
|
|
1314
|
+
targets: probe?.targets ?? 0,
|
|
1315
|
+
liveFrameActive: probe?.liveFrameActive ?? false,
|
|
1316
|
+
liveFrameChannels: probe?.liveFrameChannels ?? 0,
|
|
1317
|
+
liveFramePublishes: probe?.liveFramePublishes ?? 0,
|
|
1318
|
+
flag: probe?.flag,
|
|
1319
|
+
loadingText: probe?.loadingText ?? "",
|
|
1320
|
+
externalReady: probe?.externalReady ?? null,
|
|
1321
|
+
externalStatus: probe?.externalStatus ?? "",
|
|
1322
|
+
externalError: probe?.externalError ?? "",
|
|
1323
|
+
suppressedEntryError: probe?.suppressedEntryError ?? "",
|
|
1324
|
+
errors: probe?.errors ?? 0,
|
|
1325
|
+
bridgeError: result.error ?? ""
|
|
1663
1326
|
};
|
|
1664
|
-
if (
|
|
1665
|
-
options.onProgress(status);
|
|
1666
|
-
}
|
|
1327
|
+
if (!status.ready && options.onProgress) options.onProgress(status);
|
|
1667
1328
|
return status;
|
|
1668
1329
|
}
|
|
1669
1330
|
async function inspectWaitSelector(bridge, testId, timeoutMs = 5e3, opts = {}) {
|
|
1670
1331
|
const gone = opts.gone === true;
|
|
1671
1332
|
const result = await bridge.send(
|
|
1672
1333
|
{
|
|
1673
|
-
type: "
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
const gone = ${gone}
|
|
1678
|
-
const ID = ${JSON.stringify(testId)}
|
|
1679
|
-
const present = (node) => !!(node && node.layout && node.layout.width > 0 && node.layout.height > 0)
|
|
1680
|
-
const find = async () => {
|
|
1681
|
-
const t = window.__sootsimTest
|
|
1682
|
-
if (!t) return undefined // bridge not ready \u2014 indeterminate, not "gone"
|
|
1683
|
-
try {
|
|
1684
|
-
// gone-mode confirms a gate/screen LEFT \u2014 it only needs structural
|
|
1685
|
-
// existence + layout, NOT the full-tree occlusion pass (pruneHidden).
|
|
1686
|
-
// that pass (computeOccludedInspectNodeIds) walks the whole tree and
|
|
1687
|
-
// does O(n\xB2)-ish rect-occlusion testing EVERY poll; on a large
|
|
1688
|
-
// post-login app tree it costs seconds per poll, so --gone can never
|
|
1689
|
-
// get two clean "absent" polls inside budget and falsely reports the
|
|
1690
|
-
// gate as still blocked \u2014 the root cause of the 3pc PR-preview outage.
|
|
1691
|
-
// presence-mode keeps pruneHidden so "appeared" still means
|
|
1692
|
-
// visibly-reachable. a departed gate is unmounted or zero-sized, so the
|
|
1693
|
-
// cheap structural+layout check is correct here and ~O(n) cheap.
|
|
1694
|
-
const matches = await t.queryAll?.(gone ? { hasId: ID } : { hasId: ID, pruneHidden: true })
|
|
1695
|
-
if (!Array.isArray(matches)) return undefined
|
|
1696
|
-
return matches.find(present) || null
|
|
1697
|
-
} catch {
|
|
1698
|
-
return undefined
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
let goneStreak = 0
|
|
1702
|
-
while (Date.now() < deadline) {
|
|
1703
|
-
const node = await find()
|
|
1704
|
-
if (gone) {
|
|
1705
|
-
if (node === null) {
|
|
1706
|
-
goneStreak += 1
|
|
1707
|
-
if (goneStreak >= 2) return { found: true, elapsed: Date.now() - start }
|
|
1708
|
-
} else {
|
|
1709
|
-
goneStreak = 0
|
|
1710
|
-
}
|
|
1711
|
-
} else if (present(node)) {
|
|
1712
|
-
return { found: true, node, elapsed: Date.now() - start }
|
|
1713
|
-
}
|
|
1714
|
-
await new Promise((r) => setTimeout(r, 80))
|
|
1334
|
+
type: "waitFor",
|
|
1335
|
+
waitForOptions: {
|
|
1336
|
+
condition: { type: gone ? "absent" : "present", testID: testId },
|
|
1337
|
+
timeoutMs
|
|
1715
1338
|
}
|
|
1716
|
-
return { found: false, elapsed: Date.now() - start }
|
|
1717
|
-
})()`
|
|
1718
1339
|
},
|
|
1719
|
-
{
|
|
1720
|
-
timeoutMs: timeoutMs + 1e3
|
|
1721
|
-
}
|
|
1340
|
+
{ timeoutMs: timeoutMs + 1e3 }
|
|
1722
1341
|
);
|
|
1723
|
-
|
|
1342
|
+
if (result?.error) throw new Error(result.error);
|
|
1343
|
+
if (result?.node && !isSimSemanticTree([result.node])) {
|
|
1344
|
+
throw new Error("waitFor returned an invalid semantic node");
|
|
1345
|
+
}
|
|
1346
|
+
return {
|
|
1347
|
+
found: result?.matched === true,
|
|
1348
|
+
...result?.node ? { node: result.node } : {},
|
|
1349
|
+
elapsed: result?.elapsedMs ?? timeoutMs
|
|
1350
|
+
};
|
|
1724
1351
|
}
|
|
1725
1352
|
function consoleEntriesEval(level, limit) {
|
|
1726
1353
|
const getter = level === "error" ? "getErrors" : "getWarnings";
|
|
@@ -2183,38 +1810,30 @@ function formatTimelineEvents(events) {
|
|
|
2183
1810
|
|
|
2184
1811
|
// cli/commands/inspect/resolve-target.ts
|
|
2185
1812
|
async function resolveTargetCoords(bridge, target) {
|
|
2186
|
-
const arg = JSON.stringify(target.value);
|
|
2187
|
-
const findExpr = target.mode === "testid" ? `(await t.findByTestId(${arg})) || (await t.findById(${arg}))` : `await t.findByText(${arg})`;
|
|
2188
1813
|
const result = await bridge.send({
|
|
2189
|
-
type: "
|
|
2190
|
-
|
|
2191
|
-
const t = window.__sootsimTest
|
|
2192
|
-
if (!t) return null
|
|
2193
|
-
const n = ${findExpr}
|
|
2194
|
-
if (!n || !n.absolutePosition || !n.layout) return null
|
|
2195
|
-
const resolved =
|
|
2196
|
-
typeof n.nodeId === 'number' && typeof t.resolveTapTarget === 'function'
|
|
2197
|
-
? await t.resolveTapTarget(n.nodeId)
|
|
2198
|
-
: null
|
|
2199
|
-
const cx =
|
|
2200
|
-
typeof resolved?.cx === 'number'
|
|
2201
|
-
? resolved.cx
|
|
2202
|
-
: n.absolutePosition.x + (n.layout.width || 0) / 2
|
|
2203
|
-
const cy =
|
|
2204
|
-
typeof resolved?.cy === 'number'
|
|
2205
|
-
? resolved.cy
|
|
2206
|
-
: n.absolutePosition.y + (n.layout.height || 0) / 2
|
|
2207
|
-
return {
|
|
2208
|
-
x: cx,
|
|
2209
|
-
y: cy,
|
|
2210
|
-
id: n.id ?? null,
|
|
2211
|
-
testID: n.testID ?? null,
|
|
2212
|
-
text: ${JSON.stringify(target.mode === "text")} ? ${arg} : (n.text ?? n.accessibilityLabel ?? null),
|
|
2213
|
-
type: n.type ?? null,
|
|
2214
|
-
}
|
|
2215
|
-
})()`
|
|
1814
|
+
type: "resolve",
|
|
1815
|
+
selector: target.mode === "testid" ? { testID: target.value } : { text: target.value }
|
|
2216
1816
|
});
|
|
2217
|
-
|
|
1817
|
+
if (result === null) return null;
|
|
1818
|
+
if (typeof result !== "object") throw new Error("resolve returned an invalid result");
|
|
1819
|
+
const match = Reflect.get(result, "match");
|
|
1820
|
+
const resolvedTarget = Reflect.get(result, "target");
|
|
1821
|
+
const semanticNodes = [match, resolvedTarget];
|
|
1822
|
+
const point = Reflect.get(result, "point");
|
|
1823
|
+
const x = point !== null && typeof point === "object" ? Reflect.get(point, "x") : void 0;
|
|
1824
|
+
const y = point !== null && typeof point === "object" ? Reflect.get(point, "y") : void 0;
|
|
1825
|
+
if (!isSimSemanticTree(semanticNodes) || typeof x !== "number" || !Number.isFinite(x) || typeof y !== "number" || !Number.isFinite(y)) {
|
|
1826
|
+
throw new Error("resolve returned an invalid result");
|
|
1827
|
+
}
|
|
1828
|
+
const semanticMatch = semanticNodes[0];
|
|
1829
|
+
if (!semanticMatch) throw new Error("resolve returned an invalid result");
|
|
1830
|
+
return {
|
|
1831
|
+
x,
|
|
1832
|
+
y,
|
|
1833
|
+
testID: semanticMatch.testID ?? null,
|
|
1834
|
+
text: semanticMatch.text ?? (target.mode === "text" ? target.value : semanticMatch.label),
|
|
1835
|
+
type: semanticMatch.type
|
|
1836
|
+
};
|
|
2218
1837
|
}
|
|
2219
1838
|
|
|
2220
1839
|
// src/app-url.ts
|
|
@@ -2452,9 +2071,12 @@ var SimClient = class {
|
|
|
2452
2071
|
}
|
|
2453
2072
|
async tree(depth = 5) {
|
|
2454
2073
|
const { tree } = await inspectTree(this, depth);
|
|
2455
|
-
return tree;
|
|
2074
|
+
return Array.isArray(tree) ? tree : [];
|
|
2075
|
+
}
|
|
2076
|
+
resolve(selector) {
|
|
2077
|
+
return this.send({ type: "resolve", selector });
|
|
2456
2078
|
}
|
|
2457
|
-
/** compact visible
|
|
2079
|
+
/** compact visible semantic tree, formatted as the CLI prints it. */
|
|
2458
2080
|
describe(opts = {}) {
|
|
2459
2081
|
return inspectDescribe(this, {
|
|
2460
2082
|
describe: true,
|
|
@@ -2683,9 +2305,6 @@ export {
|
|
|
2683
2305
|
LAYOUT_GET_NATIVE_EVAL,
|
|
2684
2306
|
MERGED_CONSOLE_COUNT_EVAL,
|
|
2685
2307
|
NOISY_TIMELINE_KINDS,
|
|
2686
|
-
READY_CONTENT_NODE_FLOOR,
|
|
2687
|
-
READY_NODE_STABLE_MS,
|
|
2688
|
-
READY_PROBE_POLL_MS,
|
|
2689
2308
|
SimClient,
|
|
2690
2309
|
SimClientError,
|
|
2691
2310
|
SimConnectError,
|
|
@@ -2694,8 +2313,6 @@ export {
|
|
|
2694
2313
|
SimReadyTimeoutError,
|
|
2695
2314
|
SimResetError,
|
|
2696
2315
|
SimTapError,
|
|
2697
|
-
WAIT_READY_PROBE,
|
|
2698
|
-
WAIT_READY_PROGRESS_INTERVAL_MS,
|
|
2699
2316
|
buildSimOpenUrl,
|
|
2700
2317
|
clearConsole,
|
|
2701
2318
|
clearLogs,
|
|
@@ -2703,6 +2320,7 @@ export {
|
|
|
2703
2320
|
detectOpenNativeUI,
|
|
2704
2321
|
filterLogEntries,
|
|
2705
2322
|
formatLayoutElements,
|
|
2323
|
+
formatSemanticTree,
|
|
2706
2324
|
formatTimelineEvent,
|
|
2707
2325
|
formatTimelineEvents,
|
|
2708
2326
|
formatTimelinePayload,
|
|
@@ -2735,6 +2353,7 @@ export {
|
|
|
2735
2353
|
isInspectModeActive,
|
|
2736
2354
|
isInspectPickCommand,
|
|
2737
2355
|
isShellCommandUnavailable,
|
|
2356
|
+
isSimSemanticTree,
|
|
2738
2357
|
isTapSuccess,
|
|
2739
2358
|
layoutGetNativeEval,
|
|
2740
2359
|
rankInteractive,
|