From 550c5e32b2f03462fba1743a0c2e945edf734871 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:02:01 +0200 Subject: [PATCH] chore: extend test script to support all platforms --- scripts/testing/test-e2e.mts | 6 +- scripts/testing/test-matrix.mts | 155 +++++++++++++++++++++----------- 2 files changed, 106 insertions(+), 55 deletions(-) diff --git a/scripts/testing/test-e2e.mts b/scripts/testing/test-e2e.mts index f8f073d3e..0645a66e0 100644 --- a/scripts/testing/test-e2e.mts +++ b/scripts/testing/test-e2e.mts @@ -10,7 +10,10 @@ import { isMain } from "../helpers.js"; * Invokes a shell command with optional arguments. */ export function $(command: string, ...args: string[]) { - const { status } = spawnSync(command, args, { stdio: "inherit" }); + const { status } = spawnSync(command, args, { + stdio: "inherit", + shell: process.platform === "win32", + }); if (status !== 0) { throw new Error( `An error occurred while executing: ${command} ${args.join(" ")}` @@ -25,6 +28,7 @@ export function $(command: string, ...args: string[]) { export function $$(command: string, ...args: string[]): string { const { status, stderr, stdout } = spawnSync(command, args, { stdio: ["ignore", "pipe", "pipe"], + shell: process.platform === "win32", encoding: "utf-8", }); if (status !== 0) { diff --git a/scripts/testing/test-matrix.mts b/scripts/testing/test-matrix.mts index 8ca92fee8..3c44a2bf8 100644 --- a/scripts/testing/test-matrix.mts +++ b/scripts/testing/test-matrix.mts @@ -10,6 +10,7 @@ import { readTextFile, toVersionNumber, v } from "../helpers.js"; import { setReactVersion } from "../internal/set-react-version.mts"; import type { BuildConfig, TargetPlatform } from "../types.js"; import { green, red, yellow } from "../utils/colors.mjs"; +import { rm_r } from "../utils/filesystem.mjs"; import { getIOSSimulatorName, installPods } from "./test-apple.mts"; import { $, $$, test } from "./test-e2e.mts"; @@ -18,16 +19,37 @@ type PlatformConfig = { engines: ReadonlyArray<"hermes" | "jsc">; isAvailable: (config: Required) => boolean; prebuild: (config: Required) => Promise; + additionalBuildArgs?: () => string[]; + requiresManualTesting?: boolean; }; const DEFAULT_PLATFORMS = ["android", "ios"]; +const PACKAGE_MANAGER = "yarn"; +const TAG = "┃"; const TEST_VARIANTS = ["paper", "fabric"] as const; +function isVariantSupported({ version, variant }: Required) { + return variant === "fabric" || toVersionNumber(version) < v(0, 82, 0); +} + +function isAppleVariantSupported(config: Required) { + if (process.platform !== "darwin") { + return false; + } + + const { version, engine } = config; + if (engine === "jsc" && toVersionNumber(version) >= v(0, 80, 0)) { + return false; + } + + return isVariantSupported(config); +} + const PLATFORM_CONFIG: Record = { android: { name: "Android", engines: ["hermes"], - isAvailable: ({ engine }) => engine === "hermes", + isAvailable: isVariantSupported, prebuild: ({ variant }) => { if (variant === "fabric") { const properties = "android/gradle.properties"; @@ -43,42 +65,42 @@ const PLATFORM_CONFIG: Record = { ios: { name: "iOS", engines: ["jsc", "hermes"], - isAvailable: ({ version, engine }) => { - if (process.platform !== "darwin") { - return false; - } - - if (engine === "jsc" && toVersionNumber(version) >= v(0, 80, 0)) { - return false; - } - - return true; - }, + isAvailable: isAppleVariantSupported, prebuild: installPods, + additionalBuildArgs: () => ["--device", getIOSSimulatorName()], }, macos: { name: "macOS", engines: ["jsc", "hermes"], - isAvailable: () => false, + isAvailable: isAppleVariantSupported, prebuild: installPods, + requiresManualTesting: true, }, visionos: { name: "visionOS", engines: ["jsc", "hermes"], - isAvailable: () => false, + isAvailable: isAppleVariantSupported, prebuild: installPods, + requiresManualTesting: true, }, windows: { name: "Windows", engines: ["hermes"], - isAvailable: () => false, - prebuild: () => Promise.resolve(), + isAvailable: () => process.platform === "win32", + prebuild: () => { + rm_r("windows/ExperimentalFeatures.props"); + $( + PACKAGE_MANAGER, + "install-windows-test-app", + "--msbuildprops", + "WindowsTargetPlatformVersion=10.0.26100.0" + ); + return Promise.resolve(); + }, + requiresManualTesting: true, }, }; -const PACKAGE_MANAGER = "yarn"; -const TAG = "┃"; - const rootDir = fileURLToPath(new URL("../..", import.meta.url)); function log(message = "", tag = TAG) { @@ -92,6 +114,7 @@ function run(script: string, logPath: string) { const fd = fs.openSync(logPath, "a", 0o644); const proc = spawn(PACKAGE_MANAGER, ["run", script], { stdio: ["ignore", fd, fd], + shell: process.platform === "win32", }); return proc; } @@ -124,13 +147,10 @@ function validatePlatforms(platforms: string[]): TargetPlatform[] { switch (platform) { case "android": case "ios": - filtered.push(platform); - break; - case "macos": case "visionos": case "windows": - log(yellow(`⚠ Unsupported platform: ${platform}`)); + filtered.push(platform); break; default: @@ -153,6 +173,18 @@ function parseArgs(args: string[]) { description: "Test iOS", type: "boolean", }, + macos: { + description: "Test macOS", + type: "boolean", + }, + visionos: { + description: "Test visionOS", + type: "boolean", + }, + windows: { + description: "Test Windows", + type: "boolean", + }, }, strict: true, allowPositionals: true, @@ -168,10 +200,10 @@ function parseArgs(args: string[]) { }; } -function prestart() { +function waitForUserInput(message: string): Promise { return !process.stdin.isTTY ? Promise.resolve() - : new Promise((resolve) => { + : new Promise((resolve, reject) => { const stdin = process.stdin; const rawMode = stdin.isRaw; const encoding = stdin.readableEncoding || undefined; @@ -185,31 +217,24 @@ function prestart() { stdin.setRawMode(rawMode); if (typeof key === "string" && key === "\u0003") { showBanner("❌ Canceled"); - // eslint-disable-next-line local/no-process-exit - process.exit(1); + reject(1); + } else { + resolve(); } - resolve(true); }); - process.stdout.write( - `${TAG} Before continuing, make sure all emulators/simulators and Appium/Metro instances are closed.\n${TAG}\n${TAG} Press any key to continue...` - ); + process.stdout.write(message); }); } /** - * Invokes `react-native run-`. + * Invokes `rnx-cli run --platform `. */ function buildAndRun(platform: TargetPlatform) { - switch (platform) { - case "ios": { - const simulator = getIOSSimulatorName(); - $(PACKAGE_MANAGER, platform, "--device", simulator); - break; - } - default: { - $(PACKAGE_MANAGER, platform); - break; - } + const { additionalBuildArgs } = PLATFORM_CONFIG[platform]; + if (additionalBuildArgs) { + $(PACKAGE_MANAGER, platform, ...additionalBuildArgs()); + } else { + $(PACKAGE_MANAGER, platform); } } @@ -229,7 +254,13 @@ async function buildRunTest({ version, platform, variant }: BuildConfig) { showBanner(`Build ${setup.name} [${variant}, ${engine}]`); await setup.prebuild(configWithEngine); buildAndRun(platform); - await test(platform, [variant, engine]); + if (setup.requiresManualTesting) { + await waitForUserInput( + `${TAG}\n${TAG} ${yellow("⚠")} ${setup.name} requires manual testing. When you're done, press any key to continue...` + ); + } else { + await test(platform, [variant, engine]); + } } } @@ -265,7 +296,7 @@ async function withReactNativeVersion( reset(rootDir); if (version) { - await setReactVersion(version, true); + await setReactVersion(version, false); } else { log(); } @@ -291,15 +322,20 @@ if (platforms.length === 0) { process.exitCode = 1; showBanner(red("No valid platforms were specified")); } else { - TEST_VARIANTS.reduce((job, variant) => { - return job.then(() => - withReactNativeVersion(version, async () => { - for (const platform of platforms) { - await buildRunTest({ version, platform, variant }); - } - }) - ); - }, prestart()) + TEST_VARIANTS.reduce( + (job, variant) => { + return job.then(() => + withReactNativeVersion(version, async () => { + for (const platform of platforms) { + await buildRunTest({ version, platform, variant }); + } + }) + ); + }, + waitForUserInput( + `${TAG} Before continuing, make sure all emulators/simulators and Appium/Metro instances are closed.\n${TAG}\n${TAG} Press any key to continue...` + ) + ) .then(() => { showBanner("Initialize new app"); $( @@ -330,12 +366,23 @@ if (platforms.length === 0) { "-p", "windows", ]; - const { status } = spawnSync(PACKAGE_MANAGER, args, { stdio: "inherit" }); + const { status } = spawnSync(PACKAGE_MANAGER, args, { + stdio: "inherit", + shell: process.platform === "win32", + }); if (status !== 1) { throw new Error("Expected an error"); } }) .then(() => { showBanner(green("✔ Pass")); + }) + .catch((e) => { + if (typeof e === "number") { + process.exitCode = e; + } else { + process.exitCode = 1; + showBanner(`❌ ${e}`); + } }); }