rnxsim 0.1.549 → 0.1.550
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 +26 -0
- package/cli/commands/maestro.ts +453 -42
- package/cli/commands/test.ts +107 -23
- package/cli/flow-file.ts +7 -0
- package/cli/maestro-report.ts +178 -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/cloud.cjs +1 -1
- package/dist-lib/cloud.mjs +1 -1
- package/dist-lib/config.cjs +1 -1
- package/dist-lib/detox/index.cjs +1 -1
- package/dist-lib/dev-bundle-resolution.cjs +1 -1
- package/dist-lib/home-paths.cjs +1 -1
- package/dist-lib/host/bridge-host.cjs +8 -6
- 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/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 +28 -7
- package/dist-lib/swift-submit.cjs +1 -1
- package/dist-lib/swift-submit.mjs +1 -1
- package/dist-lib/swift.cjs +23 -5
- package/dist-lib/vite.cjs +24 -11
- package/package.json +1 -1
- package/src/rolldown-plugin-swift.ts +8 -5
- package/src/vite-plugin-swift.ts +8 -5
package/cli/commands/test.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from '../cloud-test-tunnel'
|
|
16
16
|
import { parseFlowFile } from '../flow-file'
|
|
17
17
|
import { buildShellUrl, resolveShellBaseUrlForBridgePort } from './control'
|
|
18
|
-
import { hoistLeadingSimFlag } from './flow'
|
|
18
|
+
import { FLOW_ARG_VALUE_FLAGS, hoistLeadingSimFlag } from './flow'
|
|
19
19
|
import { resolveDefaultUploadOrigin } from './upload'
|
|
20
20
|
|
|
21
21
|
export interface RunTestOptions {
|
|
@@ -30,6 +30,11 @@ interface TestAppTarget {
|
|
|
30
30
|
close: () => Promise<void>
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// every flag that swallows the token after it, so the positional scan finds
|
|
34
|
+
// the flow/suite target no matter where the flags sit (`rnx test
|
|
35
|
+
// --include-tags smoke .maestro/` must not take `smoke` as the target).
|
|
36
|
+
// maestro compat flags live here alongside the flow-runner vocabulary;
|
|
37
|
+
// without them a flag-first maestro command line mis-resolves its target.
|
|
33
38
|
const VALUE_FLAGS = new Set([
|
|
34
39
|
'--app',
|
|
35
40
|
'--bundle',
|
|
@@ -40,11 +45,22 @@ const VALUE_FLAGS = new Set([
|
|
|
40
45
|
'--tab',
|
|
41
46
|
'--port',
|
|
42
47
|
'--device',
|
|
48
|
+
'-d',
|
|
49
|
+
'--env',
|
|
50
|
+
'-e',
|
|
43
51
|
'--driver',
|
|
44
52
|
'--base-url',
|
|
45
53
|
'--url',
|
|
54
|
+
'--include-tags',
|
|
55
|
+
'--exclude-tags',
|
|
56
|
+
'--format',
|
|
57
|
+
'--output',
|
|
58
|
+
'--test-output-dir',
|
|
59
|
+
'--debug-output',
|
|
60
|
+
'--platform',
|
|
46
61
|
'-t',
|
|
47
62
|
'--testNamePattern',
|
|
63
|
+
'--testTimeout',
|
|
48
64
|
'--grep',
|
|
49
65
|
'--maxWorkers',
|
|
50
66
|
'--shard',
|
|
@@ -56,6 +72,7 @@ const VALUE_FLAGS = new Set([
|
|
|
56
72
|
'--timeout',
|
|
57
73
|
'--sidecar-ports',
|
|
58
74
|
'--cancel',
|
|
75
|
+
...FLOW_ARG_VALUE_FLAGS,
|
|
59
76
|
])
|
|
60
77
|
|
|
61
78
|
function valueAfter(args: string[], name: string): string | null {
|
|
@@ -67,7 +84,7 @@ function valueAfter(args: string[], name: string): string | null {
|
|
|
67
84
|
return value && !value.startsWith('-') ? value : null
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
function
|
|
87
|
+
function targetIndexFromArgs(args: string[]): number {
|
|
71
88
|
for (let index = 0; index < args.length; index++) {
|
|
72
89
|
const arg = args[index]
|
|
73
90
|
if (VALUE_FLAGS.has(arg)) {
|
|
@@ -77,20 +94,31 @@ function targetFromArgs(args: string[]): string | null {
|
|
|
77
94
|
if (arg.startsWith('--') && arg.includes('=')) {
|
|
78
95
|
continue
|
|
79
96
|
}
|
|
80
|
-
if (!arg.startsWith('-')) return
|
|
97
|
+
if (!arg.startsWith('-')) return index
|
|
81
98
|
}
|
|
82
|
-
return
|
|
99
|
+
return -1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function targetFromArgs(args: string[]): string | null {
|
|
103
|
+
const index = targetIndexFromArgs(args)
|
|
104
|
+
return index >= 0 ? args[index] : null
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const APP_FLAGS = ['--app', '--bundle', '--bundle-url']
|
|
108
|
+
|
|
109
|
+
function isAppFlag(arg: string): boolean {
|
|
110
|
+
return APP_FLAGS.some((flag) => arg === flag || arg.startsWith(`${flag}=`))
|
|
83
111
|
}
|
|
84
112
|
|
|
85
113
|
function withoutApp(args: string[]): string[] {
|
|
86
114
|
const result: string[] = []
|
|
87
115
|
for (let i = 0; i < args.length; i++) {
|
|
88
116
|
const arg = args[i]
|
|
89
|
-
if (arg
|
|
117
|
+
if (APP_FLAGS.includes(arg)) {
|
|
90
118
|
i += 1
|
|
91
119
|
continue
|
|
92
120
|
}
|
|
93
|
-
if (arg
|
|
121
|
+
if (isAppFlag(arg)) {
|
|
94
122
|
continue
|
|
95
123
|
}
|
|
96
124
|
result.push(arg)
|
|
@@ -98,6 +126,25 @@ function withoutApp(args: string[]): string[] {
|
|
|
98
126
|
return result
|
|
99
127
|
}
|
|
100
128
|
|
|
129
|
+
// the maestro runner reads the app target as --url; rnx test spells it
|
|
130
|
+
// --app/--bundle/--bundle-url. map each spelling explicitly: a prefix
|
|
131
|
+
// replace would turn --bundle-url into --url-url.
|
|
132
|
+
function mapAppFlagToUrl(arg: string): string {
|
|
133
|
+
if (APP_FLAGS.includes(arg)) return '--url'
|
|
134
|
+
for (const flag of APP_FLAGS) {
|
|
135
|
+
if (arg.startsWith(`${flag}=`)) return `--url=${arg.slice(flag.length + 1)}`
|
|
136
|
+
}
|
|
137
|
+
return arg
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function appFromArgs(args: string[]): string | null {
|
|
141
|
+
return (
|
|
142
|
+
valueAfter(args, '--app') ??
|
|
143
|
+
valueAfter(args, '--bundle') ??
|
|
144
|
+
valueAfter(args, '--bundle-url')
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
101
148
|
function closeServer(server: Server): Promise<void> {
|
|
102
149
|
return new Promise((resolveClose, rejectClose) => {
|
|
103
150
|
server.close((error) => {
|
|
@@ -161,14 +208,30 @@ function restoreRnxUrl(previous: string | undefined): void {
|
|
|
161
208
|
}
|
|
162
209
|
}
|
|
163
210
|
|
|
211
|
+
// `--config` is claimed by both interpreters: a YAML value is a maestro
|
|
212
|
+
// workspace config, anything else is a detox jest config.
|
|
213
|
+
function isMaestroConfigValue(value: string | null): boolean {
|
|
214
|
+
return value !== null && /\.ya?ml$/i.test(value)
|
|
215
|
+
}
|
|
216
|
+
|
|
164
217
|
// dispatch only: each interpreter retains its own selection and execution rules.
|
|
165
218
|
export function resolveTestCommand(args: string[], cwd = process.cwd()) {
|
|
166
219
|
const forwarded = hoistLeadingSimFlag(args)
|
|
167
|
-
const
|
|
168
|
-
const config = forwarded.some(
|
|
220
|
+
const hasConfig = forwarded.some(
|
|
169
221
|
(arg) => arg === '--config' || arg.startsWith('--config='),
|
|
170
222
|
)
|
|
171
|
-
|
|
223
|
+
const maestroConfig =
|
|
224
|
+
hasConfig && isMaestroConfigValue(valueAfter(forwarded, '--config'))
|
|
225
|
+
if (hasConfig && !maestroConfig) return { command: 'detox', args: forwarded }
|
|
226
|
+
// the target is the first positional, wherever it sits: maestro users put
|
|
227
|
+
// flags before the path (`maestro test --include-tags smoke .maestro/`),
|
|
228
|
+
// so reading only argv[0] breaks the exact pasted command line.
|
|
229
|
+
let target = targetFromArgs(forwarded) ?? undefined
|
|
230
|
+
if (!target && maestroConfig) {
|
|
231
|
+
// `rnx test --config e2e/config.yaml`: the config describes a workspace,
|
|
232
|
+
// so run the directory it lives in.
|
|
233
|
+
target = dirname(resolve(cwd, valueAfter(forwarded, '--config') as string))
|
|
234
|
+
}
|
|
172
235
|
if (!target) {
|
|
173
236
|
throw new Error(
|
|
174
237
|
'pass a Maestro flow/directory, a Jest suite, or --config <jest-config>',
|
|
@@ -190,19 +253,22 @@ export function resolveTestCommand(args: string[], cwd = process.cwd()) {
|
|
|
190
253
|
['.maestro', 'maestro'].includes(basename(absolute))
|
|
191
254
|
: /\.ya?ml$/.test(extname(absolute))
|
|
192
255
|
if (maestro) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
256
|
+
// when the target names a workspace config file, run its directory.
|
|
257
|
+
// the replacement lands on the target positional by index: with
|
|
258
|
+
// flag-first argv the target is not argv[0], and a path can equal a
|
|
259
|
+
// flag value elsewhere in the line.
|
|
260
|
+
const targetIndex = targetIndexFromArgs(forwarded)
|
|
261
|
+
const runConfigDir = !directory && /^config\.ya?ml$/.test(basename(absolute))
|
|
262
|
+
const mapped = forwarded.map((arg, index) => {
|
|
263
|
+
if (index === targetIndex && runConfigDir) return dirname(absolute)
|
|
264
|
+
return mapAppFlagToUrl(arg)
|
|
265
|
+
})
|
|
266
|
+
if (targetIndex < 0) {
|
|
267
|
+
// synthesized target (`--config e2e/config.yaml` with no positional):
|
|
268
|
+
// name the workspace dir so maestro does not auto-discover cwd.
|
|
269
|
+
mapped.push(target)
|
|
205
270
|
}
|
|
271
|
+
return { command: 'maestro', args: ['test', ...mapped] }
|
|
206
272
|
}
|
|
207
273
|
if (!directory && !/\.[cm]?[jt]sx?$/.test(extname(absolute))) {
|
|
208
274
|
throw new Error(`expected Maestro YAML or a Detox/Jest suite: ${target}`)
|
|
@@ -465,7 +531,11 @@ export async function runCloudTest(
|
|
|
465
531
|
let target = targetFromArgs(forwarded)
|
|
466
532
|
const config = valueAfter(forwarded, '--config')
|
|
467
533
|
if (!target && config) {
|
|
468
|
-
|
|
534
|
+
// a maestro workspace config uploads its directory; a jest config is
|
|
535
|
+
// collected from its own dir by the path logic below.
|
|
536
|
+
target = isMaestroConfigValue(config)
|
|
537
|
+
? dirname(resolve(process.cwd(), config))
|
|
538
|
+
: config
|
|
469
539
|
}
|
|
470
540
|
if (!target) {
|
|
471
541
|
for (const cand of ['.maestro', 'maestro', 'flows', 'e2e']) {
|
|
@@ -493,6 +563,20 @@ export async function runCloudTest(
|
|
|
493
563
|
return 1
|
|
494
564
|
}
|
|
495
565
|
|
|
566
|
+
const localOnly = [
|
|
567
|
+
'--include-tags',
|
|
568
|
+
'--exclude-tags',
|
|
569
|
+
'--format',
|
|
570
|
+
'--output',
|
|
571
|
+
'--test-output-dir',
|
|
572
|
+
'--debug-output',
|
|
573
|
+
].filter((flag) => forwarded.some((arg) => arg === flag || arg.startsWith(`${flag}=`)))
|
|
574
|
+
if (localOnly.length > 0) {
|
|
575
|
+
console.warn(
|
|
576
|
+
` warn: ${localOnly.join(', ')} apply to local runs only and are ignored by --cloud`,
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
|
|
496
580
|
const auth = resolveCliAuth()
|
|
497
581
|
if (!auth) {
|
|
498
582
|
console.error(' error: rnx test --cloud requires `rnx login` or RNX_API_KEY')
|
|
@@ -725,7 +809,7 @@ export async function runTest(
|
|
|
725
809
|
}
|
|
726
810
|
|
|
727
811
|
const selected = resolveTestCommand(args)
|
|
728
|
-
const app =
|
|
812
|
+
const app = appFromArgs(args)
|
|
729
813
|
const appTarget = app
|
|
730
814
|
? await resolveTestAppTarget(app)
|
|
731
815
|
: { target: '', close: async () => {} }
|
package/cli/flow-file.ts
CHANGED
|
@@ -16,6 +16,13 @@ export interface FlowFrontmatter {
|
|
|
16
16
|
env?: Record<string, string>
|
|
17
17
|
// maestro-compatible: workspace tag filtering (config.yml includeTags/excludeTags)
|
|
18
18
|
tags?: string[]
|
|
19
|
+
// maestro-compatible: display name for reports. defaults the junit
|
|
20
|
+
// testcase id, name, and classname when properties does not override them.
|
|
21
|
+
name?: string
|
|
22
|
+
// maestro-compatible: custom junit <property> entries. junitId and
|
|
23
|
+
// junitClassname are reserved: they set the testcase attributes and are
|
|
24
|
+
// not emitted as properties.
|
|
25
|
+
properties?: Record<string, string>
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
// parse flow file. accepts three shapes:
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// maestro-compatible run reports: junit xml, per-flow commands.json, and the
|
|
2
|
+
// session/flow maestro.log text. pure builders over playback receipts — no
|
|
3
|
+
// bridge, no filesystem — so the workspace tests cover them without a sim.
|
|
4
|
+
//
|
|
5
|
+
// the shapes mirror maestro's own outputs (test-reports-and-artifacts): one
|
|
6
|
+
// <testsuite name="Test Suite"> per invocation, flow-header `properties:`
|
|
7
|
+
// as <property> children, junitId/junitClassname reserved for the testcase
|
|
8
|
+
// attributes, commands.json carrying one entry per executed step.
|
|
9
|
+
|
|
10
|
+
import type { SootSimFlowTraceStep } from './bridge-flow-runner'
|
|
11
|
+
|
|
12
|
+
export function escapeXml(value: string): string {
|
|
13
|
+
return value
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
.replace(/"/g, '"')
|
|
18
|
+
.replace(/'/g, ''')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface JUnitCaseInput {
|
|
22
|
+
// flow path as printed in the run (relative to the invocation cwd)
|
|
23
|
+
file: string
|
|
24
|
+
name: string
|
|
25
|
+
id: string
|
|
26
|
+
classname: string
|
|
27
|
+
// custom flow-header properties with the reserved junit keys removed
|
|
28
|
+
properties: Record<string, string>
|
|
29
|
+
tags: string[]
|
|
30
|
+
timeSeconds: number
|
|
31
|
+
failure: { message: string; body: string } | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface JUnitSuiteInput {
|
|
35
|
+
device: string
|
|
36
|
+
cases: JUnitCaseInput[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function formatJUnitTime(seconds: number): string {
|
|
40
|
+
return (Number.isFinite(seconds) && seconds >= 0 ? seconds : 0).toFixed(3)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function buildJUnitXml(suite: JUnitSuiteInput): string {
|
|
44
|
+
const failures = suite.cases.filter((c) => c.failure !== null).length
|
|
45
|
+
const totalTime = suite.cases.reduce(
|
|
46
|
+
(sum, c) => sum + (Number.isFinite(c.timeSeconds) ? c.timeSeconds : 0),
|
|
47
|
+
0,
|
|
48
|
+
)
|
|
49
|
+
const lines = [
|
|
50
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
51
|
+
'<testsuites>',
|
|
52
|
+
` <testsuite name="Test Suite" device="${escapeXml(suite.device)}" tests="${suite.cases.length}" failures="${failures}" time="${formatJUnitTime(totalTime)}">`,
|
|
53
|
+
]
|
|
54
|
+
for (const c of suite.cases) {
|
|
55
|
+
lines.push(
|
|
56
|
+
` <testcase id="${escapeXml(c.id)}" name="${escapeXml(c.name)}" classname="${escapeXml(c.classname)}" file="${escapeXml(c.file)}" time="${formatJUnitTime(c.timeSeconds)}">`,
|
|
57
|
+
)
|
|
58
|
+
const properties: Array<{ name: string; value: string }> = []
|
|
59
|
+
if (c.tags.length > 0) {
|
|
60
|
+
properties.push({ name: 'tags', value: c.tags.join(',') })
|
|
61
|
+
}
|
|
62
|
+
for (const [name, value] of Object.entries(c.properties)) {
|
|
63
|
+
properties.push({ name, value })
|
|
64
|
+
}
|
|
65
|
+
if (properties.length > 0) {
|
|
66
|
+
lines.push(' <properties>')
|
|
67
|
+
for (const p of properties) {
|
|
68
|
+
lines.push(
|
|
69
|
+
` <property name="${escapeXml(p.name)}" value="${escapeXml(p.value)}"/>`,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
lines.push(' </properties>')
|
|
73
|
+
}
|
|
74
|
+
if (c.failure) {
|
|
75
|
+
lines.push(` <failure message="${escapeXml(c.failure.message)}">`)
|
|
76
|
+
lines.push(escapeXml(c.failure.body))
|
|
77
|
+
lines.push(' </failure>')
|
|
78
|
+
}
|
|
79
|
+
lines.push(' </testcase>')
|
|
80
|
+
}
|
|
81
|
+
lines.push(' </testsuite>')
|
|
82
|
+
lines.push('</testsuites>')
|
|
83
|
+
return `${lines.join('\n')}\n`
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// the first failed trace step as a junit failure. a flow that died before
|
|
87
|
+
// producing steps (parse error, sim never attached) has no failed step, so
|
|
88
|
+
// the caller passes its own fallback text instead of an empty failure.
|
|
89
|
+
export function failureFromTraceSteps(
|
|
90
|
+
steps: SootSimFlowTraceStep[],
|
|
91
|
+
fallbackMessage: string,
|
|
92
|
+
): { message: string; body: string } {
|
|
93
|
+
const failed = steps.find((step) => step.status === 'failure')
|
|
94
|
+
if (!failed) {
|
|
95
|
+
return { message: fallbackMessage, body: fallbackMessage }
|
|
96
|
+
}
|
|
97
|
+
const error = failed.error || 'flow step failed'
|
|
98
|
+
const message = error.split('\n')[0] || fallbackMessage
|
|
99
|
+
const bodyLines = [`step ${failed.stepIndex + 1} (${failed.stepName}): ${error}`]
|
|
100
|
+
if (failed.screenshotPath) {
|
|
101
|
+
bodyLines.push(`screenshot: ${failed.screenshotPath}`)
|
|
102
|
+
}
|
|
103
|
+
return { message, body: bodyLines.join('\n') }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface FlowCommandsEntry {
|
|
107
|
+
sequenceNumber: number
|
|
108
|
+
command: string
|
|
109
|
+
status: SootSimFlowTraceStep['status']
|
|
110
|
+
durationMs: number
|
|
111
|
+
error?: string
|
|
112
|
+
artifacts: string[]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function buildFlowCommandsJson(steps: SootSimFlowTraceStep[]): string {
|
|
116
|
+
const entries: FlowCommandsEntry[] = steps.map((step) => ({
|
|
117
|
+
sequenceNumber: step.stepIndex + 1,
|
|
118
|
+
command: step.targetLabel ? `${step.stepName} ${step.targetLabel}` : step.stepName,
|
|
119
|
+
status: step.status,
|
|
120
|
+
durationMs: step.durationMs,
|
|
121
|
+
...(step.error ? { error: step.error } : {}),
|
|
122
|
+
artifacts: step.screenshotPath ? [step.screenshotPath] : [],
|
|
123
|
+
}))
|
|
124
|
+
return `${JSON.stringify(entries, null, 2)}\n`
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface SessionLogFlow {
|
|
128
|
+
file: string
|
|
129
|
+
passed: boolean
|
|
130
|
+
durationMs: number
|
|
131
|
+
steps: SootSimFlowTraceStep[]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function formatLogDuration(ms: number): string {
|
|
135
|
+
return `${(Math.max(0, ms) / 1000).toFixed(1)}s`
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatLogStep(step: SootSimFlowTraceStep): string {
|
|
139
|
+
const label = step.targetLabel ? `${step.stepName} ${step.targetLabel}` : step.stepName
|
|
140
|
+
const base = ` ${step.stepIndex + 1}. ${label} - ${step.status} (${formatLogDuration(step.durationMs)})`
|
|
141
|
+
if (step.status === 'failure' && step.error) {
|
|
142
|
+
return `${base}: ${step.error.split('\n')[0]}`
|
|
143
|
+
}
|
|
144
|
+
return base
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// one flow's section, shared by the session log and the per-flow logs file
|
|
148
|
+
// so the two never disagree about what a flow did.
|
|
149
|
+
export function buildFlowLogLines(flow: SessionLogFlow): string[] {
|
|
150
|
+
const lines = [
|
|
151
|
+
`# flow: ${flow.file} - ${flow.passed ? 'pass' : 'FAIL'} (${formatLogDuration(flow.durationMs)}, ${flow.steps.length} steps)`,
|
|
152
|
+
]
|
|
153
|
+
for (const step of flow.steps) {
|
|
154
|
+
lines.push(formatLogStep(step))
|
|
155
|
+
}
|
|
156
|
+
return lines
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function buildSessionLog(input: {
|
|
160
|
+
startedAtIso: string
|
|
161
|
+
root: string
|
|
162
|
+
device: string
|
|
163
|
+
flows: SessionLogFlow[]
|
|
164
|
+
}): string {
|
|
165
|
+
const passed = input.flows.filter((flow) => flow.passed).length
|
|
166
|
+
const lines = [
|
|
167
|
+
'# rnx maestro session log',
|
|
168
|
+
`# started: ${input.startedAtIso}`,
|
|
169
|
+
`# root: ${input.root}`,
|
|
170
|
+
`# device: ${input.device}`,
|
|
171
|
+
`# result: ${passed}/${input.flows.length} passed`,
|
|
172
|
+
]
|
|
173
|
+
for (const flow of input.flows) {
|
|
174
|
+
lines.push('#')
|
|
175
|
+
lines.push(...buildFlowLogLines(flow))
|
|
176
|
+
}
|
|
177
|
+
return `${lines.join('\n')}\n`
|
|
178
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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.550 | (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;
|
|
@@ -865,10 +865,12 @@ function getDevice(model, posture) {
|
|
|
865
865
|
cameraCutout: null,
|
|
866
866
|
physicalPPI: 430,
|
|
867
867
|
posture: "open",
|
|
868
|
-
// open inner display
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
|
|
868
|
+
// open inner display in landscape carries the same trailing column as
|
|
869
|
+
// the closed outer display, so it takes the same 84pt inset: a ~50pt
|
|
870
|
+
// control at a ~23pt inset needs 73pt, and anything narrower would shift
|
|
871
|
+
// controls horizontally on unfold, defeating the continuous experience
|
|
872
|
+
// Apple states as the reason for the design.
|
|
873
|
+
safeAreaInsets: { top: 0, bottom: 0, left: 0, right: 84 }
|
|
872
874
|
};
|
|
873
875
|
}
|
|
874
876
|
function listDeviceModels() {
|
|
@@ -2956,7 +2958,7 @@ var init_stub_manifest = __esm({
|
|
|
2956
2958
|
|
|
2957
2959
|
// ../sootsim-engine/src/metro-asset-path.ts
|
|
2958
2960
|
function isMetroAssetRegistryModulePath(modulePath) {
|
|
2959
|
-
return modulePath.includes("polyfill:assets-registry") || modulePath.includes("@react-native/assets-registry/registry") || modulePath.includes("assets-registry/registry") || modulePath.includes("react-native/src/asset-registry");
|
|
2961
|
+
return modulePath.includes("polyfill:assets-registry") || modulePath.includes("@react-native/assets-registry/registry") || modulePath.includes("assets-registry/registry") || modulePath.includes("react-native/asset-registry") || modulePath.includes("react-native/src/asset-registry");
|
|
2960
2962
|
}
|
|
2961
2963
|
var init_metro_asset_path = __esm({
|
|
2962
2964
|
"../sootsim-engine/src/metro-asset-path.ts"() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! rnx v0.1.
|
|
1
|
+
/*! rnx v0.1.550 | (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.550 | (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;
|