@unrulysystems/rn-playwright-driver-runner 0.1.1 → 0.2.0

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/dist/cli.mjs CHANGED
@@ -47,6 +47,10 @@ var DEFAULTS = {
47
47
  androidTokenFileName: "rn-driver-touch-token"
48
48
  };
49
49
  var SECRET_PLACEHOLDER = "<token-file>";
50
+ var COMPANION_FAILURE_MARKERS = {
51
+ ios: ["** BUILD FAILED **", "** TEST FAILED **"],
52
+ android: ["INSTRUMENTATION_FAILED", "Process crashed"]
53
+ };
50
54
 
51
55
  // src/plan/env.ts
52
56
  function buildIosDriverEnv(resolved, metro, timeoutMs) {
@@ -266,7 +270,10 @@ function planAndroid(input) {
266
270
  port: resolved.touchPort,
267
271
  tokenFile: resolved.tokenFile,
268
272
  timeoutMs: resolved.companionReadyTimeoutMs
269
- }
273
+ },
274
+ // Abort early if `am instrument` reports the companion failed to start (crash / missing
275
+ // androidTest target) instead of waiting out the readiness budget.
276
+ failureMarkers: COMPANION_FAILURE_MARKERS.android
270
277
  }
271
278
  });
272
279
  push(launchStep("android.launch-2", android, launch2Command));
@@ -597,7 +604,10 @@ function planIos(input) {
597
604
  port: resolved.touchPort,
598
605
  tokenFile: resolved.tokenFile,
599
606
  timeoutMs: resolved.companionReadyTimeoutMs
600
- }
607
+ },
608
+ // Abort early if `xcodebuild test` reports a build/test failure (it lingers "alive" after, so
609
+ // the 300s readiness budget would otherwise be burnt waiting for a companion that cannot bind).
610
+ failureMarkers: COMPANION_FAILURE_MARKERS.ios
601
611
  }
602
612
  });
603
613
  if (isDevClient) {
@@ -853,8 +863,10 @@ function renderAction(action) {
853
863
  return `write ${action.path}${action.mode ? ` (mode ${action.mode.toString(8)})` : ""}`;
854
864
  case "free-port":
855
865
  return `free-port ${action.port}`;
856
- case "probe":
857
- return `probe ${renderProbe(action.probe)}`;
866
+ case "probe": {
867
+ const fastFail = action.failureMarkers?.length ? ` [fast-fail on: ${action.failureMarkers.join(", ")}]` : "";
868
+ return `probe ${renderProbe(action.probe)}${fastFail}`;
869
+ }
858
870
  default: {
859
871
  const _exhaustive = action;
860
872
  throw new Error(`unhandled action: ${JSON.stringify(_exhaustive)}`);
@@ -900,6 +912,25 @@ function renderProbe(probe) {
900
912
  }
901
913
  }
902
914
 
915
+ // src/runner/probe-failure.ts
916
+ var ProbeFailure = class extends Error {
917
+ marker;
918
+ constructor(marker, detail) {
919
+ super(
920
+ `companion process reported a terminal failure ("${marker}") \u2014 aborting the readiness wait. The build/test failed; it will not become ready.${detail ? `
921
+ ${detail}` : ""}`
922
+ );
923
+ this.name = "ProbeFailure";
924
+ this.marker = marker;
925
+ }
926
+ };
927
+ function findFailureMarker(log, markers) {
928
+ for (const marker of markers) {
929
+ if (log.includes(marker)) return marker;
930
+ }
931
+ return null;
932
+ }
933
+
903
934
  // src/runner/execute.ts
904
935
  var StageError = class extends Error {
905
936
  stage;
@@ -967,22 +998,30 @@ async function runStep(step, runner, opts, processes, isAlive) {
967
998
  case "probe": {
968
999
  const key = processKeyForProbe(action.probe);
969
1000
  const aliveFn = key === null ? () => true : () => isAlive(key);
970
- let ready = await runner.probe(action.probe, aliveFn);
971
- let remaining = action.retry?.max ?? 0;
972
- while (!ready && remaining > 0) {
973
- remaining -= 1;
974
- if (action.retry) {
975
- runner.log(`retry ${step.id}: re-running launch (${remaining} attempt(s) left)`);
976
- await runner.exec(action.retry.command);
1001
+ const watch = key !== null && action.failureMarkers && action.failureMarkers.length > 0 ? { logPath: logPathFor(opts.logDir, key), failureMarkers: action.failureMarkers } : void 0;
1002
+ try {
1003
+ let ready = await runner.probe(action.probe, aliveFn, watch);
1004
+ let remaining = action.retry?.max ?? 0;
1005
+ while (!ready && remaining > 0) {
1006
+ remaining -= 1;
1007
+ if (action.retry) {
1008
+ runner.log(`retry ${step.id}: re-running launch (${remaining} attempt(s) left)`);
1009
+ await runner.exec(action.retry.command);
1010
+ }
1011
+ ready = await runner.probe(action.probe, aliveFn, watch);
977
1012
  }
978
- ready = await runner.probe(action.probe, aliveFn);
979
- }
980
- if (!ready) {
981
- throw new StageError(
982
- step.stage,
983
- step.id,
984
- `readiness timed out after ${action.probe.timeoutMs}ms`
985
- );
1013
+ if (!ready) {
1014
+ throw new StageError(
1015
+ step.stage,
1016
+ step.id,
1017
+ `readiness timed out after ${action.probe.timeoutMs}ms`
1018
+ );
1019
+ }
1020
+ } catch (error) {
1021
+ if (error instanceof ProbeFailure) {
1022
+ throw new StageError(step.stage, step.id, error.message);
1023
+ }
1024
+ throw error;
986
1025
  }
987
1026
  return;
988
1027
  }
@@ -1121,10 +1160,15 @@ var NodeProcessRunner = class {
1121
1160
  }
1122
1161
  if (pids.length > 0) await delay(1e3);
1123
1162
  }
1124
- probe(probe, isAlive) {
1163
+ probe(probe, isAlive, watch) {
1125
1164
  const deadline = Date.now() + probe.timeoutMs;
1126
1165
  const attempt = async () => {
1127
1166
  for (; ; ) {
1167
+ if (watch) {
1168
+ const log = await readWatchedLog(watch.logPath);
1169
+ const marker = findFailureMarker(log, watch.failureMarkers);
1170
+ if (marker) throw new ProbeFailure(marker, lastLines(log));
1171
+ }
1128
1172
  if (!isAlive()) return false;
1129
1173
  if (await probeOnce(probe)) return true;
1130
1174
  if (Date.now() >= deadline) return false;
@@ -1169,6 +1213,19 @@ function delay(ms) {
1169
1213
  setTimeout(resolve, ms);
1170
1214
  });
1171
1215
  }
1216
+ async function readWatchedLog(path4) {
1217
+ try {
1218
+ return await readFile(path4, "utf8");
1219
+ } catch (error) {
1220
+ throw new Error(
1221
+ `cannot read companion log for fast-fail marker detection (${path4}): ${String(error)}`,
1222
+ { cause: error }
1223
+ );
1224
+ }
1225
+ }
1226
+ function lastLines(text, n) {
1227
+ return text.split("\n").filter((line) => line.trim().length > 0).slice(-12).join("\n");
1228
+ }
1172
1229
  async function probeOnce(probe) {
1173
1230
  switch (probe.kind) {
1174
1231
  case "metro-status":