rnxsim 0.1.391 → 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.
- package/detox/attempt-events.cjs +23 -0
- package/detox/element-types.ts +11 -2
- package/detox/expectations.ts +7 -3
- package/detox/gestures.ts +23 -2
- package/detox/index.ts +42 -62
- package/detox/navigation-lifecycle.ts +290 -0
- package/dist-lib/agent-daemon-client.cjs +1 -1
- package/dist-lib/agent-events.cjs +1 -1
- package/dist-lib/agent-identity.cjs +1 -1
- package/dist-lib/agent-sessions.cjs +1 -1
- package/dist-lib/attached-projects.cjs +1 -1
- package/dist-lib/auth/shared-session.cjs +1 -1
- package/dist-lib/backend-origin.cjs +1 -1
- package/dist-lib/beta.cjs +1 -1
- package/dist-lib/beta.mjs +1 -1
- package/dist-lib/bridge-constants.cjs +1 -1
- package/dist-lib/bridge-contract-input.cjs +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/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +240 -48
- 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 +1 -1
- package/dist-lib/jump-to-source-babel.cjs +1 -1
- package/dist-lib/menu.cjs +1 -1
- package/dist-lib/menu.mjs +1 -1
- package/dist-lib/metro-production-bundle.cjs +1 -1
- package/dist-lib/metro-production-bundle.mjs +1 -1
- package/dist-lib/metro.cjs +1 -1
- package/dist-lib/profiles.cjs +1 -1
- package/dist-lib/public-brand.cjs +1 -1
- package/dist-lib/react-native-host-modules.cjs +1 -1
- package/dist-lib/react-native-host-modules.mjs +1 -1
- package/dist-lib/render-mode.cjs +1 -1
- package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
- package/dist-lib/sdk.cjs +1 -1
- package/dist-lib/sdk.mjs +1 -1
- package/dist-lib/skills.cjs +24 -56
- package/dist-lib/vite.cjs +1 -1
- 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 }
|
package/detox/element-types.ts
CHANGED
|
@@ -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(
|
|
25
|
-
|
|
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',
|
package/detox/expectations.ts
CHANGED
|
@@ -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
|
-
|
|
504
|
-
|
|
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
|
|
312
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
972
|
-
|
|
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
|
-
|
|
1340
|
+
contextIdentity = ++nextContextIdentity
|
|
1369
1341
|
}
|
|
1370
1342
|
|
|
1371
1343
|
const url = opts?.url || BASE_URL
|
|
1372
1344
|
device._currentUrl = url
|
|
1373
|
-
await _page
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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;
|