rnxsim 0.0.0 → 0.1.312

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 (228) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -0
  3. package/detox/colors.ts +54 -0
  4. package/detox/config-loader.ts +135 -0
  5. package/detox/element-types.ts +39 -0
  6. package/detox/expectations.ts +537 -0
  7. package/detox/gestures.ts +480 -0
  8. package/detox/index.ts +2118 -0
  9. package/detox/jest-preset.cjs +51 -0
  10. package/detox/jest-setup-after-env.cjs +18 -0
  11. package/detox/matchers.ts +30 -0
  12. package/detox/motion-change.cjs +109 -0
  13. package/detox/motion-change.d.cts +35 -0
  14. package/detox/navigation.ts +43 -0
  15. package/detox/screenshots/animated-color-test-rest-norngh.png +0 -0
  16. package/detox/screenshots/color-test-after-drag-norngh.png +0 -0
  17. package/detox/screenshots/color-test-rest-norngh.png +0 -0
  18. package/detox/screenshots/theme-blue-toggle.png +0 -0
  19. package/detox/screenshots/theme-blue.png +0 -0
  20. package/detox/screenshots/theme-red-toggle.png +0 -0
  21. package/detox/screenshots/theme-red.png +0 -0
  22. package/dist-cli/bin.js +15 -0
  23. package/dist-cli/chunks/agent-NXVDDCFG.js +48 -0
  24. package/dist-cli/chunks/agent-wrapper-DXJCFKL3.js +17 -0
  25. package/dist-cli/chunks/app-fonts-W5AJZQJF.js +4 -0
  26. package/dist-cli/chunks/assert-3LOJEUDT.js +49 -0
  27. package/dist-cli/chunks/auth-MFTYYPNX.js +12 -0
  28. package/dist-cli/chunks/beta-GT3NN67B.js +4 -0
  29. package/dist-cli/chunks/camera-6EAK7CUL.js +22 -0
  30. package/dist-cli/chunks/chunk-274UD5FD.js +4 -0
  31. package/dist-cli/chunks/chunk-2SA2VTGP.js +5 -0
  32. package/dist-cli/chunks/chunk-33FKQH54.js +5 -0
  33. package/dist-cli/chunks/chunk-37I5AIS7.js +3 -0
  34. package/dist-cli/chunks/chunk-3D57IBPC.js +4 -0
  35. package/dist-cli/chunks/chunk-4BXCZHAU.js +4 -0
  36. package/dist-cli/chunks/chunk-5OX7ANRA.js +5 -0
  37. package/dist-cli/chunks/chunk-66NF2KNG.js +9 -0
  38. package/dist-cli/chunks/chunk-6UKJB2VC.js +125 -0
  39. package/dist-cli/chunks/chunk-7INBNA3U.js +26 -0
  40. package/dist-cli/chunks/chunk-7VWOSTD5.js +5 -0
  41. package/dist-cli/chunks/chunk-AY47WKS5.js +23 -0
  42. package/dist-cli/chunks/chunk-B7KMNHX7.js +4 -0
  43. package/dist-cli/chunks/chunk-BKNOLP3J.js +4 -0
  44. package/dist-cli/chunks/chunk-BRIZOR4K.js +28 -0
  45. package/dist-cli/chunks/chunk-DM6KUAOZ.js +4 -0
  46. package/dist-cli/chunks/chunk-EUAVBTKU.js +4 -0
  47. package/dist-cli/chunks/chunk-FTC4M26Q.js +4 -0
  48. package/dist-cli/chunks/chunk-FWYYCUAZ.js +4 -0
  49. package/dist-cli/chunks/chunk-G74YIEVS.js +7 -0
  50. package/dist-cli/chunks/chunk-GZDHEONW.js +507 -0
  51. package/dist-cli/chunks/chunk-HG7ORJMT.js +5 -0
  52. package/dist-cli/chunks/chunk-HMDOLCKG.js +15 -0
  53. package/dist-cli/chunks/chunk-JG6S4RSH.js +4 -0
  54. package/dist-cli/chunks/chunk-KR5BGMBZ.js +4 -0
  55. package/dist-cli/chunks/chunk-KZW5WBGU.js +983 -0
  56. package/dist-cli/chunks/chunk-L24Z3CRS.js +30 -0
  57. package/dist-cli/chunks/chunk-LDQ7HVCW.js +4 -0
  58. package/dist-cli/chunks/chunk-MENV7VLD.js +4 -0
  59. package/dist-cli/chunks/chunk-MNGQ42QE.js +568 -0
  60. package/dist-cli/chunks/chunk-N6A3SXMR.js +4 -0
  61. package/dist-cli/chunks/chunk-NLEMQ4PY.js +6 -0
  62. package/dist-cli/chunks/chunk-NPVGMH2N.js +101 -0
  63. package/dist-cli/chunks/chunk-OR3XTXEB.js +107 -0
  64. package/dist-cli/chunks/chunk-PEPUGJVI.js +4 -0
  65. package/dist-cli/chunks/chunk-Q3RGLERJ.js +142 -0
  66. package/dist-cli/chunks/chunk-SI3BUTQR.js +4 -0
  67. package/dist-cli/chunks/chunk-T4FT6CM7.js +4 -0
  68. package/dist-cli/chunks/chunk-TBMVBI26.js +7 -0
  69. package/dist-cli/chunks/chunk-TCWQJILS.js +5 -0
  70. package/dist-cli/chunks/chunk-TGQNQ6XT.js +766 -0
  71. package/dist-cli/chunks/chunk-TYE3WBSR.js +5 -0
  72. package/dist-cli/chunks/chunk-U4PN6JOT.js +6 -0
  73. package/dist-cli/chunks/chunk-VUFRB347.js +4 -0
  74. package/dist-cli/chunks/chunk-VZBLFMYC.js +10 -0
  75. package/dist-cli/chunks/chunk-WBT32R6N.js +25 -0
  76. package/dist-cli/chunks/chunk-XRGEEPJ5.js +38 -0
  77. package/dist-cli/chunks/chunk-Y2FSZSO7.js +71 -0
  78. package/dist-cli/chunks/chunk-YRV7HR3P.js +74 -0
  79. package/dist-cli/chunks/chunk-YZRFF4YA.js +14 -0
  80. package/dist-cli/chunks/chunk-ZMZTHU7V.js +4 -0
  81. package/dist-cli/chunks/cleanup-H3GWQB63.js +25 -0
  82. package/dist-cli/chunks/cli-version-3QPAJ3LC.js +4 -0
  83. package/dist-cli/chunks/compat-5WG2XJV7.js +20 -0
  84. package/dist-cli/chunks/config-ANMBVVLL.js +13 -0
  85. package/dist-cli/chunks/control-NSGNQFJG.js +4 -0
  86. package/dist-cli/chunks/daemon-JHF4BJ3U.js +4 -0
  87. package/dist-cli/chunks/debug-CHV2O2KF.js +200 -0
  88. package/dist-cli/chunks/desktop-FIPIK4CH.js +31 -0
  89. package/dist-cli/chunks/detox-CVUJP7K6.js +52 -0
  90. package/dist-cli/chunks/device-6WCUM4W6.js +18 -0
  91. package/dist-cli/chunks/diagnose-I6EMWEII.js +43 -0
  92. package/dist-cli/chunks/disk-cleanup-P27CYFTN.js +4 -0
  93. package/dist-cli/chunks/drivers-HQ6AKZT6.js +4 -0
  94. package/dist-cli/chunks/film-UOQFFYNF.js +74 -0
  95. package/dist-cli/chunks/flow-N27MRRNL.js +4 -0
  96. package/dist-cli/chunks/help-SF6FEGEE.js +4 -0
  97. package/dist-cli/chunks/hidden-runtime-alias-NJYFSJKZ.js +4 -0
  98. package/dist-cli/chunks/home-paths-XOV44JEF.js +4 -0
  99. package/dist-cli/chunks/inspect-CWFRFQTY.js +4 -0
  100. package/dist-cli/chunks/install-desktop-KLQDNXZQ.js +4 -0
  101. package/dist-cli/chunks/login-BPFQENCX.js +28 -0
  102. package/dist-cli/chunks/logout-5A45ZGY7.js +4 -0
  103. package/dist-cli/chunks/maestro-E4CSDMLM.js +104 -0
  104. package/dist-cli/chunks/maestro-generate-CKMOJQX2.js +36 -0
  105. package/dist-cli/chunks/mode-SZYNMIQF.js +26 -0
  106. package/dist-cli/chunks/optional-demo-registry-FRH4SFVG.js +4 -0
  107. package/dist-cli/chunks/perf-VXU2XJAX.js +48 -0
  108. package/dist-cli/chunks/permissions-IRIAOVND.js +13 -0
  109. package/dist-cli/chunks/record-RY7FPLLH.js +72 -0
  110. package/dist-cli/chunks/report-issue-KNWXOE53.js +29 -0
  111. package/dist-cli/chunks/runtime-AR3GJTGK.js +4 -0
  112. package/dist-cli/chunks/screenshot-command-QLALR6GY.js +159 -0
  113. package/dist-cli/chunks/screenshot-layers-OQFMFVRL.js +20 -0
  114. package/dist-cli/chunks/screenshots-capture-6TSRJFGB.js +42 -0
  115. package/dist-cli/chunks/serve-F54J2NQO.js +44 -0
  116. package/dist-cli/chunks/setup-7V6UKX4U.js +32 -0
  117. package/dist-cli/chunks/skills-QQBOSETH.js +50 -0
  118. package/dist-cli/chunks/storage-TAPMKK5O.js +37 -0
  119. package/dist-cli/chunks/store-7NX3S43H.js +4 -0
  120. package/dist-cli/chunks/telemetry-E7BUK6RA.js +4 -0
  121. package/dist-cli/chunks/timeline-IAM564NA.js +24 -0
  122. package/dist-cli/chunks/upgrade-CD55QXIX.js +7 -0
  123. package/dist-cli/chunks/upload-GZ6LIMYM.js +4 -0
  124. package/dist-cli/chunks/version-3DYJ5AS3.js +6 -0
  125. package/dist-cli/chunks/web-6NW6YLPI.js +4 -0
  126. package/dist-cli/chunks/what-happened-CZZDOOVG.js +25 -0
  127. package/dist-lib/agent-daemon-client.cjs +468 -0
  128. package/dist-lib/agent-events.cjs +48 -0
  129. package/dist-lib/agent-identity.cjs +53 -0
  130. package/dist-lib/agent-sessions.cjs +725 -0
  131. package/dist-lib/attached-projects.cjs +526 -0
  132. package/dist-lib/auth/shared-session.cjs +195 -0
  133. package/dist-lib/backend-origin.cjs +70 -0
  134. package/dist-lib/beta.cjs +41 -0
  135. package/dist-lib/beta.mjs +13 -0
  136. package/dist-lib/bridge-constants.cjs +161 -0
  137. package/dist-lib/cli-constants.cjs +32 -0
  138. package/dist-lib/config.cjs +96 -0
  139. package/dist-lib/detox/index.cjs +2863 -0
  140. package/dist-lib/detox/jest-preset.cjs +51 -0
  141. package/dist-lib/dev-bundle-resolution.cjs +223 -0
  142. package/dist-lib/home-paths.cjs +421 -0
  143. package/dist-lib/host/bridge-host.cjs +5600 -0
  144. package/dist-lib/host/fetch-proxy-handler.cjs +373 -0
  145. package/dist-lib/host/fetch-proxy-overrides.cjs +63 -0
  146. package/dist-lib/host/fetch-proxy-overrides.mjs +37 -0
  147. package/dist-lib/host/websocket-proxy.cjs +214 -0
  148. package/dist-lib/index.cjs +513 -0
  149. package/dist-lib/jump-to-source-babel.cjs +93 -0
  150. package/dist-lib/menu.cjs +620 -0
  151. package/dist-lib/menu.mjs +581 -0
  152. package/dist-lib/metro.cjs +297 -0
  153. package/dist-lib/profiles.cjs +246 -0
  154. package/dist-lib/public-brand.cjs +39 -0
  155. package/dist-lib/render-mode.cjs +54 -0
  156. package/dist-lib/scripts/dev-server-scanner.cjs +1034 -0
  157. package/dist-lib/sdk.cjs +2333 -0
  158. package/dist-lib/sdk.mjs +2240 -0
  159. package/dist-lib/skills.cjs +23911 -0
  160. package/dist-lib/vite.cjs +358 -0
  161. package/package.json +189 -18
  162. package/scripts/dev-server-scanner.ts +941 -0
  163. package/scripts/optional-demo-registry.ts +113 -0
  164. package/scripts/postinstall.cjs +3 -0
  165. package/scripts/preuninstall.cjs +118 -0
  166. package/skills/contrast/SKILL.md +171 -0
  167. package/skills/rnx-debug/SKILL.md +425 -0
  168. package/skills/rnx-perf/SKILL.md +295 -0
  169. package/skills/rnx-setup/SKILL.md +160 -0
  170. package/skills/rnx-test/SKILL.md +292 -0
  171. package/skills/rnx-visual/SKILL.md +122 -0
  172. package/src/agent-daemon-client.ts +410 -0
  173. package/src/agent-events.ts +71 -0
  174. package/src/agent-identity.ts +23 -0
  175. package/src/agent-prompt.ts +71 -0
  176. package/src/agent-sessions.ts +573 -0
  177. package/src/attached-projects.ts +539 -0
  178. package/src/auth/shared-session.ts +204 -0
  179. package/src/backend-origin.ts +49 -0
  180. package/src/beta.ts +19 -0
  181. package/src/bridge-constants.ts +171 -0
  182. package/src/browser-cache.ts +16 -0
  183. package/src/cli-constants.ts +1 -0
  184. package/src/cli-version.ts +32 -0
  185. package/src/codex-client.ts +215 -0
  186. package/src/config.ts +134 -0
  187. package/src/dev-bundle-resolution.ts +236 -0
  188. package/src/disk-cleanup.ts +466 -0
  189. package/src/home-paths.ts +552 -0
  190. package/src/host/agent-host.ts +666 -0
  191. package/src/host/bridge-host.ts +2626 -0
  192. package/src/host/fetch-proxy-handler.ts +397 -0
  193. package/src/host/fetch-proxy-overrides.ts +51 -0
  194. package/src/host/open-url.ts +89 -0
  195. package/src/host/websocket-proxy.ts +210 -0
  196. package/src/index.ts +12 -705
  197. package/src/jump-to-source-babel.ts +135 -0
  198. package/src/menu.ts +734 -0
  199. package/src/metro-plugin.ts +139 -0
  200. package/src/native-dev-bundle-url.ts +96 -0
  201. package/src/native-seam-manifest.ts +341 -0
  202. package/src/plist.ts +27 -0
  203. package/src/poll-until.ts +92 -0
  204. package/src/profiles.ts +179 -0
  205. package/src/public-brand.ts +9 -0
  206. package/src/render-mode.ts +27 -0
  207. package/src/runtime-assets.ts +228 -0
  208. package/src/runtime-delivery.ts +54 -0
  209. package/src/screenshots/compose.ts +422 -0
  210. package/src/screenshots/frame-compose.ts +454 -0
  211. package/src/screenshots/orchestrate.ts +256 -0
  212. package/src/screenshots/org-deck.ts +91 -0
  213. package/src/screenshots/registry.ts +58 -0
  214. package/src/screenshots/schema.ts +364 -0
  215. package/src/sdk.ts +7 -0
  216. package/src/settings-bundle.ts +418 -0
  217. package/src/skills/builtin/a11y-review.ts +132 -0
  218. package/src/skills/builtin/compat-check.ts +71 -0
  219. package/src/skills/builtin/maestro-test.ts +121 -0
  220. package/src/skills/builtin/perf-profile.ts +90 -0
  221. package/src/skills/builtin/screenshot-all.ts +52 -0
  222. package/src/skills/builtin/visual-diff.ts +100 -0
  223. package/src/skills/registry.ts +107 -0
  224. package/src/skills/types.ts +41 -0
  225. package/src/team-machine-prompt.ts +73 -0
  226. package/src/vite-plugin-one.ts +190 -0
  227. package/src/vite-plugin.ts +1750 -0
  228. package/src/worklets-babel.ts +134 -0
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: rnx-test
3
+ description: Run automated tests against your React Native app under rnx with Maestro YAML or Detox/Jest
4
+ ---
5
+
6
+ # rnx tests
7
+
8
+ rnx has two test runners. Maestro owns every YAML test, whether it was
9
+ hand-authored, generated from a goal, or recorded from live CLI actions.
10
+ Detox runs existing imperative Jest suites through the rnx driver.
11
+
12
+ > needs a connected, pinned sim. if `rnxsim describe` errors, load
13
+ > `/rnx-setup` first.
14
+
15
+ ## route in (decision tree)
16
+
17
+ - have an existing `.maestro/` directory? → [Maestro](#branch--maestro)
18
+ - have existing Detox specs? → [detox drop-in](#branch--detox-drop-in)
19
+ - writing tests fresh, want plain-language generation, or want live authoring? → [Maestro](#branch--maestro)
20
+
21
+ Do not invent a third runner for how a Maestro flow was authored.
22
+
23
+ ## anti-patterns (apply to both runners)
24
+
25
+ - **`sleep` is almost always a bug.** the underlying CLI auto-settles
26
+ before reads and polls layout hash after writes. if a flow fails
27
+ without a `sleep`, the fix is `waitFor`, `waitForAnimationToEnd`, or
28
+ `wait idle` — not a blind timer. the one legitimate case: a
29
+ user-controlled debounce ("press and hold for 2s").
30
+ - **prefer testID (`tapOn: { id: ... }`, `by.id(testID)`) over text.** text breaks
31
+ under i18n, copy churn, and duplicate strings. add `testID` during
32
+ implementation and author flows against it.
33
+ - **pin a sim before running a suite.** parallel CI jobs colliding
34
+ on the default bridge port is the #1 flake source. pass `--port` in
35
+ CI and `rnxsim use <id>` locally.
36
+ - **every flow ends with an assert, not a screenshot.** a screenshot
37
+ passes as long as the flow didn't throw. `assertVisible` (or
38
+ `expect(...).toBeVisible()`) actually verifies the happy path.
39
+ - **don't run a 40-step "smoke everything" flow.** when it fails, you
40
+ can't tell what regressed. split into `login.yaml`, `compose.yaml`,
41
+ `settings.yaml`, chain with `runFlow`.
42
+
43
+ ## you're done when
44
+
45
+ - the suite passes locally and in CI
46
+ - every flake is either fixed or marked `xfail` with a written reason
47
+ and a removal date
48
+ - the green run took noticeably less wall-clock than the previous
49
+ baseline (a suite that grows slower without growing wider is a smell)
50
+
51
+ ## branch — Maestro
52
+
53
+ Write declarative Maestro YAML. The same command runs existing suites,
54
+ generates tests, records live drafts, profiles, and uploads replayable results.
55
+
56
+ ```sh
57
+ rnxsim maestro test .maestro/login.yaml # run one flow
58
+ rnxsim maestro test .maestro/login.yaml --record # record video
59
+ rnxsim maestro test .maestro/login.yaml --profile # include perf stats
60
+ rnxsim maestro test .maestro/ # run the suite
61
+ ```
62
+
63
+ Author from a live interaction:
64
+
65
+ ```sh
66
+ rnxsim maestro start # begin a draft
67
+ rnxsim do tap-id loginButton # drive the app via the CLI
68
+ rnxsim maestro keep # save the step
69
+ rnxsim do type "user@example.com"
70
+ rnxsim maestro keep
71
+ rnxsim do tap-id submit
72
+ rnxsim maestro keep
73
+ rnxsim maestro end --output .maestro/login.yaml --validate
74
+ ```
75
+
76
+ the recorder emits minimal YAML — no coordinate dumps, no `sleep` noise —
77
+ because the underlying CLI already settled between every action.
78
+
79
+ exemplar flow:
80
+
81
+ ```yaml
82
+ # .maestro/login.yaml
83
+ - launchApp: {}
84
+
85
+ - waitFor:
86
+ text: 'Sign in'
87
+ timeout: 10000
88
+
89
+ - tapOn:
90
+ id: emailInput
91
+ - inputText: '${USERNAME}'
92
+
93
+ - tapOn:
94
+ id: passwordInput
95
+ - inputText: '${PASSWORD}'
96
+
97
+ - tapOn:
98
+ id: signInButton
99
+ - waitForAnimationToEnd: true
100
+
101
+ - assertVisible: 'Welcome back'
102
+ ```
103
+
104
+ run with env interpolation:
105
+
106
+ ```sh
107
+ rnxsim maestro --env USERNAME=alice --env PASSWORD=hunter2 \
108
+ test .maestro/login.yaml \
109
+ --record --profile
110
+ ```
111
+
112
+ automatic settling inside flows: every tap, type, scroll, swipe, and
113
+ drag is backed by the same primitives that auto-wait for transitions on
114
+ the way in and layout stability on the way out. you rarely need
115
+ explicit waits; when you do, `waitForAnimationToEnd: true` maps to the
116
+ CLI's `wait idle` with a generous budget. use it before any
117
+ `assertVisible` that depends on a transition longer than ~400 ms.
118
+
119
+ Generate and immediately run a Maestro flow from a plain-language goal:
120
+
121
+ ```sh
122
+ rnxsim maestro generate "log in with the demo account and verify the dashboard"
123
+ ```
124
+
125
+ Generation writes the same deterministic Maestro artifact, uploads its replay,
126
+ and registers it as a Maestro run. The dashboard does not distinguish how it
127
+ was authored.
128
+
129
+ ### existing Maestro suites
130
+
131
+ if you already have a `.maestro/` directory, the migration is one
132
+ command change:
133
+
134
+ ```sh
135
+ # before
136
+ maestro test .maestro/login.yaml
137
+
138
+ # after
139
+ rnxsim maestro test .maestro/login.yaml
140
+ ```
141
+
142
+ no test rewrites, no detox config, no simulator. the runtime auto-launches
143
+ an rnx shell if one isn't running.
144
+
145
+ ```sh
146
+ rnxsim maestro # auto-discover .maestro/ or maestro/
147
+ rnxsim maestro test .maestro/login.yaml
148
+ rnxsim maestro test .maestro/ # every flow in a directory
149
+ rnxsim maestro init # scaffold a starter flow
150
+ rnxsim maestro --list-compat # supported / partial / unsupported
151
+ rnxsim maestro --env USERNAME=alice test .maestro/login.yaml
152
+ ```
153
+
154
+ ### supported verbs
155
+
156
+ `launchApp`, `stopApp`, `clearState`, `tapOn`, `tapAtCoords`, `longPressOn`, `inputText`,
157
+ `pressKey`, `dispatchKey`, `hideKeyboard`, `eraseText`, `assertVisible`,
158
+ `assertNotVisible`, `assertTreeContains`, `waitFor`, `extendedWaitUntil`,
159
+ `waitForAnimationToEnd`, `scroll`, `scrollUntilVisible`, `scrollTo`,
160
+ `swipe`, `pinch`, `takeScreenshot`, `dumpTree`, `back`, `repeat.times`,
161
+ `runFlow`, `when` (`visible` / `notVisible` / `platform` / `true`),
162
+ `optional: true`, `onFlowStart` / `onFlowComplete`, `copyTextFrom` +
163
+ `${maestroCopiedText}`, `evalScript`, `openLink`, env var `${NAME}`
164
+ interpolation.
165
+
166
+ ### partial
167
+
168
+ - `clearKeychain` — no keychain in rnx; logs a warning and continues.
169
+ if a test depends on keychain state being reset, add an explicit
170
+ in-app "sign out" step instead.
171
+ - `when.platform` — rnx emulates iOS; android branches are skipped.
172
+ - `openLink` — navigates `window.location`; no OS-level app routing.
173
+
174
+ ### not yet implemented
175
+
176
+ these throw `unsupported maestro verb: X`:
177
+
178
+ - `travel`, `setLocation`, `setAirplaneMode`, `killApp`
179
+ - `addMedia`
180
+ - `repeat.while` (use `repeat.times`)
181
+
182
+ ### troubleshooting
183
+
184
+ - **"no maestro flows found"** — pass an explicit path
185
+ (`rnxsim maestro test path/to/flow.yaml`) or run `rnxsim maestro
186
+ init` to scaffold one.
187
+ - **"missing environment variable for flow placeholder: FOO"** — pass
188
+ `--env FOO=bar` or export it. pick one mechanism per repo and document
189
+ it in the flow directory's README.
190
+ - **flow can't find an element** — rnx's matcher uses testID + visible
191
+ text. UIKit-only labels that real maestro scrapes from the host OS won't
192
+ resolve here; switch to testID.
193
+ - **flow runs but nothing renders** — confirm the bundler your flow
194
+ targets is reachable and pass `--url <port>` or add `app: <port>`
195
+ frontmatter at the top of the YAML.
196
+
197
+ ## branch — detox drop-in
198
+
199
+ an existing detox suite runs against rnx with **one line** of jest
200
+ config and zero test changes.
201
+
202
+ `import { by, device, element, expect, waitFor } from 'detox'` is
203
+ rewritten by jest to an rnx-backed driver that drives the real shell
204
+ over playwright. matchers, assertions, and the `waitFor` API match the
205
+ upstream detox surface.
206
+
207
+ ### one-line install
208
+
209
+ ```js
210
+ // sootsim-detox.config.cjs
211
+ module.exports = {
212
+ preset: 'rnxsim/detox/jest-preset',
213
+ rootDir: __dirname,
214
+ testMatch: ['<rootDir>/e2e/**/*.test.ts'],
215
+ }
216
+ ```
217
+
218
+ or, if you already have a jest config, pull in just the module mapper:
219
+
220
+ ```js
221
+ moduleNameMapper: {
222
+ '^detox$': require.resolve('rnxsim/detox'),
223
+ }
224
+ ```
225
+
226
+ keep the **preset**, not just the mapper — the preset wires up the
227
+ global setup/teardown that tears the shell down between suites. mapper-only
228
+ works until your first hung shell.
229
+
230
+ ### commands
231
+
232
+ ```sh
233
+ rnxsim detox # run e2e/, test/e2e/, or detox/
234
+ rnxsim detox --config my.cjs # use a specific jest config
235
+ rnxsim detox --watch # jest watch mode
236
+ rnxsim detox -t "login" # --testNamePattern
237
+ rnxsim detox --headed # keep the shell window visible
238
+ rnxsim detox --no-launch # don't auto-launch a shell
239
+ rnxsim detox --port 5173 # explicit bridge port for CI
240
+ rnxsim detox init # scaffold config + e2e/example.test.ts
241
+ ```
242
+
243
+ ### compat surface
244
+
245
+ | detox API | rnx |
246
+ | ---------------------------------- | ------- |
247
+ | `element(by.id(...))` | supported |
248
+ | `element(by.text(...))` | supported — but prefer `by.id` |
249
+ | `element(by.label(...))` | supported |
250
+ | `element(by.traits([...]))` | supported for the iOS trait set |
251
+ | `.toBeVisible()` / `.not.toBeVisible()` | supported |
252
+ | `.toHaveText(...)` / `.toHaveLabel(...)` | supported |
253
+ | `.tap()` / `.longPress()` | supported |
254
+ | `.typeText(...)` / `.replaceText(...)` | supported |
255
+ | `.scroll(...)` / `.scrollTo(...)` | supported |
256
+ | `.swipe(...)` | supported |
257
+ | `.setColumnToValue(...)` | supported for uniquely named picker rows |
258
+ | `waitFor(...).toBeVisible().withTimeout(...)` | supported |
259
+ | `device.launchApp(...)` / `device.reloadReactNative()` | supported |
260
+ | `device.shake()` | partial — emits the JS event, no haptic |
261
+ | `device.sendUserNotification(...)` | not yet |
262
+
263
+ ### troubleshooting
264
+
265
+ - **"no rnx shell reachable"** — start one yourself
266
+ (`rnxsim open <port>`) or pass `--port <n>`.
267
+ - **"jest cannot find module 'detox'"** — the preset isn't wired. confirm
268
+ `preset: 'rnxsim/detox/jest-preset'` is in the jest config jest
269
+ actually loaded (`--config` overrides package.json).
270
+ - **matchers fail on elements that exist** — canvas has no DOM. use
271
+ `by.id` (testID); `rnxsim describe` shows what the shell sees.
272
+ - **passes locally, flakes in CI** — the shell boots asynchronously.
273
+ bump jest `testTimeout` to 10–15 s and add a `waitFor(element(...))
274
+ .toBeVisible().withTimeout(10000)` before the first assertion.
275
+ - **port conflicts under parallel CI** — pass `--port` explicitly per
276
+ job so workers don't collide on the default bridge port.
277
+
278
+ ## recovery — common failure modes (both runners)
279
+
280
+ - **"no flows found"** — confirm the working directory or pass an explicit
281
+ `.maestro/` path.
282
+ - **"missing env variable"** — set with `--env` (or jest env config) and
283
+ document the variable list in the flow directory's README so new
284
+ contributors aren't guessing.
285
+ - **CI flakes but local passes** — bump test timeouts to 10–15 s before
286
+ the first assertion, pin the bridge port, and run with `--video` so
287
+ failing Maestro runs land a recording next to the output.
288
+ - **a flake reproduces but resists fixing** — don't paper over with
289
+ `sleep`. open `/rnx-debug` against the same sim, capture
290
+ the timeline (`rnxsim timeline start` → reproduce → `rnxsim
291
+ what-happened`), and look for the missing animation-completion or
292
+ the unresolved fetch that's racing the assertion.
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: rnx-visual
3
+ description: Pixel-level rendering comparison between rnx and native iOS — fonts, gradients, shadows, image fidelity
4
+ ---
5
+
6
+ # rnx visual fidelity
7
+
8
+ pixel-level rendering fidelity: does the rnx canvas match native iOS
9
+ for text, gradients, shadows, blur, and image rendering? the `rnx`
10
+ CLI gives you two primitives — **canvas capture** (`rnxsim screenshot`)
11
+ and **structural diff** (`rnxsim debug snapshot`/`diff`). pixel
12
+ comparison itself is "capture both sides, diff the PNGs with a standard
13
+ image tool" — there is no single oracle-diff command, and that's fine:
14
+ the capture step is the hard part and it's fully reproducible.
15
+
16
+ > needs a connected, pinned sim. if `rnxsim describe` errors,
17
+ > load `/rnx-setup` first.
18
+
19
+ ## route in
20
+
21
+ - structural change ("which nodes / which props changed?") → load
22
+ `/rnx-debug`, debugging branch — `debug snapshot` + `debug
23
+ diff` is faster, deterministic, and tells you *why*.
24
+ - visual change (color, gradient, shadow, font metric, blur, image)? →
25
+ continue here.
26
+ - comparing rnx to your iOS simulator for fidelity? → continue.
27
+ - regressing your own app against itself across commits? → continue.
28
+
29
+ ## anti-patterns (read first)
30
+
31
+ - **run structural diff before pixel work.** `rnxsim debug snapshot` /
32
+ `debug diff` (in `/rnx-debug`) is cheaper, deterministic, and
33
+ points at the responsible nodes. only when it's empty (or the change is
34
+ pure rendering) does a pixel comparison add information.
35
+ - **don't single-screenshot animated surfaces.** a gradient
36
+ mid-animation never matches. gate the capture behind `rnxsim wait
37
+ idle` first.
38
+ - **a > 5% pixel threshold hides regressions.** ~1% is the right
39
+ ballpark for catching real font / shadow / gradient drift without
40
+ flagging anti-alias noise. go higher only with a written reason.
41
+ - **pin device and theme.** capture both sides on the same simulated
42
+ device and appearance, or you're diffing layout, not rendering. use
43
+ `--themes` to capture light and dark together.
44
+ - **commit baselines alongside the code change that produced them.**
45
+ baseline churn without code churn means someone captured noise.
46
+
47
+ ## capturing — `rnxsim screenshot`
48
+
49
+ fast, no extra setup, works on any loaded app:
50
+
51
+ ```sh
52
+ rnxsim screenshot --output app.png # full canvas
53
+ rnxsim screenshot --area 0,200,393,400 # crop to a logical rect
54
+ rnxsim screenshot --id loginButton -o btn.png # crop to a node by testID
55
+ rnxsim screenshot --text "Sign in" # crop to a node by text
56
+ rnxsim screenshot --gallery --themes # crawl screens → HTML gallery, light+dark
57
+ rnxsim screenshot --no-shell -o tenant.png # tenant surface only, no iOS chrome
58
+ rnxsim screenshot --shell-only # only the simulated iOS chrome
59
+ ```
60
+
61
+ `--gallery` crawls reachable screens and writes a browsable HTML gallery
62
+ — the fastest way to eyeball a whole app for surprise rendering changes.
63
+
64
+ ## comparing pixels
65
+
66
+ capture both sides to PNGs, then diff with any standard image tool.
67
+
68
+ **rnx vs rnx (regression across your own commits):**
69
+
70
+ ```sh
71
+ rnxsim screenshot -o before.png # baseline, before your change
72
+ # … make the rendering change, let the sim hot-reload …
73
+ rnxsim screenshot -o after.png # after your change
74
+ npx pixelmatch before.png after.png diff.png 0.1 # writes diff.png
75
+ # or: magick compare before.png after.png diff.png
76
+ ```
77
+
78
+ **rnx vs native iOS (fidelity to the real thing):**
79
+
80
+ ```sh
81
+ # native side — capture from your booted iOS simulator
82
+ xcrun simctl io booted screenshot native.png
83
+ # rnx side — same screen, same appearance
84
+ rnxsim screenshot -o rnx.png
85
+ npx pixelmatch native.png rnx.png diff.png 0.1
86
+ ```
87
+
88
+ crop both to the same region (`--area` / `--id`) when you only care
89
+ about one element — a full-screen diff buries a 3px font-baseline shift
90
+ under unrelated chrome.
91
+
92
+ ## typical workflow
93
+
94
+ 1. structural diff first — confirm the change is genuinely visual.
95
+ 2. capture the affected screen with `rnxsim screenshot` (crop to the
96
+ element if you can).
97
+ 3. diff against the baseline (previous commit, or a native capture).
98
+ 4. iterate the rendering until the diff is under threshold.
99
+ 5. open the gallery (`--gallery`) and scroll through every screen — a red
100
+ patch on one icon is easy to miss when you're focused on one case.
101
+
102
+ ## you're done when
103
+
104
+ - the diff is under threshold on every affected case, **or**
105
+ - the differences are explicitly accepted, baselines re-captured, and
106
+ both halves (code + baselines) committed together.
107
+ - the gallery shows no surprise changes when you scroll through every
108
+ screen, not just the one you targeted.
109
+
110
+ ## recovery — common failure modes
111
+
112
+ - **a single icon/glyph diffs** — font-load race or image not yet
113
+ cached. recapture after `rnxsim wait idle`, or navigate away and back
114
+ to seed the cache.
115
+ - **everything diffs by a constant offset** — you captured the two sides
116
+ on different devices or appearances. pin both, recapture.
117
+ - **baselines churn without a code change** — someone captured noise (a
118
+ lingering badge, half-open keyboard, a drifting clock). revert and
119
+ recapture from a clean, idle state.
120
+ - **persistent delta on the same font / shadow / gradient across
121
+ unrelated screens** — that's an engine-level rendering fidelity issue,
122
+ not your app. report it with the affected captures attached.