rnxsim 0.1.486 → 0.1.488
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/cli/outbound-endpoints.ts +2 -2
- package/cli/ws-bridge.ts +1 -1
- package/detox/index.ts +212 -20
- package/dist-lib/agent-daemon-client.cjs +2 -2
- 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 +2 -2
- 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.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +191 -21
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +1 -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 +2 -2
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/jump-to-source-native.cjs +1 -1
- 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 +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 +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +3 -3
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/src/agent-daemon-client.ts +1 -1
- package/src/bridge-constants.ts +6 -1
|
@@ -485,7 +485,7 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
485
485
|
count: 1,
|
|
486
486
|
},
|
|
487
487
|
// the inventory records the call expression, not the URL, so this entry can
|
|
488
|
-
// no longer show its own destination. bridgeWsUrl() is
|
|
488
|
+
// no longer show its own destination. bridgeWsUrl() is loopback-only today;
|
|
489
489
|
// when a cloud box makes it return a remote wss:// endpoint, that egress
|
|
490
490
|
// would land under this same local_development entry and go unnoticed. split
|
|
491
491
|
// the declaration then, rather than letting one line cover both.
|
|
@@ -497,7 +497,7 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
497
497
|
category: 'local_development',
|
|
498
498
|
count: 1,
|
|
499
499
|
},
|
|
500
|
-
'packages/sootsim/src/agent-daemon-client.ts :: websocket :: `ws://
|
|
500
|
+
'packages/sootsim/src/agent-daemon-client.ts :: websocket :: `ws://127.0.0.1:${this.port}`':
|
|
501
501
|
{ category: 'local_development', count: 1 },
|
|
502
502
|
"packages/sootsim/src/auth/shared-session.ts :: fetch :: `${origin.replace(/\\/$/, '')}/api/auth/me`":
|
|
503
503
|
{ category: 'contrast_user_action', count: 1 },
|
package/cli/ws-bridge.ts
CHANGED
|
@@ -75,7 +75,7 @@ export function resolvePlacement(): RnxPlacement {
|
|
|
75
75
|
* this function. a cloud endpoint added here must get its own declaration in
|
|
76
76
|
* `outbound-endpoints.ts` instead of inheriting the local_development one. */
|
|
77
77
|
export function bridgeWsUrl(wsPort: number): string {
|
|
78
|
-
return `ws://
|
|
78
|
+
return `ws://127.0.0.1:${wsPort}`
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
// resolved once per CLI process so a multi-step command keeps one world for
|
package/detox/index.ts
CHANGED
|
@@ -55,11 +55,45 @@ let _context: BrowserContext | null = null
|
|
|
55
55
|
let _page: Page | null = null
|
|
56
56
|
let _synchronizationEnabled = false
|
|
57
57
|
let _suspendedAppId: string | null = null
|
|
58
|
+
let _browserCompromised: string | null = null
|
|
58
59
|
let browserIdentity = 0
|
|
59
60
|
let contextIdentity = 0
|
|
60
61
|
let nextBrowserIdentity = 0
|
|
61
62
|
let nextContextIdentity = 0
|
|
62
63
|
|
|
64
|
+
// maximum duration for browser/context lifecycle operations (closeContext,
|
|
65
|
+
// newContext, newPage). playwright does not bound context.close() or context
|
|
66
|
+
// creation; this bound converts an opaque 180s Jest hook timeout into a
|
|
67
|
+
// named failure that identifies which lifecycle step stalled.
|
|
68
|
+
const BROWSER_LIFECYCLE_TIMEOUT_MS = 30_000
|
|
69
|
+
|
|
70
|
+
async function withLifecycleTimeout<T>(
|
|
71
|
+
actionName: string,
|
|
72
|
+
targetContextId: number,
|
|
73
|
+
fn: () => Promise<T>,
|
|
74
|
+
timeoutMs = BROWSER_LIFECYCLE_TIMEOUT_MS,
|
|
75
|
+
): Promise<T> {
|
|
76
|
+
const start = Date.now()
|
|
77
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
78
|
+
try {
|
|
79
|
+
return await Promise.race([
|
|
80
|
+
fn(),
|
|
81
|
+
new Promise<never>((_, reject) => {
|
|
82
|
+
timer = setTimeout(() => {
|
|
83
|
+
const elapsed = Date.now() - start
|
|
84
|
+
reject(
|
|
85
|
+
new Error(
|
|
86
|
+
`sootsim browser lifecycle error: ${actionName} did not settle after ${elapsed}ms (context=${targetContextId})`,
|
|
87
|
+
),
|
|
88
|
+
)
|
|
89
|
+
}, timeoutMs)
|
|
90
|
+
}),
|
|
91
|
+
])
|
|
92
|
+
} finally {
|
|
93
|
+
if (timer) clearTimeout(timer)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
63
97
|
const STATUS_BAR_OVERRIDE_EVENT = 'sootsim:statusBarOverride'
|
|
64
98
|
|
|
65
99
|
type StatusBarConfig = {
|
|
@@ -75,6 +109,9 @@ type StatusBarConfig = {
|
|
|
75
109
|
}
|
|
76
110
|
|
|
77
111
|
function getPage(): Page {
|
|
112
|
+
if (_browserCompromised) {
|
|
113
|
+
throw new Error(`sootsim browser is compromised: ${_browserCompromised}`)
|
|
114
|
+
}
|
|
78
115
|
if (!_page)
|
|
79
116
|
throw new Error('sootsim driver not initialized -- call device.launchApp() first')
|
|
80
117
|
return _page
|
|
@@ -280,13 +317,33 @@ async function waitForSootsimWorkletsIdle(page: Page, timeout = 3000): Promise<v
|
|
|
280
317
|
}
|
|
281
318
|
}
|
|
282
319
|
|
|
320
|
+
function reapOwnedBrowser(): void {
|
|
321
|
+
const reap = _reapBrowserNow
|
|
322
|
+
_reapBrowserNow = null
|
|
323
|
+
_browser = null
|
|
324
|
+
try {
|
|
325
|
+
reap?.()
|
|
326
|
+
} catch {}
|
|
327
|
+
}
|
|
328
|
+
|
|
283
329
|
async function closeContext() {
|
|
330
|
+
if (_browserCompromised) {
|
|
331
|
+
throw new Error(`sootsim browser is compromised: ${_browserCompromised}`)
|
|
332
|
+
}
|
|
333
|
+
if (!_context) return
|
|
284
334
|
const context = _context
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
335
|
+
const closingContextId = contextIdentity
|
|
336
|
+
try {
|
|
337
|
+
await withLifecycleTimeout('context.close()', closingContextId, () => context.close())
|
|
338
|
+
_context = null
|
|
339
|
+
_page = null
|
|
340
|
+
contextIdentity = 0
|
|
341
|
+
_synchronizationEnabled = false
|
|
342
|
+
} catch (error) {
|
|
343
|
+
_browserCompromised = error instanceof Error ? error.message : String(error)
|
|
344
|
+
reapOwnedBrowser()
|
|
345
|
+
throw error
|
|
346
|
+
}
|
|
290
347
|
}
|
|
291
348
|
|
|
292
349
|
async function closeBrowser() {
|
|
@@ -296,11 +353,25 @@ async function closeBrowser() {
|
|
|
296
353
|
_browser = null
|
|
297
354
|
_reapBrowserNow = null
|
|
298
355
|
browserIdentity = 0
|
|
299
|
-
|
|
356
|
+
|
|
357
|
+
let closeError: Error | null = null
|
|
358
|
+
try {
|
|
359
|
+
await closeContext()
|
|
360
|
+
} catch (error) {
|
|
361
|
+
closeError = error instanceof Error ? error : new Error(String(error))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// reap Chrome directly without waiting on an unbounded browser.close()
|
|
300
365
|
try {
|
|
301
|
-
await browser.close()
|
|
302
|
-
} finally {
|
|
303
366
|
reapBrowserNow?.()
|
|
367
|
+
} catch {}
|
|
368
|
+
|
|
369
|
+
try {
|
|
370
|
+
await browser.close()
|
|
371
|
+
} catch {}
|
|
372
|
+
|
|
373
|
+
if (closeError) {
|
|
374
|
+
throw closeError
|
|
304
375
|
}
|
|
305
376
|
}
|
|
306
377
|
|
|
@@ -730,6 +801,102 @@ async function dispatchStatusBarOverride(config: StatusBarConfig) {
|
|
|
730
801
|
await page.waitForTimeout(50)
|
|
731
802
|
}
|
|
732
803
|
|
|
804
|
+
const MAX_SETTLED_SPEED_PT_PER_SEC = 2.0
|
|
805
|
+
|
|
806
|
+
async function waitForFrameClock(page: Page, minMs = 25): Promise<void> {
|
|
807
|
+
const start = performance.now()
|
|
808
|
+
await page.evaluate(
|
|
809
|
+
() =>
|
|
810
|
+
new Promise<void>((resolve) => {
|
|
811
|
+
const timeoutId = setTimeout(() => resolve(), 50)
|
|
812
|
+
requestAnimationFrame(() => {
|
|
813
|
+
clearTimeout(timeoutId)
|
|
814
|
+
resolve()
|
|
815
|
+
})
|
|
816
|
+
}),
|
|
817
|
+
)
|
|
818
|
+
const elapsed = performance.now() - start
|
|
819
|
+
if (elapsed < minMs) {
|
|
820
|
+
await page.waitForTimeout(minMs - elapsed)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
async function findStableNodeByMatcher(
|
|
825
|
+
matcher: Matcher,
|
|
826
|
+
action = 'tap',
|
|
827
|
+
maxWaitMs = 1500,
|
|
828
|
+
): Promise<any> {
|
|
829
|
+
const page = getPage()
|
|
830
|
+
const start = performance.now()
|
|
831
|
+
let lastPos: { x: number; y: number } | null = null
|
|
832
|
+
let lastTime: number | null = null
|
|
833
|
+
let lastRate: { vx: number; vy: number; speed: number } | null = null
|
|
834
|
+
let sampleCount = 0
|
|
835
|
+
let stablePolls = 0
|
|
836
|
+
const requiredStable = 2
|
|
837
|
+
let hadNumericLayout = false
|
|
838
|
+
let disappearedMidWait = false
|
|
839
|
+
|
|
840
|
+
while (performance.now() - start < maxWaitMs) {
|
|
841
|
+
const node = await findNodeByMatcher(matcher)
|
|
842
|
+
if (!node) {
|
|
843
|
+
if (hadNumericLayout) {
|
|
844
|
+
disappearedMidWait = true
|
|
845
|
+
} else {
|
|
846
|
+
return null
|
|
847
|
+
}
|
|
848
|
+
} else {
|
|
849
|
+
disappearedMidWait = false
|
|
850
|
+
const pos = node.absolutePosition ?? node.layout
|
|
851
|
+
if (pos && typeof pos.x === 'number' && typeof pos.y === 'number') {
|
|
852
|
+
hadNumericLayout = true
|
|
853
|
+
sampleCount++
|
|
854
|
+
const now = performance.now()
|
|
855
|
+
if (lastPos && lastTime !== null) {
|
|
856
|
+
const dtSec = (now - lastTime) / 1000
|
|
857
|
+
if (dtSec > 0.005) {
|
|
858
|
+
const vx = Math.abs(pos.x - lastPos.x) / dtSec
|
|
859
|
+
const vy = Math.abs(pos.y - lastPos.y) / dtSec
|
|
860
|
+
const speed = Math.sqrt(vx * vx + vy * vy)
|
|
861
|
+
lastRate = { vx, vy, speed }
|
|
862
|
+
if (speed < MAX_SETTLED_SPEED_PT_PER_SEC) {
|
|
863
|
+
stablePolls++
|
|
864
|
+
if (stablePolls >= requiredStable) {
|
|
865
|
+
return node
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
stablePolls = 0
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
lastPos = { x: pos.x, y: pos.y }
|
|
873
|
+
lastTime = now
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
await waitForFrameClock(page, 25)
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const elapsed = Math.round(performance.now() - start)
|
|
880
|
+
if (disappearedMidWait) {
|
|
881
|
+
throw new Error(
|
|
882
|
+
`element disappeared while waiting to come to rest for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`,
|
|
883
|
+
)
|
|
884
|
+
}
|
|
885
|
+
if (!hadNumericLayout) {
|
|
886
|
+
throw new Error(
|
|
887
|
+
`element layout is not available for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`,
|
|
888
|
+
)
|
|
889
|
+
}
|
|
890
|
+
if (sampleCount < 2 || lastRate === null) {
|
|
891
|
+
throw new Error(
|
|
892
|
+
`element did not come to rest for ${action}: ${JSON.stringify(matcher)}; only one sample obtained after ${elapsed}ms`,
|
|
893
|
+
)
|
|
894
|
+
}
|
|
895
|
+
throw new Error(
|
|
896
|
+
`element did not come to rest for ${action}: ${JSON.stringify(matcher)}; still moving ${lastRate.vx.toFixed(1)} points/sec x / ${lastRate.vy.toFixed(1)} points/sec y after ${elapsed}ms`,
|
|
897
|
+
)
|
|
898
|
+
}
|
|
899
|
+
|
|
733
900
|
function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
734
901
|
const el: SootSimElement = {
|
|
735
902
|
_matcher: matcher,
|
|
@@ -737,7 +904,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
737
904
|
async tap(point) {
|
|
738
905
|
const page = getPage()
|
|
739
906
|
await waitForSootsimWorkletsIdle(page, 3000)
|
|
740
|
-
const node = await
|
|
907
|
+
const node = await findStableNodeByMatcher(matcher, 'tap')
|
|
741
908
|
if (!node) throw new Error(`element not found for tap: ${JSON.stringify(matcher)}`)
|
|
742
909
|
if (!isNodeVisible(node)) {
|
|
743
910
|
throw new Error(
|
|
@@ -766,7 +933,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
766
933
|
// center with a small inter-tap pause that stays inside RNGH's
|
|
767
934
|
// default maxDelay window of 200ms.
|
|
768
935
|
const page = getPage()
|
|
769
|
-
const node = await
|
|
936
|
+
const node = await findStableNodeByMatcher(matcher, 'multiTap')
|
|
770
937
|
if (!node)
|
|
771
938
|
throw new Error(`element not found for multiTap: ${JSON.stringify(matcher)}`)
|
|
772
939
|
if (!isNodeVisible(node)) {
|
|
@@ -796,7 +963,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
796
963
|
durationArg = 1000,
|
|
797
964
|
) {
|
|
798
965
|
const page = getPage()
|
|
799
|
-
const node = await
|
|
966
|
+
const node = await findStableNodeByMatcher(matcher, 'longPress')
|
|
800
967
|
if (!node)
|
|
801
968
|
throw new Error(`element not found for longPress: ${JSON.stringify(matcher)}`)
|
|
802
969
|
if (!isNodeVisible(node)) {
|
|
@@ -840,12 +1007,15 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
840
1007
|
) {
|
|
841
1008
|
const page = getPage()
|
|
842
1009
|
// resolve source element
|
|
843
|
-
const srcNode = await
|
|
1010
|
+
const srcNode = await findStableNodeByMatcher(matcher, 'longPressAndDrag')
|
|
844
1011
|
if (!srcNode)
|
|
845
1012
|
throw new Error(`source element not found: ${JSON.stringify(matcher)}`)
|
|
846
1013
|
|
|
847
1014
|
// resolve target element
|
|
848
|
-
const tgtNode = await
|
|
1015
|
+
const tgtNode = await findStableNodeByMatcher(
|
|
1016
|
+
targetElement._matcher,
|
|
1017
|
+
'longPressAndDrag',
|
|
1018
|
+
)
|
|
849
1019
|
if (!tgtNode)
|
|
850
1020
|
throw new Error(
|
|
851
1021
|
`target element not found: ${JSON.stringify(targetElement._matcher)}`,
|
|
@@ -1027,7 +1197,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
1027
1197
|
normalizedStartingPointX = 0.5,
|
|
1028
1198
|
normalizedStartingPointY = 0.5,
|
|
1029
1199
|
) {
|
|
1030
|
-
const node = await
|
|
1200
|
+
const node = await findStableNodeByMatcher(matcher, 'swipe')
|
|
1031
1201
|
if (!node)
|
|
1032
1202
|
throw new Error(`element not found for swipe: ${JSON.stringify(matcher)}`)
|
|
1033
1203
|
|
|
@@ -1101,7 +1271,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
1101
1271
|
throw new Error(`picker value must be a string, got ${typeof value}`)
|
|
1102
1272
|
}
|
|
1103
1273
|
|
|
1104
|
-
const picker = await
|
|
1274
|
+
const picker = await findStableNodeByMatcher(matcher, 'setColumnToValue')
|
|
1105
1275
|
if (!picker) {
|
|
1106
1276
|
throw new Error(
|
|
1107
1277
|
`element not found for setColumnToValue: ${JSON.stringify(matcher)}`,
|
|
@@ -1178,7 +1348,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
1178
1348
|
// velocity tracking sees a realistic delta. angle (radians) tilts the
|
|
1179
1349
|
// pinch axis off horizontal — matches Detox's third arg.
|
|
1180
1350
|
const page = getPage()
|
|
1181
|
-
const node = await
|
|
1351
|
+
const node = await findStableNodeByMatcher(matcher, 'pinch')
|
|
1182
1352
|
if (!node)
|
|
1183
1353
|
throw new Error(`element not found for pinch: ${JSON.stringify(matcher)}`)
|
|
1184
1354
|
if (!node.nativePinchEnabled) {
|
|
@@ -1213,7 +1383,7 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
|
|
|
1213
1383
|
// sign (its 'rotation' delta is positive for a counter-clockwise
|
|
1214
1384
|
// motion of the rear finger relative to the front finger).
|
|
1215
1385
|
const page = getPage()
|
|
1216
|
-
const node = await
|
|
1386
|
+
const node = await findStableNodeByMatcher(matcher, 'rotate')
|
|
1217
1387
|
if (!node)
|
|
1218
1388
|
throw new Error(`element not found for rotate: ${JSON.stringify(matcher)}`)
|
|
1219
1389
|
await waitForSootsimGestureHandler(page, node.nodeId, 'RotationGestureHandler')
|
|
@@ -1299,6 +1469,9 @@ export const device = {
|
|
|
1299
1469
|
// toggled on an existing context.
|
|
1300
1470
|
recordVideoDir?: string
|
|
1301
1471
|
}) {
|
|
1472
|
+
if (_browserCompromised) {
|
|
1473
|
+
throw new Error(`sootsim browser is compromised: ${_browserCompromised}`)
|
|
1474
|
+
}
|
|
1302
1475
|
if (
|
|
1303
1476
|
_page &&
|
|
1304
1477
|
opts?.newInstance === false &&
|
|
@@ -1362,7 +1535,18 @@ export const device = {
|
|
|
1362
1535
|
size: { width: 500, height: 900 },
|
|
1363
1536
|
}
|
|
1364
1537
|
}
|
|
1365
|
-
|
|
1538
|
+
const newContextIdentity = ++nextContextIdentity
|
|
1539
|
+
try {
|
|
1540
|
+
_context = await withLifecycleTimeout(
|
|
1541
|
+
'_browser.newContext()',
|
|
1542
|
+
newContextIdentity,
|
|
1543
|
+
() => _browser!.newContext(contextOpts),
|
|
1544
|
+
)
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
_browserCompromised = error instanceof Error ? error.message : String(error)
|
|
1547
|
+
reapOwnedBrowser()
|
|
1548
|
+
throw error
|
|
1549
|
+
}
|
|
1366
1550
|
await _context.exposeBinding('__sootsimHostCapture', async (source, value) => {
|
|
1367
1551
|
const request = readBrowserCompositeCaptureRequest(value)
|
|
1368
1552
|
const session = await source.page.context().newCDPSession(source.page)
|
|
@@ -1381,8 +1565,16 @@ export const device = {
|
|
|
1381
1565
|
await session.detach()
|
|
1382
1566
|
}
|
|
1383
1567
|
})
|
|
1384
|
-
|
|
1385
|
-
|
|
1568
|
+
try {
|
|
1569
|
+
_page = await withLifecycleTimeout('_context.newPage()', newContextIdentity, () =>
|
|
1570
|
+
_context!.newPage(),
|
|
1571
|
+
)
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
_browserCompromised = error instanceof Error ? error.message : String(error)
|
|
1574
|
+
reapOwnedBrowser()
|
|
1575
|
+
throw error
|
|
1576
|
+
}
|
|
1577
|
+
contextIdentity = newContextIdentity
|
|
1386
1578
|
}
|
|
1387
1579
|
|
|
1388
1580
|
const url = opts?.url || BASE_URL
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -241,7 +241,7 @@ var AgentDaemonClient = class {
|
|
|
241
241
|
constructor(opts = {}) {
|
|
242
242
|
this.port = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT;
|
|
243
243
|
this.commandTimeoutMs = opts.commandTimeoutMs ?? 15e3;
|
|
244
|
-
this.ws = new import_ws.WebSocket(`ws://
|
|
244
|
+
this.ws = new import_ws.WebSocket(`ws://127.0.0.1:${this.port}`);
|
|
245
245
|
this.ready = new Promise((resolve, reject) => {
|
|
246
246
|
const onOpen = () => {
|
|
247
247
|
this.ws.off("error", onError);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/beta.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/beta.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -136,7 +136,7 @@ function resolveSootsimWebSocketProxyUrl(input) {
|
|
|
136
136
|
}
|
|
137
137
|
function resolveSootsimBridgeWebSocketUrl(input) {
|
|
138
138
|
return resolveSootsimWebSocketProxyUrl({
|
|
139
|
-
targetUrl: `ws://
|
|
139
|
+
targetUrl: `ws://127.0.0.1:${input.bridgePort}`,
|
|
140
140
|
href: input.href
|
|
141
141
|
});
|
|
142
142
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/cloud.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/cloud.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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);
|
package/dist-lib/config.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/detox/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1881,12 +1881,38 @@ var _context = null;
|
|
|
1881
1881
|
var _page = null;
|
|
1882
1882
|
var _synchronizationEnabled = false;
|
|
1883
1883
|
var _suspendedAppId = null;
|
|
1884
|
+
var _browserCompromised = null;
|
|
1884
1885
|
var browserIdentity = 0;
|
|
1885
1886
|
var contextIdentity = 0;
|
|
1886
1887
|
var nextBrowserIdentity = 0;
|
|
1887
1888
|
var nextContextIdentity = 0;
|
|
1889
|
+
var BROWSER_LIFECYCLE_TIMEOUT_MS = 3e4;
|
|
1890
|
+
async function withLifecycleTimeout(actionName, targetContextId, fn, timeoutMs = BROWSER_LIFECYCLE_TIMEOUT_MS) {
|
|
1891
|
+
const start = Date.now();
|
|
1892
|
+
let timer;
|
|
1893
|
+
try {
|
|
1894
|
+
return await Promise.race([
|
|
1895
|
+
fn(),
|
|
1896
|
+
new Promise((_, reject) => {
|
|
1897
|
+
timer = setTimeout(() => {
|
|
1898
|
+
const elapsed = Date.now() - start;
|
|
1899
|
+
reject(
|
|
1900
|
+
new Error(
|
|
1901
|
+
`sootsim browser lifecycle error: ${actionName} did not settle after ${elapsed}ms (context=${targetContextId})`
|
|
1902
|
+
)
|
|
1903
|
+
);
|
|
1904
|
+
}, timeoutMs);
|
|
1905
|
+
})
|
|
1906
|
+
]);
|
|
1907
|
+
} finally {
|
|
1908
|
+
if (timer) clearTimeout(timer);
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1888
1911
|
var STATUS_BAR_OVERRIDE_EVENT = "sootsim:statusBarOverride";
|
|
1889
1912
|
function getPage() {
|
|
1913
|
+
if (_browserCompromised) {
|
|
1914
|
+
throw new Error(`sootsim browser is compromised: ${_browserCompromised}`);
|
|
1915
|
+
}
|
|
1890
1916
|
if (!_page)
|
|
1891
1917
|
throw new Error("sootsim driver not initialized -- call device.launchApp() first");
|
|
1892
1918
|
return _page;
|
|
@@ -2052,13 +2078,33 @@ async function waitForSootsimWorkletsIdle(page, timeout = 3e3) {
|
|
|
2052
2078
|
);
|
|
2053
2079
|
}
|
|
2054
2080
|
}
|
|
2081
|
+
function reapOwnedBrowser() {
|
|
2082
|
+
const reap = _reapBrowserNow;
|
|
2083
|
+
_reapBrowserNow = null;
|
|
2084
|
+
_browser = null;
|
|
2085
|
+
try {
|
|
2086
|
+
reap?.();
|
|
2087
|
+
} catch {
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2055
2090
|
async function closeContext() {
|
|
2091
|
+
if (_browserCompromised) {
|
|
2092
|
+
throw new Error(`sootsim browser is compromised: ${_browserCompromised}`);
|
|
2093
|
+
}
|
|
2094
|
+
if (!_context) return;
|
|
2056
2095
|
const context = _context;
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2096
|
+
const closingContextId = contextIdentity;
|
|
2097
|
+
try {
|
|
2098
|
+
await withLifecycleTimeout("context.close()", closingContextId, () => context.close());
|
|
2099
|
+
_context = null;
|
|
2100
|
+
_page = null;
|
|
2101
|
+
contextIdentity = 0;
|
|
2102
|
+
_synchronizationEnabled = false;
|
|
2103
|
+
} catch (error) {
|
|
2104
|
+
_browserCompromised = error instanceof Error ? error.message : String(error);
|
|
2105
|
+
reapOwnedBrowser();
|
|
2106
|
+
throw error;
|
|
2107
|
+
}
|
|
2062
2108
|
}
|
|
2063
2109
|
async function closeBrowser() {
|
|
2064
2110
|
if (!_browser) return;
|
|
@@ -2067,11 +2113,22 @@ async function closeBrowser() {
|
|
|
2067
2113
|
_browser = null;
|
|
2068
2114
|
_reapBrowserNow = null;
|
|
2069
2115
|
browserIdentity = 0;
|
|
2070
|
-
|
|
2116
|
+
let closeError = null;
|
|
2117
|
+
try {
|
|
2118
|
+
await closeContext();
|
|
2119
|
+
} catch (error) {
|
|
2120
|
+
closeError = error instanceof Error ? error : new Error(String(error));
|
|
2121
|
+
}
|
|
2071
2122
|
try {
|
|
2072
|
-
await browser.close();
|
|
2073
|
-
} finally {
|
|
2074
2123
|
reapBrowserNow?.();
|
|
2124
|
+
} catch {
|
|
2125
|
+
}
|
|
2126
|
+
try {
|
|
2127
|
+
await browser.close();
|
|
2128
|
+
} catch {
|
|
2129
|
+
}
|
|
2130
|
+
if (closeError) {
|
|
2131
|
+
throw closeError;
|
|
2075
2132
|
}
|
|
2076
2133
|
}
|
|
2077
2134
|
async function waitForSootsimIdle(page, maxMs = 3e3, strict = false) {
|
|
@@ -2391,13 +2448,99 @@ async function dispatchStatusBarOverride(config) {
|
|
|
2391
2448
|
);
|
|
2392
2449
|
await page.waitForTimeout(50);
|
|
2393
2450
|
}
|
|
2451
|
+
var MAX_SETTLED_SPEED_PT_PER_SEC = 2;
|
|
2452
|
+
async function waitForFrameClock(page, minMs = 25) {
|
|
2453
|
+
const start = performance.now();
|
|
2454
|
+
await page.evaluate(
|
|
2455
|
+
() => new Promise((resolve) => {
|
|
2456
|
+
const timeoutId = setTimeout(() => resolve(), 50);
|
|
2457
|
+
requestAnimationFrame(() => {
|
|
2458
|
+
clearTimeout(timeoutId);
|
|
2459
|
+
resolve();
|
|
2460
|
+
});
|
|
2461
|
+
})
|
|
2462
|
+
);
|
|
2463
|
+
const elapsed = performance.now() - start;
|
|
2464
|
+
if (elapsed < minMs) {
|
|
2465
|
+
await page.waitForTimeout(minMs - elapsed);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
async function findStableNodeByMatcher(matcher, action = "tap", maxWaitMs = 1500) {
|
|
2469
|
+
const page = getPage();
|
|
2470
|
+
const start = performance.now();
|
|
2471
|
+
let lastPos = null;
|
|
2472
|
+
let lastTime = null;
|
|
2473
|
+
let lastRate = null;
|
|
2474
|
+
let sampleCount = 0;
|
|
2475
|
+
let stablePolls = 0;
|
|
2476
|
+
const requiredStable = 2;
|
|
2477
|
+
let hadNumericLayout = false;
|
|
2478
|
+
let disappearedMidWait = false;
|
|
2479
|
+
while (performance.now() - start < maxWaitMs) {
|
|
2480
|
+
const node = await findNodeByMatcher(matcher);
|
|
2481
|
+
if (!node) {
|
|
2482
|
+
if (hadNumericLayout) {
|
|
2483
|
+
disappearedMidWait = true;
|
|
2484
|
+
} else {
|
|
2485
|
+
return null;
|
|
2486
|
+
}
|
|
2487
|
+
} else {
|
|
2488
|
+
disappearedMidWait = false;
|
|
2489
|
+
const pos = node.absolutePosition ?? node.layout;
|
|
2490
|
+
if (pos && typeof pos.x === "number" && typeof pos.y === "number") {
|
|
2491
|
+
hadNumericLayout = true;
|
|
2492
|
+
sampleCount++;
|
|
2493
|
+
const now = performance.now();
|
|
2494
|
+
if (lastPos && lastTime !== null) {
|
|
2495
|
+
const dtSec = (now - lastTime) / 1e3;
|
|
2496
|
+
if (dtSec > 5e-3) {
|
|
2497
|
+
const vx = Math.abs(pos.x - lastPos.x) / dtSec;
|
|
2498
|
+
const vy = Math.abs(pos.y - lastPos.y) / dtSec;
|
|
2499
|
+
const speed = Math.sqrt(vx * vx + vy * vy);
|
|
2500
|
+
lastRate = { vx, vy, speed };
|
|
2501
|
+
if (speed < MAX_SETTLED_SPEED_PT_PER_SEC) {
|
|
2502
|
+
stablePolls++;
|
|
2503
|
+
if (stablePolls >= requiredStable) {
|
|
2504
|
+
return node;
|
|
2505
|
+
}
|
|
2506
|
+
} else {
|
|
2507
|
+
stablePolls = 0;
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
lastPos = { x: pos.x, y: pos.y };
|
|
2512
|
+
lastTime = now;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
await waitForFrameClock(page, 25);
|
|
2516
|
+
}
|
|
2517
|
+
const elapsed = Math.round(performance.now() - start);
|
|
2518
|
+
if (disappearedMidWait) {
|
|
2519
|
+
throw new Error(
|
|
2520
|
+
`element disappeared while waiting to come to rest for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
if (!hadNumericLayout) {
|
|
2524
|
+
throw new Error(
|
|
2525
|
+
`element layout is not available for ${action}: ${JSON.stringify(matcher)} after ${elapsed}ms`
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
if (sampleCount < 2 || lastRate === null) {
|
|
2529
|
+
throw new Error(
|
|
2530
|
+
`element did not come to rest for ${action}: ${JSON.stringify(matcher)}; only one sample obtained after ${elapsed}ms`
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2533
|
+
throw new Error(
|
|
2534
|
+
`element did not come to rest for ${action}: ${JSON.stringify(matcher)}; still moving ${lastRate.vx.toFixed(1)} points/sec x / ${lastRate.vy.toFixed(1)} points/sec y after ${elapsed}ms`
|
|
2535
|
+
);
|
|
2536
|
+
}
|
|
2394
2537
|
function createSootSimElement(matcher) {
|
|
2395
2538
|
const el = {
|
|
2396
2539
|
_matcher: matcher,
|
|
2397
2540
|
async tap(point) {
|
|
2398
2541
|
const page = getPage();
|
|
2399
2542
|
await waitForSootsimWorkletsIdle(page, 3e3);
|
|
2400
|
-
const node = await
|
|
2543
|
+
const node = await findStableNodeByMatcher(matcher, "tap");
|
|
2401
2544
|
if (!node) throw new Error(`element not found for tap: ${JSON.stringify(matcher)}`);
|
|
2402
2545
|
if (!isNodeVisible(node)) {
|
|
2403
2546
|
throw new Error(
|
|
@@ -2414,7 +2557,7 @@ function createSootSimElement(matcher) {
|
|
|
2414
2557
|
},
|
|
2415
2558
|
async multiTap(times) {
|
|
2416
2559
|
const page = getPage();
|
|
2417
|
-
const node = await
|
|
2560
|
+
const node = await findStableNodeByMatcher(matcher, "multiTap");
|
|
2418
2561
|
if (!node)
|
|
2419
2562
|
throw new Error(`element not found for multiTap: ${JSON.stringify(matcher)}`);
|
|
2420
2563
|
if (!isNodeVisible(node)) {
|
|
@@ -2439,7 +2582,7 @@ function createSootSimElement(matcher) {
|
|
|
2439
2582
|
// where the point is relative to the element's origin
|
|
2440
2583
|
async longPress(pointOrDuration, durationArg = 1e3) {
|
|
2441
2584
|
const page = getPage();
|
|
2442
|
-
const node = await
|
|
2585
|
+
const node = await findStableNodeByMatcher(matcher, "longPress");
|
|
2443
2586
|
if (!node)
|
|
2444
2587
|
throw new Error(`element not found for longPress: ${JSON.stringify(matcher)}`);
|
|
2445
2588
|
if (!isNodeVisible(node)) {
|
|
@@ -2469,10 +2612,13 @@ function createSootSimElement(matcher) {
|
|
|
2469
2612
|
},
|
|
2470
2613
|
async longPressAndDrag(duration, normalizedStartX, normalizedStartY, targetElement, normalizedEndX, normalizedEndY, speed = "fast", holdDuration = 0) {
|
|
2471
2614
|
const page = getPage();
|
|
2472
|
-
const srcNode = await
|
|
2615
|
+
const srcNode = await findStableNodeByMatcher(matcher, "longPressAndDrag");
|
|
2473
2616
|
if (!srcNode)
|
|
2474
2617
|
throw new Error(`source element not found: ${JSON.stringify(matcher)}`);
|
|
2475
|
-
const tgtNode = await
|
|
2618
|
+
const tgtNode = await findStableNodeByMatcher(
|
|
2619
|
+
targetElement._matcher,
|
|
2620
|
+
"longPressAndDrag"
|
|
2621
|
+
);
|
|
2476
2622
|
if (!tgtNode)
|
|
2477
2623
|
throw new Error(
|
|
2478
2624
|
`target element not found: ${JSON.stringify(targetElement._matcher)}`
|
|
@@ -2594,7 +2740,7 @@ function createSootSimElement(matcher) {
|
|
|
2594
2740
|
}
|
|
2595
2741
|
},
|
|
2596
2742
|
async swipe(direction, speed = "fast", percentage = 0.75, normalizedStartingPointX = 0.5, normalizedStartingPointY = 0.5) {
|
|
2597
|
-
const node = await
|
|
2743
|
+
const node = await findStableNodeByMatcher(matcher, "swipe");
|
|
2598
2744
|
if (!node)
|
|
2599
2745
|
throw new Error(`element not found for swipe: ${JSON.stringify(matcher)}`);
|
|
2600
2746
|
const page = getPage();
|
|
@@ -2647,7 +2793,7 @@ function createSootSimElement(matcher) {
|
|
|
2647
2793
|
if (typeof value !== "string") {
|
|
2648
2794
|
throw new Error(`picker value must be a string, got ${typeof value}`);
|
|
2649
2795
|
}
|
|
2650
|
-
const picker = await
|
|
2796
|
+
const picker = await findStableNodeByMatcher(matcher, "setColumnToValue");
|
|
2651
2797
|
if (!picker) {
|
|
2652
2798
|
throw new Error(
|
|
2653
2799
|
`element not found for setColumnToValue: ${JSON.stringify(matcher)}`
|
|
@@ -2703,7 +2849,7 @@ function createSootSimElement(matcher) {
|
|
|
2703
2849
|
},
|
|
2704
2850
|
async pinch(scale, speed = "fast", angle = 0) {
|
|
2705
2851
|
const page = getPage();
|
|
2706
|
-
const node = await
|
|
2852
|
+
const node = await findStableNodeByMatcher(matcher, "pinch");
|
|
2707
2853
|
if (!node)
|
|
2708
2854
|
throw new Error(`element not found for pinch: ${JSON.stringify(matcher)}`);
|
|
2709
2855
|
if (!node.nativePinchEnabled) {
|
|
@@ -2732,7 +2878,7 @@ function createSootSimElement(matcher) {
|
|
|
2732
2878
|
},
|
|
2733
2879
|
async rotate(radians, speed = "fast") {
|
|
2734
2880
|
const page = getPage();
|
|
2735
|
-
const node = await
|
|
2881
|
+
const node = await findStableNodeByMatcher(matcher, "rotate");
|
|
2736
2882
|
if (!node)
|
|
2737
2883
|
throw new Error(`element not found for rotate: ${JSON.stringify(matcher)}`);
|
|
2738
2884
|
await waitForSootsimGestureHandler(page, node.nodeId, "RotationGestureHandler");
|
|
@@ -2798,6 +2944,9 @@ var device = {
|
|
|
2798
2944
|
_currentUrl: "",
|
|
2799
2945
|
_platform: DETOX_PLATFORM,
|
|
2800
2946
|
async launchApp(opts) {
|
|
2947
|
+
if (_browserCompromised) {
|
|
2948
|
+
throw new Error(`sootsim browser is compromised: ${_browserCompromised}`);
|
|
2949
|
+
}
|
|
2801
2950
|
if (_page && opts?.newInstance === false && !opts.delete && !opts.recordVideoDir && _suspendedAppId) {
|
|
2802
2951
|
const page = _page;
|
|
2803
2952
|
const appId = _suspendedAppId;
|
|
@@ -2842,7 +2991,18 @@ var device = {
|
|
|
2842
2991
|
size: { width: 500, height: 900 }
|
|
2843
2992
|
};
|
|
2844
2993
|
}
|
|
2845
|
-
|
|
2994
|
+
const newContextIdentity = ++nextContextIdentity;
|
|
2995
|
+
try {
|
|
2996
|
+
_context = await withLifecycleTimeout(
|
|
2997
|
+
"_browser.newContext()",
|
|
2998
|
+
newContextIdentity,
|
|
2999
|
+
() => _browser.newContext(contextOpts)
|
|
3000
|
+
);
|
|
3001
|
+
} catch (error) {
|
|
3002
|
+
_browserCompromised = error instanceof Error ? error.message : String(error);
|
|
3003
|
+
reapOwnedBrowser();
|
|
3004
|
+
throw error;
|
|
3005
|
+
}
|
|
2846
3006
|
await _context.exposeBinding("__sootsimHostCapture", async (source, value) => {
|
|
2847
3007
|
const request = readBrowserCompositeCaptureRequest(value);
|
|
2848
3008
|
const session = await source.page.context().newCDPSession(source.page);
|
|
@@ -2861,8 +3021,18 @@ var device = {
|
|
|
2861
3021
|
await session.detach();
|
|
2862
3022
|
}
|
|
2863
3023
|
});
|
|
2864
|
-
|
|
2865
|
-
|
|
3024
|
+
try {
|
|
3025
|
+
_page = await withLifecycleTimeout(
|
|
3026
|
+
"_context.newPage()",
|
|
3027
|
+
newContextIdentity,
|
|
3028
|
+
() => _context.newPage()
|
|
3029
|
+
);
|
|
3030
|
+
} catch (error) {
|
|
3031
|
+
_browserCompromised = error instanceof Error ? error.message : String(error);
|
|
3032
|
+
reapOwnedBrowser();
|
|
3033
|
+
throw error;
|
|
3034
|
+
}
|
|
3035
|
+
contextIdentity = newContextIdentity;
|
|
2866
3036
|
}
|
|
2867
3037
|
const url = opts?.url || BASE_URL;
|
|
2868
3038
|
device._currentUrl = url;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/home-paths.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
|
|
2
2
|
|
|
3
3
|
// src/host/fetch-proxy-overrides.ts
|
|
4
4
|
var FETCH_PROXY_BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -904,7 +904,7 @@ var DEFAULT_SOOTSIM_SHELL_URL = "http://localhost:5173/";
|
|
|
904
904
|
|
|
905
905
|
// cli/ws-bridge.ts
|
|
906
906
|
function bridgeWsUrl(wsPort) {
|
|
907
|
-
return `ws://
|
|
907
|
+
return `ws://127.0.0.1:${wsPort}`;
|
|
908
908
|
}
|
|
909
909
|
var resolvedWorld = null;
|
|
910
910
|
function liveDevBridgeLockfiles() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
package/dist-lib/menu.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/menu.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/metro.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
package/dist-lib/profiles.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/render-mode.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
package/dist-lib/sdk.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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;
|
package/dist-lib/sdk.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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);
|
package/dist-lib/skills.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
|
@@ -19233,7 +19233,7 @@ function resolveSootsimWebSocketProxyUrl(input) {
|
|
|
19233
19233
|
}
|
|
19234
19234
|
function resolveSootsimBridgeWebSocketUrl(input) {
|
|
19235
19235
|
return resolveSootsimWebSocketProxyUrl({
|
|
19236
|
-
targetUrl: `ws://
|
|
19236
|
+
targetUrl: `ws://127.0.0.1:${input.bridgePort}`,
|
|
19237
19237
|
href: input.href
|
|
19238
19238
|
});
|
|
19239
19239
|
}
|
|
@@ -30949,7 +30949,7 @@ function resolvePlacement() {
|
|
|
30949
30949
|
return process.env[RNX_PLACEMENT_ENV] === "local-box" ? "local-box" : "host";
|
|
30950
30950
|
}
|
|
30951
30951
|
function bridgeWsUrl(wsPort) {
|
|
30952
|
-
return `ws://
|
|
30952
|
+
return `ws://127.0.0.1:${wsPort}`;
|
|
30953
30953
|
}
|
|
30954
30954
|
function liveDevBridgeLockfiles() {
|
|
30955
30955
|
return readDevBridgeLockfiles().filter((lockfile) => isDevBridgeLockfileFresh(lockfile));
|
package/dist-lib/vite.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.488 | (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 __create = Object.create;
|
package/package.json
CHANGED
|
@@ -95,7 +95,7 @@ export class AgentDaemonClient {
|
|
|
95
95
|
constructor(opts: AgentDaemonClientOptions = {}) {
|
|
96
96
|
this.port = opts.port ?? DEFAULT_SOOTSIM_BRIDGE_PORT
|
|
97
97
|
this.commandTimeoutMs = opts.commandTimeoutMs ?? 15_000
|
|
98
|
-
this.ws = new WebSocket(`ws://
|
|
98
|
+
this.ws = new WebSocket(`ws://127.0.0.1:${this.port}`)
|
|
99
99
|
this.ready = new Promise<void>((resolve, reject) => {
|
|
100
100
|
const onOpen = () => {
|
|
101
101
|
this.ws.off('error', onError)
|
package/src/bridge-constants.ts
CHANGED
|
@@ -148,8 +148,13 @@ export function resolveSootsimBridgeWebSocketUrl(input: {
|
|
|
148
148
|
bridgePort: number
|
|
149
149
|
href: string
|
|
150
150
|
}): string {
|
|
151
|
+
// 127.0.0.1, not localhost. the daemon binds ipv4 only (bridge-host bindOnce)
|
|
152
|
+
// so that localhost's ::1/127.0.0.1 coinflip cannot hide it; the health probe
|
|
153
|
+
// already talks to 127.0.0.1. targeting localhost here undoes that: when node
|
|
154
|
+
// tries ::1 first and does not fall back, vite's proxy answers 502 and the
|
|
155
|
+
// browser logs a console error that fails e2e.
|
|
151
156
|
return resolveSootsimWebSocketProxyUrl({
|
|
152
|
-
targetUrl: `ws://
|
|
157
|
+
targetUrl: `ws://127.0.0.1:${input.bridgePort}`,
|
|
153
158
|
href: input.href,
|
|
154
159
|
})
|
|
155
160
|
}
|