peachsim 0.1.921 → 0.1.923

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 (90) hide show
  1. package/README.md +11 -5
  2. package/cli/commands/cpu-profile.ts +25 -0
  3. package/cli/commands/flow.ts +7 -2
  4. package/cli/commands/maestro-generate.ts +292 -98
  5. package/cli/commands/maestro.ts +51 -29
  6. package/cli/drivers/playwright-provisioning.ts +29 -0
  7. package/dist-lib/agent-daemon-client.cjs +1 -1
  8. package/dist-lib/agent-events.cjs +1 -1
  9. package/dist-lib/agent-identity.cjs +1 -1
  10. package/dist-lib/agent-sessions.cjs +1 -1
  11. package/dist-lib/attached-projects.cjs +1 -1
  12. package/dist-lib/auth/shared-session.cjs +1 -1
  13. package/dist-lib/backend-origin.cjs +1 -1
  14. package/dist-lib/beta.cjs +1 -1
  15. package/dist-lib/beta.mjs +1 -1
  16. package/dist-lib/bridge-constants.cjs +1 -1
  17. package/dist-lib/bridge-contract-input.cjs +1 -1
  18. package/dist-lib/bridge-contract-input.mjs +1 -1
  19. package/dist-lib/bridge-contract.cjs +1 -1
  20. package/dist-lib/bridge-contract.mjs +1 -1
  21. package/dist-lib/bundle-channel.cjs +1 -1
  22. package/dist-lib/capture-contract.cjs +1 -1
  23. package/dist-lib/capture-contract.mjs +1 -1
  24. package/dist-lib/cli-constants.cjs +1 -1
  25. package/dist-lib/cloud-contract.cjs +1 -1
  26. package/dist-lib/cloud-contract.mjs +1 -1
  27. package/dist-lib/cloud-test-args.cjs +1 -1
  28. package/dist-lib/cloud-test-args.mjs +1 -1
  29. package/dist-lib/cloud.cjs +1 -1
  30. package/dist-lib/cloud.mjs +1 -1
  31. package/dist-lib/config.cjs +1 -1
  32. package/dist-lib/detox/index.cjs +1 -1
  33. package/dist-lib/dev-bundle-resolution.cjs +1 -1
  34. package/dist-lib/expo-swift-bundle.cjs +1 -1
  35. package/dist-lib/expo-swift-metadata.cjs +1 -1
  36. package/dist-lib/expo-swift-metadata.mjs +1 -1
  37. package/dist-lib/home-paths.cjs +1 -1
  38. package/dist-lib/host/bridge-host.cjs +1 -1
  39. package/dist-lib/host/fetch-proxy-handler.cjs +1 -1
  40. package/dist-lib/host/fetch-proxy-overrides.cjs +1 -1
  41. package/dist-lib/host/fetch-proxy-overrides.mjs +1 -1
  42. package/dist-lib/host/replacement-module-handler.cjs +1 -1
  43. package/dist-lib/host/websocket-proxy.cjs +1 -1
  44. package/dist-lib/index.cjs +1 -1
  45. package/dist-lib/jump-to-source-babel.cjs +1 -1
  46. package/dist-lib/jump-to-source-native.cjs +1 -1
  47. package/dist-lib/kotlin-host.cjs +1 -1
  48. package/dist-lib/menu.cjs +1 -1
  49. package/dist-lib/menu.mjs +1 -1
  50. package/dist-lib/metro-fingerprint-registry.cjs +1 -1
  51. package/dist-lib/metro-fingerprint-registry.mjs +1 -1
  52. package/dist-lib/metro-production-bundle.cjs +1 -1
  53. package/dist-lib/metro-production-bundle.mjs +1 -1
  54. package/dist-lib/metro.cjs +1 -1
  55. package/dist-lib/native-app-identity.cjs +1 -1
  56. package/dist-lib/native-app-identity.mjs +1 -1
  57. package/dist-lib/native-firebase-metadata.cjs +1 -1
  58. package/dist-lib/native-firebase-metadata.mjs +1 -1
  59. package/dist-lib/plist.cjs +1 -1
  60. package/dist-lib/plist.mjs +1 -1
  61. package/dist-lib/profiles.cjs +1 -1
  62. package/dist-lib/public-brand.cjs +1 -1
  63. package/dist-lib/react-native-host-modules.cjs +1 -1
  64. package/dist-lib/react-native-host-modules.mjs +1 -1
  65. package/dist-lib/render-mode.cjs +1 -1
  66. package/dist-lib/scripts/dev-server-scanner.cjs +1 -1
  67. package/dist-lib/sdk.cjs +1 -1
  68. package/dist-lib/sdk.mjs +1 -1
  69. package/dist-lib/skills.cjs +21 -6
  70. package/dist-lib/swift-compile.cjs +1 -1
  71. package/dist-lib/swift-compile.mjs +1 -1
  72. package/dist-lib/swift-package.cjs +1 -1
  73. package/dist-lib/swift-package.mjs +1 -1
  74. package/dist-lib/swift-submit.cjs +1 -1
  75. package/dist-lib/swift-submit.mjs +1 -1
  76. package/dist-lib/swift-toolchain.cjs +1 -1
  77. package/dist-lib/swift.cjs +1 -1
  78. package/dist-lib/vite-swift.cjs +1 -1
  79. package/dist-lib/vite.cjs +1 -1
  80. package/dist-lib/worklet-executable.cjs +1 -1
  81. package/dist-lib/worklet-executable.mjs +1 -1
  82. package/package.json +1 -1
  83. package/skills/peach-debug/SKILL.md +4 -3
  84. package/skills/peach-debug/references/commands.md +1 -1
  85. package/skills/peach-perf/SKILL.md +3 -13
  86. package/skills/peach-setup/SKILL.md +5 -4
  87. package/skills/peach-test/SKILL.md +14 -7
  88. package/src/screenshots/compose.ts +2 -9
  89. package/src/screenshots/frame-compose.ts +2 -5
  90. package/src/screenshots/orchestrate.ts +3 -17
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/react-native-host-modules.ts
4
4
  var REACT_NATIVE_PASSTHROUGH_SPECIFIERS = [
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/dist-lib/sdk.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
package/dist-lib/sdk.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (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);
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -52645,6 +52645,23 @@ ${detail}` : ""}`
52645
52645
  libraryPath: libraries.libraryPath
52646
52646
  };
