rnxsim 0.1.354 → 0.1.356
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/bridge-flow-runner.ts +117 -75
- package/cli/commands/inspect/settling.ts +9 -2
- package/cli/commands/maestro.ts +9 -4
- package/cli/outbound-endpoints.ts +1 -1
- package/detox/index.ts +144 -74
- package/detox/proof-frame.cjs +107 -0
- package/dist-cli/bin.js +5 -5
- package/dist-cli/chunks/{agent-FB7RPXO6.js → agent-RHSVLLYV.js} +2 -2
- package/dist-cli/chunks/{agent-wrapper-LFU3XEIG.js → agent-wrapper-76J2FUFC.js} +2 -2
- package/dist-cli/chunks/{app-fonts-FK6SETRG.js → app-fonts-NXSVJTJO.js} +2 -2
- package/dist-cli/chunks/{assert-3KQYZDDK.js → assert-2EYNC3CJ.js} +2 -2
- package/dist-cli/chunks/{auth-RFE3J27C.js → auth-BPTC5SFR.js} +2 -2
- package/dist-cli/chunks/{beta-XVY4WOYH.js → beta-SSB7KYRA.js} +2 -2
- package/dist-cli/chunks/{camera-JROBX5GM.js → camera-VVTXBQXD.js} +2 -2
- package/dist-cli/chunks/{chunk-YIUIFZAE.js → chunk-2UIMQVMQ.js} +2 -2
- package/dist-cli/chunks/{chunk-B7OMSJXC.js → chunk-3OCUYKVX.js} +2 -2
- package/dist-cli/chunks/{chunk-ZNABCRWX.js → chunk-3QQFGK3A.js} +2 -2
- package/dist-cli/chunks/{chunk-3QLERZYJ.js → chunk-3SYKCLCM.js} +2 -2
- package/dist-cli/chunks/{chunk-BNXXNT3I.js → chunk-4HQXS5PW.js} +2 -2
- package/dist-cli/chunks/{chunk-P4GCLNFM.js → chunk-4V6JMAXO.js} +2 -2
- package/dist-cli/chunks/{chunk-SLD2EMLZ.js → chunk-5AF3BFLN.js} +2 -2
- package/dist-cli/chunks/{chunk-UI2IFVHB.js → chunk-5AKKSWL5.js} +1 -1
- package/dist-cli/chunks/{chunk-THFM2CFT.js → chunk-5DAVGCVR.js} +1 -1
- package/dist-cli/chunks/{chunk-CKKDXTKV.js → chunk-6CGOQKVQ.js} +1 -1
- package/dist-cli/chunks/{chunk-766UIGCS.js → chunk-6TGSJGCM.js} +2 -2
- package/dist-cli/chunks/{chunk-ATQPDEED.js → chunk-6TWYMJCB.js} +2 -2
- package/dist-cli/chunks/{chunk-LRVL5MAX.js → chunk-7E22W7XX.js} +1 -1
- package/dist-cli/chunks/{chunk-CUK6Y2RR.js → chunk-7EAA32OO.js} +2 -2
- package/dist-cli/chunks/{chunk-UX6D4X6B.js → chunk-A2F5B2AB.js} +2 -2
- package/dist-cli/chunks/{chunk-HUU7ZUQK.js → chunk-AA7PHU2M.js} +2 -2
- package/dist-cli/chunks/{chunk-3RW6W3ZO.js → chunk-BMMSSIRU.js} +1 -1
- package/dist-cli/chunks/{chunk-JT4IMNCO.js → chunk-BNZHWG6Z.js} +2 -2
- package/dist-cli/chunks/{chunk-UAYXEP74.js → chunk-BXT37FPN.js} +1 -1
- package/dist-cli/chunks/{chunk-QQNNY43R.js → chunk-BZTXJUGV.js} +1 -1
- package/dist-cli/chunks/{chunk-ENPWD62S.js → chunk-CK4C6GPT.js} +2 -2
- package/dist-cli/chunks/{chunk-ZJUFI3R7.js → chunk-DRHNOXRS.js} +1 -1
- package/dist-cli/chunks/{chunk-MIK6LMG4.js → chunk-E5ALNGXA.js} +1 -1
- package/dist-cli/chunks/{chunk-FPH4TYZO.js → chunk-E7DVCUK4.js} +3 -3
- package/dist-cli/chunks/{chunk-7WANPS3W.js → chunk-EVQNKPUK.js} +2 -2
- package/dist-cli/chunks/{chunk-QRYTCPNO.js → chunk-FAUFZBRY.js} +2 -2
- package/dist-cli/chunks/chunk-FVKAF4VB.js +535 -0
- package/dist-cli/chunks/{chunk-JWXAXCC6.js → chunk-FWBQKEAA.js} +2 -2
- package/dist-cli/chunks/{chunk-IGV4KCCM.js → chunk-G2SHGCDI.js} +2 -2
- package/dist-cli/chunks/{chunk-PHIYWV44.js → chunk-GM6YYN6P.js} +2 -2
- package/dist-cli/chunks/{chunk-UVIWE3LD.js → chunk-I3G6YW6L.js} +1 -1
- package/dist-cli/chunks/{chunk-HIYCRDUP.js → chunk-IUQL37TT.js} +2 -2
- package/dist-cli/chunks/{chunk-LCPZNPIB.js → chunk-JU3ISZ7X.js} +1 -1
- package/dist-cli/chunks/{chunk-4OS3SXPZ.js → chunk-NPH2KUL6.js} +2 -2
- package/dist-cli/chunks/{chunk-77CBLMD4.js → chunk-NSEPQLZF.js} +1 -1
- package/dist-cli/chunks/{chunk-OKFSCCFC.js → chunk-OUMGSDHQ.js} +1 -1
- package/dist-cli/chunks/{chunk-IUL2WUWW.js → chunk-P5F5PM6C.js} +2 -2
- package/dist-cli/chunks/{chunk-GNKXRXND.js → chunk-RBQUFMLX.js} +2 -2
- package/dist-cli/chunks/{chunk-Y4GUWO3X.js → chunk-RFSFQUUJ.js} +1 -1
- package/dist-cli/chunks/{chunk-TJRKLRNP.js → chunk-RQZCBNCD.js} +1 -1
- package/dist-cli/chunks/{chunk-LC77IVGY.js → chunk-SZD4NAQR.js} +2 -2
- package/dist-cli/chunks/{chunk-37S36DFW.js → chunk-T33T5MW2.js} +1 -1
- package/dist-cli/chunks/{chunk-4AQCDL24.js → chunk-TLA6GLYI.js} +2 -2
- package/dist-cli/chunks/{chunk-JT435OOM.js → chunk-TV5LN34R.js} +2 -2
- package/dist-cli/chunks/{chunk-EWPX6LEN.js → chunk-UC6Y77PK.js} +2 -2
- package/dist-cli/chunks/{chunk-IICFLREN.js → chunk-UUD5NLLB.js} +1 -1
- package/dist-cli/chunks/{chunk-GYGDIT5V.js → chunk-VIEN2LIY.js} +2 -2
- package/dist-cli/chunks/{chunk-X2DJK3TB.js → chunk-VSADIZVW.js} +2 -2
- package/dist-cli/chunks/{chunk-JXNR2KAB.js → chunk-VZKPDDYL.js} +2 -2
- package/dist-cli/chunks/{chunk-YXBNGSG6.js → chunk-X2YH3SPS.js} +1 -1
- package/dist-cli/chunks/{chunk-LETVUC6M.js → chunk-XJ2LMLUP.js} +1 -1
- package/dist-cli/chunks/{chunk-24DA3Y2Z.js → chunk-XMNBS5CX.js} +1 -1
- package/dist-cli/chunks/{chunk-JJOHJNUQ.js → chunk-XT6FA7AH.js} +2 -2
- package/dist-cli/chunks/chunk-Z6CATT5W.js +4 -0
- package/dist-cli/chunks/{chunk-N3CE6AZP.js → chunk-ZK67ALEG.js} +2 -2
- package/dist-cli/chunks/{chunk-FEHHM2LV.js → chunk-ZME75NBJ.js} +13 -6
- package/dist-cli/chunks/{cleanup-F74ISXJC.js → cleanup-E7TDW64X.js} +2 -2
- package/dist-cli/chunks/cli-version-HUIGZMTX.js +4 -0
- package/dist-cli/chunks/{compat-C3RGYGXL.js → compat-ST5BY7FP.js} +2 -2
- package/dist-cli/chunks/{config-CDY2RB7Q.js → config-Z5NEHXI7.js} +2 -2
- package/dist-cli/chunks/{control-J35HAFZA.js → control-JMDFMAHT.js} +2 -2
- package/dist-cli/chunks/daemon-VIL2M7RE.js +4 -0
- package/dist-cli/chunks/{debug-3P2DUTUQ.js → debug-DO2G2BA7.js} +3 -3
- package/dist-cli/chunks/{desktop-XIXW34VW.js → desktop-M3UETMNI.js} +3 -3
- package/dist-cli/chunks/{detox-KAKTJ4P4.js → detox-7JI56JTD.js} +2 -2
- package/dist-cli/chunks/{device-4MAWLZMR.js → device-YTRCB26J.js} +2 -2
- package/dist-cli/chunks/{diagnose-A3R7HUYA.js → diagnose-BJT3RXDI.js} +2 -2
- package/dist-cli/chunks/{disk-cleanup-5R7JYCO6.js → disk-cleanup-HWA4ATEL.js} +2 -2
- package/dist-cli/chunks/drivers-HIKMT3EH.js +4 -0
- package/dist-cli/chunks/{film-GY5U752Q.js → film-GLGYFHGZ.js} +3 -3
- package/dist-cli/chunks/flow-AJT2OYLT.js +4 -0
- package/dist-cli/chunks/help-DSDQRHN3.js +4 -0
- package/dist-cli/chunks/{hidden-runtime-alias-HSGN3GN4.js → hidden-runtime-alias-A4TMRHFA.js} +2 -2
- package/dist-cli/chunks/{home-paths-BIUN4CBO.js → home-paths-A2ANFZHA.js} +2 -2
- package/dist-cli/chunks/inspect-NXE6LUT6.js +4 -0
- package/dist-cli/chunks/install-desktop-DMTXQF3R.js +4 -0
- package/dist-cli/chunks/{login-WKNMS4GG.js → login-OWD44PHN.js} +2 -2
- package/dist-cli/chunks/{logout-6UFUOESK.js → logout-WV5QPIUW.js} +2 -2
- package/dist-cli/chunks/{maestro-SJGV7B3W.js → maestro-AQ342FXT.js} +18 -13
- package/dist-cli/chunks/{maestro-generate-PMF2LLKX.js → maestro-generate-AUTBK5QS.js} +3 -3
- package/dist-cli/chunks/{mode-MN7LZCLB.js → mode-XICLRQZH.js} +2 -2
- package/dist-cli/chunks/{optional-demo-registry-HFA27YSP.js → optional-demo-registry-FOXLAYLM.js} +2 -2
- package/dist-cli/chunks/{perf-XGANSJT3.js → perf-PUT7IITA.js} +2 -2
- package/dist-cli/chunks/{permissions-25UWEJKW.js → permissions-AHJWQPCL.js} +2 -2
- package/dist-cli/chunks/privacy-AYLEWFZX.js +4 -0
- package/dist-cli/chunks/{record-MA3AI752.js → record-V4MZ33XI.js} +3 -3
- package/dist-cli/chunks/{report-issue-4DTESWE2.js → report-issue-QBB54OJ5.js} +2 -2
- package/dist-cli/chunks/reset-BMGMOHRM.js +4 -0
- package/dist-cli/chunks/runtime-SYUM3ASK.js +4 -0
- package/dist-cli/chunks/{screenshot-command-JUXMWXS5.js → screenshot-command-PK54GDUY.js} +3 -3
- package/dist-cli/chunks/{screenshot-layers-DJF2E4PE.js → screenshot-layers-AQZK3GRM.js} +2 -2
- package/dist-cli/chunks/{screenshots-capture-UGZU5GOW.js → screenshots-capture-KXQDIF2S.js} +2 -2
- package/dist-cli/chunks/{serve-H7GNKJSM.js → serve-YPNVL33J.js} +2 -2
- package/dist-cli/chunks/setup-FM3JVRMY.js +4 -0
- package/dist-cli/chunks/{skills-4JGPOSFE.js → skills-4CBIW5IP.js} +2 -2
- package/dist-cli/chunks/{state-DEAQFKVP.js → state-AWM3LRRR.js} +2 -2
- package/dist-cli/chunks/{storage-Y6F5NTGH.js → storage-S2LC3T7Q.js} +2 -2
- package/dist-cli/chunks/store-SZX3GXCL.js +4 -0
- package/dist-cli/chunks/telemetry-ANUHW6KD.js +4 -0
- package/dist-cli/chunks/{timeline-HOHYYDOC.js → timeline-4AWI6M2T.js} +2 -2
- package/dist-cli/chunks/{upgrade-QF5YHLSG.js → upgrade-WSYVILOP.js} +2 -2
- package/dist-cli/chunks/upload-U2KPYBGD.js +4 -0
- package/dist-cli/chunks/{version-PDCKYJLB.js → version-XE4ZTCON.js} +2 -2
- package/dist-cli/chunks/{web-C5EUAFFW.js → web-ZS6L2NLF.js} +2 -2
- package/dist-cli/chunks/welcome-UE273JKY.js +5 -0
- package/dist-cli/chunks/{what-happened-L56ZFIMJ.js → what-happened-6AHCKADV.js} +2 -2
- 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.cjs +1 -1
- package/dist-lib/cli-constants.cjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +196 -54
- 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 +10 -3
- 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 +10 -3
- package/dist-lib/sdk.mjs +10 -3
- package/dist-lib/skills.cjs +4778 -211
- package/dist-lib/vite.cjs +1 -1
- package/package.json +1 -1
- package/skills/rnx-test/SKILL.md +6 -2
- package/src/bridge-contract.ts +8 -2
- package/dist-cli/chunks/chunk-BAWV2UEF.js +0 -543
- package/dist-cli/chunks/chunk-YATZ7U4S.js +0 -4
- package/dist-cli/chunks/cli-version-VSDCFXXN.js +0 -4
- package/dist-cli/chunks/daemon-DLLEODJY.js +0 -4
- package/dist-cli/chunks/drivers-BB3BRKDS.js +0 -4
- package/dist-cli/chunks/flow-ALMSRBVL.js +0 -4
- package/dist-cli/chunks/help-3ZL7MRUX.js +0 -4
- package/dist-cli/chunks/inspect-RCU7XVAO.js +0 -4
- package/dist-cli/chunks/install-desktop-LYVKLTCU.js +0 -4
- package/dist-cli/chunks/privacy-SIDMP5EF.js +0 -4
- package/dist-cli/chunks/reset-GPHG5XN3.js +0 -4
- package/dist-cli/chunks/runtime-VOEIOODA.js +0 -4
- package/dist-cli/chunks/setup-G2WYLPM6.js +0 -4
- package/dist-cli/chunks/store-3EQT7VU6.js +0 -4
- package/dist-cli/chunks/telemetry-FANCQHKG.js +0 -4
- package/dist-cli/chunks/upload-QKJHFAX7.js +0 -4
- package/dist-cli/chunks/welcome-3CQ2GE4F.js +0 -5
|
@@ -2,8 +2,11 @@ import * as fs from 'fs'
|
|
|
2
2
|
import * as path from 'path'
|
|
3
3
|
import { devices, type DeviceModel } from 'sootsim-engine/settings'
|
|
4
4
|
import yaml from 'yaml'
|
|
5
|
+
import {
|
|
6
|
+
REQUIRED_IDENTICAL_PROOF_FRAMES,
|
|
7
|
+
framesIdentical,
|
|
8
|
+
} from '../detox/proof-frame.cjs'
|
|
5
9
|
import { composeFramedScreenshot } from '../src/screenshots/frame-compose'
|
|
6
|
-
import { resetGuestAppStateEval } from './app-state-reset'
|
|
7
10
|
import {
|
|
8
11
|
READY_NODE_STABLE_MS,
|
|
9
12
|
READY_PROBE_POLL_MS,
|
|
@@ -19,6 +22,10 @@ import { MaestroJsContext, scriptConditionIsTruthy } from './maestro-js'
|
|
|
19
22
|
import { ensureCliRecordingEntitlement } from './recording-access'
|
|
20
23
|
import type { PerformResult, PerformStep, ResetResult } from '../src/bridge-contract'
|
|
21
24
|
import type { WsBridge } from './ws-bridge'
|
|
25
|
+
import type {
|
|
26
|
+
SootSimExternalAppLifecycleOptions,
|
|
27
|
+
SootSimLaunchArguments,
|
|
28
|
+
} from '@sootsim/globals'
|
|
22
29
|
|
|
23
30
|
const DEFAULT_TIMEOUT = 10000
|
|
24
31
|
// hosted-runner previews reach first-render much slower than local dev: the
|
|
@@ -407,6 +414,8 @@ export class SootSimBridgeFlowRunner {
|
|
|
407
414
|
})
|
|
408
415
|
|
|
409
416
|
private firstLaunchDone = false
|
|
417
|
+
private appStopped = false
|
|
418
|
+
private launchArgumentsActive = false
|
|
410
419
|
private profilingEnabled = false
|
|
411
420
|
private recordingEnabled = false
|
|
412
421
|
private recordingAccessChecked = false
|
|
@@ -415,7 +424,6 @@ export class SootSimBridgeFlowRunner {
|
|
|
415
424
|
private lastRecordingDurationMs: number | null = null
|
|
416
425
|
private lastRecordingFrameStats: unknown = null
|
|
417
426
|
private flowTraceSteps: SootSimFlowTraceStep[] = []
|
|
418
|
-
private lastVisualSettledAtMs = 0
|
|
419
427
|
private simRouteHint: string | null = null
|
|
420
428
|
// set by runStep when a step throws — lets the outer flow command
|
|
421
429
|
// dump a rich per-step failure bundle (screenshot + describe.json +
|
|
@@ -1489,23 +1497,6 @@ export class SootSimBridgeFlowRunner {
|
|
|
1489
1497
|
layers?: 'full' | 'tenant' | 'shell'
|
|
1490
1498
|
},
|
|
1491
1499
|
): Promise<string> {
|
|
1492
|
-
// give transitions + last-moment data paint a brief settle window before
|
|
1493
|
-
// freezing the frame. screenshot flows care more about visual completeness
|
|
1494
|
-
// than minimum latency. waitForVisualSettle drains in-flight animations
|
|
1495
|
-
// (it returns immediately if nothing is animating), then we hold for an
|
|
1496
|
-
// additional 250ms paint window for fade-ins / image decodes / etc.
|
|
1497
|
-
// before this change, a 250ms sleep was the *only* settle window — flows
|
|
1498
|
-
// that took a screenshot at the tail of a screen transition would catch
|
|
1499
|
-
// a half-finished frame on slow machines.
|
|
1500
|
-
if (!this.hasFreshVisualSettle()) {
|
|
1501
|
-
try {
|
|
1502
|
-
await this.waitForVisualSettle()
|
|
1503
|
-
await sleep(250)
|
|
1504
|
-
} catch {
|
|
1505
|
-
// settling helper can throw on disconnected sims; fall through so a
|
|
1506
|
-
// stale sim still produces *some* screenshot.
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
1500
|
// flow `takeScreenshot: <name>` may be a plain name ("hero") or a full
|
|
1510
1501
|
// relative path with slashes ("apps/foo/screenshots/en/02-forum") —
|
|
1511
1502
|
// maestro flows commonly use the latter. mkdir the parent of the
|
|
@@ -1531,9 +1522,32 @@ export class SootSimBridgeFlowRunner {
|
|
|
1531
1522
|
simId: this.opts.simId,
|
|
1532
1523
|
}
|
|
1533
1524
|
if (layers && layers !== 'full') screenshotRequest.layers = layers
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1525
|
+
let previousBuffer: Buffer | null = null
|
|
1526
|
+
let identicalFrames = 1
|
|
1527
|
+
let rawBuffer: Buffer | null = null
|
|
1528
|
+
for (let attempt = 0; attempt < 12; attempt++) {
|
|
1529
|
+
await this.waitForVisualSettle()
|
|
1530
|
+
const dataUrl: string = await this.bridge.send(screenshotRequest)
|
|
1531
|
+
const current = Buffer.from(
|
|
1532
|
+
dataUrl.replace(/^data:image\/png;base64,/, ''),
|
|
1533
|
+
'base64',
|
|
1534
|
+
)
|
|
1535
|
+
if (framesIdentical(previousBuffer, current)) {
|
|
1536
|
+
identicalFrames++
|
|
1537
|
+
if (identicalFrames >= REQUIRED_IDENTICAL_PROOF_FRAMES) {
|
|
1538
|
+
rawBuffer = current
|
|
1539
|
+
break
|
|
1540
|
+
}
|
|
1541
|
+
} else {
|
|
1542
|
+
identicalFrames = 1
|
|
1543
|
+
}
|
|
1544
|
+
previousBuffer = current
|
|
1545
|
+
}
|
|
1546
|
+
if (!rawBuffer) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`screenshot did not reach ${REQUIRED_IDENTICAL_PROOF_FRAMES} identical idle frames`,
|
|
1549
|
+
)
|
|
1550
|
+
}
|
|
1537
1551
|
if (spec.withFrame) {
|
|
1538
1552
|
const model = await this.readCurrentDeviceModel()
|
|
1539
1553
|
if (!model) {
|
|
@@ -1563,13 +1577,7 @@ export class SootSimBridgeFlowRunner {
|
|
|
1563
1577
|
}
|
|
1564
1578
|
|
|
1565
1579
|
async captureScreenshot(outputPath: string) {
|
|
1566
|
-
|
|
1567
|
-
const dataUrl: string = await this.bridge.send({
|
|
1568
|
-
type: 'screenshot',
|
|
1569
|
-
simId: this.opts.simId,
|
|
1570
|
-
})
|
|
1571
|
-
const base64 = dataUrl.replace(/^data:image\/png;base64,/, '')
|
|
1572
|
-
fs.writeFileSync(outputPath, Buffer.from(base64, 'base64'))
|
|
1580
|
+
await this.takeScreenshot({ path: outputPath })
|
|
1573
1581
|
}
|
|
1574
1582
|
|
|
1575
1583
|
// writes a per-failure debug bundle: screenshot + structured describe
|
|
@@ -1880,7 +1888,9 @@ export class SootSimBridgeFlowRunner {
|
|
|
1880
1888
|
stablePolls: 2,
|
|
1881
1889
|
strict: true,
|
|
1882
1890
|
})
|
|
1883
|
-
if (result.settled)
|
|
1891
|
+
if (!result.settled) {
|
|
1892
|
+
throw new Error(`animation did not settle within ${maxMs}ms`)
|
|
1893
|
+
}
|
|
1884
1894
|
}
|
|
1885
1895
|
|
|
1886
1896
|
async back() {
|
|
@@ -1962,12 +1972,17 @@ export class SootSimBridgeFlowRunner {
|
|
|
1962
1972
|
await this.refreshSimId()
|
|
1963
1973
|
}
|
|
1964
1974
|
|
|
1965
|
-
private async resetGuestAppData(
|
|
1975
|
+
private async resetGuestAppData(
|
|
1976
|
+
launchOptions?: Pick<
|
|
1977
|
+
SootSimExternalAppLifecycleOptions,
|
|
1978
|
+
'initialUrl' | 'launchArguments'
|
|
1979
|
+
>,
|
|
1980
|
+
) {
|
|
1966
1981
|
const reset: ResetResult = await this.bridge.send(
|
|
1967
1982
|
{
|
|
1968
1983
|
type: 'reset',
|
|
1969
1984
|
simId: this.opts.simId,
|
|
1970
|
-
resetOptions: { strategy: 'data' },
|
|
1985
|
+
resetOptions: { strategy: 'data', ...launchOptions },
|
|
1971
1986
|
},
|
|
1972
1987
|
{ timeoutMs: 120000 },
|
|
1973
1988
|
)
|
|
@@ -1982,14 +1997,16 @@ export class SootSimBridgeFlowRunner {
|
|
|
1982
1997
|
}
|
|
1983
1998
|
}
|
|
1984
1999
|
|
|
1985
|
-
private async
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2000
|
+
private async reloadGuestAppRuntime(options: SootSimExternalAppLifecycleOptions) {
|
|
2001
|
+
await this.bridge.send(
|
|
2002
|
+
{
|
|
2003
|
+
type: 'call',
|
|
2004
|
+
simId: this.opts.simId,
|
|
2005
|
+
path: 'SootSim.bridges.hotRemount.reloadExternalApp',
|
|
2006
|
+
args: [options],
|
|
2007
|
+
},
|
|
2008
|
+
{ timeoutMs: 120000 },
|
|
1989
2009
|
)
|
|
1990
|
-
if (!reloaded) {
|
|
1991
|
-
throw new Error('guest app soft reload bridge unavailable')
|
|
1992
|
-
}
|
|
1993
2010
|
}
|
|
1994
2011
|
|
|
1995
2012
|
private async rearmCaptureAfterRuntimeReload() {
|
|
@@ -2016,12 +2033,14 @@ export class SootSimBridgeFlowRunner {
|
|
|
2016
2033
|
|
|
2017
2034
|
async launchApp(opts: any) {
|
|
2018
2035
|
await this.refreshSimId()
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2036
|
+
let hasLaunchArguments = false
|
|
2037
|
+
let launchArguments: SootSimLaunchArguments | undefined
|
|
2038
|
+
if (isObjectRecord(opts) && Object.prototype.hasOwnProperty.call(opts, 'arguments')) {
|
|
2039
|
+
if (!isObjectRecord(opts.arguments)) {
|
|
2040
|
+
throw new Error('launchApp.arguments must be a JSON object')
|
|
2041
|
+
}
|
|
2042
|
+
hasLaunchArguments = true
|
|
2043
|
+
launchArguments = opts.arguments
|
|
2025
2044
|
}
|
|
2026
2045
|
const wantsClear = !!(opts && typeof opts === 'object' && opts.clearState)
|
|
2027
2046
|
const wantsResetRuntime = readBooleanKey(opts, 'resetRuntime')
|
|
@@ -2031,7 +2050,15 @@ export class SootSimBridgeFlowRunner {
|
|
|
2031
2050
|
// Maestro's "launch the app" boundary through the shell. Otherwise the
|
|
2032
2051
|
// guest app is already booting from the initial sim open, so just wait.
|
|
2033
2052
|
this.firstLaunchDone = true
|
|
2034
|
-
if (
|
|
2053
|
+
if (hasLaunchArguments) {
|
|
2054
|
+
const launched = await this.launchShellAppFromHomeIfNeeded(opts)
|
|
2055
|
+
if (launched) await this.waitForTree(120000)
|
|
2056
|
+
await this.reloadGuestAppRuntime({
|
|
2057
|
+
launchArguments,
|
|
2058
|
+
initialUrl: null,
|
|
2059
|
+
})
|
|
2060
|
+
await this.waitForTree(120000)
|
|
2061
|
+
} else if (wantsResetRuntime) {
|
|
2035
2062
|
// SootSim benchmark flows need app auth/storage kept warm while the
|
|
2036
2063
|
// guest React runtime starts fresh inside the timed/recorded boundary.
|
|
2037
2064
|
// the reload re-runs the app entry inside the mounted app surface, so
|
|
@@ -2045,7 +2072,14 @@ export class SootSimBridgeFlowRunner {
|
|
|
2045
2072
|
// second readiness wait would only pad the measured reset.
|
|
2046
2073
|
const launched = await this.launchShellAppFromHomeIfNeeded(opts)
|
|
2047
2074
|
if (launched) await this.waitForTree(120000)
|
|
2048
|
-
await this.
|
|
2075
|
+
await this.reloadGuestAppRuntime({})
|
|
2076
|
+
await this.waitForTree(120000)
|
|
2077
|
+
} else if (this.appStopped) {
|
|
2078
|
+
await this.launchShellAppFromHomeIfNeeded(opts)
|
|
2079
|
+
await this.reloadGuestAppRuntime({
|
|
2080
|
+
launchArguments: null,
|
|
2081
|
+
initialUrl: null,
|
|
2082
|
+
})
|
|
2049
2083
|
await this.waitForTree(120000)
|
|
2050
2084
|
} else {
|
|
2051
2085
|
await this.launchShellAppFromHomeIfNeeded(opts)
|
|
@@ -2054,7 +2088,15 @@ export class SootSimBridgeFlowRunner {
|
|
|
2054
2088
|
} else {
|
|
2055
2089
|
this.firstLaunchDone = true
|
|
2056
2090
|
if (wantsClear) {
|
|
2057
|
-
await this.resetGuestAppData(
|
|
2091
|
+
await this.resetGuestAppData({
|
|
2092
|
+
launchArguments: hasLaunchArguments ? launchArguments : null,
|
|
2093
|
+
initialUrl: null,
|
|
2094
|
+
})
|
|
2095
|
+
} else if (hasLaunchArguments || this.launchArgumentsActive || this.appStopped) {
|
|
2096
|
+
await this.reloadGuestAppRuntime({
|
|
2097
|
+
launchArguments: hasLaunchArguments ? launchArguments : null,
|
|
2098
|
+
initialUrl: null,
|
|
2099
|
+
})
|
|
2058
2100
|
} else {
|
|
2059
2101
|
await this.reloadGuestApp()
|
|
2060
2102
|
}
|
|
@@ -2072,6 +2114,9 @@ export class SootSimBridgeFlowRunner {
|
|
|
2072
2114
|
}
|
|
2073
2115
|
}
|
|
2074
2116
|
|
|
2117
|
+
this.appStopped = false
|
|
2118
|
+
this.launchArgumentsActive = hasLaunchArguments
|
|
2119
|
+
|
|
2075
2120
|
if (this.profilingEnabled) {
|
|
2076
2121
|
await this.startProfile()
|
|
2077
2122
|
}
|
|
@@ -2082,31 +2127,19 @@ export class SootSimBridgeFlowRunner {
|
|
|
2082
2127
|
}
|
|
2083
2128
|
|
|
2084
2129
|
private async waitForVisualSettle() {
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
}
|
|
2095
|
-
this.markVisualSettled()
|
|
2096
|
-
} catch {
|
|
2097
|
-
await sleep(180)
|
|
2098
|
-
this.markVisualSettled()
|
|
2130
|
+
const result = await waitForSootsimIdle({
|
|
2131
|
+
bridge: this.bridge,
|
|
2132
|
+
simId: this.opts.simId,
|
|
2133
|
+
maxMs: 10_000,
|
|
2134
|
+
pollMs: 32,
|
|
2135
|
+
stablePolls: 3,
|
|
2136
|
+
strict: true,
|
|
2137
|
+
})
|
|
2138
|
+
if (!result.settled) {
|
|
2139
|
+
throw new Error(`visual state did not settle within ${result.elapsed}ms`)
|
|
2099
2140
|
}
|
|
2100
2141
|
}
|
|
2101
2142
|
|
|
2102
|
-
private markVisualSettled() {
|
|
2103
|
-
this.lastVisualSettledAtMs = Date.now()
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
private hasFreshVisualSettle() {
|
|
2107
|
-
return Date.now() - this.lastVisualSettledAtMs < 750
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
2143
|
private resolveShellLaunchAppId(opts: unknown, state: ShellState | null): string {
|
|
2111
2144
|
if (typeof opts === 'string' && opts.length > 0) return opts
|
|
2112
2145
|
if (isObjectRecord(opts)) {
|
|
@@ -2188,12 +2221,10 @@ export class SootSimBridgeFlowRunner {
|
|
|
2188
2221
|
}
|
|
2189
2222
|
|
|
2190
2223
|
// maestro stopApp: tear down the current app runtime without clearing app
|
|
2191
|
-
// data.
|
|
2192
|
-
// nearest equivalent to killing and later re-launching the tenant runtime.
|
|
2193
|
-
// State clearing belongs to clearState / launchApp({ clearState: true }).
|
|
2224
|
+
// data. the next launchApp or openLink creates its replacement worker.
|
|
2194
2225
|
async stopApp() {
|
|
2195
|
-
await this.
|
|
2196
|
-
|
|
2226
|
+
await this.reloadGuestAppRuntime({ relaunch: false })
|
|
2227
|
+
this.appStopped = true
|
|
2197
2228
|
}
|
|
2198
2229
|
|
|
2199
2230
|
// maestro clearState: wipe app-owned storage and relaunch fresh.
|
|
@@ -2265,6 +2296,17 @@ export class SootSimBridgeFlowRunner {
|
|
|
2265
2296
|
async openLink(target: string | { link: string }) {
|
|
2266
2297
|
const link = typeof target === 'string' ? target : target.link
|
|
2267
2298
|
if (!link) throw new Error('openLink: missing link')
|
|
2299
|
+
if (this.appStopped) {
|
|
2300
|
+
await this.reloadGuestAppRuntime({
|
|
2301
|
+
initialUrl: link,
|
|
2302
|
+
launchArguments: null,
|
|
2303
|
+
})
|
|
2304
|
+
await this.waitForTree(120000)
|
|
2305
|
+
this.appStopped = false
|
|
2306
|
+
this.launchArgumentsActive = false
|
|
2307
|
+
this.firstLaunchDone = true
|
|
2308
|
+
return
|
|
2309
|
+
}
|
|
2268
2310
|
const result = await this.callTest<{ ok: boolean; error?: string }>(
|
|
2269
2311
|
'openDeepLink',
|
|
2270
2312
|
link,
|
|
@@ -58,16 +58,21 @@ export async function waitForSootsimIdle({
|
|
|
58
58
|
|
|
59
59
|
const readSnapshot = async () => {
|
|
60
60
|
let animating = false
|
|
61
|
+
let layoutDirty = false
|
|
61
62
|
let pendingFetches = 0
|
|
62
63
|
let requestTotal = 0
|
|
63
64
|
let requestInFlight = 0
|
|
65
|
+
let renderStatsAvailable = false
|
|
64
66
|
try {
|
|
65
67
|
const stats = await window.__sootsimRenderHost?.queryStats?.()
|
|
66
68
|
if (stats) {
|
|
69
|
+
renderStatsAvailable = true
|
|
67
70
|
animating =
|
|
68
71
|
stats.hasActiveAnims === true ||
|
|
69
72
|
stats.hasActiveNativeAnimations === true ||
|
|
70
73
|
stats.hasPendingAnimationFrames === true
|
|
74
|
+
layoutDirty =
|
|
75
|
+
stats.layoutDirty === true && stats.renderRequested === true
|
|
71
76
|
// a freshly-pushed screen showing a skeleton is layout-stable
|
|
72
77
|
// but not actually settled — its data/images are still in
|
|
73
78
|
// flight. treat bounded image-loader fetches as not-idle so
|
|
@@ -94,7 +99,7 @@ export async function waitForSootsimIdle({
|
|
|
94
99
|
}
|
|
95
100
|
walk(root)
|
|
96
101
|
}
|
|
97
|
-
return { layout: nodes.join(';'), animating, pendingFetches, requestTotal, requestInFlight }
|
|
102
|
+
return { layout: nodes.join(';'), animating, layoutDirty, pendingFetches, renderStatsAvailable, requestTotal, requestInFlight }
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
// how long generic background network is allowed to keep us waiting AFTER
|
|
@@ -114,7 +119,9 @@ export async function waitForSootsimIdle({
|
|
|
114
119
|
let visuallyStillSince = 0
|
|
115
120
|
while (Date.now() < deadline) {
|
|
116
121
|
const snapshot = await readSnapshot()
|
|
117
|
-
const strictOk =
|
|
122
|
+
const strictOk =
|
|
123
|
+
!strict ||
|
|
124
|
+
(snapshot.renderStatsAvailable && !snapshot.animating && !snapshot.layoutDirty)
|
|
118
125
|
// visually settled: no animations, no image content loading, layout
|
|
119
126
|
// unchanged since the last poll.
|
|
120
127
|
const visuallyStill =
|
package/cli/commands/maestro.ts
CHANGED
|
@@ -73,7 +73,7 @@ function printCompatMatrix() {
|
|
|
73
73
|
rnx maestro — compatibility matrix
|
|
74
74
|
|
|
75
75
|
supported verbs:
|
|
76
|
-
launchApp, stopApp, clearState, clearKeychain (warn-only),
|
|
76
|
+
launchApp (including arguments), stopApp, clearState, clearKeychain (warn-only),
|
|
77
77
|
tapOn, tapAtCoords, longPressOn,
|
|
78
78
|
inputText, pressKey, dispatchKey, hideKeyboard, eraseText,
|
|
79
79
|
assertVisible, assertNotVisible, assertTreeContains,
|
|
@@ -92,12 +92,17 @@ workspace mode (directory runs):
|
|
|
92
92
|
config.yaml / config.yml discovery, flows: inclusion globs,
|
|
93
93
|
includeTags / excludeTags against flow frontmatter tags.
|
|
94
94
|
|
|
95
|
+
launch behavior:
|
|
96
|
+
launchApp.arguments: a JSON object available before guest app entry through
|
|
97
|
+
react-native-launch-arguments and NativeModules.SettingsManager.settings.
|
|
98
|
+
stopApp: keeps the guest stopped until launchApp or openLink starts it.
|
|
99
|
+
openLink: cold-starts a stopped guest with the initial URL; a running guest
|
|
100
|
+
receives the URL through React Native Linking.
|
|
101
|
+
|
|
95
102
|
partial:
|
|
96
103
|
clearKeychain — warn-only, rnx has no keychain surface.
|
|
97
104
|
when.platform — rnx reports iOS for this runner.
|
|
98
|
-
openLink
|
|
99
|
-
(react-navigation deep links work); no OS-level Safari fallback.
|
|
100
|
-
launchApp.arguments — ignored; rnx has no native process.
|
|
105
|
+
openLink: app routing works; no OS-level Safari fallback.
|
|
101
106
|
launchApp.resetRuntime — sootsim-only extension for recorded benchmarks:
|
|
102
107
|
keep tenant storage/auth, but soft-reload the guest app inside launchApp.
|
|
103
108
|
takeScreenshot — maestro string form works unchanged; rnx also accepts
|
|
@@ -125,7 +125,7 @@ export const DECLARED_OUTBOUND_CALLS: Record<string, OutboundCallDeclaration> =
|
|
|
125
125
|
count: 3,
|
|
126
126
|
},
|
|
127
127
|
'packages/sootsim-engine/src/components/native-ui/LottieHost.tsx :: fetch :: descriptor.value':
|
|
128
|
-
{ category: 'guest_app_network', count:
|
|
128
|
+
{ category: 'guest_app_network', count: 2 },
|
|
129
129
|
'packages/sootsim-engine/src/demo/demo-share-register.ts :: fetch :: shareUrl': {
|
|
130
130
|
category: 'guest_app_network',
|
|
131
131
|
count: 1,
|