Skip to content

Commit 65d82bc

Browse files
committed
test(ci): mitigate e2e flakes with cleaner host/app/test-runner orchestration
1 parent 9be491e commit 65d82bc

12 files changed

Lines changed: 374 additions & 120 deletions

File tree

.yarn/patches/jet-npm-0.9.0-dev.13-3321aeea6e.patch

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
11
diff --git a/lib/commonjs/cli.js b/lib/commonjs/cli.js
2-
index 35c04da87c63a79bc26707aaffe3156fb98eca79..cc9243fabdc014bc945dd6e85cbe55d85692ad56 100644
2+
index 35c04da87c63a79bc26707aaffe3156fb98eca79..3ad2cea4c214e8c3c4f4698acacfff2610902863 100644
33
--- a/lib/commonjs/cli.js
44
+++ b/lib/commonjs/cli.js
5-
@@ -72,22 +72,64 @@ function cleanup() {
5+
@@ -71,23 +71,77 @@ function cleanup() {
6+
console.log(`[🟥] Failed to run a cleanup task: ${err.message}`);
67
});
78
}
8-
async function startServer(server, config, after) {
9+
-async function startServer(server, config, after) {
10+
+async function startServer(server, config, after, runControl) {
911
+ let reconnectGraceTimer = null;
1012
+ let pendingDisconnectCode = null;
1113
+ let disconnectStartedAt = 0;
14+
+ let orchestratePhase = 'jet-starting';
15+
+ let lastTestTitle = '';
16+
+ let lastSuiteTitle = '';
17+
+ let coverageUploadInProgress = false;
1218
server.on('started', s => {
1319
const url = s.url;
1420
console.log(`[🟩] Jet remote server listening at "${url}".`);
21+
+ startControlHttpServer(config, {
22+
+ setOrchestratePhase: phase => {
23+
+ orchestratePhase = phase || orchestratePhase;
24+
+ },
25+
+ onLaunchReady: () => runControl?.beginRun?.(),
26+
+ });
1527
});
1628
server.on('connection', (_, req) => {
1729
console.log(`[🟩] Jet client connected from "${req.socket.remoteAddress + ':' + req.socket.remotePort}".`);
@@ -34,12 +46,14 @@ index 35c04da87c63a79bc26707aaffe3156fb98eca79..cc9243fabdc014bc945dd6e85cbe55d8
3446
- cleanup();
3547
+ try {
3648
+ const os = require('os');
49+
+ const suite = lastSuiteTitle.replace(/"/g, "'");
50+
+ const test = lastTestTitle.replace(/"/g, "'");
3751
+ console.warn(
38-
+ `[jet-ws] disconnect_context code=${code} loadavg=${os.loadavg().join(',')} mem=${JSON.stringify(process.memoryUsage())}`,
52+
+ `[jet-ws] disconnect_context code=${code} loadavg=${os.loadavg().join(',')} orchestratePhase=${orchestratePhase} currentTest="${test}" currentSuite="${suite}" coverageUpload=${coverageUploadInProgress} mem=${JSON.stringify(process.memoryUsage())}`,
3953
+ );
4054
+ } catch (_) {
4155
+ // Best-effort diagnostics only.
42-
}
56+
+ }
4357
+ if (code === 1000 || !config.exitOnError) {
4458
+ return;
4559
+ }
@@ -63,30 +77,41 @@ index 35c04da87c63a79bc26707aaffe3156fb98eca79..cc9243fabdc014bc945dd6e85cbe55d8
6377
+ cleanup();
6478
+ }, graceMs);
6579
+ return;
66-
+ }
80+
}
6781
+ console.error(`[jet-ws] fatal_disconnect code=${code} reason="${reason || ''}" immediate=true`);
6882
+ print(`[🟥] Exiting after an abnormal disconnect.`);
6983
+ process.exitCode = 1;
7084
+ cleanup();
7185
});
7286
server.on('error', error => {
7387
if (error instanceof _mochaRemoteServer.ClientError) {
74-
@@ -100,6 +142,14 @@ async function startServer(server, config, after) {
88+
@@ -100,6 +154,25 @@ async function startServer(server, config, after) {
7589
cleanup();
7690
}
7791
});
92+
+ server.on('coverage-upload-start', () => {
93+
+ coverageUploadInProgress = true;
94+
+ });
7895
+ server.on('coverage-data', coverage => {
96+
+ coverageUploadInProgress = false;
7997
+ const incomingKeys = Object.keys(coverage).length;
8098
+ console.log(`[jet-coverage] WS received ${incomingKeys} file(s)`);
8199
+ coverageMap.merge(coverage);
82100
+ global.__coverage__ = coverageMap.toJSON();
83101
+ const totalKeys = Object.keys(global.__coverage__).length;
84102
+ console.log(`[jet-coverage] WS merged total ${totalKeys} file(s)`);
103+
+ });
104+
+ server.on('running', runner => {
105+
+ const constants = require('mocha').Runner.constants;
106+
+ runner.on(constants.EVENT_TEST_BEGIN, test => {
107+
+ lastTestTitle = test?.title || '';
108+
+ lastSuiteTitle = test?.parent?.fullTitle?.() || test?.parent?.title || '';
109+
+ });
85110
+ });
86111
cleanupTasks.add(async () => {
87112
if (server.listening) {
88113
await server.stop();
89-
@@ -115,41 +165,6 @@ async function startServer(server, config, after) {
114+
@@ -115,39 +188,48 @@ async function startServer(server, config, after) {
90115
process.on('SIGINT', cleanup);
91116
process.on('exit', cleanup);
92117
}
@@ -98,12 +123,25 @@ index 35c04da87c63a79bc26707aaffe3156fb98eca79..cc9243fabdc014bc945dd6e85cbe55d8
98123
- }
99124
- server.on('request', (req, res) => {
100125
- if (req.url === '/coverage' && req.method === 'POST') {
101-
- let body = '';
102-
- req.on('data', chunk => {
103-
- body += chunk.toString();
104-
- });
105-
- req.on('end', () => {
106-
- try {
126+
+function startControlHttpServer(config, handlers) {
127+
+ const http = require('http');
128+
+ const controlPort = parseInt(process.env.RNFB_JET_CONTROL_PORT || String(config.port + 1), 10);
129+
+ const server = http.createServer((req, res) => {
130+
+ const url = req.url?.split('?')[0];
131+
+ if (url === '/launch-ready' && req.method === 'POST') {
132+
+ console.log('[jet-control] launch-ready received');
133+
+ handlers.onLaunchReady?.();
134+
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
135+
+ res.end('ok');
136+
+ return;
137+
+ }
138+
+ if (url === '/orchestrate-state' && req.method === 'POST') {
139+
let body = '';
140+
req.on('data', chunk => {
141+
body += chunk.toString();
142+
});
143+
req.on('end', () => {
144+
try {
107145
- coverageMap.merge(JSON.parse(body));
108146
- global.__coverage__ = coverageMap.toJSON();
109147
- res.end(JSON.stringify({
@@ -113,36 +151,85 @@ index 35c04da87c63a79bc26707aaffe3156fb98eca79..cc9243fabdc014bc945dd6e85cbe55d8
113151
- res.end(JSON.stringify({
114152
- error: 'Invalid JSON'
115153
- }));
116-
- }
117-
- });
154+
+ const parsed = JSON.parse(body || '{}');
155+
+ if (parsed.phase) {
156+
+ handlers.setOrchestratePhase?.(parsed.phase);
157+
+ console.log(`[jet-control] orchestrate-state=${parsed.phase}`);
158+
+ }
159+
+ } catch (_) {
160+
+ // Best-effort only.
161+
}
162+
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
163+
+ res.end('ok');
164+
});
118165
- } else {
119166
- res.writeHead(404, {
120167
- 'Content-Type': 'application/json'
121168
- });
122169
- res.end(JSON.stringify({
123170
- error: 'Not Found'
124171
- }));
125-
- }
126-
- });
127-
-}
172+
+ return;
173+
}
174+
+ res.writeHead(404, { 'Content-Type': 'application/json' });
175+
+ res.end(JSON.stringify({ error: 'Not Found' }));
176+
+ });
177+
+ server.listen(controlPort, config.hostname, () => {
178+
+ console.log(`[jet-control] listening on http://${config.hostname}:${controlPort}`);
179+
+ });
180+
+ cleanupTasks.add(async () => {
181+
+ await new Promise(resolve => {
182+
+ server.close(() => resolve());
183+
+ });
184+
});
185+
}
128186
(0, _yargs.default)((0, _helpers.hideBin)(process.argv)).scriptName('jet').version('version', 'Show version number & exit', jetPackageJson.version).alias('v', 'version').option('target', {
129-
description: 'The name of the test target to run as defined in your "jet" package.json config.',
130-
type: 'string',
131-
@@ -302,12 +317,11 @@ function attachHttpServer(wss) {
187+
@@ -301,17 +383,34 @@ function attachHttpServer(wss) {
188+
timeout: finalConfig.timeout,
132189
slow: finalConfig.slow
133190
});
134-
return startServer(server, finalConfig, target.after).then(() => {
191+
- return startServer(server, finalConfig, target.after).then(() => {
135192
- if (finalConfig.coverage) {
136193
- attachHttpServer(server.wss);
137194
- }
138-
if (!finalConfig.watch) {
139-
server.run(async failures => {
140-
global.__coverage__ = coverageMap.toJSON();
141-
+ const mergedKeys = Object.keys(global.__coverage__).length;
142-
+ console.log(`[jet-coverage] merged ${mergedKeys} file(s) before NYC shutdown`);
143-
await cleanup();
144-
process.exit(failures > 0 ? 1 : 0);
145-
});
195+
- if (!finalConfig.watch) {
196+
- server.run(async failures => {
197+
- global.__coverage__ = coverageMap.toJSON();
198+
- await cleanup();
199+
- process.exit(failures > 0 ? 1 : 0);
200+
- });
201+
+ const deferRun =
202+
+ process.env.RNFB_JET_DEFER_RUN === '1' || process.env.RNFB_JET_DEFER_RUN === 'true';
203+
+ const runControl = {
204+
+ beginRun: null,
205+
+ };
206+
+ return startServer(server, finalConfig, target.after, runControl).then(() => {
207+
+ let runStarted = false;
208+
+ const beginRun = () => {
209+
+ if (runStarted) {
210+
+ return;
211+
+ }
212+
+ runStarted = true;
213+
+ if (!finalConfig.watch) {
214+
+ server.run(async failures => {
215+
+ global.__coverage__ = coverageMap.toJSON();
216+
+ const mergedKeys = Object.keys(global.__coverage__).length;
217+
+ console.log(`[jet-coverage] merged ${mergedKeys} file(s) before NYC shutdown`);
218+
+ await cleanup();
219+
+ process.exit(failures > 0 ? 1 : 0);
220+
+ });
221+
+ }
222+
+ };
223+
+ runControl.beginRun = beginRun;
224+
+ if (deferRun) {
225+
+ console.log('[jet-control] deferring server.run until POST /launch-ready');
226+
+ return;
227+
}
228+
+ beginRun();
229+
});
230+
}).help(true).alias('h', 'help').argv;
231+
//# sourceMappingURL=cli.js.map
232+
\ No newline at end of file
146233
diff --git a/lib/commonjs/index.js b/lib/commonjs/index.js
147234
index a72380ed2ace01b19b028bb8e6f9d29dc3affbcd..030475cff75349b2b7f820cf8a3556fb1d7bea45 100644
148235
--- a/lib/commonjs/index.js

.yarn/patches/mocha-remote-server-npm-1.13.2-619a29d2e3.patch

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
diff --git a/dist/Server.js b/dist/Server.js
2-
index ad9debe2086ab9b96e97a69aec966da8114ad102..51cc18658e11d5aec5d5a76c4eb5f965b32a587d 100644
2+
index ad9debe2086ab9b96e97a69aec966da8114ad102..93c3f65524d4fa86ecef7045840ed5fc333f66e5 100644
33
--- a/dist/Server.js
44
+++ b/dist/Server.js
55
@@ -73,38 +73,59 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
@@ -108,12 +108,13 @@ index ad9debe2086ab9b96e97a69aec966da8114ad102..51cc18658e11d5aec5d5a76c4eb5f965
108108
if (typeof msg.action !== "string") {
109109
throw new Error("Expected message to have an action property");
110110
}
111-
@@ -130,6 +172,18 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
111+
@@ -130,6 +172,19 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
112112
throw new Error("Received a message from the client, but server wasn't running");
113113
}
114114
}
115115
+ else if (msg.action === "coverage-ready") {
116116
+ console.warn(`[jet-coverage] server recv coverage-ready`);
117+
+ this.emit("coverage-upload-start");
117118
+ this.send({ action: "pull-coverage" });
118119
+ }
119120
+ else if (msg.action === "coverage-data") {
@@ -127,7 +128,7 @@ index ad9debe2086ab9b96e97a69aec966da8114ad102..51cc18658e11d5aec5d5a76c4eb5f965
127128
else if (msg.action === "error") {
128129
if (typeof msg.message !== "string") {
129130
throw new Error("Expected 'error' action to have an error argument with a message");
130-
@@ -152,10 +206,45 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
131+
@@ -152,10 +207,45 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
131132
}
132133
}
133134
};
@@ -173,15 +174,15 @@ index ad9debe2086ab9b96e97a69aec966da8114ad102..51cc18658e11d5aec5d5a76c4eb5f965
173174
// Forget everything about the runner and the client
174175
const { runner, client } = this;
175176
delete this.runner;
176-
@@ -263,6 +352,7 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
177+
@@ -263,6 +353,7 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
177178
// this.runner = new Mocha.Runner(this.suite, this.options.delay || false);
178179
// TODO: Stub this to match the Runner's interface even better
179180
this.runner = new FakeRunner_1.FakeRunner();
180181
+ this._awaitingInitialClientRun = true;
181182
// Attach event listeners to update stats
182183
(0, stats_collector_1.createStatsCollector)(this.runner);
183184
// Set the client options, to be passed to the next running client
184-
@@ -370,7 +460,8 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
185+
@@ -370,7 +461,8 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
185186
this.client.send(data);
186187
}
187188
else {

okf-bundle/ci-workflows/android.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Under load, Jet may run only a small prefix before mocha-remote desync, often af
5757

5858
| Change | Location |
5959
|--------|----------|
60+
| Defer `server.run()` until host `POST /launch-ready` on control port **8091** (`RNFB_JET_DEFER_RUN=1`) | Jet patch + `firebase.test.js`[Jet host orchestration](../testing/running-e2e.md#jet-host-orchestration-ports-and-launch-gate) |
6061
| Inbound parse buffer + `tryDeserialize` in `serialization.js` / `parse_skip` logging | mocha-remote-server patch |
6162
| Outbound queue flushed on reconnect | mocha-remote-client patch |
6263
| `JET_PROTOCOL_ERROR_RE` → retryable Jet session (attempt 2) | `tests/e2e/firebase.test.js` |

okf-bundle/ci-workflows/detox-patches.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Patches are in-repo. Prefer direct patch-file edits or headless workflow; `yarn
1717
| Ignore missing adb reverse on teardown | `ADB.js` | Android | Jet WS 1006 triggers mid-run `reverse --remove` → adb exit 1 |
1818
| **2× device-registry lock stale** | `ExclusiveLockfile.js` | iOS, macOS, Android | `proper-lockfile` `ECOMPROMISED` before tests start |
1919

20-
Related patches: `jet`, `mocha-remote-client`, `mocha-remote-server` — WS reconnect grace, coverage handshake, client keepalive, parse buffering, reconnect assignment order; HTTP POST `/coverage` removed. See [coverage design](../testing/coverage-design.md), [iOS issues 6–8](ios.md#6-jet-websocket-disconnect-1006--1001).
20+
Related patches: `jet`, `mocha-remote-client`, `mocha-remote-server` — WS reconnect grace, coverage handshake, client keepalive, parse buffering, reconnect assignment order; **host control HTTP on `JET_REMOTE_PORT + 1` (default 8091)** for launch defer + orchestrate phase (`RNFB_JET_DEFER_RUN`); HTTP POST `/coverage` on 8090 removed. See [Jet host orchestration](../testing/running-e2e.md#jet-host-orchestration-ports-and-launch-gate), [coverage design](../testing/coverage-design.md), [iOS issues 6–8](ios.md#6-jet-websocket-disconnect-1006--1001).
2121

2222
## Updating the jet patch (headless)
2323

@@ -35,7 +35,7 @@ yarn patch-commit -s "$PATCH_DIR"
3535

3636
3. `yarn install` from repo root — confirm updated `yarn.lock` patch hash and applied files in `tests/node_modules/jet/`.
3737

38-
**Touch list:** `lib/commonjs/cli.js` (server: WS `coverage-data`, reconnect grace; **no** `attachHttpServer`), `lib/commonjs/index.js` + `src/index.tsx` (client: `client.uploadCoverage()`). Metro resolves Jet via `"react-native": "src/index"` — patching `lib/` alone does not fix macOS bundles.
38+
**Touch list:** `lib/commonjs/cli.js` (server: WS `coverage-data`, reconnect grace, **`startControlHttpServer` on port `JET_REMOTE_PORT + 1`**, defer `server.run()` until `POST /launch-ready` when `RNFB_JET_DEFER_RUN=1`; **no** control HTTP on the 8090 WebSocket stack), `lib/commonjs/index.js` + `src/index.tsx` (client: `client.uploadCoverage()`). Metro resolves Jet via `"react-native": "src/index"` — patching `lib/` alone does not fix macOS bundles.
3939

4040
## Device registry lock (`ECOMPROMISED`)
4141

okf-bundle/ci-workflows/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ GitHub Actions job shape, platform reliability, and artifact triage.
1010

1111
## Shared E2E dependencies
1212

13+
* [Jet host orchestration](../testing/running-e2e.md#jet-host-orchestration-ports-and-launch-gate) — ports 8090/8091, defer-run launch gate (canonical)
1314
* [Detox patches](detox-patches.md) — inventory, `ECOMPROMISED`, patch workflow
1415
* [Cloud API quota triage](../testing/firebase-testing-project.md#ci-triage-cloud-api-quota-pressure) — live FIS/RC pressure (all platforms)
1516
* [iOS diagnostics](ios.md#operational-notes)`resource-monitor.sh`, `flake-summary.sh`

okf-bundle/ci-workflows/ios.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ Error: Unable to update lock within the stale threshold
140140
| Change | Location |
141141
|--------|----------|
142142
| Wait for Jet (port 8090) before `launchApp` | `tests/e2e/firebase.test.js` |
143+
| Defer `server.run()` until host `POST /launch-ready` on control port **8091** (`RNFB_JET_DEFER_RUN=1`) | Jet patch + `firebase.test.js`[Jet host orchestration](../testing/running-e2e.md#jet-host-orchestration-ports-and-launch-gate) |
144+
| `POST /orchestrate-state` + `[rnfb-e2e] orchestrate-state=` for launch/retry triage | `firebase.test.js` + Jet patch |
143145
| Wait for Metro (`/status`) before `launchApp` | `tests/e2e/firebase.test.js` (debug Detox configs only; release skips Metro wait) |
144146
| Bounded `launchApp` timeout (debug default 180s; release default 120s via `RNFB_LAUNCH_APP_RELEASE_TIMEOUT_MS`) | `tests/e2e/firebase.test.js` |
145147
| `detoxEnableSynchronization: 'NO'` at launch | `tests/e2e/firebase.test.js` |
@@ -278,7 +280,7 @@ The app process (`testing[<pid>]`) often stays alive in `testing.log` / `simulat
278280
| mocha-remote-server preserves runner + sends `pull-coverage` on reconnect | `.yarn/patches/mocha-remote-server-npm-1.13.2-*.patch` |
279281
| Assign `this.client` **before** `emit('connection')`; `Server.send()` warns instead of throwing | mocha-remote-server patch → `Server.js` |
280282
| Client WS keepalive (`ping` when supported) + `readyState` logging on send failure | `.yarn/patches/mocha-remote-client-npm-1.13.2-*.patch` |
281-
| `disconnect_context` logs `loadavg` + `process.memoryUsage()` on disconnect | Jet patch → `cli.js` |
283+
| `disconnect_context` logs `loadavg`, `process.memoryUsage()`, orchestrate phase, current test/suite, coverage-upload flag | Jet patch → `cli.js` |
282284
| One Jet e2e retry when grace expires (`RETRYABLE_DISCONNECT`) or session/coverage retryable | `tests/e2e/firebase.test.js` |
283285
| Structured WS / orchestration logging | `[jet-ws]`, `[rnfb-e2e]`, `[mocha-remote-ws]` prefixes |
284286

@@ -288,8 +290,8 @@ The app process (`testing[<pid>]`) often stays alive in `testing.log` / `simulat
288290
# Jet WS lifecycle
289291
rg '\[jet-ws\]|\[rnfb-e2e\]|\[mocha-remote-ws\]|Jet client disconnected|RETRYABLE_DISCONNECT' detox-step.log
290292

291-
# Disconnect resource context
292-
rg '\[jet-ws\] disconnect_context' detox-step.log
293+
# Disconnect resource context (+ orchestrate phase when defer-run enabled)
294+
rg '\[jet-ws\] disconnect_context|\[rnfb-e2e\] orchestrate-state' detox-step.log
293295

294296
# Grace window recovered (no e2e retry needed)
295297
rg '\[jet-ws\] reconnect_recovered|\[mocha-remote-ws\] reconnect_recovered' detox-step.log

okf-bundle/testing/coverage-design.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ Jet self-wraps under NYC with `--coverage`.
140140
- Metro bundles `packages/*/dist/module/**` with inline source maps (`tests/.babelrc`: `useInlineSourceMaps: true`).
141141
- NYC (`tests/nyc.config.js`) remaps to `packages/*/lib/**`**`coverage/lcov.info`** (`cwd: '..'`).
142142
- Jet re-invokes under `tests/node_modules/.bin/nyc` (checks `NYC_CONFIG`). Detox/macOS need no extra `nyc` prefix; Jet must run from `tests/`.
143-
- **Transfer:** patched Jet/mocha-remote WS only (`coverage-ready``pull-coverage``coverage-data``coverage-ack`); HTTP POST `/coverage` deleted (`attachHttpServer` removed). Patches: `.yarn/patches/` (`jet`, `mocha-remote-client`, `mocha-remote-server`). See [iOS issues 6–6b](../ci-workflows/ios.md#6-jet-websocket-disconnect-1006--1001), [issue 8](../ci-workflows/ios.md#8-coverage-teardown-handshake-failure-tests-pass-nyc-00), [jet patch workflow](../ci-workflows/detox-patches.md#updating-the-jet-patch-headless).
143+
- **Transfer:** patched Jet/mocha-remote WS only (`coverage-ready``pull-coverage``coverage-data``coverage-ack`); HTTP POST `/coverage` deleted (`attachHttpServer` removed). Host launch/orchestrate control uses a **separate** HTTP server on **8091** (not the 8090 WS stack) — see [Jet host orchestration](running-e2e.md#jet-host-orchestration-ports-and-launch-gate). Patches: `.yarn/patches/` (`jet`, `mocha-remote-client`, `mocha-remote-server`). See [iOS issues 6–6b](../ci-workflows/ios.md#6-jet-websocket-disconnect-1006--1001), [issue 8](../ci-workflows/ios.md#8-coverage-teardown-handshake-failure-tests-pass-nyc-00), [jet patch workflow](../ci-workflows/detox-patches.md#updating-the-jet-patch-headless).
144144

145145
**NYC settings:**
146146

0 commit comments

Comments
 (0)