@unrulysystems/rn-playwright-driver-runner 0.1.0 → 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/index.mjs CHANGED
@@ -40,6 +40,10 @@ var DEFAULTS = {
40
40
  androidTokenFileName: "rn-driver-touch-token"
41
41
  };
42
42
  var SECRET_PLACEHOLDER = "<token-file>";
43
+ var COMPANION_FAILURE_MARKERS = {
44
+ ios: ["** BUILD FAILED **", "** TEST FAILED **"],
45
+ android: ["INSTRUMENTATION_FAILED", "Process crashed"]
46
+ };
43
47
 
44
48
  // src/plan/env.ts
45
49
  function buildIosDriverEnv(resolved, metro, timeoutMs) {
@@ -202,9 +206,10 @@ function planAndroid(input) {
202
206
  }
203
207
  }
204
208
  });
205
- const launchCommand = launchCommandFor(android, serial);
206
- push(launchStep("android.launch-1", android, serial));
207
- push(hermesStep("android.hermes-1", android, metro, resolved, hermesDeviceName, launchCommand));
209
+ const launch1Command = launchCommandFor(android, resolved, serial, { forceStopBefore: true });
210
+ const launch2Command = launchCommandFor(android, resolved, serial, { forceStopBefore: false });
211
+ push(launchStep("android.launch-1", android, launch1Command));
212
+ push(hermesStep("android.hermes-1", android, metro, resolved, hermesDeviceName, launch1Command));
208
213
  push({
209
214
  id: "android.forward-clean",
210
215
  stage: "companion",
@@ -258,11 +263,14 @@ function planAndroid(input) {
258
263
  port: resolved.touchPort,
259
264
  tokenFile: resolved.tokenFile,
260
265
  timeoutMs: resolved.companionReadyTimeoutMs
261
- }
266
+ },
267
+ // Abort early if `am instrument` reports the companion failed to start (crash / missing
268
+ // androidTest target) instead of waiting out the readiness budget.
269
+ failureMarkers: COMPANION_FAILURE_MARKERS.android
262
270
  }
263
271
  });
