peachsim 0.1.628 → 0.1.630
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 +8 -4
- package/cli/bridge-flow-runner.ts +10 -3
- package/cli/cli-update.ts +2 -7
- package/cli/cloud-test-tunnel.ts +16 -3
- package/cli/commands/config.ts +1 -1
- package/cli/commands/control.ts +28 -3
- package/cli/commands/daemon-mac-app.ts +4 -2
- package/cli/commands/daemon.ts +4 -4
- package/cli/commands/device.ts +2 -3
- package/cli/commands/inspect/actions.ts +125 -90
- package/cli/commands/inspect/core.ts +5 -0
- package/cli/commands/inspect.ts +27 -13
- package/cli/commands/platform.ts +1 -1
- package/cli/commands/screenshot-layers.ts +1 -1
- package/cli/commands/screenshot.ts +1 -1
- package/cli/commands/skills.ts +0 -31
- package/cli/drivers/playwright-provisioning.ts +3 -2
- package/cli/drivers/playwright-sim-host.ts +25 -9
- package/cli/main.ts +8 -2
- package/cli/outbound-endpoints.ts +0 -4
- package/detox/index.ts +74 -43
- 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 +1 -1
- package/dist-lib/bridge-contract-input.mjs +1 -1
- package/dist-lib/bridge-contract.cjs +1 -1
- package/dist-lib/bridge-contract.mjs +1 -1
- package/dist-lib/capture-contract.cjs +1 -1
- package/dist-lib/capture-contract.mjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/cloud-contract.cjs +1 -1
- package/dist-lib/cloud-contract.mjs +1 -1
- package/dist-lib/cloud-test-args.cjs +1 -1
- package/dist-lib/cloud-test-args.mjs +1 -1
- package/dist-lib/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +129 -27
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +290 -315
- 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 +253 -121
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- package/dist-lib/{vite-kotlin.cjs → kotlin-host.cjs} +130 -19
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-fingerprint-registry.cjs +1 -1
- package/dist-lib/metro-fingerprint-registry.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 +132 -9
- package/dist-lib/native-app-identity.cjs +278 -0
- package/dist-lib/native-app-identity.mjs +245 -0
- 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 +110 -87
- package/dist-lib/sdk.mjs +110 -87
- package/dist-lib/skills.cjs +841 -643
- package/dist-lib/swift-compile.cjs +1 -1
- package/dist-lib/swift-compile.mjs +1 -1
- package/dist-lib/swift-package.cjs +1 -1
- package/dist-lib/swift-package.mjs +1 -1
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +282 -177
- package/dist-lib/vite-swift.cjs +1 -1
- package/dist-lib/vite.cjs +11 -28
- package/kotlin/README.md +32 -4
- package/kotlin/fixtures/counter/Counter.kt +26 -0
- package/kotlin/fixtures/counter/CounterScreen.kt +40 -0
- package/kotlin/fixtures/headless-counter/Headless.kt +228 -0
- package/kotlin/fixtures/runtime-state/RuntimeState.kt +200 -0
- package/kotlin/fork/LICENSE +178 -0
- package/kotlin/fork/NOTICE +25 -0
- package/kotlin/fork/README.md +59 -0
- package/kotlin/fork/build.sh +128 -0
- package/kotlin/fork/compose-worker-globals.patch +96 -0
- package/kotlin/fork/fork.json +33 -0
- package/kotlin/fork/publish.ts +77 -0
- package/kotlin/fork/skiko-worker-clock.patch +33 -0
- package/kotlin/toolchain.json +288 -0
- package/package.json +12 -5
- package/src/automatic-update-policy.ts +17 -0
- package/src/host/bridge-host.ts +3 -1
- package/src/{vite-plugin-kotlin.ts → kotlin-host.ts} +155 -12
- package/src/metro-plugin.ts +18 -0
- package/src/native-app-identity.ts +376 -0
- package/src/native-firebase-config.ts +90 -0
- package/src/screenshots/frame-compose.ts +5 -2
- package/src/screenshots/registry.ts +1 -1
- package/src/screenshots/schema.ts +2 -2
- package/src/vite-plugin-one.ts +11 -28
- package/src/vite-plugin.ts +6 -1
package/dist-lib/sdk.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.630 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
let __sootsim_import_meta_url = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; } catch {}
|
|
3
3
|
"use strict";
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -1799,16 +1799,26 @@ function payloadFromResolved(resolved, match, extra = {}) {
|
|
|
1799
1799
|
...extra
|
|
1800
1800
|
};
|
|
1801
1801
|
}
|
|
1802
|
-
function collectTextCandidates(nodes, ancestors = [], pressableOwnerText = "") {
|
|
1802
|
+
function collectTextCandidates(nodes, ancestors = [], pressableOwnerText = "", actionableAncestor = false, actionableRole) {
|
|
1803
1803
|
const out = [];
|
|
1804
1804
|
for (const node of nodes) {
|
|
1805
1805
|
const text = nodeText(node);
|
|
1806
1806
|
const caption = !!text && text === pressableOwnerText && !node.pressable;
|
|
1807
|
-
|
|
1807
|
+
const actionable = !!node.pressable || actionableAncestor;
|
|
1808
|
+
const role = node.role ?? actionableRole;
|
|
1809
|
+
if (!caption) out.push({ node, ancestorTestIDs: ancestors, actionable, role });
|
|
1808
1810
|
if (node.children?.length) {
|
|
1809
1811
|
const next = node.testID ? [...ancestors, node.testID] : ancestors;
|
|
1810
1812
|
const owner = text ? node.pressable ? text : "" : pressableOwnerText;
|
|
1811
|
-
out.push(
|
|
1813
|
+
out.push(
|
|
1814
|
+
...collectTextCandidates(
|
|
1815
|
+
node.children,
|
|
1816
|
+
next,
|
|
1817
|
+
owner,
|
|
1818
|
+
actionable,
|
|
1819
|
+
node.pressable ? node.role : actionableRole
|
|
1820
|
+
)
|
|
1821
|
+
);
|
|
1812
1822
|
}
|
|
1813
1823
|
}
|
|
1814
1824
|
return out;
|
|
@@ -1832,85 +1842,96 @@ async function tapByText(bridge, query, options = {}, opts = {}) {
|
|
|
1832
1842
|
agent: opts.agent,
|
|
1833
1843
|
timing: opts.timing,
|
|
1834
1844
|
textFallback: query,
|
|
1835
|
-
resolve:
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
const ay = node2.geometry.y;
|
|
1849
|
-
if (options.minX != null && !(ax >= options.minX)) return false;
|
|
1850
|
-
if (options.maxX != null && !(ax <= options.maxX)) return false;
|
|
1851
|
-
if (options.minY != null && !(ay >= options.minY)) return false;
|
|
1852
|
-
if (options.maxY != null && !(ay <= options.maxY)) return false;
|
|
1853
|
-
return true;
|
|
1854
|
-
});
|
|
1855
|
-
if (options.near) {
|
|
1856
|
-
const { x, y } = options.near;
|
|
1857
|
-
candidates.sort(
|
|
1858
|
-
(a, b) => Math.hypot(a.node.geometry.x - x, a.node.geometry.y - y) - Math.hypot(b.node.geometry.x - x, b.node.geometry.y - y)
|
|
1859
|
-
);
|
|
1860
|
-
} else {
|
|
1861
|
-
candidates.sort((a, b) => {
|
|
1862
|
-
if (Math.abs(a.node.geometry.y - b.node.geometry.y) > 2) {
|
|
1863
|
-
return a.node.geometry.y - b.node.geometry.y;
|
|
1864
|
-
}
|
|
1865
|
-
return a.node.geometry.x - b.node.geometry.x;
|
|
1866
|
-
});
|
|
1867
|
-
}
|
|
1868
|
-
const total = candidates.length;
|
|
1869
|
-
if (total === 0) return { matched: 0, total: 0 };
|
|
1870
|
-
let idx = 0;
|
|
1871
|
-
if (options.nth != null) {
|
|
1872
|
-
idx = options.nth < 0 ? total + options.nth : options.nth;
|
|
1873
|
-
if (idx < 0 || idx >= total) {
|
|
1874
|
-
return { matched: 0, total, nthOutOfRange: true, nth: options.nth };
|
|
1875
|
-
}
|
|
1876
|
-
} else if (total > 1 && !options.first && !options.near) {
|
|
1877
|
-
return {
|
|
1878
|
-
ambiguous: true,
|
|
1879
|
-
total,
|
|
1880
|
-
candidates: candidates.slice(0, 10).map((candidate, index) => ({
|
|
1881
|
-
idx: index,
|
|
1882
|
-
nodeId: candidate.node.nodeId,
|
|
1883
|
-
type: candidate.node.type,
|
|
1884
|
-
testID: candidate.node.testID ?? null,
|
|
1885
|
-
text: nodeText(candidate.node).slice(0, 60),
|
|
1886
|
-
abs: { x: candidate.node.geometry.x, y: candidate.node.geometry.y },
|
|
1887
|
-
layout: {
|
|
1888
|
-
width: Math.round(candidate.node.geometry.width),
|
|
1889
|
-
height: Math.round(candidate.node.geometry.height)
|
|
1890
|
-
},
|
|
1891
|
-
ancestorTestIDs: candidate.ancestorTestIDs.slice(0, 5)
|
|
1892
|
-
}))
|
|
1893
|
-
};
|
|
1845
|
+
resolve: () => resolveTextTapTarget(bridge, query, options)
|
|
1846
|
+
});
|
|
1847
|
+
}
|
|
1848
|
+
async function resolveTextTapTarget(bridge, query, options = {}) {
|
|
1849
|
+
const { tree } = await inspectTree(bridge, 50);
|
|
1850
|
+
if (!Array.isArray(tree)) return { matched: 0, total: 0 };
|
|
1851
|
+
const needle = options.exact ? query : query.toLowerCase();
|
|
1852
|
+
let candidates = collectTextCandidates(tree).filter(
|
|
1853
|
+
({ node: node2, ancestorTestIDs, role }) => {
|
|
1854
|
+
const text = nodeText(node2);
|
|
1855
|
+
if (!text) return false;
|
|
1856
|
+
if (options.exact ? text !== needle : !text.toLowerCase().includes(needle)) {
|
|
1857
|
+
return false;
|
|
1894
1858
|
}
|
|
1895
|
-
|
|
1896
|
-
if (!
|
|
1897
|
-
const
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
if (!
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1859
|
+
if (options.role && role !== options.role) return false;
|
|
1860
|
+
if (options.within && !ancestorTestIDs.includes(options.within)) return false;
|
|
1861
|
+
const ax = node2.geometry.x;
|
|
1862
|
+
const ay = node2.geometry.y;
|
|
1863
|
+
if (options.minX != null && !(ax >= options.minX)) return false;
|
|
1864
|
+
if (options.maxX != null && !(ax <= options.maxX)) return false;
|
|
1865
|
+
if (options.minY != null && !(ay >= options.minY)) return false;
|
|
1866
|
+
if (options.maxY != null && !(ay <= options.maxY)) return false;
|
|
1867
|
+
return true;
|
|
1868
|
+
}
|
|
1869
|
+
);
|
|
1870
|
+
if (options.nth == null && !options.first && !options.near) {
|
|
1871
|
+
const actionable = candidates.filter((candidate) => candidate.actionable);
|
|
1872
|
+
if (actionable.length > 0) candidates = actionable;
|
|
1873
|
+
}
|
|
1874
|
+
if (options.near) {
|
|
1875
|
+
const { x, y } = options.near;
|
|
1876
|
+
candidates.sort(
|
|
1877
|
+
(a, b) => Math.hypot(a.node.geometry.x - x, a.node.geometry.y - y) - Math.hypot(b.node.geometry.x - x, b.node.geometry.y - y)
|
|
1878
|
+
);
|
|
1879
|
+
} else {
|
|
1880
|
+
candidates.sort((a, b) => {
|
|
1881
|
+
if (Math.abs(a.node.geometry.y - b.node.geometry.y) > 2) {
|
|
1882
|
+
return a.node.geometry.y - b.node.geometry.y;
|
|
1910
1883
|
}
|
|
1911
|
-
return
|
|
1884
|
+
return a.node.geometry.x - b.node.geometry.x;
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
const total = candidates.length;
|
|
1888
|
+
if (total === 0) return { matched: 0, total: 0 };
|
|
1889
|
+
let idx = 0;
|
|
1890
|
+
if (options.nth != null) {
|
|
1891
|
+
idx = options.nth < 0 ? total + options.nth : options.nth;
|
|
1892
|
+
if (idx < 0 || idx >= total) {
|
|
1893
|
+
return { matched: 0, total, nthOutOfRange: true, nth: options.nth };
|
|
1912
1894
|
}
|
|
1895
|
+
} else if (total > 1 && !options.first && !options.near) {
|
|
1896
|
+
return {
|
|
1897
|
+
ambiguous: true,
|
|
1898
|
+
total,
|
|
1899
|
+
candidates: candidates.slice(0, 10).map((candidate, index) => ({
|
|
1900
|
+
idx: index,
|
|
1901
|
+
nodeId: candidate.node.nodeId,
|
|
1902
|
+
type: candidate.node.type,
|
|
1903
|
+
testID: candidate.node.testID ?? null,
|
|
1904
|
+
text: nodeText(candidate.node).slice(0, 60),
|
|
1905
|
+
abs: { x: candidate.node.geometry.x, y: candidate.node.geometry.y },
|
|
1906
|
+
layout: {
|
|
1907
|
+
width: Math.round(candidate.node.geometry.width),
|
|
1908
|
+
height: Math.round(candidate.node.geometry.height)
|
|
1909
|
+
},
|
|
1910
|
+
ancestorTestIDs: candidate.ancestorTestIDs.slice(0, 5)
|
|
1911
|
+
}))
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
const picked = candidates[idx];
|
|
1915
|
+
if (!picked) return { matched: 0, total };
|
|
1916
|
+
const node = picked.node;
|
|
1917
|
+
const tap = await bridge.send({
|
|
1918
|
+
type: "call",
|
|
1919
|
+
path: "SootSim.bridges.test.resolveTapTarget",
|
|
1920
|
+
args: [node.nodeId]
|
|
1913
1921
|
});
|
|
1922
|
+
if (!isTapTargetInfo(tap)) return { matched: 0, total };
|
|
1923
|
+
return payloadFromResolved(
|
|
1924
|
+
{
|
|
1925
|
+
x: tap.cx,
|
|
1926
|
+
y: tap.cy,
|
|
1927
|
+
nodeId: tap.target.nodeId,
|
|
1928
|
+
testID: tap.target.testID,
|
|
1929
|
+
text: tap.target.text ?? tap.target.accessibilityLabel,
|
|
1930
|
+
type: tap.target.type
|
|
1931
|
+
},
|
|
1932
|
+
node,
|
|
1933
|
+
{ total, idx }
|
|
1934
|
+
);
|
|
1914
1935
|
}
|
|
1915
1936
|
var TAP_QUERY_FINDERS = {
|
|
1916
1937
|
label: "findByLabel",
|
|
@@ -1974,17 +1995,19 @@ async function tapBest(bridge, query, opts = {}) {
|
|
|
1974
1995
|
}
|
|
1975
1996
|
};
|
|
1976
1997
|
}
|
|
1977
|
-
const byText = await
|
|
1978
|
-
if (byText) {
|
|
1998
|
+
const byText = await resolveTextTapTarget(bridge, query);
|
|
1999
|
+
if ("ambiguous" in byText) {
|
|
2000
|
+
return {
|
|
2001
|
+
ambiguous: true,
|
|
2002
|
+
error: `multiple text matches for "${query}"; use tap-text with --nth, --within or --near`
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
if ("cx" in byText && typeof byText.cx === "number") {
|
|
1979
2006
|
return {
|
|
1980
|
-
...
|
|
2007
|
+
...byText,
|
|
1981
2008
|
strategy: "text",
|
|
1982
2009
|
node: {
|
|
1983
|
-
|
|
1984
|
-
id: byText.testID ?? null,
|
|
1985
|
-
testID: byText.testID ?? null,
|
|
1986
|
-
type: byText.type ?? null,
|
|
1987
|
-
text: byText.text ?? query
|
|
2010
|
+
...byText.target
|
|
1988
2011
|
}
|
|
1989
2012
|
};
|
|
1990
2013
|
}
|
package/dist-lib/sdk.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.630 | (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);
|
|
@@ -1701,16 +1701,26 @@ function payloadFromResolved(resolved, match, extra = {}) {
|
|
|
1701
1701
|
...extra
|
|
1702
1702
|
};
|
|
1703
1703
|
}
|
|
1704
|
-
function collectTextCandidates(nodes, ancestors = [], pressableOwnerText = "") {
|
|
1704
|
+
function collectTextCandidates(nodes, ancestors = [], pressableOwnerText = "", actionableAncestor = false, actionableRole) {
|
|
1705
1705
|
const out = [];
|
|
1706
1706
|
for (const node of nodes) {
|
|
1707
1707
|
const text = nodeText(node);
|
|
1708
1708
|
const caption = !!text && text === pressableOwnerText && !node.pressable;
|
|
1709
|
-
|
|
1709
|
+
const actionable = !!node.pressable || actionableAncestor;
|
|
1710
|
+
const role = node.role ?? actionableRole;
|
|
1711
|
+
if (!caption) out.push({ node, ancestorTestIDs: ancestors, actionable, role });
|
|
1710
1712
|
if (node.children?.length) {
|
|
1711
1713
|
const next = node.testID ? [...ancestors, node.testID] : ancestors;
|
|
1712
1714
|
const owner = text ? node.pressable ? text : "" : pressableOwnerText;
|
|
1713
|
-
out.push(
|
|
1715
|
+
out.push(
|
|
1716
|
+
...collectTextCandidates(
|
|
1717
|
+
node.children,
|
|
1718
|
+
next,
|
|
1719
|
+
owner,
|
|
1720
|
+
actionable,
|
|
1721
|
+
node.pressable ? node.role : actionableRole
|
|
1722
|
+
)
|
|
1723
|
+
);
|
|
1714
1724
|
}
|
|
1715
1725
|
}
|
|
1716
1726
|
return out;
|
|
@@ -1734,85 +1744,96 @@ async function tapByText(bridge, query, options = {}, opts = {}) {
|
|
|
1734
1744
|
agent: opts.agent,
|
|
1735
1745
|
timing: opts.timing,
|
|
1736
1746
|
textFallback: query,
|
|
1737
|
-
resolve:
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
const ay = node2.geometry.y;
|
|
1751
|
-
if (options.minX != null && !(ax >= options.minX)) return false;
|
|
1752
|
-
if (options.maxX != null && !(ax <= options.maxX)) return false;
|
|
1753
|
-
if (options.minY != null && !(ay >= options.minY)) return false;
|
|
1754
|
-
if (options.maxY != null && !(ay <= options.maxY)) return false;
|
|
1755
|
-
return true;
|
|
1756
|
-
});
|
|
1757
|
-
if (options.near) {
|
|
1758
|
-
const { x, y } = options.near;
|
|
1759
|
-
candidates.sort(
|
|
1760
|
-
(a, b) => Math.hypot(a.node.geometry.x - x, a.node.geometry.y - y) - Math.hypot(b.node.geometry.x - x, b.node.geometry.y - y)
|
|
1761
|
-
);
|
|
1762
|
-
} else {
|
|
1763
|
-
candidates.sort((a, b) => {
|
|
1764
|
-
if (Math.abs(a.node.geometry.y - b.node.geometry.y) > 2) {
|
|
1765
|
-
return a.node.geometry.y - b.node.geometry.y;
|
|
1766
|
-
}
|
|
1767
|
-
return a.node.geometry.x - b.node.geometry.x;
|
|
1768
|
-
});
|
|
1769
|
-
}
|
|
1770
|
-
const total = candidates.length;
|
|
1771
|
-
if (total === 0) return { matched: 0, total: 0 };
|
|
1772
|
-
let idx = 0;
|
|
1773
|
-
if (options.nth != null) {
|
|
1774
|
-
idx = options.nth < 0 ? total + options.nth : options.nth;
|
|
1775
|
-
if (idx < 0 || idx >= total) {
|
|
1776
|
-
return { matched: 0, total, nthOutOfRange: true, nth: options.nth };
|
|
1777
|
-
}
|
|
1778
|
-
} else if (total > 1 && !options.first && !options.near) {
|
|
1779
|
-
return {
|
|
1780
|
-
ambiguous: true,
|
|
1781
|
-
total,
|
|
1782
|
-
candidates: candidates.slice(0, 10).map((candidate, index) => ({
|
|
1783
|
-
idx: index,
|
|
1784
|
-
nodeId: candidate.node.nodeId,
|
|
1785
|
-
type: candidate.node.type,
|
|
1786
|
-
testID: candidate.node.testID ?? null,
|
|
1787
|
-
text: nodeText(candidate.node).slice(0, 60),
|
|
1788
|
-
abs: { x: candidate.node.geometry.x, y: candidate.node.geometry.y },
|
|
1789
|
-
layout: {
|
|
1790
|
-
width: Math.round(candidate.node.geometry.width),
|
|
1791
|
-
height: Math.round(candidate.node.geometry.height)
|
|
1792
|
-
},
|
|
1793
|
-
ancestorTestIDs: candidate.ancestorTestIDs.slice(0, 5)
|
|
1794
|
-
}))
|
|
1795
|
-
};
|
|
1747
|
+
resolve: () => resolveTextTapTarget(bridge, query, options)
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
async function resolveTextTapTarget(bridge, query, options = {}) {
|
|
1751
|
+
const { tree } = await inspectTree(bridge, 50);
|
|
1752
|
+
if (!Array.isArray(tree)) return { matched: 0, total: 0 };
|
|
1753
|
+
const needle = options.exact ? query : query.toLowerCase();
|
|
1754
|
+
let candidates = collectTextCandidates(tree).filter(
|
|
1755
|
+
({ node: node2, ancestorTestIDs, role }) => {
|
|
1756
|
+
const text = nodeText(node2);
|
|
1757
|
+
if (!text) return false;
|
|
1758
|
+
if (options.exact ? text !== needle : !text.toLowerCase().includes(needle)) {
|
|
1759
|
+
return false;
|
|
1796
1760
|
}
|
|
1797
|
-
|
|
1798
|
-
if (!
|
|
1799
|
-
const
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
if (!
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1761
|
+
if (options.role && role !== options.role) return false;
|
|
1762
|
+
if (options.within && !ancestorTestIDs.includes(options.within)) return false;
|
|
1763
|
+
const ax = node2.geometry.x;
|
|
1764
|
+
const ay = node2.geometry.y;
|
|
1765
|
+
if (options.minX != null && !(ax >= options.minX)) return false;
|
|
1766
|
+
if (options.maxX != null && !(ax <= options.maxX)) return false;
|
|
1767
|
+
if (options.minY != null && !(ay >= options.minY)) return false;
|
|
1768
|
+
if (options.maxY != null && !(ay <= options.maxY)) return false;
|
|
1769
|
+
return true;
|
|
1770
|
+
}
|
|
1771
|
+
);
|
|
1772
|
+
if (options.nth == null && !options.first && !options.near) {
|
|
1773
|
+
const actionable = candidates.filter((candidate) => candidate.actionable);
|
|
1774
|
+
if (actionable.length > 0) candidates = actionable;
|
|
1775
|
+
}
|
|
1776
|
+
if (options.near) {
|
|
1777
|
+
const { x, y } = options.near;
|
|
1778
|
+
candidates.sort(
|
|
1779
|
+
(a, b) => Math.hypot(a.node.geometry.x - x, a.node.geometry.y - y) - Math.hypot(b.node.geometry.x - x, b.node.geometry.y - y)
|
|
1780
|
+
);
|
|
1781
|
+
} else {
|
|
1782
|
+
candidates.sort((a, b) => {
|
|
1783
|
+
if (Math.abs(a.node.geometry.y - b.node.geometry.y) > 2) {
|
|
1784
|
+
return a.node.geometry.y - b.node.geometry.y;
|
|
1812
1785
|
}
|
|
1813
|
-
return
|
|
1786
|
+
return a.node.geometry.x - b.node.geometry.x;
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
const total = candidates.length;
|
|
1790
|
+
if (total === 0) return { matched: 0, total: 0 };
|
|
1791
|
+
let idx = 0;
|
|
1792
|
+
if (options.nth != null) {
|
|
1793
|
+
idx = options.nth < 0 ? total + options.nth : options.nth;
|
|
1794
|
+
if (idx < 0 || idx >= total) {
|
|
1795
|
+
return { matched: 0, total, nthOutOfRange: true, nth: options.nth };
|
|
1814
1796
|
}
|
|
1797
|
+
} else if (total > 1 && !options.first && !options.near) {
|
|
1798
|
+
return {
|
|
1799
|
+
ambiguous: true,
|
|
1800
|
+
total,
|
|
1801
|
+
candidates: candidates.slice(0, 10).map((candidate, index) => ({
|
|
1802
|
+
idx: index,
|
|
1803
|
+
nodeId: candidate.node.nodeId,
|
|
1804
|
+
type: candidate.node.type,
|
|
1805
|
+
testID: candidate.node.testID ?? null,
|
|
1806
|
+
text: nodeText(candidate.node).slice(0, 60),
|
|
1807
|
+
abs: { x: candidate.node.geometry.x, y: candidate.node.geometry.y },
|
|
1808
|
+
layout: {
|
|
1809
|
+
width: Math.round(candidate.node.geometry.width),
|
|
1810
|
+
height: Math.round(candidate.node.geometry.height)
|
|
1811
|
+
},
|
|
1812
|
+
ancestorTestIDs: candidate.ancestorTestIDs.slice(0, 5)
|
|
1813
|
+
}))
|
|
1814
|
+
};
|
|
1815
|
+
}
|
|
1816
|
+
const picked = candidates[idx];
|
|
1817
|
+
if (!picked) return { matched: 0, total };
|
|
1818
|
+
const node = picked.node;
|
|
1819
|
+
const tap = await bridge.send({
|
|
1820
|
+
type: "call",
|
|
1821
|
+
path: "SootSim.bridges.test.resolveTapTarget",
|
|
1822
|
+
args: [node.nodeId]
|
|
1815
1823
|
});
|
|
1824
|
+
if (!isTapTargetInfo(tap)) return { matched: 0, total };
|
|
1825
|
+
return payloadFromResolved(
|
|
1826
|
+
{
|
|
1827
|
+
x: tap.cx,
|
|
1828
|
+
y: tap.cy,
|
|
1829
|
+
nodeId: tap.target.nodeId,
|
|
1830
|
+
testID: tap.target.testID,
|
|
1831
|
+
text: tap.target.text ?? tap.target.accessibilityLabel,
|
|
1832
|
+
type: tap.target.type
|
|
1833
|
+
},
|
|
1834
|
+
node,
|
|
1835
|
+
{ total, idx }
|
|
1836
|
+
);
|
|
1816
1837
|
}
|
|
1817
1838
|
var TAP_QUERY_FINDERS = {
|
|
1818
1839
|
label: "findByLabel",
|
|
@@ -1876,17 +1897,19 @@ async function tapBest(bridge, query, opts = {}) {
|
|
|
1876
1897
|
}
|
|
1877
1898
|
};
|
|
1878
1899
|
}
|
|
1879
|
-
const byText = await
|
|
1880
|
-
if (byText) {
|
|
1900
|
+
const byText = await resolveTextTapTarget(bridge, query);
|
|
1901
|
+
if ("ambiguous" in byText) {
|
|
1902
|
+
return {
|
|
1903
|
+
ambiguous: true,
|
|
1904
|
+
error: `multiple text matches for "${query}"; use tap-text with --nth, --within or --near`
|
|
1905
|
+
};
|
|
1906
|
+
}
|
|
1907
|
+
if ("cx" in byText && typeof byText.cx === "number") {
|
|
1881
1908
|
return {
|
|
1882
|
-
...
|
|
1909
|
+
...byText,
|
|
1883
1910
|
strategy: "text",
|
|
1884
1911
|
node: {
|
|
1885
|
-
|
|
1886
|
-
id: byText.testID ?? null,
|
|
1887
|
-
testID: byText.testID ?? null,
|
|
1888
|
-
type: byText.type ?? null,
|
|
1889
|
-
text: byText.text ?? query
|
|
1912
|
+
...byText.target
|
|
1890
1913
|
}
|
|
1891
1914
|
};
|
|
1892
1915
|
}
|