Skip to content

Commit aafaf4b

Browse files
authored
chore: extend test script to support all platforms (#2558)
1 parent e0e8b56 commit aafaf4b

File tree

2 files changed

+106
-55
lines changed

2 files changed

+106
-55
lines changed

scripts/testing/test-e2e.mts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import { isMain } from "../helpers.js";
1010
* Invokes a shell command with optional arguments.
1111
*/
1212
export function $(command: string, ...args: string[]) {
13-
const { status } = spawnSync(command, args, { stdio: "inherit" });
13+
const { status } = spawnSync(command, args, {
14+
stdio: "inherit",
15+
shell: process.platform === "win32",
16+
});
1417
if (status !== 0) {
1518
throw new Error(
1619
`An error occurred while executing: ${command} ${args.join(" ")}`
@@ -25,6 +28,7 @@ export function $(command: string, ...args: string[]) {
2528
export function $$(command: string, ...args: string[]): string {
2629
const { status, stderr, stdout } = spawnSync(command, args, {
2730
stdio: ["ignore", "pipe", "pipe"],
31+
shell: process.platform === "win32",
2832
encoding: "utf-8",
2933
});
3034
if (status !== 0) {

scripts/testing/test-matrix.mts

Lines changed: 101 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { readTextFile, toVersionNumber, v } from "../helpers.js";
1010
import { setReactVersion } from "../internal/set-react-version.mts";
1111
import type { BuildConfig, TargetPlatform } from "../types.js";
1212
import { green, red, yellow } from "../utils/colors.mjs";
13+
import { rm_r } from "../utils/filesystem.mjs";
1314
import { getIOSSimulatorName, installPods } from "./test-apple.mts";
1415
import { $, $$, test } from "./test-e2e.mts";
1516

@@ -18,16 +19,37 @@ type PlatformConfig = {
1819
engines: ReadonlyArray<"hermes" | "jsc">;
1920
isAvailable: (config: Required<BuildConfig>) => boolean;
2021
prebuild: (config: Required<BuildConfig>) => Promise<void>;
22+
additionalBuildArgs?: () => string[];
23+
requiresManualTesting?: boolean;
2124
};
2225

2326
const DEFAULT_PLATFORMS = ["android", "ios"];
27+
const PACKAGE_MANAGER = "yarn";
28+
const TAG = "┃";
2429
const TEST_VARIANTS = ["paper", "fabric"] as const;
2530

31+
function isVariantSupported({ version, variant }: Required<BuildConfig>) {
32+
return variant === "fabric" || toVersionNumber(version) < v(0, 82, 0);
33+
}
34+
35+
function isAppleVariantSupported(config: Required<BuildConfig>) {
36+
if (process.platform !== "darwin") {
37+
return false;
38+
}
39+
40+
const { version, engine } = config;
41+
if (engine === "jsc" && toVersionNumber(version) >= v(0, 80, 0)) {
42+
return false;
43+
}
44+
45+
return isVariantSupported(config);
46+
}
47+
2648
const PLATFORM_CONFIG: Record<TargetPlatform, PlatformConfig> = {
2749
android: {
2850
name: "Android",
2951
engines: ["hermes"],
30-
isAvailable: ({ engine }) => engine === "hermes",
52+
isAvailable: isVariantSupported,
3153
prebuild: ({ variant }) => {
3254
if (variant === "fabric") {
3355
const properties = "android/gradle.properties";
@@ -43,42 +65,42 @@ const PLATFORM_CONFIG: Record<TargetPlatform, PlatformConfig> = {
4365
ios: {
4466
name: "iOS",
4567
engines: ["jsc", "hermes"],
46-
isAvailable: ({ version, engine }) => {
47-
if (process.platform !== "darwin") {
48-
return false;
49-
}
50-
51-
if (engine === "jsc" && toVersionNumber(version) >= v(0, 80, 0)) {
52-
return false;
53-
}
54-
55-
return true;
56-
},
68+
isAvailable: isAppleVariantSupported,
5769
prebuild: installPods,
70+
additionalBuildArgs: () => ["--device", getIOSSimulatorName()],
5871
},
5972
macos: {
6073
name: "macOS",
6174
engines: ["jsc", "hermes"],
62-
isAvailable: () => false,
75+
isAvailable: isAppleVariantSupported,
6376
prebuild: installPods,
77+
requiresManualTesting: true,
6478
},
6579
visionos: {
6680
name: "visionOS",
6781
engines: ["jsc", "hermes"],
68-
isAvailable: () => false,
82+
isAvailable: isAppleVariantSupported,
6983
prebuild: installPods,
84+
requiresManualTesting: true,
7085
},
7186
windows: {
7287
name: "Windows",
7388
engines: ["hermes"],
74-
isAvailable: () => false,
75-
prebuild: () => Promise.resolve(),
89+
isAvailable: () => process.platform === "win32",
90+
prebuild: () => {
91+
rm_r("windows/ExperimentalFeatures.props");
92+
$(
93+
PACKAGE_MANAGER,
94+
"install-windows-test-app",
95+
"--msbuildprops",
96+
"WindowsTargetPlatformVersion=10.0.26100.0"
97+
);
98+
return Promise.resolve();
99+
},
100+
requiresManualTesting: true,
76101
},
77102
};
78103

79-
const PACKAGE_MANAGER = "yarn";
80-
const TAG = "┃";
81-
82104
const rootDir = fileURLToPath(new URL("../..", import.meta.url));
83105

84106
function log(message = "", tag = TAG) {
@@ -92,6 +114,7 @@ function run(script: string, logPath: string) {
92114
const fd = fs.openSync(logPath, "a", 0o644);
93115
const proc = spawn(PACKAGE_MANAGER, ["run", script], {
94116
stdio: ["ignore", fd, fd],
117+
shell: process.platform === "win32",
95118
});
96119
return proc;
97120
}
@@ -124,13 +147,10 @@ function validatePlatforms(platforms: string[]): TargetPlatform[] {
124147
switch (platform) {
125148
case "android":
126149
case "ios":
127-
filtered.push(platform);
128-
break;
129-
130150
case "macos":
131151
case "visionos":
132152
case "windows":
133-
log(yellow(`⚠ Unsupported platform: ${platform}`));
153+
filtered.push(platform);
134154
break;
135155

136156
default:
@@ -153,6 +173,18 @@ function parseArgs(args: string[]) {
153173
description: "Test iOS",
154174
type: "boolean",
155175
},
176+
macos: {
177+
description: "Test macOS",
178+
type: "boolean",
179+
},
180+
visionos: {
181+
description: "Test visionOS",
182+
type: "boolean",
183+
},
184+
windows: {
185+
description: "Test Windows",
186+
type: "boolean",
187+
},
156188
},
157189
strict: true,
158190
allowPositionals: true,
@@ -168,10 +200,10 @@ function parseArgs(args: string[]) {
168200
};
169201
}
170202

171-
function prestart() {
203+
function waitForUserInput(message: string): Promise<void> {
172204
return !process.stdin.isTTY
173205
? Promise.resolve()
174-
: new Promise((resolve) => {
206+
: new Promise((resolve, reject) => {
175207
const stdin = process.stdin;
176208
const rawMode = stdin.isRaw;
177209
const encoding = stdin.readableEncoding || undefined;
@@ -185,31 +217,24 @@ function prestart() {
185217
stdin.setRawMode(rawMode);
186218
if (typeof key === "string" && key === "\u0003") {
187219
showBanner("❌ Canceled");
188-
// eslint-disable-next-line local/no-process-exit
189-
process.exit(1);
220+
reject(1);
221+
} else {
222+
resolve();
190223
}
191-
resolve(true);
192224
});
193-
process.stdout.write(
194-
`${TAG} Before continuing, make sure all emulators/simulators and Appium/Metro instances are closed.\n${TAG}\n${TAG} Press any key to continue...`
195-
);
225+
process.stdout.write(message);
196226
});
197227
}
198228

199229
/**
200-
* Invokes `react-native run-<platform>`.
230+
* Invokes `rnx-cli run --platform <platform>`.
201231
*/
202232
function buildAndRun(platform: TargetPlatform) {
203-
switch (platform) {
204-
case "ios": {
205-
const simulator = getIOSSimulatorName();
206-
$(PACKAGE_MANAGER, platform, "--device", simulator);
207-
break;
208-
}
209-
default: {
210-
$(PACKAGE_MANAGER, platform);
211-
break;
212-
}
233+
const { additionalBuildArgs } = PLATFORM_CONFIG[platform];
234+
if (additionalBuildArgs) {
235+
$(PACKAGE_MANAGER, platform, ...additionalBuildArgs());
236+
} else {
237+
$(PACKAGE_MANAGER, platform);
213238
}
214239
}
215240

@@ -229,7 +254,13 @@ async function buildRunTest({ version, platform, variant }: BuildConfig) {
229254
showBanner(`Build ${setup.name} [${variant}, ${engine}]`);
230255
await setup.prebuild(configWithEngine);
231256
buildAndRun(platform);
232-
await test(platform, [variant, engine]);
257+
if (setup.requiresManualTesting) {
258+
await waitForUserInput(
259+
`${TAG}\n${TAG} ${yellow("⚠")} ${setup.name} requires manual testing. When you're done, press any key to continue...`
260+
);
261+
} else {
262+
await test(platform, [variant, engine]);
263+
}
233264
}
234265
}
235266

@@ -265,7 +296,7 @@ async function withReactNativeVersion(
265296
reset(rootDir);
266297

267298
if (version) {
268-
await setReactVersion(version, true);
299+
await setReactVersion(version, false);
269300
} else {
270301
log();
271302
}
@@ -291,15 +322,20 @@ if (platforms.length === 0) {
291322
process.exitCode = 1;
292323
showBanner(red("No valid platforms were specified"));
293324
} else {
294-
TEST_VARIANTS.reduce((job, variant) => {
295-
return job.then(() =>
296-
withReactNativeVersion(version, async () => {
297-
for (const platform of platforms) {
298-
await buildRunTest({ version, platform, variant });
299-
}
300-
})
301-
);
302-
}, prestart())
325+
TEST_VARIANTS.reduce(
326+
(job, variant) => {
327+
return job.then(() =>
328+
withReactNativeVersion(version, async () => {
329+
for (const platform of platforms) {
330+
await buildRunTest({ version, platform, variant });
331+
}
332+
})
333+
);
334+
},
335+
waitForUserInput(
336+
`${TAG} Before continuing, make sure all emulators/simulators and Appium/Metro instances are closed.\n${TAG}\n${TAG} Press any key to continue...`
337+
)
338+
)
303339
.then(() => {
304340
showBanner("Initialize new app");
305341
$(
@@ -330,12 +366,23 @@ if (platforms.length === 0) {
330366
"-p",
331367
"windows",
332368
];
333-
const { status } = spawnSync(PACKAGE_MANAGER, args, { stdio: "inherit" });
369+
const { status } = spawnSync(PACKAGE_MANAGER, args, {
370+
stdio: "inherit",
371+
shell: process.platform === "win32",
372+
});
334373
if (status !== 1) {
335374
throw new Error("Expected an error");
336375
}
337376
})
338377
.then(() => {
339378
showBanner(green("✔ Pass"));
379+
})
380+
.catch((e) => {
381+
if (typeof e === "number") {
382+
process.exitCode = e;
383+
} else {
384+
process.exitCode = 1;
385+
showBanner(`❌ ${e}`);
386+
}
340387
});
341388
}

0 commit comments

Comments
 (0)