264
- push(launchStep("android.launch-2", android, serial));
265
- push(hermesStep("android.hermes-2", android, metro, resolved, hermesDeviceName, launchCommand));
272
+ push(launchStep("android.launch-2", android, launch2Command));
273
+ push(hermesStep("android.hermes-2", android, metro, resolved, hermesDeviceName, launch2Command));
266
274
  const cleanup = [
267
275
  {
268
276
  type: "kill-process",
@@ -317,22 +325,34 @@ function planAndroid(input) {
317
325
  playwright: playwrightCommand(playwright, specs, passthrough)
318
326
  };
319
327
  }
320
- function launchCommandFor(android, serial) {
321
- return adb(serial, [
322
- "shell",
323
- "am",
324
- "start",
325
- "-W",
326
- "-n",
327
- `${android.packageName}/${android.activity}`
328
- ]);
328
+ function launchCommandFor(android, resolved, serial, opts) {
329
+ if (android.launch.kind === "plain") {
330
+ return adb(serial, [
331
+ "shell",
332
+ "am",
333
+ "start",
334
+ "-W",
335
+ "-n",
336
+ `${android.packageName}/${android.activity}`
337
+ ]);
338
+ }
339
+ if (!android.scheme) {
340
+ throw new Error('android.scheme is required when android.launch.kind is "expo-dev-client"');
341
+ }
342
+ const launchScript = `am start -a android.intent.action.VIEW -d ${shellSingleQuote(
343
+ devClientUrl(android.scheme, resolved.initialUrl)
344
+ )}`;
345
+ return adbShellScript(
346
+ serial,
347
+ opts.forceStopBefore ? `am force-stop ${android.packageName} && ${launchScript}` : launchScript
348
+ );
329
349
  }
330
- function launchStep(id, android, serial) {
350
+ function launchStep(id, android, command) {
331
351
  return {
332
352
  id,
333
353
  stage: "app-launch",
334
354
  description: `Launch ${android.packageName}/${android.activity}`,
335
- action: { type: "command", command: launchCommandFor(android, serial) }
355
+ action: { type: "command", command }
336
356
  };
337
357
  }
338
358
  function hermesStep(id, android, metro, resolved, deviceNameMatch, launchCommand) {
@@ -366,6 +386,12 @@ function debugHostXml(host) {
366
386
  function adb(serial, args) {
367
387
  return { command: "adb", args: ["-s", serial, ...args] };
368
388
  }
389
+ function devClientUrl(scheme, initialUrl) {
390
+ return `${scheme}://expo-development-client/?url=${initialUrl}`;
391
+ }
392
+ function shellSingleQuote(value) {
393
+ return `'${value.replaceAll("'", "'\\''")}'`;
394
+ }
369
395
  function adbShellScript(serial, remote) {
370
396
  return { command: "adb", args: ["-s", serial, "shell", remote] };
371
397
  }
@@ -411,8 +437,7 @@ function planIos(input) {
411
437
  // Pass the resolved UI-test scheme so a custom `ios.uitestScheme` scaffolds
412
438
  // the SAME target that companion startup later builds (default is
413
439
  // `${appScheme}UITests`).
414
- command: npx([
415
- "rn-driver-xctest-scaffold",
440
+ command: cmd("node_modules/.bin/rn-driver-xctest-scaffold", [
416
441
  "--ios-dir",
417
442
  "ios",
418
443
  "--project-name",
@@ -572,7 +597,10 @@ function planIos(input) {
572
597
  port: resolved.touchPort,
573
598
  tokenFile: resolved.tokenFile,
574
599
  timeoutMs: resolved.companionReadyTimeoutMs
575
- }
600
+ },
601
+ // Abort early if `xcodebuild test` reports a build/test failure (it lingers "alive" after, so
602
+ // the 300s readiness budget would otherwise be burnt waiting for a companion that cannot bind).
603
+ failureMarkers: COMPANION_FAILURE_MARKERS.ios
576
604
  }
577
605
  });
578
606
  if (isDevClient) {
@@ -706,7 +734,7 @@ function placeholderIos(ios, metro) {
706
734
  initialUrl: ios.launch.initialUrl ?? metro.url
707
735
  };
708
736
  }
709
- function placeholderAndroid(android, _metro) {
737
+ function placeholderAndroid(android, metro) {
710
738
  return {
711
739
  serial: "<android-serial>",
712
740
  touchPort: android.companion?.port ?? DEFAULTS.companionPort,
@@ -714,7 +742,8 @@ function placeholderAndroid(android, _metro) {
714
742
  hermesTimeoutMs: DEFAULTS.hermesTargetTimeoutMs,
715
743
  tokenFile: SECRET_PLACEHOLDER,
716
744
  deviceTokenFileName: DEFAULTS.androidTokenFileName,
717
- instrumentationTarget: instrumentationTarget(android)
745
+ instrumentationTarget: instrumentationTarget(android),
746
+ initialUrl: android.launch.initialUrl ?? metro.url
718
747
  };
719
748
  }
720
749
 
@@ -741,7 +770,7 @@ function buildDryRunPlan(config, platform, opts = {}) {
741
770
  return planAndroid({
742
771
  android,
743
772
  metro,
744
- resolved: placeholderAndroid(android),
773
+ resolved: placeholderAndroid(android, metro),
745
774
  playwright: config.playwright,
746
775
  timeoutMs: config.timeoutMs,
747
776
  specs,
@@ -783,8 +812,10 @@ function renderAction(action) {
783
812
  return `write ${action.path}${action.mode ? ` (mode ${action.mode.toString(8)})` : ""}`;
784
813
  case "free-port":
785
814
  return `free-port ${action.port}`;
786
- case "probe":
787
- return `probe ${renderProbe(action.probe)}`;
815
+ case "probe": {
816
+ const fastFail = action.failureMarkers?.length ? ` [fast-fail on: ${action.failureMarkers.join(", ")}]` : "";
817
+ return `probe ${renderProbe(action.probe)}${fastFail}`;
818
+ }
788
819
  default: {
789
820
  const _exhaustive = action;
790
821
  throw new Error(`unhandled action: ${JSON.stringify(_exhaustive)}`);
@@ -850,6 +881,7 @@ var IOS_KEYS = /* @__PURE__ */ new Set([
850
881
  var ANDROID_KEYS = /* @__PURE__ */ new Set([
851
882
  "packageName",
852
883
  "activity",
884
+ "scheme",
853
885
  "gradleTasks",
854
886
  "appApkPath",
855
887
  "testApkPath",
@@ -951,6 +983,8 @@ function validateAndroid(android, errors) {
951
983
  reportUnknownKeys("config.android", android, ANDROID_KEYS, errors);
952
984
  requireAndroidPackage("config.android.packageName", android.packageName, errors);
953
985
  requireAndroidActivity("config.android.activity", android.activity, errors);
986
+ if (android.scheme !== void 0)
987
+ requireAppScheme("config.android.scheme", android.scheme, errors);
954
988
  optionalString("config.android.appApkPath", android.appApkPath, errors);
955
989
  optionalString("config.android.testApkPath", android.testApkPath, errors);
956
990
  if (android.instrumentationTarget !== void 0)
@@ -963,7 +997,10 @@ function validateAndroid(android, errors) {
963
997
  errors.push("config.android.gradleTasks: expected an array of strings");
964
998
  }
965
999
  validateCompanion("config.android.companion", android.companion, errors);
966
- validateLaunch("config.android.launch", android.launch, errors);
1000
+ const launch = validateLaunch("config.android.launch", android.launch, errors);
1001
+ if (launch?.kind === "expo-dev-client" && android.scheme === void 0) {
1002
+ errors.push('config.android.scheme: required when android.launch.kind is "expo-dev-client"');
1003
+ }
967
1004
  }
968
1005
  function validateCompanion(path, companion, errors) {
969
1006
  if (companion === void 0) return;
@@ -1009,6 +1046,7 @@ function requireString(path, value, errors) {
1009
1046
  }
1010
1047
  var ANDROID_PACKAGE_RE = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
1011
1048
  var ANDROID_ACTIVITY_RE = /^\.?[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*$/;
1049
+ var APP_SCHEME_RE = /^[a-z][a-z0-9+.-]*$/;
1012
1050
  function requireAndroidPackage(path, value, errors) {
1013
1051
  if (typeof value !== "string" || value.trim() === "") {
1014
1052
  errors.push(`${path}: required non-empty string`);
@@ -1025,6 +1063,17 @@ function requireAndroidActivity(path, value, errors) {
1025
1063
  if (!ANDROID_ACTIVITY_RE.test(value))
1026
1064
  errors.push(`${path}: expected an activity name (e.g. .MainActivity)`);
1027
1065
  }
1066
+ function requireAppScheme(path, value, errors) {
1067
+ if (typeof value !== "string" || value.trim() === "") {
1068
+ errors.push(`${path}: required non-empty string`);
1069
+ return;
1070
+ }
1071
+ if (!APP_SCHEME_RE.test(value)) {
1072
+ errors.push(
1073
+ `${path}: expected a valid URL scheme (lowercase letter, then lowercase letters, digits, +, ., or -)`
1074
+ );
1075
+ }
1076
+ }
1028
1077
  var ANDROID_INSTRUMENTATION_RE = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*\/[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*$/;
1029
1078
  function requireInstrumentationTarget(path, value, errors) {
1030
1079
  if (typeof value !== "string" || value.trim() === "") {