rnxsim 0.1.392 → 0.1.393

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.
Files changed (53) hide show
  1. package/detox/attempt-events.cjs +23 -0
  2. package/detox/element-types.ts +11 -2
  3. package/detox/expectations.ts +7 -3
  4. package/detox/gestures.ts +23 -2
  5. package/detox/index.ts +42 -62
  6. package/detox/navigation-lifecycle.ts +290 -0
  7. package/dist-lib/agent-daemon-client.cjs +1 -1
  8. package/dist-lib/agent-events.cjs +1 -1
  9. package/dist-lib/agent-identity.cjs +1 -1
  10. package/dist-lib/agent-sessions.cjs +1 -1
  11. package/dist-lib/attached-projects.cjs +1 -1
  12. package/dist-lib/auth/shared-session.cjs +1 -1
  13. package/dist-lib/backend-origin.cjs +1 -1
  14. package/dist-lib/beta.cjs +1 -1
  15. package/dist-lib/beta.mjs +1 -1
  16. package/dist-lib/bridge-constants.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.cjs +1 -1
  18. package/dist-lib/bridge-contract-input.mjs +1 -1
  19. package/dist-lib/bridge-contract.cjs +1 -1
  20. package/dist-lib/bridge-contract.mjs +1 -1
  21. package/dist-lib/capture-contract.cjs +1 -1
  22. package/dist-lib/capture-contract.mjs +1 -1
  23. package/dist-lib/cli-constants.cjs +1 -1
  24. package/dist-lib/cloud-contract.cjs +1 -1
  25. package/dist-lib/cloud-contract.mjs +1 -1
  26. package/dist-lib/config.cjs +1 -1
  27. package/dist-lib/detox/index.cjs +240 -48
  28. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  29. package/dist-lib/home-paths.cjs +1 -1
  30. package/dist-lib/host/bridge-host.cjs +1 -1
  31. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  32. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  33. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  34. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  35. package/dist-lib/host/websocket-proxy.cjs +1 -1
  36. package/dist-lib/index.cjs +1 -1
  37. package/dist-lib/jump-to-source-babel.cjs +1 -1
  38. package/dist-lib/menu.cjs +1 -1
  39. package/dist-lib/menu.mjs +1 -1
  40. package/dist-lib/metro-production-bundle.cjs +1 -1
  41. package/dist-lib/metro-production-bundle.mjs +1 -1
  42. package/dist-lib/metro.cjs +1 -1
  43. package/dist-lib/profiles.cjs +1 -1
  44. package/dist-lib/public-brand.cjs +1 -1
  45. package/dist-lib/react-native-host-modules.cjs +1 -1
  46. package/dist-lib/react-native-host-modules.mjs +1 -1
  47. package/dist-lib/render-mode.cjs +1 -1
  48. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  49. package/dist-lib/sdk.cjs +1 -1
  50. package/dist-lib/sdk.mjs +1 -1
  51. package/dist-lib/skills.cjs +24 -56
  52. package/dist-lib/vite.cjs +1 -1
  53. package/package.json +1 -1
