@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.d.mts
CHANGED
|
@@ -90,6 +90,8 @@ interface AndroidConfig {
|
|
|
90
90
|
packageName: string;
|
|
91
91
|
/** Launch activity, e.g. `.MainActivity`. */
|
|
92
92
|
activity: string;
|
|
93
|
+
/** App URL scheme used for Expo dev-client deep links, e.g. `myapp`. */
|
|
94
|
+
scheme?: string;
|
|
93
95
|
/** Gradle tasks that build the app + androidTest APKs. */
|
|
94
96
|
gradleTasks?: string[];
|
|
95
97
|
/** Built app APK path. Defaults to the standard debug output path. */
|
|
@@ -194,6 +196,13 @@ type StepAction = {
|
|
|
194
196
|
readonly command: CommandSpec;
|
|
195
197
|
readonly max: number;
|
|
196
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* Terminal failure substrings to watch for in the backing process's captured log. If one
|
|
201
|
+
* appears, the readiness wait aborts EARLY (the build/test failed and will never become
|
|
202
|
+
* ready) instead of burning the full timeout. Set by the planner on the companion-ready step
|
|
203
|
+
* (see COMPANION_FAILURE_MARKERS); the executor pairs them with the process's log path.
|
|
204
|
+
*/
|
|
205
|
+
readonly failureMarkers?: readonly string[];
|
|
197
206
|
};
|
|
198
207
|
/** The lifecycle stage a step belongs to. A failure is attributed to its stage. */
|
|
199
208
|
type Stage = 'config' | 'metro' | 'device' | 'build' | 'companion' | 'app-launch' | 'hermes-target' | 'playwright' | 'cleanup';
|
package/dist/index.d.ts
CHANGED
|
@@ -90,6 +90,8 @@ interface AndroidConfig {
|
|
|
90
90
|
packageName: string;
|
|
91
91
|
/** Launch activity, e.g. `.MainActivity`. */
|
|
92
92
|
activity: string;
|
|
93
|
+
/** App URL scheme used for Expo dev-client deep links, e.g. `myapp`. */
|
|
94
|
+
scheme?: string;
|
|
93
95
|
/** Gradle tasks that build the app + androidTest APKs. */
|
|
94
96
|
gradleTasks?: string[];
|
|
95
97
|
/** Built app APK path. Defaults to the standard debug output path. */
|
|
@@ -194,6 +196,13 @@ type StepAction = {
|
|
|
194
196
|
readonly command: CommandSpec;
|
|
195
197
|
readonly max: number;
|
|
196
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* Terminal failure substrings to watch for in the backing process's captured log. If one
|
|
201
|
+
* appears, the readiness wait aborts EARLY (the build/test failed and will never become
|
|
202
|
+
* ready) instead of burning the full timeout. Set by the planner on the companion-ready step
|
|
203
|
+
* (see COMPANION_FAILURE_MARKERS); the executor pairs them with the process's log path.
|
|
204
|
+
*/
|
|
205
|
+
readonly failureMarkers?: readonly string[];
|
|
197
206
|
};
|
|
198
207
|
/** The lifecycle stage a step belongs to. A failure is attributed to its stage. */
|
|
199
208
|
type Stage = 'config' | 'metro' | 'device' | 'build' | 'companion' | 'app-launch' | 'hermes-target' | 'playwright' | 'cleanup';
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,10 @@ var DEFAULTS = {
|
|
|
42
42
|
androidTokenFileName: "rn-driver-touch-token"
|
|
43
43
|
};
|
|
44
44
|
var SECRET_PLACEHOLDER = "<token-file>";
|
|
45
|
+
var COMPANION_FAILURE_MARKERS = {
|
|
46
|
+
ios: ["** BUILD FAILED **", "** TEST FAILED **"],
|
|
47
|
+
android: ["INSTRUMENTATION_FAILED", "Process crashed"]
|
|
48
|
+
};
|
|
45
49
|
|
|
46
50
|
// src/plan/env.ts
|
|
47
51
|
function buildIosDriverEnv(resolved, metro, timeoutMs) {
|
|
@@ -204,9 +208,10 @@ function planAndroid(input) {
|
|
|
204
208
|
}
|
|
205
209
|
}
|
|
206
210
|
});
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
push(
|
|
211
|
+
const launch1Command = launchCommandFor(android, resolved, serial, { forceStopBefore: true });
|
|
212
|
+
const launch2Command = launchCommandFor(android, resolved, serial, { forceStopBefore: false });
|
|
213
|
+
push(launchStep("android.launch-1", android, launch1Command));
|
|
214
|
+
push(hermesStep("android.hermes-1", android, metro, resolved, hermesDeviceName, launch1Command));
|
|
210
215
|
push({
|
|
211
216
|
id: "android.forward-clean",
|
|
212
217
|
stage: "companion",
|
|
@@ -260,11 +265,14 @@ function planAndroid(input) {
|
|
|
260
265
|
port: resolved.touchPort,
|
|
261
266
|
tokenFile: resolved.tokenFile,
|
|
262
267
|
timeoutMs: resolved.companionReadyTimeoutMs
|
|
263
|
-
}
|
|
268
|
+
},
|
|
269
|
+
// Abort early if `am instrument` reports the companion failed to start (crash / missing
|
|
270
|
+
// androidTest target) instead of waiting out the readiness budget.
|
|
271
|
+
failureMarkers: COMPANION_FAILURE_MARKERS.android
|
|
264
272
|
}
|
|
265
273
|
});
|
|
266
|
-
push(launchStep("android.launch-2", android,
|
|
267
|
-
push(hermesStep("android.hermes-2", android, metro, resolved, hermesDeviceName,
|
|
274
|
+
push(launchStep("android.launch-2", android, launch2Command));
|
|
275
|
+
push(hermesStep("android.hermes-2", android, metro, resolved, hermesDeviceName, launch2Command));
|
|
268
276
|
const cleanup = [
|
|
269
277
|
{
|
|
270
278
|
type: "kill-process",
|
|
@@ -319,22 +327,34 @@ function planAndroid(input) {
|
|
|
319
327
|
playwright: playwrightCommand(playwright, specs, passthrough)
|
|
320
328
|
};
|
|
321
329
|
}
|
|
322
|
-
function launchCommandFor(android, serial) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
function launchCommandFor(android, resolved, serial, opts) {
|
|
331
|
+
if (android.launch.kind === "plain") {
|
|
332
|
+
return adb(serial, [
|
|
333
|
+
"shell",
|
|
334
|
+
"am",
|
|
335
|
+
"start",
|
|
336
|
+
"-W",
|
|
337
|
+
"-n",
|
|
338
|
+
`${android.packageName}/${android.activity}`
|
|
339
|
+
]);
|
|
340
|
+
}
|
|
341
|
+
if (!android.scheme) {
|
|
342
|
+
throw new Error('android.scheme is required when android.launch.kind is "expo-dev-client"');
|
|
343
|
+
}
|
|
344
|
+
const launchScript = `am start -a android.intent.action.VIEW -d ${shellSingleQuote(
|
|
345
|
+
devClientUrl(android.scheme, resolved.initialUrl)
|
|
346
|
+
)}`;
|
|
347
|
+
return adbShellScript(
|
|
348
|
+
serial,
|
|
349
|
+
opts.forceStopBefore ? `am force-stop ${android.packageName} && ${launchScript}` : launchScript
|
|
350
|
+
);
|
|
331
351
|
}
|
|
332
|
-
function launchStep(id, android,
|
|
352
|
+
function launchStep(id, android, command) {
|
|
333
353
|
return {
|
|
334
354
|
id,
|
|
335
355
|
stage: "app-launch",
|
|
336
356
|
description: `Launch ${android.packageName}/${android.activity}`,
|
|
337
|
-
action: { type: "command", command
|
|
357
|
+
action: { type: "command", command }
|
|
338
358
|
};
|
|
339
359
|
}
|
|
340
360
|
function hermesStep(id, android, metro, resolved, deviceNameMatch, launchCommand) {
|
|
@@ -368,6 +388,12 @@ function debugHostXml(host) {
|
|
|
368
388
|
function adb(serial, args) {
|
|
369
389
|
return { command: "adb", args: ["-s", serial, ...args] };
|
|
370
390
|
}
|
|
391
|
+
function devClientUrl(scheme, initialUrl) {
|
|
392
|
+
return `${scheme}://expo-development-client/?url=${initialUrl}`;
|
|
393
|
+
}
|
|
394
|
+
function shellSingleQuote(value) {
|
|
395
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
396
|
+
}
|
|
371
397
|
function adbShellScript(serial, remote) {
|
|
372
398
|
return { command: "adb", args: ["-s", serial, "shell", remote] };
|
|
373
399
|
}
|
|
@@ -413,8 +439,7 @@ function planIos(input) {
|
|
|
413
439
|
// Pass the resolved UI-test scheme so a custom `ios.uitestScheme` scaffolds
|
|
414
440
|
// the SAME target that companion startup later builds (default is
|
|
415
441
|
// `${appScheme}UITests`).
|
|
416
|
-
command:
|
|
417
|
-
"rn-driver-xctest-scaffold",
|
|
442
|
+
command: cmd("node_modules/.bin/rn-driver-xctest-scaffold", [
|
|
418
443
|
"--ios-dir",
|
|
419
444
|
"ios",
|
|
420
445
|
"--project-name",
|
|
@@ -574,7 +599,10 @@ function planIos(input) {
|
|
|
574
599
|
port: resolved.touchPort,
|
|
575
600
|
tokenFile: resolved.tokenFile,
|
|
576
601
|
timeoutMs: resolved.companionReadyTimeoutMs
|
|
577
|
-
}
|
|
602
|
+
},
|
|
603
|
+
// Abort early if `xcodebuild test` reports a build/test failure (it lingers "alive" after, so
|
|
604
|
+
// the 300s readiness budget would otherwise be burnt waiting for a companion that cannot bind).
|
|
605
|
+
failureMarkers: COMPANION_FAILURE_MARKERS.ios
|
|
578
606
|
}
|
|
579
607
|
});
|
|
580
608
|
if (isDevClient) {
|
|
@@ -708,7 +736,7 @@ function placeholderIos(ios, metro) {
|
|
|
708
736
|
initialUrl: ios.launch.initialUrl ?? metro.url
|
|
709
737
|
};
|
|
710
738
|
}
|
|
711
|
-
function placeholderAndroid(android,
|
|
739
|
+
function placeholderAndroid(android, metro) {
|
|
712
740
|
return {
|
|
713
741
|
serial: "<android-serial>",
|
|
714
742
|
touchPort: android.companion?.port ?? DEFAULTS.companionPort,
|
|
@@ -716,7 +744,8 @@ function placeholderAndroid(android, _metro) {
|
|
|
716
744
|
hermesTimeoutMs: DEFAULTS.hermesTargetTimeoutMs,
|
|
717
745
|
tokenFile: SECRET_PLACEHOLDER,
|
|
718
746
|
deviceTokenFileName: DEFAULTS.androidTokenFileName,
|
|
719
|
-
instrumentationTarget: instrumentationTarget(android)
|
|
747
|
+
instrumentationTarget: instrumentationTarget(android),
|
|
748
|
+
initialUrl: android.launch.initialUrl ?? metro.url
|
|
720
749
|
};
|
|
721
750
|
}
|
|
722
751
|
|
|
@@ -743,7 +772,7 @@ function buildDryRunPlan(config, platform, opts = {}) {
|
|
|
743
772
|
return planAndroid({
|
|
744
773
|
android,
|
|
745
774
|
metro,
|
|
746
|
-
resolved: placeholderAndroid(android),
|
|
775
|
+
resolved: placeholderAndroid(android, metro),
|
|
747
776
|
playwright: config.playwright,
|
|
748
777
|
timeoutMs: config.timeoutMs,
|
|
749
778
|
specs,
|
|
@@ -785,8 +814,10 @@ function renderAction(action) {
|
|
|
785
814
|
return `write ${action.path}${action.mode ? ` (mode ${action.mode.toString(8)})` : ""}`;
|
|
786
815
|
case "free-port":
|
|
787
816
|
return `free-port ${action.port}`;
|
|
788
|
-
case "probe":
|
|
789
|
-
|
|
817
|
+
case "probe": {
|
|
818
|
+
const fastFail = action.failureMarkers?.length ? ` [fast-fail on: ${action.failureMarkers.join(", ")}]` : "";
|
|
819
|
+
return `probe ${renderProbe(action.probe)}${fastFail}`;
|
|
820
|
+
}
|
|
790
821
|
default: {
|
|
791
822
|
const _exhaustive = action;
|
|
792
823
|
throw new Error(`unhandled action: ${JSON.stringify(_exhaustive)}`);
|
|
@@ -852,6 +883,7 @@ var IOS_KEYS = /* @__PURE__ */ new Set([
|
|
|
852
883
|
var ANDROID_KEYS = /* @__PURE__ */ new Set([
|
|
853
884
|
"packageName",
|
|
854
885
|
"activity",
|
|
886
|
+
"scheme",
|
|
855
887
|
"gradleTasks",
|
|
856
888
|
"appApkPath",
|
|
857
889
|
"testApkPath",
|
|
@@ -953,6 +985,8 @@ function validateAndroid(android, errors) {
|
|
|
953
985
|
reportUnknownKeys("config.android", android, ANDROID_KEYS, errors);
|
|
954
986
|
requireAndroidPackage("config.android.packageName", android.packageName, errors);
|
|
955
987
|
requireAndroidActivity("config.android.activity", android.activity, errors);
|
|
988
|
+
if (android.scheme !== void 0)
|
|
989
|
+
requireAppScheme("config.android.scheme", android.scheme, errors);
|
|
956
990
|
optionalString("config.android.appApkPath", android.appApkPath, errors);
|
|
957
991
|
optionalString("config.android.testApkPath", android.testApkPath, errors);
|
|
958
992
|
if (android.instrumentationTarget !== void 0)
|
|
@@ -965,7 +999,10 @@ function validateAndroid(android, errors) {
|
|
|
965
999
|
errors.push("config.android.gradleTasks: expected an array of strings");
|
|
966
1000
|
}
|
|
967
1001
|
validateCompanion("config.android.companion", android.companion, errors);
|
|
968
|
-
validateLaunch("config.android.launch", android.launch, errors);
|
|
1002
|
+
const launch = validateLaunch("config.android.launch", android.launch, errors);
|
|
1003
|
+
if (launch?.kind === "expo-dev-client" && android.scheme === void 0) {
|
|
1004
|
+
errors.push('config.android.scheme: required when android.launch.kind is "expo-dev-client"');
|
|
1005
|
+
}
|
|
969
1006
|
}
|
|
970
1007
|
function validateCompanion(path, companion, errors) {
|
|
971
1008
|
if (companion === void 0) return;
|
|
@@ -1011,6 +1048,7 @@ function requireString(path, value, errors) {
|
|
|
1011
1048
|
}
|
|
1012
1049
|
var ANDROID_PACKAGE_RE = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
1013
1050
|
var ANDROID_ACTIVITY_RE = /^\.?[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)*$/;
|
|
1051
|
+
var APP_SCHEME_RE = /^[a-z][a-z0-9+.-]*$/;
|
|
1014
1052
|
function requireAndroidPackage(path, value, errors) {
|
|
1015
1053
|
if (typeof value !== "string" || value.trim() === "") {
|
|
1016
1054
|
errors.push(`${path}: required non-empty string`);
|
|
@@ -1027,6 +1065,17 @@ function requireAndroidActivity(path, value, errors) {
|
|
|
1027
1065
|
if (!ANDROID_ACTIVITY_RE.test(value))
|
|
1028
1066
|
errors.push(`${path}: expected an activity name (e.g. .MainActivity)`);
|
|
1029
1067
|
}
|
|
1068
|
+
function requireAppScheme(path, value, errors) {
|
|
1069
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
1070
|
+
errors.push(`${path}: required non-empty string`);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
if (!APP_SCHEME_RE.test(value)) {
|
|
1074
|
+
errors.push(
|
|
1075
|
+
`${path}: expected a valid URL scheme (lowercase letter, then lowercase letters, digits, +, ., or -)`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1030
1079
|
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_]*)*$/;
|
|
1031
1080
|
function requireInstrumentationTarget(path, value, errors) {
|
|
1032
1081
|
if (typeof value !== "string" || value.trim() === "") {
|