From 723db1d89476d2cd9a81c96d17f26b76f2486c78 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:04:43 +0900 Subject: [PATCH 01/44] chore(e2e): add project scaffolding with tsconfig and config --- e2e/config.ts | 14 ++++++++++++++ e2e/tsconfig.json | 13 +++++++++++++ package.json | 3 ++- 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 e2e/config.ts create mode 100644 e2e/tsconfig.json diff --git a/e2e/config.ts b/e2e/config.ts new file mode 100644 index 00000000..adf45012 --- /dev/null +++ b/e2e/config.ts @@ -0,0 +1,14 @@ +import path from "path"; + +export const MOCK_SERVER_PORT = 18081; +export const EXAMPLES_DIR = path.resolve(__dirname, "../Examples"); +export const MOCK_DATA_DIR = path.resolve(__dirname, "mock-server/data"); + +export function getMockServerHost(platform: "ios" | "android"): string { + const host = platform === "android" ? "10.0.2.2" : "localhost"; + return `http://${host}:${MOCK_SERVER_PORT}`; +} + +export function getAppPath(appName: string): string { + return path.join(EXAMPLES_DIR, appName); +} \ No newline at end of file diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 00000000..cc40c40f --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "moduleResolution": "Node16", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["./**/*.ts"] +} \ No newline at end of file diff --git a/package.json b/package.json index c7fa17c9..42ae9a8f 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,8 @@ "prepack": "npm run build:cli", "publish": "npm publish --access=public", "eslint": "eslint --quiet .", - "jest": "jest src/versioning/* expo/* && npm run --workspace cli test" + "jest": "jest src/versioning/* expo/* && npm run --workspace cli test", + "e2e": "ts-node --project e2e/tsconfig.json e2e/run.ts" }, "repository": { "type": "git", From 30ed9e5bf5f40516956877eb4a2aab7741c763fa Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:05:14 +0900 Subject: [PATCH 02/44] chore(e2e): add local filesystem code-push config template --- e2e/templates/code-push.config.local.ts | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 e2e/templates/code-push.config.local.ts diff --git a/e2e/templates/code-push.config.local.ts b/e2e/templates/code-push.config.local.ts new file mode 100644 index 00000000..1f2fc78a --- /dev/null +++ b/e2e/templates/code-push.config.local.ts @@ -0,0 +1,71 @@ +// @ts-nocheck +import { + CliConfigInterface, + ReleaseHistoryInterface, +} from "@bravemobile/react-native-code-push"; +import * as fs from "fs"; +import * as path from "path"; + +const MOCK_DATA_DIR = process.env.E2E_MOCK_DATA_DIR; +if (!MOCK_DATA_DIR) { + throw new Error("E2E_MOCK_DATA_DIR environment variable is required"); +} +const MOCK_SERVER_HOST = process.env.E2E_MOCK_SERVER_HOST; +if (!MOCK_SERVER_HOST) { + throw new Error("E2E_MOCK_SERVER_HOST environment variable is required"); +} + +function ensureDir(dir: string) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } +} + +const Config: CliConfigInterface = { + bundleUploader: async ( + source: string, + platform: "ios" | "android", + identifier = "staging", + ): Promise<{ downloadUrl: string }> => { + const fileName = path.basename(source); + const destDir = path.join(MOCK_DATA_DIR, "bundles", platform, identifier); + ensureDir(destDir); + const destPath = path.join(destDir, fileName); + fs.copyFileSync(source, destPath); + + const downloadUrl = `${MOCK_SERVER_HOST}/bundles/${platform}/${identifier}/${fileName}`; + console.log("Bundle copied to:", destPath); + console.log("Download URL:", downloadUrl); + return { downloadUrl }; + }, + + getReleaseHistory: async ( + targetBinaryVersion: string, + platform: "ios" | "android", + identifier = "staging", + ): Promise => { + const jsonPath = path.join( + MOCK_DATA_DIR, "histories", platform, identifier, `${targetBinaryVersion}.json`, + ); + if (!fs.existsSync(jsonPath)) { + return {} as ReleaseHistoryInterface; + } + return JSON.parse(fs.readFileSync(jsonPath, "utf8")); + }, + + setReleaseHistory: async ( + targetBinaryVersion: string, + jsonFilePath: string, + _releaseInfo: ReleaseHistoryInterface, + platform: "ios" | "android", + identifier = "staging", + ): Promise => { + const destDir = path.join(MOCK_DATA_DIR, "histories", platform, identifier); + ensureDir(destDir); + const destPath = path.join(destDir, `${targetBinaryVersion}.json`); + fs.copyFileSync(jsonFilePath, destPath); + console.log("Release history saved to:", destPath); + }, +}; + +module.exports = Config; \ No newline at end of file From aeec72aca101992773c1ba0603625d3f5074dc43 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:05:38 +0900 Subject: [PATCH 03/44] chore(e2e): add mock HTTP server for serving bundles and history --- e2e/mock-server/server.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 e2e/mock-server/server.ts diff --git a/e2e/mock-server/server.ts b/e2e/mock-server/server.ts new file mode 100644 index 00000000..d0fa41b9 --- /dev/null +++ b/e2e/mock-server/server.ts @@ -0,0 +1,39 @@ +import express from "express"; +import path from "path"; +import { MOCK_DATA_DIR, MOCK_SERVER_PORT } from "../config"; +import type { Server } from "http"; + +let server: Server | null = null; + +export function startMockServer(): Promise { + return new Promise((resolve, reject) => { + const app = express(); + app.use(express.static(MOCK_DATA_DIR)); + + app.use((_req, res) => { + res.status(404).json({ error: "Not found" }); + }); + + server = app.listen(MOCK_SERVER_PORT, () => { + console.log(`Mock server started on port ${MOCK_SERVER_PORT}`); + console.log(`Serving files from: ${MOCK_DATA_DIR}`); + resolve(server!); + }); + + server.on("error", reject); + }); +} + +export function stopMockServer(): Promise { + return new Promise((resolve) => { + if (server) { + server.close(() => { + console.log("Mock server stopped"); + server = null; + resolve(); + }); + } else { + resolve(); + } + }); +} \ No newline at end of file From 93870500b52661c65c4c5a16ec6647bb4ef2729b Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:06:23 +0900 Subject: [PATCH 04/44] chore(e2e): add config preparation helper with backup/restore --- e2e/helpers/prepare-config.ts | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 e2e/helpers/prepare-config.ts diff --git a/e2e/helpers/prepare-config.ts b/e2e/helpers/prepare-config.ts new file mode 100644 index 00000000..e62a8214 --- /dev/null +++ b/e2e/helpers/prepare-config.ts @@ -0,0 +1,52 @@ +import fs from "fs"; +import path from "path"; +import { getMockServerHost } from "../config"; + +const BACKUP_SUFFIX = ".e2e-backup"; + +export function prepareConfig(appPath: string, platform: "ios" | "android"): void { + patchAppTsx(appPath, platform); + copyLocalConfig(appPath); +} + +export function restoreConfig(appPath: string): void { + restoreFile(path.join(appPath, "App.tsx")); + const localConfig = path.join(appPath, "code-push.config.local.ts"); + if (fs.existsSync(localConfig)) { + fs.unlinkSync(localConfig); + } +} + +function patchAppTsx(appPath: string, platform: "ios" | "android"): void { + const appTsxPath = path.join(appPath, "App.tsx"); + backupFile(appTsxPath); + + let content = fs.readFileSync(appTsxPath, "utf8"); + const host = getMockServerHost(platform); + content = content.replace( + /const CODEPUSH_HOST = '[^']*'/, + `const CODEPUSH_HOST = '${host}'`, + ); + fs.writeFileSync(appTsxPath, content, "utf8"); + console.log(`App.tsx CODEPUSH_HOST set to: ${host}`); +} + +function copyLocalConfig(appPath: string): void { + const templatePath = path.resolve(__dirname, "../templates/code-push.config.local.ts"); + const destPath = path.join(appPath, "code-push.config.local.ts"); + fs.copyFileSync(templatePath, destPath); + console.log("code-push.config.local.ts copied to app directory"); +} + +function backupFile(filePath: string): void { + const backupPath = filePath + BACKUP_SUFFIX; + fs.copyFileSync(filePath, backupPath); +} + +function restoreFile(filePath: string): void { + const backupPath = filePath + BACKUP_SUFFIX; + if (fs.existsSync(backupPath)) { + fs.copyFileSync(backupPath, filePath); + fs.unlinkSync(backupPath); + } +} \ No newline at end of file From b2e813dc53a5116e52a54d1ef8fe4f191b7a0fb6 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:07:05 +0900 Subject: [PATCH 05/44] chore(e2e): add bundle preparation helper with code-push release --- e2e/helpers/prepare-bundle.ts | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 e2e/helpers/prepare-bundle.ts diff --git a/e2e/helpers/prepare-bundle.ts b/e2e/helpers/prepare-bundle.ts new file mode 100644 index 00000000..e7c74175 --- /dev/null +++ b/e2e/helpers/prepare-bundle.ts @@ -0,0 +1,67 @@ +import fs from "fs"; +import path from "path"; +import { spawn } from "child_process"; +import { MOCK_DATA_DIR, getMockServerHost } from "../config"; + +export async function prepareBundle( + appPath: string, + platform: "ios" | "android", + appName: string, +): Promise { + const appTsxPath = path.join(appPath, "App.tsx"); + + // Temporarily set IS_RELEASING_BUNDLE = true + let content = fs.readFileSync(appTsxPath, "utf8"); + content = content.replace( + /const IS_RELEASING_BUNDLE = false/, + "const IS_RELEASING_BUNDLE = true", + ); + fs.writeFileSync(appTsxPath, content, "utf8"); + + try { + await runCodePushRelease(appPath, platform, appName); + } finally { + // Restore IS_RELEASING_BUNDLE = false + content = fs.readFileSync(appTsxPath, "utf8"); + content = content.replace( + /const IS_RELEASING_BUNDLE = true/, + "const IS_RELEASING_BUNDLE = false", + ); + fs.writeFileSync(appTsxPath, content, "utf8"); + } +} + +function runCodePushRelease( + appPath: string, + platform: "ios" | "android", + appName: string, +): Promise { + const args = [ + "code-push", "release", + "-c", "code-push.config.local.ts", + "-b", "1.0.0", + "-v", "1.0.1", + "-p", platform, + "-i", appName, + "-m", "true", + ]; + + console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); + + return new Promise((resolve, reject) => { + const child = spawn("npx", args, { + cwd: appPath, + stdio: "inherit", + env: { + ...process.env, + E2E_MOCK_DATA_DIR: MOCK_DATA_DIR, + E2E_MOCK_SERVER_HOST: getMockServerHost(platform), + }, + }); + child.on("error", reject); + child.on("close", (code) => { + if (code === 0) resolve(); + else reject(new Error(`code-push release failed (exit code: ${code})`)); + }); + }); +} \ No newline at end of file From 8f814ba11038461c6763d4feaaf666373291ca83 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:07:37 +0900 Subject: [PATCH 06/44] chore(e2e): add app build helper for iOS and Android Release builds --- e2e/helpers/build-app.ts | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 e2e/helpers/build-app.ts diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts new file mode 100644 index 00000000..775da017 --- /dev/null +++ b/e2e/helpers/build-app.ts @@ -0,0 +1,42 @@ +import { spawn } from "child_process"; + +export async function buildApp( + appPath: string, + platform: "ios" | "android", +): Promise { + if (platform === "ios") { + await buildIos(appPath); + } else { + await buildAndroid(appPath); + } +} + +function buildIos(appPath: string): Promise { + const args = [ + "react-native", "build-ios", + "--mode", "Release", + "--simulator", "iPhone 16", + ]; + console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); + return executeCommand("npx", args, appPath); +} + +function buildAndroid(appPath: string): Promise { + const args = [ + "react-native", "build-android", + "--mode", "release", + ]; + console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); + return executeCommand("npx", args, appPath); +} + +function executeCommand(command: string, args: string[], cwd: string): Promise { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { cwd, stdio: "inherit" }); + child.on("error", reject); + child.on("close", (code) => { + if (code === 0) resolve(); + else reject(new Error(`${command} ${args[0]} failed (exit code: ${code})`)); + }); + }); +} \ No newline at end of file From 15d7caf56a580d462dcec1c97e3049e1f93f65e5 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:08:38 +0900 Subject: [PATCH 07/44] chore(e2e): add Maestro flow YAML files for 4 test scenarios --- e2e/flows/01-app-launch.yaml | 7 +++++++ e2e/flows/02-restart-no-crash.yaml | 9 +++++++++ e2e/flows/03-update-flow.yaml | 21 +++++++++++++++++++++ e2e/flows/04-clear-and-restart.yaml | 16 ++++++++++++++++ e2e/flows/CLAUDE.md | 3 +++ 5 files changed, 56 insertions(+) create mode 100644 e2e/flows/01-app-launch.yaml create mode 100644 e2e/flows/02-restart-no-crash.yaml create mode 100644 e2e/flows/03-update-flow.yaml create mode 100644 e2e/flows/04-clear-and-restart.yaml create mode 100644 e2e/flows/CLAUDE.md diff --git a/e2e/flows/01-app-launch.yaml b/e2e/flows/01-app-launch.yaml new file mode 100644 index 00000000..b391f320 --- /dev/null +++ b/e2e/flows/01-app-launch.yaml @@ -0,0 +1,7 @@ +appId: ${APP_ID} +--- +- launchApp +- assertVisible: "React Native" +- assertVisible: "Check for updates" +- assertVisible: "Clear updates" +- assertVisible: "Restart app" \ No newline at end of file diff --git a/e2e/flows/02-restart-no-crash.yaml b/e2e/flows/02-restart-no-crash.yaml new file mode 100644 index 00000000..fee17cf1 --- /dev/null +++ b/e2e/flows/02-restart-no-crash.yaml @@ -0,0 +1,9 @@ +appId: ${APP_ID} +--- +- launchApp +- assertVisible: "React Native" +- tapOn: "Restart app" +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: "React Native" +- assertVisible: "Check for updates" \ No newline at end of file diff --git a/e2e/flows/03-update-flow.yaml b/e2e/flows/03-update-flow.yaml new file mode 100644 index 00000000..077fc8ad --- /dev/null +++ b/e2e/flows/03-update-flow.yaml @@ -0,0 +1,21 @@ +appId: ${APP_ID} +--- +- launchApp +- assertVisible: "React Native" +- assertNotVisible: "UPDATED!" + +# Check for updates triggers sync with updateDialog: true +- tapOn: "Check for updates" + +# Mandatory update dialog appears — tap "Install" +- waitForAnimationToEnd: + timeout: 5000 +- tapOn: "Install" + +# Wait for download + install + auto-restart (mandatory = IMMEDIATE) +- waitForAnimationToEnd: + timeout: 30000 + +# After restart, UPDATED! should be visible +- assertVisible: "UPDATED!" +- assertVisible: "React Native" \ No newline at end of file diff --git a/e2e/flows/04-clear-and-restart.yaml b/e2e/flows/04-clear-and-restart.yaml new file mode 100644 index 00000000..89bb94e0 --- /dev/null +++ b/e2e/flows/04-clear-and-restart.yaml @@ -0,0 +1,16 @@ +appId: ${APP_ID} +--- +- launchApp +- assertVisible: "UPDATED!" + +# Clear updates +- tapOn: "Clear updates" + +# Restart to apply +- tapOn: "Restart app" +- waitForAnimationToEnd: + timeout: 5000 + +# UPDATED! should be gone +- assertVisible: "React Native" +- assertNotVisible: "UPDATED!" \ No newline at end of file diff --git a/e2e/flows/CLAUDE.md b/e2e/flows/CLAUDE.md new file mode 100644 index 00000000..59ab83fc --- /dev/null +++ b/e2e/flows/CLAUDE.md @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 0ad958c380082c28f6de66f68ba2a9569147aa8e Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:09:26 +0900 Subject: [PATCH 08/44] chore(e2e): add main orchestration script with CLI interface --- e2e/run.ts | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 e2e/run.ts diff --git a/e2e/run.ts b/e2e/run.ts new file mode 100644 index 00000000..644c30f0 --- /dev/null +++ b/e2e/run.ts @@ -0,0 +1,117 @@ +import { Command } from "commander"; +import { spawn } from "child_process"; +import path from "path"; +import fs from "fs"; +import { getAppPath, MOCK_DATA_DIR } from "./config"; +import { prepareConfig, restoreConfig } from "./helpers/prepare-config"; +import { prepareBundle } from "./helpers/prepare-bundle"; +import { buildApp } from "./helpers/build-app"; +import { startMockServer, stopMockServer } from "./mock-server/server"; + +interface CliOptions { + app: string; + platform: "ios" | "android"; + maestroOnly?: boolean; +} + +const program = new Command() + .name("e2e") + .description("Run E2E tests with Maestro for CodePush example apps") + .requiredOption("--app ", "Example app name (e.g. RN0840RC5)") + .requiredOption("--platform ", "Platform: ios or android") + .option("--maestro-only", "Skip build, only run Maestro flows", false); + +async function main() { + const options = program.parse(process.argv).opts(); + const appPath = getAppPath(options.app); + + if (!fs.existsSync(appPath)) { + console.error(`Example app not found: ${appPath}`); + process.exitCode = 1; + return; + } + + try { + // 1. Prepare config + console.log("\n=== [prepare] ==="); + prepareConfig(appPath, options.platform); + + // 2. Build (unless --maestro-only) + if (!options.maestroOnly) { + console.log("\n=== [build] ==="); + await buildApp(appPath, options.platform); + } + + // 3. Prepare update bundle + console.log("\n=== [prepare-bundle] ==="); + cleanMockData(); + await prepareBundle(appPath, options.platform, options.app); + + // 4. Start mock server + console.log("\n=== [start-mock-server] ==="); + await startMockServer(); + + // 5. Run Maestro + console.log("\n=== [run-maestro] ==="); + const appId = getAppId(appPath, options.platform); + await runMaestro(options.platform, appId); + + console.log("\n=== E2E tests passed ==="); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + console.error(`\nE2E test failed: ${message}`); + process.exitCode = 1; + } finally { + // 6. Cleanup + console.log("\n=== [cleanup] ==="); + await stopMockServer(); + restoreConfig(appPath); + } +} + +function cleanMockData(): void { + if (fs.existsSync(MOCK_DATA_DIR)) { + fs.rmSync(MOCK_DATA_DIR, { recursive: true }); + } + fs.mkdirSync(MOCK_DATA_DIR, { recursive: true }); +} + +function getAppId(appPath: string, platform: "ios" | "android"): string { + if (platform === "ios") { + const appJsonPath = path.join(appPath, "app.json"); + const appJson = JSON.parse(fs.readFileSync(appJsonPath, "utf8")); + return `org.reactjs.native.example.${appJson.name}`; + } + // Android: read from build.gradle + const buildGradlePath = path.join(appPath, "android", "app", "build.gradle"); + const content = fs.readFileSync(buildGradlePath, "utf8"); + const match = content.match(/applicationId\s+"([^"]+)"/); + if (!match) { + throw new Error("Could not find applicationId in build.gradle"); + } + return match[1]; +} + +function runMaestro(platform: "ios" | "android", appId: string): Promise { + const flowsDir = path.resolve(__dirname, "flows"); + const args = ["test", flowsDir, "--env", `APP_ID=${appId}`]; + + if (platform === "android") { + args.push("--platform", "android"); + } else { + args.push("--platform", "ios"); + } + + console.log(`[command] maestro ${args.join(" ")}`); + + return new Promise((resolve, reject) => { + const child = spawn("maestro", args, { stdio: "inherit" }); + child.on("error", reject); + child.on("close", (code) => { + if (code === 0) resolve(); + else reject(new Error(`Maestro tests failed (exit code: ${code})`)); + }); + }); +} + +void main(); \ No newline at end of file From ca9ccbb670753eb8743088840a3d23db6752766b Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:10:12 +0900 Subject: [PATCH 09/44] fix(e2e): resolve TypeScript errors in mock server, add @types/express --- e2e/mock-server/server.ts | 10 ++--- package-lock.json | 89 +++++++++++++++++++++++++++++++++++++++ package.json | 5 ++- 3 files changed, 97 insertions(+), 7 deletions(-) diff --git a/e2e/mock-server/server.ts b/e2e/mock-server/server.ts index d0fa41b9..de506760 100644 --- a/e2e/mock-server/server.ts +++ b/e2e/mock-server/server.ts @@ -1,5 +1,4 @@ import express from "express"; -import path from "path"; import { MOCK_DATA_DIR, MOCK_SERVER_PORT } from "../config"; import type { Server } from "http"; @@ -10,17 +9,18 @@ export function startMockServer(): Promise { const app = express(); app.use(express.static(MOCK_DATA_DIR)); - app.use((_req, res) => { + app.use((_req: express.Request, res: express.Response) => { res.status(404).json({ error: "Not found" }); }); - server = app.listen(MOCK_SERVER_PORT, () => { + const s = app.listen(MOCK_SERVER_PORT, () => { console.log(`Mock server started on port ${MOCK_SERVER_PORT}`); console.log(`Serving files from: ${MOCK_DATA_DIR}`); - resolve(server!); + resolve(s); }); - server.on("error", reject); + s.on("error", reject); + server = s; }); } diff --git a/package-lock.json b/package-lock.json index fc813733..8651691e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@babel/preset-typescript": "^7.27.1", "@eslint/js": "^9.13.0", "@types/assert": "^1.5.2", + "@types/express": "^5.0.6", "@types/mkdirp": "^1.0.1", "@types/mocha": "^9.0.0", "@types/node": "^18.19.129", @@ -4526,11 +4527,57 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "dev": true, "license": "MIT" }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", + "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/glob": { "version": "7.2.0", "dev": true, @@ -4548,6 +4595,13 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "license": "MIT" @@ -4603,11 +4657,46 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.5.8", "dev": true, "license": "MIT" }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, "node_modules/@types/shelljs": { "version": "0.8.15", "dev": true, diff --git a/package.json b/package.json index 42ae9a8f..086a8d21 100644 --- a/package.json +++ b/package.json @@ -83,8 +83,8 @@ "semver": "^7.3.5", "shelljs": "^0.10.0", "xcode": "^3.0.1", - "yazl": "^3.3.1", - "yauzl": "^3.2.0" + "yauzl": "^3.2.0", + "yazl": "^3.3.1" }, "peerDependencies": { "expo": ">=50.0.0", @@ -101,6 +101,7 @@ "@babel/preset-typescript": "^7.27.1", "@eslint/js": "^9.13.0", "@types/assert": "^1.5.2", + "@types/express": "^5.0.6", "@types/mkdirp": "^1.0.1", "@types/mocha": "^9.0.0", "@types/node": "^18.19.129", From 48b62c87364328d31bfce009fde439ca475299e5 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:16:03 +0900 Subject: [PATCH 10/44] chore(e2e): make iOS simulator configurable, default to booted --- e2e/helpers/build-app.ts | 9 ++++++--- e2e/run.ts | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index 775da017..e3e1420d 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -3,20 +3,23 @@ import { spawn } from "child_process"; export async function buildApp( appPath: string, platform: "ios" | "android", + simulator?: string, ): Promise { if (platform === "ios") { - await buildIos(appPath); + await buildIos(appPath, simulator); } else { await buildAndroid(appPath); } } -function buildIos(appPath: string): Promise { +function buildIos(appPath: string, simulator?: string): Promise { const args = [ "react-native", "build-ios", "--mode", "Release", - "--simulator", "iPhone 16", ]; + if (simulator) { + args.push("--simulator", simulator); + } console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); return executeCommand("npx", args, appPath); } diff --git a/e2e/run.ts b/e2e/run.ts index 644c30f0..fdb96eac 100644 --- a/e2e/run.ts +++ b/e2e/run.ts @@ -11,6 +11,7 @@ import { startMockServer, stopMockServer } from "./mock-server/server"; interface CliOptions { app: string; platform: "ios" | "android"; + simulator?: string; maestroOnly?: boolean; } @@ -19,6 +20,7 @@ const program = new Command() .description("Run E2E tests with Maestro for CodePush example apps") .requiredOption("--app ", "Example app name (e.g. RN0840RC5)") .requiredOption("--platform ", "Platform: ios or android") + .option("--simulator ", "iOS simulator name (default: booted)") .option("--maestro-only", "Skip build, only run Maestro flows", false); async function main() { @@ -39,7 +41,7 @@ async function main() { // 2. Build (unless --maestro-only) if (!options.maestroOnly) { console.log("\n=== [build] ==="); - await buildApp(appPath, options.platform); + await buildApp(appPath, options.platform, options.simulator); } // 3. Prepare update bundle From 6c3adad19c89e720084a0671a2ade7a5d85b0448 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:17:09 +0900 Subject: [PATCH 11/44] chore(e2e): add --active-arch-only to Android build --- e2e/helpers/build-app.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index e3e1420d..ce516a1e 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -28,6 +28,7 @@ function buildAndroid(appPath: string): Promise { const args = [ "react-native", "build-android", "--mode", "release", + "--active-arch-only", ]; console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); return executeCommand("npx", args, appPath); From b71fb8e3611cb53b4294a7d226ebdf7b7649fcac Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:18:39 +0900 Subject: [PATCH 12/44] fix(e2e): use Gradle directly for Android Release build to avoid Metro --- e2e/helpers/build-app.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index ce516a1e..895d5abd 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -1,4 +1,5 @@ import { spawn } from "child_process"; +import path from "path"; export async function buildApp( appPath: string, @@ -25,13 +26,10 @@ function buildIos(appPath: string, simulator?: string): Promise { } function buildAndroid(appPath: string): Promise { - const args = [ - "react-native", "build-android", - "--mode", "release", - "--active-arch-only", - ]; - console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); - return executeCommand("npx", args, appPath); + const cwd = path.join(appPath, "android"); + const args = ["assembleRelease", "-PreactNativeArchitectures=arm64-v8a"]; + console.log(`[command] ./gradlew ${args.join(" ")} (cwd: ${cwd})`); + return executeCommand("./gradlew", args, cwd); } function executeCommand(command: string, args: string[], cwd: string): Promise { From 0c00a12bb564e861ddc3ea21c4a8cb38b257bd53 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:20:07 +0900 Subject: [PATCH 13/44] Revert "fix(e2e): use Gradle directly for Android Release build to avoid Metro" This reverts commit b71fb8e3611cb53b4294a7d226ebdf7b7649fcac. --- e2e/helpers/build-app.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index 895d5abd..ce516a1e 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -1,5 +1,4 @@ import { spawn } from "child_process"; -import path from "path"; export async function buildApp( appPath: string, @@ -26,10 +25,13 @@ function buildIos(appPath: string, simulator?: string): Promise { } function buildAndroid(appPath: string): Promise { - const cwd = path.join(appPath, "android"); - const args = ["assembleRelease", "-PreactNativeArchitectures=arm64-v8a"]; - console.log(`[command] ./gradlew ${args.join(" ")} (cwd: ${cwd})`); - return executeCommand("./gradlew", args, cwd); + const args = [ + "react-native", "build-android", + "--mode", "release", + "--active-arch-only", + ]; + console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); + return executeCommand("npx", args, appPath); } function executeCommand(command: string, args: string[], cwd: string): Promise { From d5f0bd0124381357547f064be87c08d065d25f66 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:21:39 +0900 Subject: [PATCH 14/44] fix(e2e): set entry file to index.js for code-push release --- e2e/helpers/prepare-bundle.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/helpers/prepare-bundle.ts b/e2e/helpers/prepare-bundle.ts index e7c74175..7959952e 100644 --- a/e2e/helpers/prepare-bundle.ts +++ b/e2e/helpers/prepare-bundle.ts @@ -43,6 +43,7 @@ function runCodePushRelease( "-v", "1.0.1", "-p", platform, "-i", appName, + "-e", "index.js", "-m", "true", ]; From 06adf7c9a6afd82f859d760fb303164e513559c5 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:24:19 +0900 Subject: [PATCH 15/44] fix(e2e): use run-ios/run-android to build, install, and launch app --- e2e/helpers/build-app.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index ce516a1e..c4379d28 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -14,7 +14,7 @@ export async function buildApp( function buildIos(appPath: string, simulator?: string): Promise { const args = [ - "react-native", "build-ios", + "react-native", "run-ios", "--mode", "Release", ]; if (simulator) { @@ -26,7 +26,7 @@ function buildIos(appPath: string, simulator?: string): Promise { function buildAndroid(appPath: string): Promise { const args = [ - "react-native", "build-android", + "react-native", "run-android", "--mode", "release", "--active-arch-only", ]; From cd64facce5758b7c1ed5300feeccf11af7a6cc63 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:28:57 +0900 Subject: [PATCH 16/44] fix(e2e): add --no-packager to run commands, remove unused port killer --- e2e/helpers/build-app.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index c4379d28..83ca30b3 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -16,6 +16,7 @@ function buildIos(appPath: string, simulator?: string): Promise { const args = [ "react-native", "run-ios", "--mode", "Release", + "--no-packager", ]; if (simulator) { args.push("--simulator", simulator); @@ -29,6 +30,7 @@ function buildAndroid(appPath: string): Promise { "react-native", "run-android", "--mode", "release", "--active-arch-only", + "--no-packager", ]; console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); return executeCommand("npx", args, appPath); From b589c65883fe1b7dcb08410638361828b5ed7d27 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:33:00 +0900 Subject: [PATCH 17/44] fix(e2e): use regex pattern for React Native version text matching --- e2e/flows/01-app-launch.yaml | 2 +- e2e/flows/02-restart-no-crash.yaml | 4 ++-- e2e/flows/03-update-flow.yaml | 4 ++-- e2e/flows/04-clear-and-restart.yaml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e/flows/01-app-launch.yaml b/e2e/flows/01-app-launch.yaml index b391f320..8ca6a7cf 100644 --- a/e2e/flows/01-app-launch.yaml +++ b/e2e/flows/01-app-launch.yaml @@ -1,7 +1,7 @@ appId: ${APP_ID} --- - launchApp -- assertVisible: "React Native" +- assertVisible: "React Native.*" - assertVisible: "Check for updates" - assertVisible: "Clear updates" - assertVisible: "Restart app" \ No newline at end of file diff --git a/e2e/flows/02-restart-no-crash.yaml b/e2e/flows/02-restart-no-crash.yaml index fee17cf1..4d760768 100644 --- a/e2e/flows/02-restart-no-crash.yaml +++ b/e2e/flows/02-restart-no-crash.yaml @@ -1,9 +1,9 @@ appId: ${APP_ID} --- - launchApp -- assertVisible: "React Native" +- assertVisible: "React Native.*" - tapOn: "Restart app" - waitForAnimationToEnd: timeout: 5000 -- assertVisible: "React Native" +- assertVisible: "React Native.*" - assertVisible: "Check for updates" \ No newline at end of file diff --git a/e2e/flows/03-update-flow.yaml b/e2e/flows/03-update-flow.yaml index 077fc8ad..37d4e63e 100644 --- a/e2e/flows/03-update-flow.yaml +++ b/e2e/flows/03-update-flow.yaml @@ -1,7 +1,7 @@ appId: ${APP_ID} --- - launchApp -- assertVisible: "React Native" +- assertVisible: "React Native.*" - assertNotVisible: "UPDATED!" # Check for updates triggers sync with updateDialog: true @@ -18,4 +18,4 @@ appId: ${APP_ID} # After restart, UPDATED! should be visible - assertVisible: "UPDATED!" -- assertVisible: "React Native" \ No newline at end of file +- assertVisible: "React Native.*" \ No newline at end of file diff --git a/e2e/flows/04-clear-and-restart.yaml b/e2e/flows/04-clear-and-restart.yaml index 89bb94e0..c48ccdc2 100644 --- a/e2e/flows/04-clear-and-restart.yaml +++ b/e2e/flows/04-clear-and-restart.yaml @@ -12,5 +12,5 @@ appId: ${APP_ID} timeout: 5000 # UPDATED! should be gone -- assertVisible: "React Native" +- assertVisible: "React Native.*" - assertNotVisible: "UPDATED!" \ No newline at end of file From bd0816f7e73457b0342986a0891ac9f92cc242b7 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 20:37:48 +0900 Subject: [PATCH 18/44] chore(e2e): add request logging to mock server for debugging --- e2e/mock-server/server.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/e2e/mock-server/server.ts b/e2e/mock-server/server.ts index de506760..f137e775 100644 --- a/e2e/mock-server/server.ts +++ b/e2e/mock-server/server.ts @@ -7,6 +7,12 @@ let server: Server | null = null; export function startMockServer(): Promise { return new Promise((resolve, reject) => { const app = express(); + + app.use((req: express.Request, _res: express.Response, next: express.NextFunction) => { + console.log(`[mock-server] ${req.method} ${req.url}`); + next(); + }); + app.use(express.static(MOCK_DATA_DIR)); app.use((_req: express.Request, res: express.Response) => { From 4ff3bd28f8a0de42a6cbbde509e0b8ce0ed9aa28 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:07:22 +0900 Subject: [PATCH 19/44] fix(e2e): patch AndroidManifest.xml to allow cleartext HTTP for mock server --- e2e/helpers/prepare-config.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/e2e/helpers/prepare-config.ts b/e2e/helpers/prepare-config.ts index e62a8214..49773c40 100644 --- a/e2e/helpers/prepare-config.ts +++ b/e2e/helpers/prepare-config.ts @@ -7,10 +7,14 @@ const BACKUP_SUFFIX = ".e2e-backup"; export function prepareConfig(appPath: string, platform: "ios" | "android"): void { patchAppTsx(appPath, platform); copyLocalConfig(appPath); + if (platform === "android") { + patchAndroidManifest(appPath); + } } export function restoreConfig(appPath: string): void { restoreFile(path.join(appPath, "App.tsx")); + restoreFile(getAndroidManifestPath(appPath)); const localConfig = path.join(appPath, "code-push.config.local.ts"); if (fs.existsSync(localConfig)) { fs.unlinkSync(localConfig); @@ -38,6 +42,26 @@ function copyLocalConfig(appPath: string): void { console.log("code-push.config.local.ts copied to app directory"); } +function getAndroidManifestPath(appPath: string): string { + return path.join(appPath, "android", "app", "src", "main", "AndroidManifest.xml"); +} + +function patchAndroidManifest(appPath: string): void { + const manifestPath = getAndroidManifestPath(appPath); + if (!fs.existsSync(manifestPath)) { + return; + } + backupFile(manifestPath); + + let content = fs.readFileSync(manifestPath, "utf8"); + content = content.replace( + /android:usesCleartextTraffic="\$\{usesCleartextTraffic\}"/, + 'android:usesCleartextTraffic="true"', + ); + fs.writeFileSync(manifestPath, content, "utf8"); + console.log("AndroidManifest.xml usesCleartextTraffic set to true"); +} + function backupFile(filePath: string): void { const backupPath = filePath + BACKUP_SUFFIX; fs.copyFileSync(filePath, backupPath); From 1ba0c438c52d3373f98a471cd3a65be139513771 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:19:55 +0900 Subject: [PATCH 20/44] fix(e2e): remove updateDialog for silent mandatory update in E2E --- e2e/flows/03-update-flow.yaml | 7 +------ e2e/helpers/prepare-config.ts | 5 +++++ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e/flows/03-update-flow.yaml b/e2e/flows/03-update-flow.yaml index 37d4e63e..2a95771e 100644 --- a/e2e/flows/03-update-flow.yaml +++ b/e2e/flows/03-update-flow.yaml @@ -4,14 +4,9 @@ appId: ${APP_ID} - assertVisible: "React Native.*" - assertNotVisible: "UPDATED!" -# Check for updates triggers sync with updateDialog: true +# Check for updates — mandatory update installs silently - tapOn: "Check for updates" -# Mandatory update dialog appears — tap "Install" -- waitForAnimationToEnd: - timeout: 5000 -- tapOn: "Install" - # Wait for download + install + auto-restart (mandatory = IMMEDIATE) - waitForAnimationToEnd: timeout: 30000 diff --git a/e2e/helpers/prepare-config.ts b/e2e/helpers/prepare-config.ts index 49773c40..df17d7fe 100644 --- a/e2e/helpers/prepare-config.ts +++ b/e2e/helpers/prepare-config.ts @@ -31,8 +31,13 @@ function patchAppTsx(appPath: string, platform: "ios" | "android"): void { /const CODEPUSH_HOST = '[^']*'/, `const CODEPUSH_HOST = '${host}'`, ); + content = content.replace( + /CodePush\.sync\(\s*\{\s*updateDialog:\s*true\s*\}/, + "CodePush.sync({}", + ); fs.writeFileSync(appTsxPath, content, "utf8"); console.log(`App.tsx CODEPUSH_HOST set to: ${host}`); + console.log("App.tsx updateDialog removed for silent mandatory update"); } function copyLocalConfig(appPath: string): void { From 32821b1ea70f20b1722dbd1cfa54c59cd13baa67 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:21:59 +0900 Subject: [PATCH 21/44] fix(e2e): replace Alert.alert with console.log to prevent dialog blocking --- e2e/helpers/prepare-config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/e2e/helpers/prepare-config.ts b/e2e/helpers/prepare-config.ts index df17d7fe..a15b8e3d 100644 --- a/e2e/helpers/prepare-config.ts +++ b/e2e/helpers/prepare-config.ts @@ -35,9 +35,9 @@ function patchAppTsx(appPath: string, platform: "ios" | "android"): void { /CodePush\.sync\(\s*\{\s*updateDialog:\s*true\s*\}/, "CodePush.sync({}", ); + content = content.replace(/Alert\.alert\(/g, "console.log("); fs.writeFileSync(appTsxPath, content, "utf8"); - console.log(`App.tsx CODEPUSH_HOST set to: ${host}`); - console.log("App.tsx updateDialog removed for silent mandatory update"); + console.log(`App.tsx patched: CODEPUSH_HOST, updateDialog, Alert.alert`); } function copyLocalConfig(appPath: string): void { From 4e3c1fb8f6ac67d7f31a7bb13337b9e197c985cd Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:27:37 +0900 Subject: [PATCH 22/44] fix(e2e): enhance update flow with clean state setup and metadata verification --- e2e/flows/03-update-flow.yaml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/e2e/flows/03-update-flow.yaml b/e2e/flows/03-update-flow.yaml index 2a95771e..546eb2d4 100644 --- a/e2e/flows/03-update-flow.yaml +++ b/e2e/flows/03-update-flow.yaml @@ -2,8 +2,21 @@ appId: ${APP_ID} --- - launchApp - assertVisible: "React Native.*" + +# Ensure clean state — clear any previous updates +- tapOn: "Clear updates" +- tapOn: "Restart app" +- waitForAnimationToEnd: + timeout: 5000 +- assertVisible: "React Native.*" - assertNotVisible: "UPDATED!" +# Verify no running update metadata +- tapOn: "Get update metadata" +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: "null" + # Check for updates — mandatory update installs silently - tapOn: "Check for updates" @@ -13,4 +26,10 @@ appId: ${APP_ID} # After restart, UPDATED! should be visible - assertVisible: "UPDATED!" -- assertVisible: "React Native.*" \ No newline at end of file +- assertVisible: "React Native.*" + +# Verify running update metadata is populated with package info +- tapOn: "Get update metadata" +- waitForAnimationToEnd: + timeout: 2000 +- assertVisible: "packageHash" \ No newline at end of file From 447fb823b4b2e4e25aa17c3a4df6835acfe25b26 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:30:20 +0900 Subject: [PATCH 23/44] fix(e2e): scroll down to find packageHash in update metadata --- e2e/flows/03-update-flow.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/e2e/flows/03-update-flow.yaml b/e2e/flows/03-update-flow.yaml index 546eb2d4..a240b3ae 100644 --- a/e2e/flows/03-update-flow.yaml +++ b/e2e/flows/03-update-flow.yaml @@ -32,4 +32,7 @@ appId: ${APP_ID} - tapOn: "Get update metadata" - waitForAnimationToEnd: timeout: 2000 -- assertVisible: "packageHash" \ No newline at end of file +- scrollUntilVisible: + element: "packageHash" + direction: DOWN + timeout: 5000 \ No newline at end of file From 6dce79108f3d2f718260cfeddd91cc4366629ff1 Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:33:19 +0900 Subject: [PATCH 24/44] fix(e2e): replace TextInput with Text in MetadataBlock for Maestro visibility --- e2e/helpers/prepare-config.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/e2e/helpers/prepare-config.ts b/e2e/helpers/prepare-config.ts index a15b8e3d..b41a39f8 100644 --- a/e2e/helpers/prepare-config.ts +++ b/e2e/helpers/prepare-config.ts @@ -36,8 +36,13 @@ function patchAppTsx(appPath: string, platform: "ios" | "android"): void { "CodePush.sync({}", ); content = content.replace(/Alert\.alert\(/g, "console.log("); + // Replace TextInput with Text in MetadataBlock so Maestro can read the content + content = content.replace( + //, + "{String(value)}", + ); fs.writeFileSync(appTsxPath, content, "utf8"); - console.log(`App.tsx patched: CODEPUSH_HOST, updateDialog, Alert.alert`); + console.log(`App.tsx patched: CODEPUSH_HOST, updateDialog, Alert.alert, MetadataBlock`); } function copyLocalConfig(appPath: string): void { From 85b7aca7540f1d6439c5603a1e148b62c90b7c9e Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:36:43 +0900 Subject: [PATCH 25/44] fix(e2e): ensure IS_RELEASING_BUNDLE is false in base app build --- e2e/helpers/prepare-config.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/e2e/helpers/prepare-config.ts b/e2e/helpers/prepare-config.ts index b41a39f8..b0718966 100644 --- a/e2e/helpers/prepare-config.ts +++ b/e2e/helpers/prepare-config.ts @@ -31,6 +31,10 @@ function patchAppTsx(appPath: string, platform: "ios" | "android"): void { /const CODEPUSH_HOST = '[^']*'/, `const CODEPUSH_HOST = '${host}'`, ); + content = content.replace( + /const IS_RELEASING_BUNDLE = true/, + "const IS_RELEASING_BUNDLE = false", + ); content = content.replace( /CodePush\.sync\(\s*\{\s*updateDialog:\s*true\s*\}/, "CodePush.sync({}", From a2c499dcb484eb0c7619f2a34f95d49c5748fbea Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:41:48 +0900 Subject: [PATCH 26/44] fix(e2e): add metadata status Text indicator instead of reading TextInput --- e2e/flows/03-update-flow.yaml | 9 +++------ e2e/helpers/prepare-config.ts | 8 ++++---- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/e2e/flows/03-update-flow.yaml b/e2e/flows/03-update-flow.yaml index a240b3ae..58ee7f6b 100644 --- a/e2e/flows/03-update-flow.yaml +++ b/e2e/flows/03-update-flow.yaml @@ -15,7 +15,7 @@ appId: ${APP_ID} - tapOn: "Get update metadata" - waitForAnimationToEnd: timeout: 2000 -- assertVisible: "null" +- assertVisible: "METADATA_NULL" # Check for updates — mandatory update installs silently - tapOn: "Check for updates" @@ -28,11 +28,8 @@ appId: ${APP_ID} - assertVisible: "UPDATED!" - assertVisible: "React Native.*" -# Verify running update metadata is populated with package info +# Verify UI is not frozen — metadata fetch should update the indicator - tapOn: "Get update metadata" - waitForAnimationToEnd: timeout: 2000 -- scrollUntilVisible: - element: "packageHash" - direction: DOWN - timeout: 5000 \ No newline at end of file +- assertVisible: "METADATA_LOADED" \ No newline at end of file diff --git a/e2e/helpers/prepare-config.ts b/e2e/helpers/prepare-config.ts index b0718966..f80dade6 100644 --- a/e2e/helpers/prepare-config.ts +++ b/e2e/helpers/prepare-config.ts @@ -40,13 +40,13 @@ function patchAppTsx(appPath: string, platform: "ios" | "android"): void { "CodePush.sync({}", ); content = content.replace(/Alert\.alert\(/g, "console.log("); - // Replace TextInput with Text in MetadataBlock so Maestro can read the content + // Add a Text indicator for running metadata status (Maestro can't read TextInput values) content = content.replace( - //, - "{String(value)}", + '', + `{runningMetadata === '' ? 'METADATA_IDLE' : runningMetadata === 'null' ? 'METADATA_NULL' : 'METADATA_LOADED'}\n `, ); fs.writeFileSync(appTsxPath, content, "utf8"); - console.log(`App.tsx patched: CODEPUSH_HOST, updateDialog, Alert.alert, MetadataBlock`); + console.log(`App.tsx patched: CODEPUSH_HOST, updateDialog, Alert.alert, metadata status`); } function copyLocalConfig(appPath: string): void { From d2854ac8eb294e80c380f2d29fb6a995e165daec Mon Sep 17 00:00:00 2001 From: floydkim Date: Mon, 16 Feb 2026 22:47:01 +0900 Subject: [PATCH 27/44] feat(e2e): add rollback test with two-phase Maestro execution --- e2e/flows-rollback/01-rollback.yaml | 19 +++++++++ .../02-clear-and-restart.yaml} | 0 e2e/run.ts | 40 ++++++++++++++++--- 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 e2e/flows-rollback/01-rollback.yaml rename e2e/{flows/04-clear-and-restart.yaml => flows-rollback/02-clear-and-restart.yaml} (100%) diff --git a/e2e/flows-rollback/01-rollback.yaml b/e2e/flows-rollback/01-rollback.yaml new file mode 100644 index 00000000..03d13d93 --- /dev/null +++ b/e2e/flows-rollback/01-rollback.yaml @@ -0,0 +1,19 @@ +appId: ${APP_ID} +--- +# App should have the update installed from phase 1 +- launchApp +- assertVisible: "UPDATED!" + +# Check for updates — server has disabled the release, triggers rollback +- tapOn: "Check for updates" +- waitForAnimationToEnd: + timeout: 10000 + +# Restart to apply rollback +- tapOn: "Restart app" +- waitForAnimationToEnd: + timeout: 5000 + +# After rollback, UPDATED! should be gone +- assertVisible: "React Native.*" +- assertNotVisible: "UPDATED!" diff --git a/e2e/flows/04-clear-and-restart.yaml b/e2e/flows-rollback/02-clear-and-restart.yaml similarity index 100% rename from e2e/flows/04-clear-and-restart.yaml rename to e2e/flows-rollback/02-clear-and-restart.yaml diff --git a/e2e/run.ts b/e2e/run.ts index fdb96eac..be7cdfab 100644 --- a/e2e/run.ts +++ b/e2e/run.ts @@ -53,10 +53,20 @@ async function main() { console.log("\n=== [start-mock-server] ==="); await startMockServer(); - // 5. Run Maestro - console.log("\n=== [run-maestro] ==="); + // 5. Run Maestro — Phase 1: main flows + console.log("\n=== [run-maestro: phase 1] ==="); const appId = getAppId(appPath, options.platform); - await runMaestro(options.platform, appId); + const flowsDir = path.resolve(__dirname, "flows"); + await runMaestro(flowsDir, options.platform, appId); + + // 6. Disable release for rollback test + console.log("\n=== [disable-release] ==="); + disableRelease(options.platform, options.app, "1.0.0"); + + // 7. Run Maestro — Phase 2: rollback flow + console.log("\n=== [run-maestro: phase 2 (rollback)] ==="); + const rollbackDir = path.resolve(__dirname, "flows-rollback"); + await runMaestro(rollbackDir, options.platform, appId); console.log("\n=== E2E tests passed ==="); } catch (error) { @@ -64,7 +74,7 @@ async function main() { console.error(`\nE2E test failed: ${message}`); process.exitCode = 1; } finally { - // 6. Cleanup + // 8. Cleanup console.log("\n=== [cleanup] ==="); await stopMockServer(); restoreConfig(appPath); @@ -94,8 +104,26 @@ function getAppId(appPath: string, platform: "ios" | "android"): string { return match[1]; } -function runMaestro(platform: "ios" | "android", appId: string): Promise { - const flowsDir = path.resolve(__dirname, "flows"); +function disableRelease( + platform: "ios" | "android", + appName: string, + binaryVersion: string, +): void { + const historyPath = path.join( + MOCK_DATA_DIR, "histories", platform, appName, `${binaryVersion}.json`, + ); + if (!fs.existsSync(historyPath)) { + throw new Error(`Release history not found: ${historyPath}`); + } + const history = JSON.parse(fs.readFileSync(historyPath, "utf8")); + for (const version of Object.keys(history)) { + history[version].enabled = false; + } + fs.writeFileSync(historyPath, JSON.stringify(history), "utf8"); + console.log(`All releases disabled in: ${historyPath}`); +} + +function runMaestro(flowsDir: string, platform: "ios" | "android", appId: string): Promise { const args = ["test", flowsDir, "--env", `APP_ID=${appId}`]; if (platform === "android") { From 6af7bba6e2298e86ae183fded0ae2de15badd155 Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:20:12 +0900 Subject: [PATCH 28/44] chore(e2e): add pod install before iOS build, gitignore mock data, fix metadata assertion --- .gitignore | 3 +++ e2e/flows/03-update-flow.yaml | 8 +++++--- e2e/helpers/build-app.ts | 5 ++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 257665b2..7d092d57 100644 --- a/.gitignore +++ b/.gitignore @@ -169,3 +169,6 @@ cli/dist/ !bin/ bin/* !bin/code-push.js + +# E2E mock server data +e2e/mock-server/data/ diff --git a/e2e/flows/03-update-flow.yaml b/e2e/flows/03-update-flow.yaml index 58ee7f6b..31b63068 100644 --- a/e2e/flows/03-update-flow.yaml +++ b/e2e/flows/03-update-flow.yaml @@ -12,9 +12,10 @@ appId: ${APP_ID} - assertNotVisible: "UPDATED!" # Verify no running update metadata +- assertVisible: "METADATA_IDLE" - tapOn: "Get update metadata" - waitForAnimationToEnd: - timeout: 2000 + timeout: 3000 - assertVisible: "METADATA_NULL" # Check for updates — mandatory update installs silently @@ -29,7 +30,8 @@ appId: ${APP_ID} - assertVisible: "React Native.*" # Verify UI is not frozen — metadata fetch should update the indicator +- assertVisible: "METADATA_IDLE" - tapOn: "Get update metadata" - waitForAnimationToEnd: - timeout: 2000 -- assertVisible: "METADATA_LOADED" \ No newline at end of file + timeout: 3000 +- assertVisible: "METADATA_LOADED" diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index 83ca30b3..e3b9c5e0 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -12,7 +12,10 @@ export async function buildApp( } } -function buildIos(appPath: string, simulator?: string): Promise { +async function buildIos(appPath: string, simulator?: string): Promise { + console.log(`[command] npm run setup:pods (cwd: ${appPath})`); + await executeCommand("npm", ["run", "setup:pods"], appPath); + const args = [ "react-native", "run-ios", "--mode", "Release", From 91034deb92b5ee57d1bd467be8f8bf202786c576 Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:32:49 +0900 Subject: [PATCH 29/44] fix(e2e): disable code signing for iOS simulator builds --- e2e/helpers/build-app.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index e3b9c5e0..5ee7c238 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -20,6 +20,7 @@ async function buildIos(appPath: string, simulator?: string): Promise { "react-native", "run-ios", "--mode", "Release", "--no-packager", + "--extra-params", "CODE_SIGNING_ALLOWED=NO", ]; if (simulator) { args.push("--simulator", simulator); From 869dabb51baa3c4de153a2634a03ec354b6493ed Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:35:54 +0900 Subject: [PATCH 30/44] fix(e2e): use ad-hoc signing for iOS simulator instead of disabling signing --- e2e/helpers/build-app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index 5ee7c238..a44907fd 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -20,7 +20,7 @@ async function buildIos(appPath: string, simulator?: string): Promise { "react-native", "run-ios", "--mode", "Release", "--no-packager", - "--extra-params", "CODE_SIGNING_ALLOWED=NO", + "--extra-params", "CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO", ]; if (simulator) { args.push("--simulator", simulator); From 6ec91d37d13e693d49362560b01ad306eb841273 Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:38:01 +0900 Subject: [PATCH 31/44] fix(e2e): clear entitlements for iOS simulator Release build --- e2e/helpers/build-app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index a44907fd..6bd5b79d 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -20,7 +20,7 @@ async function buildIos(appPath: string, simulator?: string): Promise { "react-native", "run-ios", "--mode", "Release", "--no-packager", - "--extra-params", "CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO", + "--extra-params", "CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS=", ]; if (simulator) { args.push("--simulator", simulator); From 184a42dcec1d71d9e82b1f1687479f170b316c3d Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:39:59 +0900 Subject: [PATCH 32/44] fix(e2e): use xcodebuild + simctl directly for iOS to bypass signing issues --- e2e/helpers/build-app.ts | 65 ++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index 6bd5b79d..d22f0c4b 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -1,4 +1,5 @@ -import { spawn } from "child_process"; +import { execSync, spawn } from "child_process"; +import path from "path"; export async function buildApp( appPath: string, @@ -16,17 +17,63 @@ async function buildIos(appPath: string, simulator?: string): Promise { console.log(`[command] npm run setup:pods (cwd: ${appPath})`); await executeCommand("npm", ["run", "setup:pods"], appPath); + const appName = path.basename(appPath); + const destination = simulator + ? `platform=iOS Simulator,name=${simulator}` + : `platform=iOS Simulator,id=${getBootedSimulatorId()}`; + const args = [ - "react-native", "run-ios", - "--mode", "Release", - "--no-packager", - "--extra-params", "CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS=", + "-workspace", `ios/${appName}.xcworkspace`, + "-scheme", appName, + "-configuration", "Release", + "-sdk", "iphonesimulator", + "-destination", destination, + "-derivedDataPath", "ios/build", + "CODE_SIGN_IDENTITY=-", + "CODE_SIGNING_REQUIRED=NO", + "CODE_SIGNING_ALLOWED=NO", ]; - if (simulator) { - args.push("--simulator", simulator); + + console.log(`[command] xcodebuild ${args.join(" ")} (cwd: ${appPath})`); + await executeCommand("xcodebuild", args, appPath); + + // Install on simulator + const appBundlePath = `ios/build/Build/Products/Release-iphonesimulator/${appName}.app`; + const simId = simulator + ? getSimulatorId(simulator) + : getBootedSimulatorId(); + + console.log(`[command] xcrun simctl install ${simId} ${appBundlePath}`); + await executeCommand("xcrun", ["simctl", "install", simId, appBundlePath], appPath); + + console.log(`[command] xcrun simctl launch ${simId} org.reactjs.native.example.${appName}`); + await executeCommand("xcrun", ["simctl", "launch", simId, `org.reactjs.native.example.${appName}`], appPath); +} + +function getBootedSimulatorId(): string { + const output = execSync("xcrun simctl list devices booted -j", { encoding: "utf8" }); + const data = JSON.parse(output); + for (const runtime of Object.values(data.devices) as any[]) { + for (const device of runtime) { + if (device.state === "Booted") { + return device.udid; + } + } } - console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); - return executeCommand("npx", args, appPath); + throw new Error("No booted iOS simulator found"); +} + +function getSimulatorId(name: string): string { + const output = execSync("xcrun simctl list devices available -j", { encoding: "utf8" }); + const data = JSON.parse(output); + for (const runtime of Object.values(data.devices) as any[]) { + for (const device of runtime) { + if (device.name === name) { + return device.udid; + } + } + } + throw new Error(`Simulator "${name}" not found`); } function buildAndroid(appPath: string): Promise { From f813ef481d9cb276908fc0312457fa65066588f0 Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:43:59 +0900 Subject: [PATCH 33/44] fix(e2e): set ad-hoc signing in xcodeproj via setup script, revert to run-ios --- .../ios/RN0840RC5.xcodeproj/project.pbxproj | 500 ++++++++++++++++++ e2e/helpers/build-app.ts | 66 +-- scripts/setupExampleApp/runSetupExampleApp.ts | 6 + 3 files changed, 515 insertions(+), 57 deletions(-) create mode 100644 Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj diff --git a/Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj b/Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj new file mode 100644 index 00000000..23f725e3 --- /dev/null +++ b/Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj @@ -0,0 +1,500 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0C80B921A6F3F58F76C31292 /* libPods-RN0840RC5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-RN0840RC5.a */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 369ED7940DF4E0813288875B /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 13B07F961A680F5B00A75B9A /* RN0840RC5.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RN0840RC5.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = RN0840RC5/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RN0840RC5/Info.plist; sourceTree = ""; }; + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = RN0840RC5/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 3B4392A12AC88292D35C810B /* Pods-RN0840RC5.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RN0840RC5.debug.xcconfig"; path = "Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5.debug.xcconfig"; sourceTree = ""; }; + 5709B34CF0A7D63546082F79 /* Pods-RN0840RC5.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RN0840RC5.release.xcconfig"; path = "Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5.release.xcconfig"; sourceTree = ""; }; + 5DCACB8F33CDC322A6C60F78 /* libPods-RN0840RC5.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RN0840RC5.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = RN0840RC5/AppDelegate.swift; sourceTree = ""; }; + 7F927E3D42A2494C94937D36 /* RN0840RC5-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "RN0840RC5-Bridging-Header.h"; path = "RN0840RC5/RN0840RC5-Bridging-Header.h"; sourceTree = ""; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = RN0840RC5/LaunchScreen.storyboard; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0C80B921A6F3F58F76C31292 /* libPods-RN0840RC5.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* RN0840RC5 */ = { + isa = PBXGroup; + children = ( + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 761780EC2CA45674006654EE /* AppDelegate.swift */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, + 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */, + 7F927E3D42A2494C94937D36 /* RN0840RC5-Bridging-Header.h */, + ); + name = RN0840RC5; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + 5DCACB8F33CDC322A6C60F78 /* libPods-RN0840RC5.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* RN0840RC5 */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + BBD78D7AC51CEA395F1C20DB /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* RN0840RC5.app */, + ); + name = Products; + sourceTree = ""; + }; + BBD78D7AC51CEA395F1C20DB /* Pods */ = { + isa = PBXGroup; + children = ( + 3B4392A12AC88292D35C810B /* Pods-RN0840RC5.debug.xcconfig */, + 5709B34CF0A7D63546082F79 /* Pods-RN0840RC5.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* RN0840RC5 */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RN0840RC5" */; + buildPhases = ( + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */, + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RN0840RC5; + productName = RN0840RC5; + productReference = 13B07F961A680F5B00A75B9A /* RN0840RC5.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1210; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1120; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "RN0840RC5" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* RN0840RC5 */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 369ED7940DF4E0813288875B /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/.xcode.env", + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"\\\"$WITH_ENVIRONMENT\\\" \\\"$REACT_NATIVE_XCODE\\\"\"\n"; + }; + 00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RN0840RC5-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RN0840RC5/Pods-RN0840RC5-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-RN0840RC5.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = RN0840RC5/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = RN0840RC5; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_OBJC_BRIDGING_HEADER = "RN0840RC5/RN0840RC5-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-RN0840RC5.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = RN0840RC5/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = RN0840RC5; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SWIFT_OBJC_BRIDGING_HEADER = "RN0840RC5/RN0840RC5-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + SWIFT_OBJC_BRIDGING_HEADER = "RN0840RC5/RN0840RC5-Bridging-Header.h"; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = ( + "$(inherited)", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + "-DFOLLY_HAVE_CLOCK_GETTIME=1", + "-DRCT_REMOVE_LEGACY_ARCH=1", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ENABLE_EXPLICIT_MODULES = NO; + SWIFT_OBJC_BRIDGING_HEADER = "RN0840RC5/RN0840RC5-Bridging-Header.h"; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "RN0840RC5" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "RN0840RC5" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/e2e/helpers/build-app.ts b/e2e/helpers/build-app.ts index d22f0c4b..b3d3f3d8 100644 --- a/e2e/helpers/build-app.ts +++ b/e2e/helpers/build-app.ts @@ -1,5 +1,4 @@ -import { execSync, spawn } from "child_process"; -import path from "path"; +import { spawn } from "child_process"; export async function buildApp( appPath: string, @@ -17,63 +16,16 @@ async function buildIos(appPath: string, simulator?: string): Promise { console.log(`[command] npm run setup:pods (cwd: ${appPath})`); await executeCommand("npm", ["run", "setup:pods"], appPath); - const appName = path.basename(appPath); - const destination = simulator - ? `platform=iOS Simulator,name=${simulator}` - : `platform=iOS Simulator,id=${getBootedSimulatorId()}`; - const args = [ - "-workspace", `ios/${appName}.xcworkspace`, - "-scheme", appName, - "-configuration", "Release", - "-sdk", "iphonesimulator", - "-destination", destination, - "-derivedDataPath", "ios/build", - "CODE_SIGN_IDENTITY=-", - "CODE_SIGNING_REQUIRED=NO", - "CODE_SIGNING_ALLOWED=NO", + "react-native", "run-ios", + "--mode", "Release", + "--no-packager", ]; - - console.log(`[command] xcodebuild ${args.join(" ")} (cwd: ${appPath})`); - await executeCommand("xcodebuild", args, appPath); - - // Install on simulator - const appBundlePath = `ios/build/Build/Products/Release-iphonesimulator/${appName}.app`; - const simId = simulator - ? getSimulatorId(simulator) - : getBootedSimulatorId(); - - console.log(`[command] xcrun simctl install ${simId} ${appBundlePath}`); - await executeCommand("xcrun", ["simctl", "install", simId, appBundlePath], appPath); - - console.log(`[command] xcrun simctl launch ${simId} org.reactjs.native.example.${appName}`); - await executeCommand("xcrun", ["simctl", "launch", simId, `org.reactjs.native.example.${appName}`], appPath); -} - -function getBootedSimulatorId(): string { - const output = execSync("xcrun simctl list devices booted -j", { encoding: "utf8" }); - const data = JSON.parse(output); - for (const runtime of Object.values(data.devices) as any[]) { - for (const device of runtime) { - if (device.state === "Booted") { - return device.udid; - } - } - } - throw new Error("No booted iOS simulator found"); -} - -function getSimulatorId(name: string): string { - const output = execSync("xcrun simctl list devices available -j", { encoding: "utf8" }); - const data = JSON.parse(output); - for (const runtime of Object.values(data.devices) as any[]) { - for (const device of runtime) { - if (device.name === name) { - return device.udid; - } - } + if (simulator) { + args.push("--simulator", simulator); } - throw new Error(`Simulator "${name}" not found`); + console.log(`[command] npx ${args.join(" ")} (cwd: ${appPath})`); + return executeCommand("npx", args, appPath); } function buildAndroid(appPath: string): Promise { @@ -96,4 +48,4 @@ function executeCommand(command: string, args: string[], cwd: string): Promise { "IPHONEOS_DEPLOYMENT_TARGET = 16.0;", "IPHONEOS_DEPLOYMENT_TARGET" ); + nextContent = replaceAllOrThrow( + nextContent, + /"CODE_SIGN_IDENTITY\[sdk=iphoneos\*\]" = "iPhone Developer";/g, + '"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-";', + "CODE_SIGN_IDENTITY" + ); return nextContent; }); From 5016138e55f48eaebac94743bd5bd9788b908f2a Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:46:17 +0900 Subject: [PATCH 34/44] fix(e2e): add CODE_SIGNING_ALLOWED=NO and CODE_SIGNING_REQUIRED=NO to pbxproj --- Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj | 4 ++++ scripts/setupExampleApp/runSetupExampleApp.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj b/Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj index 23f725e3..e27664d9 100644 --- a/Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj +++ b/Examples/RN0840RC5/ios/RN0840RC5.xcodeproj/project.pbxproj @@ -345,6 +345,8 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-"; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; COPY_PHASE_STRIP = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; @@ -428,6 +430,8 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-"; + CODE_SIGNING_ALLOWED = NO; + CODE_SIGNING_REQUIRED = NO; COPY_PHASE_STRIP = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; diff --git a/scripts/setupExampleApp/runSetupExampleApp.ts b/scripts/setupExampleApp/runSetupExampleApp.ts index ac8861e8..f0f3146c 100644 --- a/scripts/setupExampleApp/runSetupExampleApp.ts +++ b/scripts/setupExampleApp/runSetupExampleApp.ts @@ -204,7 +204,7 @@ async function configureIosVersioning(context: SetupContext): Promise { nextContent = replaceAllOrThrow( nextContent, /"CODE_SIGN_IDENTITY\[sdk=iphoneos\*\]" = "iPhone Developer";/g, - '"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-";', + '"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "-";\n\t\t\t\tCODE_SIGNING_ALLOWED = NO;\n\t\t\t\tCODE_SIGNING_REQUIRED = NO;', "CODE_SIGN_IDENTITY" ); return nextContent; From 77f5bb392e9c867d0e2859baeaf9d2d322622e2f Mon Sep 17 00:00:00 2001 From: floydkim Date: Tue, 17 Feb 2026 23:58:54 +0900 Subject: [PATCH 35/44] refactor(e2e): move static patches to setup script and App template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move E2E patches that don't need runtime application into the app template and setup script: updateDialog removal, Alert→console.log, METADATA indicator, usesCleartextTraffic, SUPPORTED_PLATFORMS restriction to iphonesimulator only. --- Examples/RN0840RC5/App.tsx | 187 ++++++++++++++++++ .../ios/RN0840RC5.xcodeproj/project.pbxproj | 8 +- e2e/helpers/prepare-config.ts | 38 +--- scripts/setupExampleApp/runSetupExampleApp.ts | 23 +++ scripts/setupExampleApp/templates/App.tsx.txt | 14 +- 5 files changed, 223 insertions(+), 47 deletions(-) create mode 100644 Examples/RN0840RC5/App.tsx diff --git a/Examples/RN0840RC5/App.tsx b/Examples/RN0840RC5/App.tsx new file mode 100644 index 00000000..2cdc733e --- /dev/null +++ b/Examples/RN0840RC5/App.tsx @@ -0,0 +1,187 @@ +import React, { useCallback, useState } from 'react'; +import { + Button, + Platform, + ScrollView, + StatusBar, + Text, + TextInput, + View, +} from 'react-native'; +import CodePush, { + ReleaseHistoryInterface, + UpdateCheckRequest, +} from '@bravemobile/react-native-code-push'; +import axios from 'axios'; +import { SafeAreaProvider, useSafeAreaInsets } from 'react-native-safe-area-context'; + +// Set this to true before run `npx code-push release` to release a new bundle +const IS_RELEASING_BUNDLE = true; + +const REACT_NATIVE_VERSION = (() => { + const { major, minor, patch, prerelease } = Platform.constants.reactNativeVersion; + return `${major}.${minor}.${patch}` + (prerelease ? `-${prerelease}` : ''); +})(); + +function App() { + const { top } = useSafeAreaInsets(); + const [syncResult, setSyncResult] = useState(''); + const [progress, setProgress] = useState(0); + const [runningMetadata, setRunningMetadata] = useState(''); + const [pendingMetadata, setPendingMetadata] = useState(''); + const [latestMetadata, setLatestMetadata] = useState(''); + + const handleSync = useCallback(() => { + CodePush.sync({}, + status => { + setSyncResult(findKeyByValue(CodePush.SyncStatus, status) ?? ''); + }, + ({ receivedBytes, totalBytes }) => { + setProgress(Math.round((receivedBytes / totalBytes) * 100)); + }, + mismatch => { + console.log('CodePush mismatch', JSON.stringify(mismatch, null, 2)); + }, + ).catch(error => { + console.error(error); + console.log('Sync failed', error.message ?? 'Unknown error'); + }); + }, []); + + const handleMetadata = useCallback(async () => { + const [running, pending, latest] = await Promise.all([ + CodePush.getUpdateMetadata(CodePush.UpdateState.RUNNING), + CodePush.getUpdateMetadata(CodePush.UpdateState.PENDING), + CodePush.getUpdateMetadata(CodePush.UpdateState.LATEST), + ]); + setRunningMetadata(JSON.stringify(running, null, 2)); + setPendingMetadata(JSON.stringify(pending, null, 2)); + setLatestMetadata(JSON.stringify(latest, null, 2)); + }, []); + + return ( + + + {`React Native ${REACT_NATIVE_VERSION}`} + + {IS_RELEASING_BUNDLE && + {'UPDATED!'} + } + + + +