@@ -0,0 +1,23 @@
1
+ const fs = require('node:fs')
2
+ const path = require('node:path')
3
+
4
+ function attemptEventsPath() {
5
+ const enabled =
6
+ process.env.CONFORMANCE_PROOF === '1' || Boolean(process.env.CONFORMANCE_PROOF_DIR)
7
+ if (!enabled) return null
8
+ const root = path.resolve(
9
+ process.cwd(),
10
+ process.env.CONFORMANCE_PROOF_DIR || 'artifacts/conformance-proof/latest',
11
+ )
12
+ return path.join(root, 'events.jsonl')
13
+ }
14
+
15
+ function writeAttemptEvent(event) {
16
+ const output = attemptEventsPath()
17
+ if (output === null) return false
18
+ fs.mkdirSync(path.dirname(output), { recursive: true })
19
+ fs.appendFileSync(output, `${JSON.stringify(event)}\n`)
20
+ return true
21
+ }
22
+
23
+ module.exports = { attemptEventsPath, writeAttemptEvent }
@@ -21,8 +21,17 @@ export interface SootSimElement {
21
21
  typeText(text: string): Promise<void>
22
22
  replaceText(text: string): Promise<void>
23
23
  clearText(): Promise<void>
24
- scroll(pixels: number, direction: 'up' | 'down' | 'left' | 'right'): Promise<void>
25
- scrollTo(edge: 'top' | 'bottom' | 'left' | 'right'): Promise<void>
24
+ scroll(
25
+ pixels: number,
26
+ direction: 'up' | 'down' | 'left' | 'right',
27
+ startPositionX?: number,
28
+ startPositionY?: number,
29
+ ): Promise<void>
30
+ scrollTo(
31
+ edge: 'top' | 'bottom' | 'left' | 'right',
32
+ startPositionX?: number,
33
+ startPositionY?: number,
34
+ ): Promise<void>
26
35
  swipe(
27
36
  direction: 'up' | 'down' | 'left' | 'right',
28
37
  speed?: 'fast' | 'slow',
@@ -22,13 +22,15 @@ async function scrollMatcherElement(
22
22
  matcher: Matcher,
23
23
  pixels: number,
24
24
  direction: 'up' | 'down' | 'left' | 'right',
25
+ startPositionX?: number,
26
+ startPositionY?: number,
25
27
  ): Promise<number> {
26
28
  const node = await findNode(matcher)
27
29
  if (!node) {
28
30
  throw new Error(`scroll container not found: ${JSON.stringify(matcher)}`)
29
31
  }
30
32
  const before = scrollOffsetOf(node)
31
- await dragScrollNode(page, node, pixels, direction)
33
+ await dragScrollNode(page, node, pixels, direction, startPositionX, startPositionY)
32
34
  const after = scrollOffsetOf(await findNode(matcher))
33
35
  return Math.abs(after.x - before.x) + Math.abs(after.y - before.y)
34
36
  }
@@ -500,8 +502,8 @@ class SootSimWaitForScrollAction {
500
502
  async scroll(
501
503
  pixels: number,
502
504
  direction: 'up' | 'down' | 'left' | 'right',
503
- _startPositionX?: number,
504
- _startPositionY?: number,
505
+ startPositionX?: number,
506
+ startPositionY?: number,
505
507
  ): Promise<void> {
506
508
  const page = this.getPage()
507
509
  let lastError: Error | null = null
@@ -523,6 +525,8 @@ class SootSimWaitForScrollAction {
523
525
  this.matcher,
524
526
  pixels,
525
527
  direction,
528
+ startPositionX,
529
+ startPositionY,
526
530
  )
527
531
  stalledSteps = travelled < 0.5 ? stalledSteps + 1 : 0
528
532
  }
package/detox/gestures.ts CHANGED
@@ -27,6 +27,17 @@ type WorkletCallbackStats = {
27
27
  shellSent: number
28
28
  }
29
29
 
30
+ export function resolveScrollStartPosition(
31
+ value: number | undefined,
32
+ label: string,
33
+ ): number {
34
+ const normalized = value === undefined || Number.isNaN(value) ? 0.5 : value
35
+ if (!Number.isFinite(normalized) || normalized < 0 || normalized > 1) {
36
+ throw new Error(`${label} must be between 0 and 1`)
37
+ }
38
+ return normalized
39
+ }
40
+
30
41
  type ShellGestureSeamDebug = {
31
42
  attached?: Array<{
32
43
  viewTag?: unknown
@@ -307,9 +318,19 @@ export async function dragScrollNode(
307
318
  },
308
319
  pixels: number,
309
320
  direction: 'up' | 'down' | 'left' | 'right',
321
+ startPositionX?: number,
322
+ startPositionY?: number,
310
323
  ): Promise<void> {
311
- const startSootSimX = node.absolutePosition.x + node.layout.width / 2
312
- const startSootSimY = node.absolutePosition.y + node.layout.height / 2
324
+ const normalizedStartX = resolveScrollStartPosition(
325
+ startPositionX,
326
+ 'scroll startPositionX',
327
+ )
328
+ const normalizedStartY = resolveScrollStartPosition(
329
+ startPositionY,
330
+ 'scroll startPositionY',
331
+ )
332
+ const startSootSimX = node.absolutePosition.x + node.layout.width * normalizedStartX
333
+ const startSootSimY = node.absolutePosition.y + node.layout.height * normalizedStartY
313
334
  let endSootSimX = startSootSimX
314
335
  let endSootSimY = startSootSimY
315
336
 
package/detox/index.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  dragScrollNode,
14
14
  readSootsimInteractiveViewport,
15
15
  readSootsimViewport,
16
+ resolveScrollStartPosition,
16
17
  sootsimToPage,
17
18
  waitForSootsimGestureHandler,
18
19
  } from './gestures'
@@ -23,6 +24,7 @@ import {
23
24
  type MotionChangeDefinition,
24
25
  type MotionChangeMeasurement,
25
26
  } from './motion-change.cjs'
27
+ import { describeSootsimBridgeFailure, navigateSootsimPage } from './navigation-lifecycle'
26
28
  import { REQUIRED_IDENTICAL_PROOF_FRAMES, framesIdentical } from './proof-frame.cjs'
27
29
  import type { SootSimElement } from './element-types'
28
30
  import type { Browser, BrowserContext, Page } from 'playwright'
@@ -45,7 +47,10 @@ let _context: BrowserContext | null = null
45
47
  let _page: Page | null = null
46
48
  let _synchronizationEnabled = false
47
49
  let _suspendedAppId: string | null = null
48
- const pageDiagnostics = new WeakMap<Page, string[]>()
50
+ let browserIdentity = 0
51
+ let contextIdentity = 0
52
+ let nextBrowserIdentity = 0
53
+ let nextContextIdentity = 0
49
54
 
50
55
  const STATUS_BAR_OVERRIDE_EVENT = 'sootsim:statusBarOverride'
51
56
 
@@ -67,59 +72,6 @@ function getPage(): Page {
67
72
  return _page
68
73
  }
69
74
 
70
- function recordPageDiagnostic(page: Page, message: string) {
71
- const entries = pageDiagnostics.get(page)
72
- if (!entries) return
73
- entries.push(message)
74
- if (entries.length > 40) entries.shift()
75
- }
76
-
77
- function attachPageDiagnostics(page: Page) {
78
- if (pageDiagnostics.has(page)) return
79
- pageDiagnostics.set(page, [])
80
- page.on('console', (message) => {
81
- recordPageDiagnostic(page, `[console:${message.type()}] ${message.text()}`)
82
- })
83
- page.on('pageerror', (error) => {
84
- recordPageDiagnostic(page, `[pageerror] ${error.message}`)
85
- })
86
- page.on('requestfailed', (request) => {
87
- const failure = request.failure()
88
- recordPageDiagnostic(
89
- page,
90
- `[requestfailed] ${request.method()} ${request.url()} ${failure?.errorText ?? ''}`,
91
- )
92
- })
93
- }
94
-
95
- async function describeSootsimBridgeFailure(page: Page, cause: unknown) {
96
- let pageState: unknown = null
97
- try {
98
- pageState = await page.evaluate(() => ({
99
- bodyText: document.body?.innerText?.slice(0, 500) ?? '',
100
- globals: Object.keys(window)
101
- .filter((key) => key.startsWith('__sootsim') || key === 'SootSim')
102
- .sort(),
103
- readyState: document.readyState,
104
- title: document.title,
105
- url: window.location.href,
106
- }))
107
- } catch (error) {
108
- pageState = {
109
- evaluateError: error instanceof Error ? error.message : String(error),
110
- }
111
- }
112
-
113
- return new Error(
114
- [
115
- `timed out waiting for sootsim test bridge`,
116
- `cause: ${cause instanceof Error ? cause.message : String(cause)}`,
117
- `page: ${JSON.stringify(pageState)}`,
118
- `recent page diagnostics:\n${(pageDiagnostics.get(page) ?? []).join('\n') || '(none)'}`,
119
- ].join('\n'),
120
- )
121
- }
122
-
123
75
  async function waitForSootsimTree(page: Page, timeout = 30000): Promise<void> {
124
76
  try {
125
77
  await page.waitForFunction(() => !!window.__sootsimTest?.waitForTree, {
@@ -324,6 +276,7 @@ async function closeContext() {
324
276
  const context = _context
325
277
  _context = null
326
278
  _page = null
279
+ contextIdentity = 0
327
280
  _synchronizationEnabled = false
328
281
  if (context) await context.close()
329
282
  }
@@ -334,6 +287,7 @@ async function closeBrowser() {
334
287
  const reapBrowserNow = _reapBrowserNow
335
288
  _browser = null
336
289
  _reapBrowserNow = null
290
+ browserIdentity = 0
337
291
  await closeContext()
338
292
  try {
339
293
  await browser.close()
@@ -943,15 +897,24 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
943
897
  }
944
898
  },
945
899
 
946
- async scroll(pixels: number, direction: 'up' | 'down' | 'left' | 'right') {
900
+ async scroll(
901
+ pixels: number,
902
+ direction: 'up' | 'down' | 'left' | 'right',
903
+ startPositionX?: number,
904
+ startPositionY?: number,
905
+ ) {
947
906
  const node = await findNodeByMatcher(matcher)
948
907
  if (!node)
949
908
  throw new Error(`element not found for scroll: ${JSON.stringify(matcher)}`)
950
909
  const page = getPage()
951
- await dragScrollNode(page, node, pixels, direction)
910
+ await dragScrollNode(page, node, pixels, direction, startPositionX, startPositionY)
952
911
  },
953
912
 
954
- async scrollTo(edge: 'top' | 'bottom' | 'left' | 'right') {
913
+ async scrollTo(
914
+ edge: 'top' | 'bottom' | 'left' | 'right',
915
+ startPositionX?: number,
916
+ startPositionY?: number,
917
+ ) {
955
918
  const node = await findNodeByMatcher(matcher)
956
919
  if (!node)
957
920
  throw new Error(`element not found for scrollTo: ${JSON.stringify(matcher)}`)
@@ -968,8 +931,16 @@ function createSootSimElement(matcher: Matcher): SootSimElement {
968
931
  )
969
932
  }
970
933
 
971
- const hitX = node.absolutePosition.x + node.layout.width / 2
972
- const hitY = node.absolutePosition.y + node.layout.height / 2
934
+ const normalizedStartX = resolveScrollStartPosition(
935
+ startPositionX,
936
+ 'scrollTo startPositionX',
937
+ )
938
+ const normalizedStartY = resolveScrollStartPosition(
939
+ startPositionY,
940
+ 'scrollTo startPositionY',
941
+ )
942
+ const hitX = node.absolutePosition.x + node.layout.width * normalizedStartX
943
+ const hitY = node.absolutePosition.y + node.layout.height * normalizedStartY
973
944
  const page = getPage()
974
945
  const result = await page.evaluate(
975
946
  async ({ edge, hitX, hitY, targetId }) => {
@@ -1348,6 +1319,7 @@ export const device = {
1348
1319
  )
1349
1320
  _browser = launched.browser
1350
1321
  _reapBrowserNow = launched.reapNow
1322
+ browserIdentity = ++nextBrowserIdentity
1351
1323
  }
1352
1324
 
1353
1325
  if (!_page || opts?.newInstance || opts?.delete || opts?.recordVideoDir) {
@@ -1365,12 +1337,16 @@ export const device = {
1365
1337
  }
1366
1338
  _context = await _browser.newContext(contextOpts)
1367
1339
  _page = await _context.newPage()
1368
- attachPageDiagnostics(_page)
1340
+ contextIdentity = ++nextContextIdentity
1369
1341
  }
1370
1342
 
1371
1343
  const url = opts?.url || BASE_URL
1372
1344
  device._currentUrl = url
1373
- await _page!.goto(url, { waitUntil: 'load', timeout: 30000 })
1345
+ await navigateSootsimPage(_page!, url, {
1346
+ browserId: browserIdentity,
1347
+ contextId: contextIdentity,
1348
+ operation: 'launchApp',
1349
+ })
1374
1350
 
1375
1351
  await waitForSootsimTree(_page!, 30000)
1376
1352
  await waitForSootsimSurfaceMetrics(_page!, 30000)
@@ -1406,7 +1382,11 @@ export const device = {
1406
1382
 
1407
1383
  async openURL(url: { url: string; sourceApp?: string }) {
1408
1384
  const page = getPage()
1409
- await page.goto(url.url, { waitUntil: 'load', timeout: 30000 })
1385
+ await navigateSootsimPage(page, url.url, {
1386
+ browserId: browserIdentity,
1387
+ contextId: contextIdentity,
1388
+ operation: 'openURL',
1389
+ })
1410
1390
  await waitForSootsimTree(page, 10000)
1411
1391
  await waitForSootsimSurfaceMetrics(page, 10000)
1412
1392
  await waitForSootsimShellReady(page, 10000)
@@ -0,0 +1,290 @@
1
+ import { writeAttemptEvent } from './attempt-events.cjs'
2
+ import type { Page, Request, Response } from 'playwright'
3
+
4
+ export type SootsimNavigationIdentity = {
5
+ browserId: number
6
+ contextId: number
7
+ operation: 'launchApp' | 'openURL'
8
+ }
9
+
10
+ type SootsimPageState = {
11
+ bodyText: string
12
+ bridges: {
13
+ sootsim: boolean
14
+ test: boolean
15
+ }
16
+ globals: string[]
17
+ readyState: string
18
+ title: string
19
+ url: string
20
+ }
21
+
22
+ type ActiveNavigation = SootsimNavigationIdentity & {
23
+ navigationUrl: string
24
+ navigationId: number
25
+ }
26
+
27
+ type NavigationMilestone =
28
+ | 'document-request'
29
+ | 'document-request-failed'
30
+ | 'document-request-finished'
31
+ | 'document-response'
32
+ | 'domcontentloaded'
33
+ | 'error'
34
+ | 'load'
35
+ | 'start'
36
+
37
+ type NavigationDiagnostic = {
38
+ browserId: number
39
+ capturedAt: string
40
+ contextId: number
41
+ errorText?: string
42
+ failurePhase?: 'bridge' | 'navigation'
43
+ headersReceived?: boolean
44
+ method?: string
45
+ milestone: NavigationMilestone
46
+ navigationUrl: string
47
+ navigationId: number
48
+ operation: SootsimNavigationIdentity['operation']
49
+ pageState?: SootsimPageState | { evaluateError: string }
50
+ status?: number
51
+ target: string
52
+ type: 'rnx-navigation'
53
+ url: string
54
+ }
55
+
56
+ type PageDiagnostic =
57
+ | NavigationDiagnostic
58
+ | {
59
+ capturedAt: string
60
+ level: string
61
+ message: string
62
+ type: 'page-console'
63
+ }
64
+ | {
65
+ capturedAt: string
66
+ message: string
67
+ type: 'page-error'
68
+ }
69
+ | {
70
+ capturedAt: string
71
+ errorText: string
72
+ method: string
73
+ type: 'subresource-request-failed'
74
+ url: string
75
+ }
76
+
77
+ const DIAGNOSTIC_LIMIT = 40
78
+ const pageDiagnostics = new WeakMap<Page, PageDiagnostic[]>()
79
+ const activeNavigations = new WeakMap<Page, ActiveNavigation>()
80
+ const lastNavigations = new WeakMap<Page, ActiveNavigation>()
81
+ let nextNavigationIdentity = 0
82
+
83
+ function recordPageDiagnostic(page: Page, diagnostic: PageDiagnostic, persist = false) {
84
+ const entries = pageDiagnostics.get(page)
85
+ if (!entries) return
86
+ entries.push(diagnostic)
87
+ if (entries.length > DIAGNOSTIC_LIMIT) entries.shift()
88
+ if (persist) writeAttemptEvent(diagnostic)
89
+ }
90
+
91
+ function isTopLevelDocumentRequest(page: Page, request: Request) {
92
+ return (
93
+ request.isNavigationRequest() &&
94
+ request.resourceType() === 'document' &&
95
+ request.frame() === page.mainFrame()
96
+ )
97
+ }
98
+
99
+ function recordNavigationMilestoneFor(
100
+ page: Page,
101
+ active: ActiveNavigation,
102
+ milestone: NavigationMilestone,
103
+ details: Partial<
104
+ Pick<
105
+ NavigationDiagnostic,
106
+ | 'errorText'
107
+ | 'failurePhase'
108
+ | 'headersReceived'
109
+ | 'method'
110
+ | 'pageState'
111
+ | 'status'
112
+ | 'url'
113
+ >
114
+ > = {},
115
+ ) {
116
+ recordPageDiagnostic(
117
+ page,
118
+ {
119
+ ...active,
120
+ ...details,
121
+ capturedAt: new Date().toISOString(),
122
+ milestone,
123
+ target: process.env.CONFORMANCE_PROOF_TARGET || 'sootsim',
124
+ type: 'rnx-navigation',
125
+ url: details.url ?? active.navigationUrl,
126
+ },
127
+ true,
128
+ )
129
+ }
130
+
131
+ function recordNavigationMilestone(
132
+ page: Page,
133
+ milestone: NavigationMilestone,
134
+ details: Parameters<typeof recordNavigationMilestoneFor>[3] = {},
135
+ ) {
136
+ const active = activeNavigations.get(page)
137
+ if (active) recordNavigationMilestoneFor(page, active, milestone, details)
138
+ }
139
+
140
+ function attachPageDiagnostics(page: Page) {
141
+ if (pageDiagnostics.has(page)) return
142
+ pageDiagnostics.set(page, [])
143
+ page.on('console', (message) => {
144
+ recordPageDiagnostic(page, {
145
+ capturedAt: new Date().toISOString(),
146
+ level: message.type(),
147
+ message: message.text(),
148
+ type: 'page-console',
149
+ })
150
+ })
151
+ page.on('pageerror', (error) => {
152
+ recordPageDiagnostic(page, {
153
+ capturedAt: new Date().toISOString(),
154
+ message: error.message,
155
+ type: 'page-error',
156
+ })
157
+ })
158
+ page.on('request', (request) => {
159
+ if (!isTopLevelDocumentRequest(page, request)) return
160
+ recordNavigationMilestone(page, 'document-request', {
161
+ method: request.method(),
162
+ url: request.url(),
163
+ })
164
+ })
165
+ page.on('response', (response) => {
166
+ const request = response.request()
167
+ if (!isTopLevelDocumentRequest(page, request)) return
168
+ recordNavigationMilestone(page, 'document-response', {
169
+ headersReceived: true,
170
+ status: response.status(),
171
+ url: request.url(),
172
+ })
173
+ })
174
+ page.on('requestfinished', (request) => {
175
+ if (!isTopLevelDocumentRequest(page, request)) return
176
+ recordNavigationMilestone(page, 'document-request-finished', { url: request.url() })
177
+ })
178
+ page.on('requestfailed', (request) => {
179
+ const failure = request.failure()
180
+ const errorText = failure?.errorText ?? ''
181
+ if (isTopLevelDocumentRequest(page, request)) {
182
+ recordNavigationMilestone(page, 'document-request-failed', {
183
+ errorText,
184
+ url: request.url(),
185
+ })
186
+ return
187
+ }
188
+ recordPageDiagnostic(page, {
189
+ capturedAt: new Date().toISOString(),
190
+ errorText,
191
+ method: request.method(),
192
+ type: 'subresource-request-failed',
193
+ url: request.url(),
194
+ })
195
+ })
196
+ page.on('domcontentloaded', () => {
197
+ recordNavigationMilestone(page, 'domcontentloaded')
198
+ })
199
+ page.on('load', () => {
200
+ recordNavigationMilestone(page, 'load')
201
+ })
202
+ }
203
+
204
+ async function readSootsimPageState(
205
+ page: Page,
206
+ ): Promise<SootsimPageState | { evaluateError: string }> {
207
+ try {
208
+ return await page.evaluate(() => ({
209
+ bodyText: document.body?.innerText?.slice(0, 500) ?? '',
210
+ bridges: {
211
+ sootsim: Boolean(window.SootSim),
212
+ test: Boolean(window.__sootsimTest),
213
+ },
214
+ globals: Object.keys(window)
215
+ .filter((key) => key.startsWith('__sootsim') || key === 'SootSim')
216
+ .sort(),
217
+ readyState: document.readyState,
218
+ title: document.title,
219
+ url: window.location.href,
220
+ }))
221
+ } catch (error) {
222
+ return {
223
+ evaluateError: error instanceof Error ? error.message : String(error),
224
+ }
225
+ }
226
+ }
227
+
228
+ export async function describeSootsimBridgeFailure(
229
+ page: Page,
230
+ cause: unknown,
231
+ summary = 'timed out waiting for sootsim test bridge',
232
+ pageState?: SootsimPageState | { evaluateError: string },
233
+ ) {
234
+ const recordBridgeFailure = pageState === undefined
235
+ const state = pageState ?? (await readSootsimPageState(page))
236
+ const navigation = lastNavigations.get(page)
237
+ if (recordBridgeFailure && navigation) {
238
+ recordNavigationMilestoneFor(page, navigation, 'error', {
239
+ errorText: cause instanceof Error ? cause.message : String(cause),
240
+ failurePhase: 'bridge',
241
+ pageState: state,
242
+ })
243
+ }
244
+ return new Error(
245
+ [
246
+ summary,
247
+ `cause: ${cause instanceof Error ? cause.message : String(cause)}`,
248
+ `page: ${JSON.stringify(state)}`,
249
+ `recent page diagnostics:\n${JSON.stringify(getSootsimNavigationDiagnostics(page))}`,
250
+ ].join('\n'),
251
+ )
252
+ }
253
+
254
+ export async function navigateSootsimPage(
255
+ page: Page,
256
+ url: string,
257
+ identity: SootsimNavigationIdentity,
258
+ ): Promise<Response | null> {
259
+ attachPageDiagnostics(page)
260
+ const navigation = {
261
+ ...identity,
262
+ navigationUrl: url,
263
+ navigationId: ++nextNavigationIdentity,
264
+ }
265
+ activeNavigations.set(page, navigation)
266
+ lastNavigations.set(page, navigation)
267
+ recordNavigationMilestone(page, 'start')
268
+ try {
269
+ return await page.goto(url, { waitUntil: 'load', timeout: 30000 })
270
+ } catch (error) {
271
+ const pageState = await readSootsimPageState(page)
272
+ recordNavigationMilestone(page, 'error', {
273
+ errorText: error instanceof Error ? error.message : String(error),
274
+ failurePhase: 'navigation',
275
+ pageState,
276
+ })
277
+ throw await describeSootsimBridgeFailure(
278
+ page,
279
+ error,
280
+ 'sootsim navigation failed',
281
+ pageState,
282
+ )
283
+ } finally {
284
+ activeNavigations.delete(page)
285
+ }
286
+ }
287
+
288
+ export function getSootsimNavigationDiagnostics(page: Page): readonly PageDiagnostic[] {
289
+ return [...(pageDiagnostics.get(page) ?? [])]
290
+ }
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/beta.ts
4
4
  var IS_BETA = true;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (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.392 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.393 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/bridge-contract-input.ts
4
4
  var BridgeInputError = class extends Error {