52647
52647
  }
52648
+ async function launchProvisionedPlaywright() {
52649
+ const resolved = await provisionPlaywright();
52650
+ const env = { ...process.env };
52651
+ const browser = await ensurePlaywrightBrowser(resolved, softwareChromeAllowed(), env);
52652
+ if (!browser.ok) throw new Error(browser.message);
52653
+ const playwright = (0, import_node_module5.createRequire)(resolved.modulePath)(
52654
+ resolved.modulePath
52655
+ );
52656
+ const browserEnv = browser.libraryPath ? {
52657
+ ...env,
52658
+ LD_LIBRARY_PATH: [browser.libraryPath, env.LD_LIBRARY_PATH].filter(Boolean).join(":")
52659
+ } : env;
52660
+ return launchReapedChrome(
52661
+ (options) => playwright.chromium.launch({ ...options, env: browserEnv }),
52662
+ { headless: true }
52663
+ );
52664
+ }
52648
52665
  var import_node_child_process15, import_node_fs40, import_node_module5, import_node_path42;
52649
52666
  var init_playwright_provisioning = __esm({
52650
52667
  "cli/drivers/playwright-provisioning.ts"() {
@@ -52654,6 +52671,7 @@ var init_playwright_provisioning = __esm({
52654
52671
  import_node_module5 = require("node:module");
52655
52672
  import_node_path42 = require("node:path");
52656
52673
  init_src();
52674
+ init_reap_browser();
52657
52675
  init_home_paths();
52658
52676
  init_is_record();
52659
52677
  init_internal_child();
@@ -55933,10 +55951,7 @@ function renderFrameHtml(layout, rawImageDataUrl) {
55933
55951
  </html>`;
55934
55952
  }
55935
55953
  async function composeFramedScreenshot(rawPng, model) {
55936
- const { chromium } = await import("playwright");
55937
- const browser = await launchReapedChrome((options) => chromium.launch(options), {
55938
- headless: true
55939
- });
55954
+ const browser = await launchProvisionedPlaywright();
55940
55955
  try {
55941
55956
  const composer = await createFramedScreenshotComposer(browser);
55942
55957
  try {
@@ -55997,7 +56012,7 @@ var init_frame_compose = __esm({
55997
56012
  import_chrome_metrics = require("peach-engine/ios/chrome-metrics");
55998
56013
  import_duo_housing = require("peach-engine/ios/duo-housing");
55999
56014
  import_devices3 = require("peach-engine/settings/devices");
56000
- init_reap_browser();
56015
+ init_playwright_provisioning();
56001
56016
  METALLIC_RING_SHADOW = "inset 0 0 0 0.5px #000, inset 0 0 0 2px #757575, inset 0 0 0 5px #212121";
56002
56017
  FRAME_OUTLINE_BLEED = 1;
56003
56018
  }
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-compile.ts
4
4
  import { execFile as execFile2, execFileSync } from "node:child_process";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-package.ts
4
4
  function isSwiftManifestFile(file) {
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/swift-submit.ts
4
4
  var SWIFT_COMPILE_PATH = "/api/swift-compile";
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
package/dist-lib/vite.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __create = Object.create;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
  let __sootsim_import_meta_url = '', __sootsim_import_meta_dirname = ''; try { __sootsim_import_meta_url = require('url').pathToFileURL(__filename).href; __sootsim_import_meta_dirname = __dirname; } catch {}
3
3
  "use strict";
4
4
  var __defProp = Object.defineProperty;
@@ -1,4 +1,4 @@
1
- /*! rnx v0.1.921 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
1
+ /*! rnx v0.1.923 | (c) 2026 Tamagui LLC | Proprietary — see LICENSE */
2
2
 
3
3
  // src/worklet-executable.ts
4
4
  import { parse } from "acorn";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peachsim",
3
- "version": "0.1.921",
3
+ "version": "0.1.923",
4
4
  "description": "Vite and Metro plugins, testing drivers, and SDK exports for peach.",
5
5
  "author": "Tamagui LLC",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -24,7 +24,8 @@ tree, `debug snapshot` + `debug diff` to capture before/after, `debug
24
24
  recent` on a channel and `perf shell` for frame-by-frame stats, and the
25
25
  timeline for "what just happened". **do not** debug these by eye with
26
26
  screenshots, sprinkle `console.log` through the render path, or spin up a
27
- one-off browser-automation script. the canvas has no DOM to inspect, and a
27
+ one-off browser-automation script. visual rendering uses canvas; the
28
+ accessibility mirror exposes semantic DOM. a
28
29
  CLI capture is reproducible, diffable, and shareable in a way a screenshot
29
30
  never is.
30
31
 
@@ -52,8 +53,8 @@ regression test. a pile of ad-hoc logs helps no one and gets deleted.
52
53
 
53
54
  - **`a1`/`a2`/`b0` style ids mean Peach sims, never browser tabs.** when a user
54
55
  says "look at a3", that's the `peach list` sim id. do not open chrome
55
- MCP unless the user explicitly says "use chrome" — the canvas has no
56
- DOM to inspect anyway.
56
+ MCP unless the user explicitly says "use chrome". use Peach's node-tree
57
+ inspection for the visual surface and its accessibility tools for semantics.
57
58
  - **the CLI auto-settles. don't add `sleep`.** every read waits up to
58
59
  1800 ms for in-flight transitions to start; every write polls layout
59
60
  hash until stable. if a read keeps racing its write, the fix is **never**
@@ -21,7 +21,7 @@ peach do type "hello world" # type into focused input
21
21
  peach do type-into emailInput "a@b" # focus then type
22
22
  peach do key return # special key (return, backspace, …)
23
23
  peach do scroll feed-list 0 500 # scroll a testID to absolute x/y offsets
24
- peach do swipe feed-list left # directional swipe
24
+ peach do swipe 196 700 196 300 # swipe between screen coordinates
25
25
  peach do long-press messageRow 800 # ms hold
26
26
  ```
27
27
 
@@ -234,19 +234,9 @@ engaged. preserve failed receipts instead of retrying them out of the data set.
234
234
 
235
235
  ### confirm which build you measured, every time
236
236
 
237
- Against a dev-source sim, the FIRST reload after an engine edit serves the
238
- PREVIOUS module. The sim reports ready, the app runs, the numbers look real, and
239
- they describe the build before yours. Two screenshots were read as a fidelity
240
- regression this way before the cause was found.
241
-
242
- So make every capture say which build it came from, rather than assuming the
243
- reload took. Put a marker in something the capture already returns - a counter
244
- name in `saveLayerReasons`, a debug event, a log line - and treat a capture with
245
- no marker as a capture of the wrong build. Reload twice and re-read the marker.
246
-
247
- This costs one line and it is the difference between a measurement and a guess.
248
- The same rule covers any A/B where you edit between arms: an arm you cannot
249
- identify from its own output is not evidence.
237
+ Record the CLI and runtime versions with `peach version`. For a development
238
+ build, include a build marker in the capture and verify it after reloading.
239
+ An A/B measurement must identify each build from its own output.
250
240
 
251
241
 
252
242
  deeper tools when the frame report isn't enough:
@@ -180,8 +180,9 @@ before launching. if open errors with "port unreachable": confirm your dev
180
180
  server is actually serving — the bundler has to be up before Peach can
181
181
  attach.
182
182
 
183
- **two sims appear unexpectedly.** an earlier `peach claim` didn't
184
- release on exit. close the orphan explicitly:
183
+ **two sims appear unexpectedly.** `peach open --new` creates a separate sim;
184
+ `peach claim` only reserves an existing one. check ownership before closing
185
+ a sim left by your own earlier run:
185
186
 
186
187
  ```sh
187
188
  peach list # note the stale id
@@ -192,8 +193,8 @@ peach close <id> # release it cleanly
192
193
  Run `peach runtime install`, then retry `peach open <port>`.
193
194
 
194
195
  **`peach open` times out ("timed out waiting for opened sim to connect").**
195
- do not retry in a loop. verify the development server first: `__soot/` must
196
- respond and the engine watchers must be running. inspect the driver diagnostic
196
+ do not retry in a loop. verify the app's development server is reachable and
197
+ `peach runtime list` shows an installed runtime. inspect the driver diagnostic
197
198
  path printed by the failed command. restart the broken development stack at its
198
199
  supervisor, then retry once. Peach owns the isolated browser profile and its
199
200
  process tree; do not open the shell URL through an operating-system browser.
@@ -171,12 +171,19 @@ a Peach shell if one isn't running.
171
171
  ```sh
172
172
  peach maestro # auto-discover .maestro/ or maestro/
173
173
  peach maestro test .maestro/login.yaml
174
- peach maestro test .maestro/ # every flow in a directory
174
+ peach maestro test .maestro/ # workspace globs, root flows by default
175
175
  peach maestro init # scaffold a starter flow
176
176
  peach maestro --list-compat # supported / partial / unsupported
177
177
  peach maestro --env USERNAME=alice test .maestro/login.yaml
178
178
  ```
179
179
 
180
+ Directory discovery selects root YAML flows by default, as Maestro does.
181
+ For nested suites, pass their folder, such as `.maestro/flows`, or add
182
+ `config.yaml` with `flows: ["flows/**"]`. Keep shared subflows outside those
183
+ globs; `runFlow` can still load them.
184
+ Preview and hosted-run result receipts go to `--test-output-dir`, or to
185
+ `--out` (default `/tmp/peach-recordings`); the CLI prints their directory.
186
+
180
187
  ### supported verbs
181
188
 
182
189
  `launchApp` (including JSON `arguments`), `stopApp`, `clearState`, `clearKeychain`, `tapOn`, `tapAtCoords`, `longPressOn`, `inputText`,
@@ -250,9 +257,9 @@ moduleNameMapper: {
250
257
  }
251
258
  ```
252
259
 
253
- keep the **preset**, not just the mapper — the preset wires up the
254
- global setup/teardown that tears the shell down between suites. mapper-only
255
- works until your first hung shell.
260
+ keep the **preset**, not just the mapper. its `setupFilesAfterEnv` hook closes
261
+ the driver's browser after each test file. mapper-only configs need their own
262
+ `afterAll` calling the driver's `cleanup()`.
256
263
 
257
264
  ### commands
258
265
 
@@ -262,8 +269,8 @@ peach detox --config my.cjs # use a specific jest config
262
269
  peach detox --watch # jest watch mode
263
270
  peach detox -t "login" # --testNamePattern
264
271
  peach detox --headed # keep the shell window visible
265
- peach detox --no-launch # don't auto-launch a shell
266
- peach detox --port 5173 # explicit bridge port for CI
272
+ peach detox --no-launch # skip the running-shell check
273
+ peach detox --port 5173 # explicit shell HTTP port for CI
267
274
  peach detox init # scaffold config + e2e/example.test.ts
268
275
  ```
269
276
 
@@ -284,7 +291,7 @@ peach detox init # scaffold config + e2e/example.test.ts
284
291
  | `.setColumnToValue(...)` | supported for uniquely named picker rows |
285
292
  | `waitFor(...).toBeVisible().withTimeout(...)` | supported |
286
293
  | `device.launchApp(...)` / `device.reloadReactNative()` | supported |
287
- | `device.shake()` | partial — emits the JS event, no haptic |
294
+ | `device.shake()` | no-op |
288
295
  | `device.sendUserNotification(...)` | not yet |
289
296
 
290
297
  ### troubleshooting
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync } from 'fs'
2
2
  import path from 'path'
3
- import { launchReapedChrome } from '../../../../scripts/lib/reap-browser'
3
+ import { launchProvisionedPlaywright } from '../../cli/drivers/playwright-provisioning'
4
4
  import {
5
5
  DEFAULT_FRAME_SHADOW,
6
6
  toAssetKey,
@@ -343,14 +343,7 @@ export async function composeMarketingScreenshots(
343
343
  browserOverride?: MarketingBrowserLike,
344
344
  ): Promise<ComposeResult> {
345
345
  const entries: ComposeManifestEntry[] = []
346
- const browser =
347
- browserOverride ??
348
- (await (async () => {
349
- const { chromium } = await import('playwright')
350
- return launchReapedChrome((options) => chromium.launch(options), {
351
- headless: true,
352
- })
353
- })())
346
+ const browser = browserOverride ?? (await launchProvisionedPlaywright())
354
347
  try {
355
348
  // each slide's canvas width/height is the authored output raster (the
356
349
  // composed image is written at exactly that size), so density stays 1:1
@@ -20,7 +20,7 @@ import {
20
20
  type CornerRadii,
21
21
  type DeviceModel,
22
22
  } from 'peach-engine/settings/devices'
23
- import { launchReapedChrome } from '../../../../scripts/lib/reap-browser'
23
+ import { launchProvisionedPlaywright } from '../../cli/drivers/playwright-provisioning'
24
24
 
25
25
  const METALLIC_RING_SHADOW =
26
26
  'inset 0 0 0 0.5px #000, inset 0 0 0 2px #757575, inset 0 0 0 5px #212121'
@@ -410,10 +410,7 @@ export async function composeFramedScreenshot(
410
410
  rawPng: Buffer,
411
411
  model: DeviceModel,
412
412
  ): Promise<Buffer> {
413
- const { chromium } = await import('playwright')
414
- const browser = await launchReapedChrome((options) => chromium.launch(options), {
415
- headless: true,
416
- })
413
+ const browser = await launchProvisionedPlaywright()
417
414
  try {
418
415
  const composer = await createFramedScreenshotComposer(browser)
419
416
  try {
@@ -1,8 +1,8 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync } from 'fs'
2
2
  import { tmpdir } from 'os'
3
3
  import path from 'path'
4
- import { launchReapedChrome } from '../../../../scripts/lib/reap-browser'
5
4
  import { runFlowPlayback } from '../../cli/commands/flow'
5
+ import { launchProvisionedPlaywright } from '../../cli/drivers/playwright-provisioning'
6
6
  import {
7
7
  composeMarketingScreenshots,
8
8
  type MarketingBrowserLike,
@@ -100,14 +100,7 @@ async function buildFramedSources(
100
100
  ): Promise<Map<string, string>> {
101
101
  const sources = new Map<string, string>()
102
102
  mkdirSync(plan.capture.framedDir, { recursive: true })
103
- const browser =
104
- browserOverride ??
105
- (await (async () => {
106
- const { chromium } = await import('playwright')
107
- return launchReapedChrome((options) => chromium.launch(options), {
108
- headless: true,
109
- })
110
- })())
103
+ const browser = browserOverride ?? (await launchProvisionedPlaywright())
111
104
  try {
112
105
  const composer = await createFramedScreenshotComposer(browser)
113
106
  try {
@@ -204,14 +197,7 @@ export async function runScreenshotsPlan(
204
197
  plan.capture.mode !== 'raw' ||
205
198
  (!overrides.captureOnly && plan.compose.frame.show === true)
206
199
  const needsSharedBrowser = needsFramedSources || !overrides.captureOnly
207
- const sharedBrowser = needsSharedBrowser
208
- ? await (async () => {
209
- const { chromium } = await import('playwright')
210
- return launchReapedChrome((options) => chromium.launch(options), {
211
- headless: true,
212
- })
213
- })()
214
- : null
200
+ const sharedBrowser = needsSharedBrowser ? await launchProvisionedPlaywright() : null
215
201
  try {
216
202
  const framedSources = needsFramedSources
217
203
  ? await buildFramedSources(plan, sharedBrowser ?? undefined)