@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/CHANGELOG.md +23 -0
- package/README.md +79 -6
- package/bin/rn-driver.mjs +9 -0
- package/dist/cli.js +140 -45
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +140 -45
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +75 -26
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +75 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -3
- package/bin/rn-driver.ts +0 -17
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
|
|
206
|
-
|
|
207
|
-
push(
|
|
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,
|
|
265
|
-
push(hermesStep("android.hermes-2", android, metro, resolved, hermesDeviceName,
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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,
|
|
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
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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() === "") {
|