From 9e7b0c2c7b96dd5b0f8aa78a12124d234b527d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 13 Oct 2026 15:11:52 +0200 Subject: [PATCH 01/13] feat: repack e2e tests --- .github/workflows/e2e.yml | 150 ++++ apps/tester-federation-v2/.gitignore | 3 + apps/tester-federation-v2/e2e.config.ts | 13 + apps/tester-federation-v2/e2e/smoke.e2e.ts | 40 ++ apps/tester-federation-v2/package.json | 5 +- .../src/host/screens/DetailScreen.tsx | 2 +- .../src/host/screens/HomeScreen.tsx | 2 + .../src/host/screens/MiniAppScreen.tsx | 2 +- .../src/mini/screens/GalleryScreen.tsx | 1 + pnpm-lock.yaml | 663 ++++++++++++++++++ 10 files changed, 878 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/e2e.yml create mode 100644 apps/tester-federation-v2/e2e.config.ts create mode 100644 apps/tester-federation-v2/e2e/smoke.e2e.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml new file mode 100644 index 000000000..fe278b18d --- /dev/null +++ b/.github/workflows/e2e.yml @@ -0,0 +1,150 @@ +name: e2e + +# Module Federation v2 e2e on a real Android emulator, mirroring the local flow: +# start both dev servers, build the app, run the tests on a booted device. +# +# `pull_request_target` runs workflow code from the base revision, so a +# contributor cannot change what executes; the PR head is checked out +# explicitly below. The job reads no secrets (every test is deterministic) and +# the token is pinned to `contents: read`. +on: + pull_request_target: + types: [opened, reopened, synchronize, labeled, unlabeled] + push: + branches: [main] + workflow_dispatch: + +# An emulator run is too expensive to let superseded commits queue up behind it. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + android: + name: e2e (Android) + # Same-repo PRs run unconditionally; a fork PR runs while the `e2e` label + # is on it (the label does not exist in repo settings yet). No workflow-level + # `paths:` filter — a path-filtered required check is never created and the + # merge hangs forever. + if: >- + github.event_name != 'pull_request_target' || + github.event.pull_request.head.repo.full_name == github.repository || + contains(github.event.pull_request.labels.*.name, 'e2e') + runs-on: ubuntu-latest + timeout-minutes: 45 + # Measurement phase: flip to false and mark the check required in branch + # protection in the same change — a required check on a continue-on-error + # job is permanently green, which is worse than no check. + continue-on-error: true + + steps: + # pull_request_target checks out the base by default, which would test + # the wrong code; every other trigger has no PR context and falls + # through to github.sha. + - name: Checkout pull request head + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Setup workspace + uses: ./.github/actions/setup-pnpm-node + with: + # e2e requires Node ^22.22.3 || >=24.8.0; exact pin so the floor + # cannot drift. + node-version: '24.18.0' + + - name: Setup Java + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # tag=v6.0.1 + with: + distribution: temurin + java-version: '17' + + # Required by android-emulator-runner on GitHub-hosted runners. + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Cache Gradle + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('apps/tester-federation-v2/android/gradle/wrapper/gradle-wrapper.properties', 'apps/tester-federation-v2/android/**/*.gradle', 'apps/tester-federation-v2/android/gradle.properties', 'pnpm-lock.yaml') }} + + # The emulator runner action has no caching of its own. + - name: Cache Android emulator + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ runner.os }}-api-35 + + # The dev servers are the thing under test: this is how the PR's Repack + # change reaches them (workspace dist/ is gitignored). + - name: Build workspace packages + run: pnpm --filter tester-federation-v2... build + + # Servers before Gradle: the cold compile overlaps the build and the + # emulator boot. nohup/disown because the action runs each script line + # in its own shell and these must outlive this step. The /status wait is + # not ceremony: react-native-test-app picks its bundle source once at app + # start and never retries, so an app launched while 8081 is still booting + # shows an alert no later tap dismisses. + - name: Start dev servers + working-directory: apps/tester-federation-v2 + run: | + nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & + disown + nohup pnpm start:miniapp > /tmp/miniapp.log 2>&1 & + disown + timeout 180 sh -c 'until [ "$(curl -sf localhost:8081/status)" = "packager-status:running" ] && [ "$(curl -sf localhost:8082/status)" = "packager-status:running" ]; do sleep 2; done' + + - name: Build Android app + working-directory: apps/tester-federation-v2/android + run: ./gradlew :app:assembleDebug -PreactNativeArchitectures=x86_64 + + # The reverses are load-bearing for 8082: the MiniApp remote URL is baked + # into the bundle as http://localhost:8082/..., and the device's localhost + # is the device itself (8081 needs no reverse — the app reaches it via + # 10.0.2.2). Proven locally: with reverses wiped, exactly the Gallery + # test fails. google_apis, never google_apis_playstore — the Play image + # could not launch the app in earlier testing (unconfirmed mechanism; do + # not switch without verifying). + - name: Run e2e on an emulator + uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # tag=v2.38.0 + with: + api-level: 35 + target: google_apis + arch: x86_64 + profile: pixel_7 + working-directory: apps/tester-federation-v2 + script: | + adb reverse tcp:8081 tcp:8081 + adb reverse tcp:8082 tcp:8082 + adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk + pnpm exec e2e run --target android --reporter list,junit + + # !cancelled() keeps evidence from a test that failed then passed on retry. + - name: Upload e2e artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 + with: + name: e2e-android + path: apps/tester-federation-v2/.e2e + if-no-files-found: warn + retention-days: 7 + + # The nohup'd server logs are otherwise invisible on a red run. + - name: Show dev server logs on failure + if: ${{ failure() }} + run: tail -n 60 /tmp/hostapp.log /tmp/miniapp.log + + # iOS is Phase 1b: needs an xcodebuild lane, a pinned simulator runtime and a + # ~/.agent-device cache, none measured yet. diff --git a/apps/tester-federation-v2/.gitignore b/apps/tester-federation-v2/.gitignore index de13ae3b6..86faa27e0 100644 --- a/apps/tester-federation-v2/.gitignore +++ b/apps/tester-federation-v2/.gitignore @@ -62,3 +62,6 @@ yarn-error.log # MF **/@mf-types + +# e2e +.e2e/ diff --git a/apps/tester-federation-v2/e2e.config.ts b/apps/tester-federation-v2/e2e.config.ts new file mode 100644 index 000000000..d25c5f722 --- /dev/null +++ b/apps/tester-federation-v2/e2e.config.ts @@ -0,0 +1,13 @@ +import { mobile } from '@e2e-dev/mobile'; +import type { E2EConfig } from 'e2e'; + +export default { + tests: 'e2e/**/*.e2e.ts', + targets: [ + { + name: 'android', + engine: mobile({ platform: 'android' }), + app: { bundleId: 'com.tester.federationV2' }, + }, + ], +} satisfies E2EConfig; diff --git a/apps/tester-federation-v2/e2e/smoke.e2e.ts b/apps/tester-federation-v2/e2e/smoke.e2e.ts new file mode 100644 index 000000000..2097ed870 --- /dev/null +++ b/apps/tester-federation-v2/e2e/smoke.e2e.ts @@ -0,0 +1,40 @@ +import { test } from '@e2e-dev/mobile'; +import { expect } from 'e2e'; + +test('the host app boots to Home', async ({ app, screen }) => { + await app.open(); + + await expect(screen.getByTestId('home-details-button')).toBeVisible({ + timeout: 60_000, + }); + await expect(screen.getByTestId('home-gallery-button')).toBeVisible(); +}); + +test('navigates from Home to Detail and back', async ({ app, screen }) => { + await app.open(); + await expect(screen.getByTestId('home-details-button')).toBeVisible({ + timeout: 60_000, + }); + + await screen.getByTestId('home-details-button').tap(); + await expect(screen.getByTestId('detail-screen')).toBeVisible(); + + await app.back(); + await expect(screen.getByTestId('home-details-button')).toBeVisible(); + await expect(screen.getByTestId('detail-screen')).toBeHidden(); +}); + +test('Home opens the Gallery MiniApp', { + timeout: 120_000, +}, async ({ app, screen }) => { + await app.open(); + await expect(screen.getByTestId('home-gallery-button')).toBeVisible({ + timeout: 60_000, + }); + + await screen.getByTestId('home-gallery-button').tap(); + await expect(screen.getByTestId('miniapp-gallery')).toBeVisible({ + timeout: 45_000, + }); + await expect(screen.getByText('Picture 1')).toBeVisible(); +}); diff --git a/apps/tester-federation-v2/package.json b/apps/tester-federation-v2/package.json index 222874f47..ae6f2afae 100644 --- a/apps/tester-federation-v2/package.json +++ b/apps/tester-federation-v2/package.json @@ -8,7 +8,8 @@ "pods": "(cd ios && bundle install && (bundle exec pod install || bundle exec pod update))", "pods:update": "(cd ios && bundle install && bundle exec pod update)", "start:hostapp": "react-native webpack-start --config config.host-app.mts", - "start:miniapp": "react-native webpack-start --config config.mini-app.mts --port 8082" + "start:miniapp": "react-native webpack-start --config config.mini-app.mts --port 8082", + "e2e": "pnpm exec e2e run" }, "dependencies": { "@callstack/repack": "workspace:*", @@ -24,6 +25,7 @@ }, "devDependencies": { "@babel/core": "^7.29.7", + "@e2e-dev/mobile": "0.11.0", "@react-native-community/cli": "catalog:testers", "@react-native-community/cli-platform-android": "catalog:testers", "@react-native-community/cli-platform-ios": "catalog:testers", @@ -34,6 +36,7 @@ "@swc/helpers": "catalog:", "@types/jest": "^29.5.13", "@types/react": "catalog:testers", + "e2e": "0.19.0", "react-native-test-app": "catalog:testers", "terser-webpack-plugin": "catalog:", "typescript": "catalog:", diff --git a/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx b/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx index 211051c96..3097b001a 100644 --- a/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/DetailScreen.tsx @@ -2,7 +2,7 @@ import { StyleSheet, Text, View } from 'react-native'; const DetailScreen = () => { return ( - + DetailScreen ); diff --git a/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx b/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx index 766bce133..20dfbb5b6 100644 --- a/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/HomeScreen.tsx @@ -13,6 +13,7 @@ const HomeScreen = () => { Explore the host app and its features navigation.navigate('Detail')} > @@ -29,6 +30,7 @@ const HomeScreen = () => { Mini Apps navigation.navigate('MiniApp')} > diff --git a/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx b/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx index af41cfd97..47e70358a 100644 --- a/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx +++ b/apps/tester-federation-v2/src/host/screens/MiniAppScreen.tsx @@ -16,7 +16,7 @@ class ErrorBoundary extends React.Component< render() { if (this.state.hasError) { return ( - + Failed to load Mini App ); diff --git a/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx b/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx index d8a0b5023..4d4af8175 100644 --- a/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx +++ b/apps/tester-federation-v2/src/mini/screens/GalleryScreen.tsx @@ -41,6 +41,7 @@ const Row = ({ const GalleryScreen = () => { return ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0fce08385..2340f4421 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -359,6 +359,9 @@ importers: '@babel/core': specifier: ^7.29.7 version: 7.29.7 + '@e2e-dev/mobile': + specifier: 0.11.0 + version: 0.11.0(e2e@0.19.0) '@react-native-community/cli': specifier: catalog:testers version: 20.2.0(typescript@7.0.2) @@ -389,6 +392,9 @@ importers: '@types/react': specifier: catalog:testers version: 19.2.17 + e2e: + specifier: 0.19.0 + version: 0.19.0 react-native-test-app: specifier: catalog:testers version: 5.4.5(@react-native-community/cli-types@20.2.0)(memfs@4.64.0)(metro@0.84.4)(react-native@0.86.0(@babel/core@7.29.7)(@react-native-community/cli@20.2.0(typescript@7.0.2))(@types/react@19.2.17)(react@19.2.8))(react@19.2.8) @@ -872,6 +878,10 @@ importers: packages: + '@ai-sdk/provider@4.0.21': + resolution: {integrity: sha512-UpbC9C1oht8dhfKPbVXSLRZS3DI8uk8n5v2uMjBPNIYGsC2kL045ywq7D8Hh9KgnH9rP5W/GxQdYtWScRouqlA==} + engines: {node: '>=22'} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -1766,13 +1776,31 @@ packages: '@clack/core@0.4.1': resolution: {integrity: sha512-Pxhij4UXg8KSr7rPek6Zowm+5M22rbd2g1nfojHJkxp5YkFqiZ2+YLEM/XGVIzvGOcM0nqjIFxrpDwWRZYWYjA==} + '@clack/core@1.5.1': + resolution: {integrity: sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==} + engines: {node: '>= 20.12.0'} + '@clack/prompts@0.9.1': resolution: {integrity: sha512-JIpyaboYZeWYlyP0H+OoPPxd6nqueG/CmN6ixBiNFsIDHREevjIf0n0Ohh5gr5C8pEDknzgvz+pIJ8dMhzWIeg==} + '@clack/prompts@1.8.1': + resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==} + engines: {node: '>= 20.12.0'} + '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} + '@e2e-dev/mobile@0.11.0': + resolution: {integrity: sha512-wvZ/VVUmNs65qnXqgh49huCw1Rgl1+3sngOEd4umLaNtPAcQBTzbX9Fz5zdXR5tQuN3HPejVez7IOSHYE3/dyQ==} + engines: {node: ^22.22.3 || >=24.8.0} + peerDependencies: + ai: ^7.0.0 + e2e: '>=0.15.0 <1' + peerDependenciesMeta: + ai: + optional: true + '@emnapi/core@1.11.3': resolution: {integrity: sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==} @@ -2383,6 +2411,14 @@ packages: '@types/react': '>=16' react: '>=16' + '@modelcontextprotocol/core@2.3.0': + resolution: {integrity: sha512-09BHFaNVBe5pB2AhzCfLj0cqmsKLERvZQ4FblzP90UMGLaXVqdZPByFjURwsUYCmxonUW2yAwgTTvle+8O2/8w==} + engines: {node: '>=20'} + + '@modelcontextprotocol/server@2.3.0': + resolution: {integrity: sha512-+6b0LdsQLmHsvS7J6sQJ4weTpzUn61fU1JKS+321/+IyDYoHW5CR2Tgqcs6GgAAQvlesCo6yX3xttgmx+XGtJw==} + engines: {node: '>=20'} + '@modern-js/node-bundle-require@2.67.6': resolution: {integrity: sha512-rRiDQkrm3kgn0E/GNrcvqo4c71PaUs2R8Xmpv6GUKbEr6lz7VNgfZmAhdAQPtNfRfiBe+1sFLzEcwfEdDo/dTA==} @@ -2858,6 +2894,257 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-parser/binding-android-arm-eabi@0.152.0': + resolution: {integrity: sha512-393VtirINkae4XHBTkq/yQxhOwEr4V+r0et37P2fp+sYZSdYgZTRDPo5Um8vBh0QjMZ+RmaMy4PP0qwr4cTMwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.152.0': + resolution: {integrity: sha512-iYFcExATJNZ0rZj0o02THbD1Nc9jAW0nVgIQPee6ejbSASpbZXlIMw8jxtpzaG14JQTWwGSZ6U5dEtRJXE6V3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.152.0': + resolution: {integrity: sha512-fnU+DfUzi5/rFy0LzmipYRz06PAUUkOS8kCJQ3Eg6oqlunC1XZXoffFqxmdL51RmcbYtUG3P8DVG1U5fckoa2A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.152.0': + resolution: {integrity: sha512-Y3U2okryWTs7hDld6UhjJwLNf8/do+x4g4C+1U5SiGwrTLwbO83WJYJA8+l9iFSilWBgnfpd/+U76tfeNxUHzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.152.0': + resolution: {integrity: sha512-XkhkRrrPffEw+ZLv/d/5pEni0buxinm/E/N6fLbJCunD1DBbeCTtVP23ESV+4CWCWdSz397asks1ViGslpmWiQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': + resolution: {integrity: sha512-MLzsRRbdfmdzo6JEhbC1yKU9ItfvM8tEf18HM32jCiNYZEz6J3bRBRl3cTkKR0vBdrk9ZVlcARXtwHccs+epDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': + resolution: {integrity: sha512-HHerTEJz7Iy10Ji6BGsk4gqG6PU0UzFuBu91jIEOX89FnrI5ECWSEr7BLpkYQhp7U0QFtMnBhNfQKI0hYZK78w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': + resolution: {integrity: sha512-9Bb8GyzZ2uSLzRDZ27usaKokr07mxiBe2dDF5b6bd2janmaJTT1gL51UfP7AU30yFiXrJvcRyFKREAgKwZYs0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-arm64-musl@0.152.0': + resolution: {integrity: sha512-om7iDMUroI74A/lfRRWY9/Ev3VWrVsrUZ4tyvyh8raBey/sdcfyAD7tYWc+iykAiotnmftyKTpUKFr1QnaBrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': + resolution: {integrity: sha512-Lf1OL2lMTqwDMFC6jnRb30B+WYHKV0XT+bPHcFM6xHtNVwxpobiTzUIvKH6a0LWpvfwyru8Yu6zCGHqqFHH+gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': + resolution: {integrity: sha512-BYWSMQK+YJ9ZILStPcmmiSVq8KEG9Qdq/c4BHPC3rUEq/fEDCVjUIwdj5ZzjgZ9P9is9PKu0tcAXzq1ZXg4urQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': + resolution: {integrity: sha512-d2LqHNNNqdH185sRK5dPKWZu201JrNu9/CRFuVEuT+ht7n/QaVblEMWT9MALQPrDWYvhG10P+NSKJFquDWZ09w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': + resolution: {integrity: sha512-8gBeHt5rA/8+pKhHl+zLggfa3L/V/L2at96p7ty6pBE7id6lFO19nFUc15u2PcjC4OYeYK465MKy1fhhbpFIGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-gnu@0.152.0': + resolution: {integrity: sha512-ltgrSF6L+yPTVsIdaAyqKq3zTqzj/1VlNbxWZuxAe8flR3sWdyNsKvdXukoO8/ucJbLickhmieRBge+o/e8Ugw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-parser/binding-linux-x64-musl@0.152.0': + resolution: {integrity: sha512-LcMp6BvrBnewSfdkLHoSqidhoAq4syKOTBV5TYHWd+2VnGkfD/nPRRVIzTBt/Lkbt7pfOFDGtkiTntFS0p6sQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-parser/binding-openharmony-arm64@0.152.0': + resolution: {integrity: sha512-jbhoMCslVxzRCRNQ8TSHxjYdSKRvk8O6ciLVDkahJvmyHDzBqba/bMQHM6ULKj/rgHNNbSzlIiakdx8SEfkJzA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': + resolution: {integrity: sha512-JZYVlhv36mr/nertnRB7vchJECOBb1Lz6lltalILsB+o9M/is+H6BEDRLQKdqA4JKs6NjJszo46+iM5sfwj6jQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': + resolution: {integrity: sha512-1X9oganvVsunRH0lH10UU05N+H9atpX6WsOybByUjwnfcd3h/0tYwvuv16XuHglrIUOnxV2TodbLVEwwafd64g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.152.0': + resolution: {integrity: sha512-sw0M80/dn9rcpu+Rqqph/C+MjvDDce6MlXmFjsNQtPEymha0eJy3ml6+foSUTqFfpDIDyHZZ4eoJ43IoMe8bJw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/runtime@0.152.0': + resolution: {integrity: sha512-fsq/7JVJo8YAmMPnt3Z7akMdMoCokRND5jjawa5DxuKJBEaI0Dl3ZqEXFlLabKoZhlApIexZ7aycXFcjcTcWvw==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@oxc-project/types@0.152.0': + resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + + '@oxc-transform/binding-android-arm-eabi@0.152.0': + resolution: {integrity: sha512-GBVYZdUggANq7ndUuE9HBi1jO6d2+oud3cevfQkpqrV7JsDNwQU5UCGa8rZCvzjIvyn6ikQuJu+sj4RvLyN6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-transform/binding-android-arm64@0.152.0': + resolution: {integrity: sha512-pqeCLGUnozzyJE9Ed9js2w+V1lHQ43qPImMfgTh7it+a3LJqUjAt+OesJ6pqkPycRFV6b0pUWZm6EkpM8mk7ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-transform/binding-darwin-arm64@0.152.0': + resolution: {integrity: sha512-0d68wdepj2bjnDijPdvC/3MnrZcs8hoCp19qnGX2tyxnSIPevgqa9RX5i31ex5JMczht/VGp4aKeV1p12dwzQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-transform/binding-darwin-x64@0.152.0': + resolution: {integrity: sha512-jFTRTyZzYhqSrxkaI9hTwClPTXtdqRyPSXUFGLJmStYS+b6qnhbI1ottivWdAg0HJXytyg8YeqgUJWSu2lU/jA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-transform/binding-freebsd-x64@0.152.0': + resolution: {integrity: sha512-L4Y8IfrcaxjK9kK3ddRAyZPa6nzJHn6fgkVIKoBlP53vmCobPL1mtuIdXQThD0VmV6e4aSAJg2An6qkdjeZC8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-transform/binding-linux-arm-gnueabihf@0.152.0': + resolution: {integrity: sha512-GrohqmIKEK/YXCzAGP8XGQrPVBdUHPpvqdhvw+VutpWZofxDOq39Zed+Gqlea2mycXo+/UNeq1194eaAYkqKQQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-transform/binding-linux-arm-musleabihf@0.152.0': + resolution: {integrity: sha512-wUI+JoZEs4+GntxjK2YOKtQDG822wv+aGYm1OUZ2+NAgi8ghOTUP3RH/GX8Vq0qJ2Yw3OwKLUIZzufXklQ7pCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-transform/binding-linux-arm64-gnu@0.152.0': + resolution: {integrity: sha512-EcY52e0BOfynOA/brCInvQO3ZSukbAEc+BsbpRoRdJ4qV10NK0aS/FheBvkj/V3cFidee6QlrUpqgpN4WKV+Sg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-arm64-musl@0.152.0': + resolution: {integrity: sha512-L4OGw0UuuyYKAPnUvvW+tvq6nS/tBoolVuPekNJqYnDYfe5Nh1619ImfhP0o0Wz+SG4OQW35ByRWjVf/oK3x0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-linux-ppc64-gnu@0.152.0': + resolution: {integrity: sha512-5K9qNjNKlyhBdGkkIhYFonO/H3Yc37miy5cXefTB71aPOv9jv6HQrDuj6AJcBkggbmkeyEJhlFamUbGMsMGBiA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-riscv64-gnu@0.152.0': + resolution: {integrity: sha512-WlGvnZhmRItWqjsGd2OBKPan75FbqpF4HPuGL7P6f7Oz1VHm6IatnwCK2MI16QXH7BEs6GRohZHpmgThc0V/uw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-riscv64-musl@0.152.0': + resolution: {integrity: sha512-TWZX7QGkU4LF9NS+HBUVWsbQXWDuWt58r+L/31gSJ/iHCJw/3jM2yD408IRVjXYX5jFgmsJbxrUlW3xlVeAL3Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-linux-s390x-gnu@0.152.0': + resolution: {integrity: sha512-VIP7463d0nIAGFv2VXb4mRf20qrpZg0kNHBw8E3fLuHj0bt4HVHDgsqAgeFWOonWwSwsM415TFjNmHhlaStCLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-x64-gnu@0.152.0': + resolution: {integrity: sha512-hwnHMVCnGIoS/eMh7T836mJpWim2iCSWGlCS+aZ+SjhEEUvSSUE54ljua7bk25vaj4L84cFnruTCa7HqyaQ2YQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxc-transform/binding-linux-x64-musl@0.152.0': + resolution: {integrity: sha512-5NPItMgM9GfIW0SbpOXvEFIijin5VcNEParmBEm9j+RXr8Ko7pZ3a1ydk/Sy+EOvaSTkXMY6TkVyLfczwdudSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxc-transform/binding-openharmony-arm64@0.152.0': + resolution: {integrity: sha512-pNzMq4AhBUV8E/cc41SgYTjzW/AWq1DtEKz1l56Mn1TKfidJwP8sGnIw0EXhD6zKFUlPoqkWfvoe5TiDAYeT+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-transform/binding-win32-arm64-msvc@0.152.0': + resolution: {integrity: sha512-PU6cGmVDYo2HHG9ul6vKj189YJPZrcscdWzQakJD6NU4CqvqISbZLUHwz6DVe0v8CbOe2d24zUdipILaRofZ5g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-transform/binding-win32-ia32-msvc@0.152.0': + resolution: {integrity: sha512-Sdcoky580dPgj9Qp+i4zrCV7NpIYZW6bBrcQvOwyDmNlJQ2iYK/V3Q3FGNHLulrxgXrpzqQwMcLs8KRIg0u0yw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-transform/binding-win32-x64-msvc@0.152.0': + resolution: {integrity: sha512-QnN+/Hsig/vI9zoCNVbbqv0GmZWUGqlPg2ZTEyVw/PvgAXd8pRRfN6CxdKEtJTnaVwJFVYaw/5LR6ZWCVBwDeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -4159,6 +4446,9 @@ packages: '@vitest/expect@4.1.10': resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + '@vitest/expect@5.0.3': + resolution: {integrity: sha512-N/VGY122AqBx25OB4EatqlVu8PBayvm9dQzpJ8kVq/pOvqAhXTE5MAmoSMzdB8klDU9wcgSEUliwgMNMtxPa1w==} + '@vitest/mocker@4.1.10': resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} peerDependencies: @@ -4173,6 +4463,9 @@ packages: '@vitest/pretty-format@4.1.10': resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + '@vitest/pretty-format@5.0.3': + resolution: {integrity: sha512-3hr5eV5z2ff0l2Q1vaKQbB4j3M3tElw8stDokEOTqXJB3Es5VrT8gBRIMY59gGuFp8Y61sWlWFK1aNUH5dHDYw==} + '@vitest/runner@4.1.10': resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} @@ -4182,9 +4475,15 @@ packages: '@vitest/spy@4.1.10': resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + '@vitest/spy@5.0.3': + resolution: {integrity: sha512-XhFysQTB8AZ+P4gMi+Lpo99vg2AZi0qKpaB9yXQl37+CaMEAPO3iH/wGVnSyL5MPERiLezpqTVtrR6UZH5GCXg==} + '@vitest/utils@4.1.10': resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} + '@vitest/utils@5.0.3': + resolution: {integrity: sha512-77fDTZv8eu1CzlIfgPU2yrl5MXSUaUAOvgdZEeVm3X3WIxUubIX2SforqS6176lT32HCF6uBVMYVqfHxw7o6eg==} + '@vscode/sudo-prompt@9.3.2': resolution: {integrity: sha512-gcXoCN00METUNFeQOFJ+C9xUI0DKB+0EGMVg7wbVYRHBw2Eq3fKisDZOkRdOz3kqXRKOENMfShPOmypw1/8nOw==} @@ -4295,6 +4594,16 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agent-device@0.21.22: + resolution: {integrity: sha512-ndUh5b+lZ2fRzzY80TTl/KmxR4bh+3FezSH0UGCjPCpN//1trSqtzzNUWunc4woY/vW3DEiPx5xYIwG+fBOXWA==} + engines: {node: '>=22.12'} + hasBin: true + peerDependencies: + ai: ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + ai: + optional: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} @@ -4772,6 +5081,10 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -5091,6 +5404,31 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + e2e@0.19.0: + resolution: {integrity: sha512-tTWgVR9tQTeESXkAxCh8H/gR4bGxbSbidw07/GngKT5P5tAPdsJ+bW5bQwIaAL/LUTwIG106V64/oiWLXkS29Q==} + engines: {node: ^22.22.3 || >=24.8.0} + hasBin: true + peerDependencies: + '@ai-sdk/anthropic': ^4.0.0 + '@ai-sdk/google': ^4.0.0 + '@ai-sdk/openai': ^4.0.0 + '@ai-sdk/openai-compatible': ^3.0.0 + '@ai-sdk/xai': ^5.0.0 + ai: ^7.0.0 + peerDependenciesMeta: + '@ai-sdk/anthropic': + optional: true + '@ai-sdk/google': + optional: true + '@ai-sdk/openai': + optional: true + '@ai-sdk/openai-compatible': + optional: true + '@ai-sdk/xai': + optional: true + ai: + optional: true + ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} @@ -5340,15 +5678,27 @@ packages: fast-json-stringify@7.0.1: resolution: {integrity: sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==} + fast-png@8.0.0: + resolution: {integrity: sha512-gCysNasJ8KEMgfdYIKd/wTDo6ENK1PWT0RJO7O+0pgmuHPw2O6tA1WvdxFRJoLf9V8yFYpG0FA1YgI8X97OhJA==} + fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + fast-uri@3.1.5: resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} fast-uri@4.1.2: resolution: {integrity: sha512-TyGmBcbDTZXcb2cj5MV89DrF42DKvb3y5DDUNh95iO+IMeAzMkVSxK1PZRrRIpc9yg8U2GhGdbofNa0LS/a4Bw==} + fast-wrap-ansi@0.2.2: + resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==} + fast-xml-builder@1.3.0: resolution: {integrity: sha512-F74cZEdCvuw9P41GAC3rod4X04jjWGM1JPEv/GWSqFTWLsdyMSBMBMlm9Hk3GLBgLBbdBNY8yee0pQh2RBVESQ==} @@ -5389,6 +5739,9 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + figures@6.1.0: resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} engines: {node: '>=18'} @@ -5571,6 +5924,9 @@ packages: resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} engines: {node: '>=18'} + get-tsconfig@4.14.3: + resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==} + github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -5847,6 +6203,9 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + iobuffer@6.0.1: + resolution: {integrity: sha512-SZWYkWNfjIXIBYSDpXDYIgshqtbOPsi4lviawAEceR1Kqk+sHDlcQjWrzNQsii80AyBY0q5c8HCTNjqo74ul+Q==} + ipaddr.js@2.4.0: resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} engines: {node: '>= 10'} @@ -6214,6 +6573,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + json-stream-stringify@3.0.1: resolution: {integrity: sha512-vuxs3G1ocFDiAQ/SX0okcZbtqXwgj1g71qE9+vrjJ2EkjKQlEFDAcUNRxRU8O+GekV4v5cM2qXP0Wyt/EMDBiQ==} @@ -7031,6 +7393,14 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + oxc-parser@0.152.0: + resolution: {integrity: sha512-ZztsZgaCWmPeKYMdoTT5e0oV/oxD1NeLWNR3kAtr/G707cUPYaaozRlVDHLCGnszHJRszN9mJwmPNpqgwloC0A==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-transform@0.152.0: + resolution: {integrity: sha512-9rs8qImolzWswXGlhmye9Yz9Mce9hOe4Pivb7rSEhF/6VKn1Q122BUe9Mr1O9xQAoBX+SrF6xXfdgxKHeQf+uQ==} + engines: {node: ^20.19.0 || >=22.12.0} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -7185,6 +7555,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@8.0.0: + resolution: {integrity: sha512-cIqOLfh5k52us715qf3B3+kTOxZv+WDv77HN7xokLScYQGYQHi4m/ynoDIPpvF2LvCyUJGyvzY64i0QCXtzu1g==} + hasBin: true + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -7201,6 +7575,10 @@ packages: resolution: {integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==} engines: {node: '>=10.4.0'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + portfinder@1.0.38: resolution: {integrity: sha512-rEwq/ZHlJIKw++XtLAO8PPuOQA/zaPJOZJ37BVuN97nLpMJeuDVLVGRwbFoBgLudgdTMP2hdRJP++H+8QOA3vg==} engines: {node: '>= 10.12'} @@ -7634,6 +8012,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve.exports@2.0.3: resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} engines: {node: '>=10'} @@ -8151,6 +8532,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyrainbow@3.2.0: + resolution: {integrity: sha512-LgO3D9yZJjApUiuUfl9iFAwrtaX4+lok3wJIqttGoKCHlWUqHqbQpnxCf82L8FjgKsh4iGo78hqJwgL8F6To2A==} + engines: {node: '>=14.0.0'} + tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} @@ -8643,11 +9028,18 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.6.1: + resolution: {integrity: sha512-341aRWQsve0rvronKNTqZpjmzdbUDlFuzHaI/XLg/Ej82qffDJRRfBTCuv7+9q/rMjB6LSLyEBnW4InJeMtt/Q==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: + '@ai-sdk/provider@4.0.21': + dependencies: + json-schema: 0.4.0 + '@alloc/quick-lru@5.2.0': {} '@ast-grep/napi-darwin-arm64@0.37.0': @@ -9793,14 +10185,33 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 + '@clack/core@1.5.1': + dependencies: + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + '@clack/prompts@0.9.1': dependencies: '@clack/core': 0.4.1 picocolors: 1.1.1 sisteransi: 1.0.5 + '@clack/prompts@1.8.1': + dependencies: + '@clack/core': 1.5.1 + fast-string-width: 3.0.2 + fast-wrap-ansi: 0.2.2 + sisteransi: 1.0.5 + '@discoveryjs/json-ext@0.5.7': {} + '@e2e-dev/mobile@0.11.0(e2e@0.19.0)': + dependencies: + agent-device: 0.21.22 + e2e: 0.19.0 + pngjs: 7.0.0 + zod: 4.6.1 + '@emnapi/core@1.11.3': dependencies: '@emnapi/wasi-threads': 1.2.3 @@ -10428,6 +10839,15 @@ snapshots: '@types/react': 18.3.31 react: 19.2.8 + '@modelcontextprotocol/core@2.3.0': + dependencies: + zod: 4.6.1 + + '@modelcontextprotocol/server@2.3.0': + dependencies: + '@modelcontextprotocol/core': 2.3.0 + zod: 4.6.1 + '@modern-js/node-bundle-require@2.67.6': dependencies: '@modern-js/utils': 2.67.6 @@ -11260,6 +11680,124 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oxc-parser/binding-android-arm-eabi@0.152.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.152.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.152.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.152.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.152.0': + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.152.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.152.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.152.0': + optional: true + + '@oxc-project/runtime@0.152.0': {} + + '@oxc-project/types@0.152.0': {} + + '@oxc-transform/binding-android-arm-eabi@0.152.0': + optional: true + + '@oxc-transform/binding-android-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-darwin-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-darwin-x64@0.152.0': + optional: true + + '@oxc-transform/binding-freebsd-x64@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm-gnueabihf@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm-musleabihf@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-arm64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-linux-ppc64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-riscv64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-riscv64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-linux-s390x-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-x64-gnu@0.152.0': + optional: true + + '@oxc-transform/binding-linux-x64-musl@0.152.0': + optional: true + + '@oxc-transform/binding-openharmony-arm64@0.152.0': + optional: true + + '@oxc-transform/binding-win32-arm64-msvc@0.152.0': + optional: true + + '@oxc-transform/binding-win32-ia32-msvc@0.152.0': + optional: true + + '@oxc-transform/binding-win32-x64-msvc@0.152.0': + optional: true + '@pinojs/redact@0.4.0': {} '@react-native-async-storage/async-storage@3.1.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native-community/cli@20.2.0(typescript@7.0.2))(@types/react@19.2.17)(react@19.2.8))(react@19.2.8)': @@ -12828,6 +13366,15 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/expect@5.0.3': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 5.0.3 + '@vitest/utils': 5.0.3 + chai: 6.2.2 + tinyrainbow: 3.2.0 + '@vitest/mocker@4.1.10(vite@7.3.6(@types/node@20.19.43)(jiti@2.7.0)(lightningcss@1.33.0)(terser@5.49.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.10 @@ -12848,6 +13395,10 @@ snapshots: dependencies: tinyrainbow: 3.1.0 + '@vitest/pretty-format@5.0.3': + dependencies: + tinyrainbow: 3.2.0 + '@vitest/runner@4.1.10': dependencies: '@vitest/utils': 4.1.10 @@ -12862,12 +13413,20 @@ snapshots: '@vitest/spy@4.1.10': {} + '@vitest/spy@5.0.3': {} + '@vitest/utils@4.1.10': dependencies: '@vitest/pretty-format': 4.1.10 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@vitest/utils@5.0.3': + dependencies: + '@vitest/pretty-format': 5.0.3 + convert-source-map: 2.0.0 + tinyrainbow: 3.2.0 + '@vscode/sudo-prompt@9.3.2': {} '@webassemblyjs/ast@1.14.1': @@ -12996,6 +13555,8 @@ snapshots: agent-base@7.1.4: {} + agent-device@0.21.22: {} + ajv-formats@2.1.1: dependencies: ajv: 8.20.0 @@ -13507,6 +14068,8 @@ snapshots: commander@12.1.0: {} + commander@15.0.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -13783,6 +14346,23 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + e2e@0.19.0: + dependencies: + '@ai-sdk/provider': 4.0.21 + '@clack/prompts': 1.8.1 + '@jridgewell/remapping': 2.3.5 + '@modelcontextprotocol/server': 2.3.0 + '@oxc-project/runtime': 0.152.0 + '@vitest/expect': 5.0.3 + commander: 15.0.0 + fast-png: 8.0.0 + get-tsconfig: 4.14.3 + oxc-parser: 0.152.0 + oxc-transform: 0.152.0 + picocolors: 1.1.1 + pixelmatch: 8.0.0 + zod: 4.6.1 + ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 @@ -14092,14 +14672,29 @@ snapshots: json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 + fast-png@8.0.0: + dependencies: + fflate: 0.8.3 + iobuffer: 6.0.1 + fast-querystring@1.1.2: dependencies: fast-decode-uri-component: 1.0.1 + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + fast-uri@3.1.5: {} fast-uri@4.1.2: {} + fast-wrap-ansi@0.2.2: + dependencies: + fast-string-width: 3.0.2 + fast-xml-builder@1.3.0: dependencies: path-expression-matcher: 1.6.2 @@ -14154,6 +14749,8 @@ snapshots: optionalDependencies: picomatch: 4.0.5 + fflate@0.8.3: {} + figures@6.1.0: dependencies: is-unicode-supported: 2.1.0 @@ -14336,6 +14933,10 @@ snapshots: '@sec-ant/readable-stream': 0.4.1 is-stream: 4.0.1 + get-tsconfig@4.14.3: + dependencies: + resolve-pkg-maps: 1.0.0 + github-slugger@2.0.0: {} glob-parent@5.1.2: @@ -14731,6 +15332,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + iobuffer@6.0.1: {} + ipaddr.js@2.4.0: {} is-absolute-url@4.0.1: {} @@ -15271,6 +15874,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema@0.4.0: {} + json-stream-stringify@3.0.1: {} json5@2.2.3: {} @@ -16442,6 +17047,52 @@ snapshots: outdent@0.5.0: {} + oxc-parser@0.152.0: + dependencies: + '@oxc-project/types': 0.152.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.152.0 + '@oxc-parser/binding-android-arm64': 0.152.0 + '@oxc-parser/binding-darwin-arm64': 0.152.0 + '@oxc-parser/binding-darwin-x64': 0.152.0 + '@oxc-parser/binding-freebsd-x64': 0.152.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.152.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.152.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.152.0 + '@oxc-parser/binding-linux-arm64-musl': 0.152.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.152.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.152.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-gnu': 0.152.0 + '@oxc-parser/binding-linux-x64-musl': 0.152.0 + '@oxc-parser/binding-openharmony-arm64': 0.152.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.152.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.152.0 + '@oxc-parser/binding-win32-x64-msvc': 0.152.0 + + oxc-transform@0.152.0: + optionalDependencies: + '@oxc-transform/binding-android-arm-eabi': 0.152.0 + '@oxc-transform/binding-android-arm64': 0.152.0 + '@oxc-transform/binding-darwin-arm64': 0.152.0 + '@oxc-transform/binding-darwin-x64': 0.152.0 + '@oxc-transform/binding-freebsd-x64': 0.152.0 + '@oxc-transform/binding-linux-arm-gnueabihf': 0.152.0 + '@oxc-transform/binding-linux-arm-musleabihf': 0.152.0 + '@oxc-transform/binding-linux-arm64-gnu': 0.152.0 + '@oxc-transform/binding-linux-arm64-musl': 0.152.0 + '@oxc-transform/binding-linux-ppc64-gnu': 0.152.0 + '@oxc-transform/binding-linux-riscv64-gnu': 0.152.0 + '@oxc-transform/binding-linux-riscv64-musl': 0.152.0 + '@oxc-transform/binding-linux-s390x-gnu': 0.152.0 + '@oxc-transform/binding-linux-x64-gnu': 0.152.0 + '@oxc-transform/binding-linux-x64-musl': 0.152.0 + '@oxc-transform/binding-openharmony-arm64': 0.152.0 + '@oxc-transform/binding-win32-arm64-msvc': 0.152.0 + '@oxc-transform/binding-win32-ia32-msvc': 0.152.0 + '@oxc-transform/binding-win32-x64-msvc': 0.152.0 + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -16578,6 +17229,10 @@ snapshots: pirates@4.0.7: {} + pixelmatch@8.0.0: + dependencies: + pngjs: 7.0.0 + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -16596,6 +17251,8 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + pngjs@7.0.0: {} + portfinder@1.0.38: dependencies: async: 3.2.6 @@ -17110,6 +17767,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve.exports@2.0.3: {} resolve@1.22.12: @@ -17678,6 +18337,8 @@ snapshots: tinyrainbow@3.1.0: {} + tinyrainbow@3.2.0: {} + tmpl@1.0.5: {} to-regex-range@5.0.1: @@ -18217,4 +18878,6 @@ snapshots: zod@3.25.76: {} + zod@4.6.1: {} + zwitch@2.0.4: {} From 39c525c3feab45929ed1e804fbe6883a05d9d83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Wed, 14 Oct 2026 09:53:45 +0200 Subject: [PATCH 02/13] fix: log more --- .github/workflows/e2e.yml | 87 ++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index fe278b18d..8986ed5c5 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -35,10 +35,11 @@ jobs: contains(github.event.pull_request.labels.*.name, 'e2e') runs-on: ubuntu-latest timeout-minutes: 45 - # Measurement phase: flip to false and mark the check required in branch - # protection in the same change — a required check on a continue-on-error - # job is permanently green, which is worse than no check. - continue-on-error: true + # NOT continue-on-error. A job that cannot fail reports green over red + # tests: run 38408377768 concluded "success" with the Gallery test failing. + # Still not a *required* check — branch protection is a separate decision + # and the suite has no stability history — but "not required" and "cannot + # fail" are independent switches, and only the first one is set here. steps: # pull_request_target checks out the base by default, which would test @@ -91,12 +92,14 @@ jobs: - name: Build workspace packages run: pnpm --filter tester-federation-v2... build - # Servers before Gradle: the cold compile overlaps the build and the - # emulator boot. nohup/disown because the action runs each script line - # in its own shell and these must outlive this step. The /status wait is - # not ceremony: react-native-test-app picks its bundle source once at app - # start and never retries, so an app launched while 8081 is still booting - # shows an alert no later tap dismisses. + # Servers before Gradle so their cold compile overlaps the build and the + # emulator boot — but "listening" is not "compiled", so the overlap only + # happens if something actually asks for a bundle (see the warm step). + # nohup/disown because the action runs each script line in its own shell + # and these must outlive this step. The /status wait is not ceremony: + # react-native-test-app picks its bundle source once at app start and + # never retries, so an app launched while 8081 is still booting shows an + # alert no later tap dismisses. - name: Start dev servers working-directory: apps/tester-federation-v2 run: | @@ -106,6 +109,35 @@ jobs: disown timeout 180 sh -c 'until [ "$(curl -sf localhost:8081/status)" = "packager-status:running" ] && [ "$(curl -sf localhost:8082/status)" = "packager-status:running" ]; do sleep 2; done' + # /status answers as soon as the server *listens* (createServer.ts + # registers it as a static reply — it never touches the compiler), and + # Re.Pack builds a platform lazily, on that platform's first asset + # request: Compiler.ts `getAsset` -> `activatePlatform`, with a `watchRun` + # hook that parks the initial build until then. So the previous step's + # wait started the overlap it was meant to have but never achieved: in run + # 38408377768 both servers listened at 06:09:13 and the host did not + # compile until 06:16:55, and 8082 never compiled at all — the device + # never sent it a single request, which is why the Gallery test hung. + # These two fetches are what make the overlap real, and they move 8082's + # cold build off the device's critical path, where it currently lands + # *after* a tap and gets killed when the step ends. + # + # The paths are RNTA's dev-bundle shape (verified against a live 8082: + # `/android/index.bundle?platform=android` -> 200 + "Compiled android", + # while the Metro-style `/index.android.bundle` 404s). The compile is + # keyed per platform, so any accepted filename triggers it; the params + # mirror what the app itself sends. + # + # A cold miss takes seconds (host: 10.5s, warm cache). 300s of budget, and + # -f so a server that 404s or dies fails the step here rather than + # presenting as a test failure 15 minutes later. + - name: Warm both compilers + working-directory: apps/tester-federation-v2 + run: | + curl -sf --max-time 300 -o /dev/null 'http://localhost:8081/android/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.tester.federationV2&modulesOnly=false&runModule=true' + curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/android/index.bundle?platform=android&dev=true&lazy=true&minify=false&app=com.tester.federationV2&modulesOnly=false&runModule=true' + echo 'Both bundles served; compilers are warm.' + - name: Build Android app working-directory: apps/tester-federation-v2/android run: ./gradlew :app:assembleDebug -PreactNativeArchitectures=x86_64 @@ -125,26 +157,49 @@ jobs: arch: x86_64 profile: pixel_7 working-directory: apps/tester-federation-v2 + # Every line here is run in its own `sh -c`: the action's + # script-parser.ts splits this input on newlines and drops lines + # starting with '#', so no multi-line construct (no `for`/`if`/here-doc + # blocks) and no comments may be relied on. Keep each check one line. script: | adb reverse tcp:8081 tcp:8081 adb reverse tcp:8082 tcp:8082 + adb reverse --list + adb reverse --list | grep -q 'tcp:8081' || { echo '::error::adb reverse missing for tcp:8081'; exit 1; } + adb reverse --list | grep -q 'tcp:8082' || { echo '::error::adb reverse missing for tcp:8082'; exit 1; } adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk pnpm exec e2e run --target android --reporter list,junit - # !cancelled() keeps evidence from a test that failed then passed on retry. + # !cancelled() keeps evidence from a test that failed then passed on + # retry. include-hidden-files is the load-bearing line: upload-artifact + # v7.0.1 passes `excludeHiddenFiles: !includeHiddenFiles` into + # @actions/glob and the input defaults to false, so a path whose basename + # starts with a dot — `.e2e` — matches zero files. Run 38408377768 then + # reported the step a *success* having uploaded nothing (`total_count: 0` + # artifacts, one `##[warning]No files were found`), and the trace and + # screenshots that would have said what was on screen were gone. Verified + # by experiment against @actions/glob, not inferred. if-no-files-found: + # error so a missing evidence directory is a build failure, never a + # warning that scrolls past again. - name: Upload e2e artifacts if: ${{ !cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 with: name: e2e-android path: apps/tester-federation-v2/.e2e - if-no-files-found: warn + include-hidden-files: true + if-no-files-found: error retention-days: 7 - # The nohup'd server logs are otherwise invisible on a red run. - - name: Show dev server logs on failure - if: ${{ failure() }} - run: tail -n 60 /tmp/hostapp.log /tmp/miniapp.log + # The nohup'd server logs are otherwise invisible, and they are the one + # diagnostic that survives on its own (they live outside `.e2e`, so they + # arrive even when the upload above does not). On a red run read this step + # first: "Compiled android" under each port is the whole story of whether + # the device ever reached that server. !cancelled() rather than failure() + # so a cancelled run keeps them too. + - name: Show dev server logs + if: ${{ !cancelled() }} + run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log # iOS is Phase 1b: needs an xcodebuild lane, a pinned simulator runtime and a # ~/.agent-device cache, none measured yet. From dee4ae620f5578eaa879fd963226a5d703ad7d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Wed, 14 Oct 2026 13:35:49 +0200 Subject: [PATCH 03/13] fix: v3 --- .github/workflows/e2e.yml | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8986ed5c5..06212366c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -70,23 +70,15 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: Cache Gradle - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: gradle-${{ runner.os }}-${{ hashFiles('apps/tester-federation-v2/android/gradle/wrapper/gradle-wrapper.properties', 'apps/tester-federation-v2/android/**/*.gradle', 'apps/tester-federation-v2/android/gradle.properties', 'pnpm-lock.yaml') }} - - # The emulator runner action has no caching of its own. - - name: Cache Android emulator - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # tag=v6.1.0 - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ runner.os }}-api-35 - + # No caching, deliberately, until this lane is reliably green. Neither the + # Gradle nor the AVD cache had ever been written or hit — `actions/cache` + # only saves on a green run and every run so far was red — so deleting them + # costs no time and removes a variable: every run is then explainable by the + # workflow you can see, with no dependence on state left by an earlier run. + # Re-add as one change once green. The AVD cache is only worth having + # together with snapshot boot, since the emulator's default `-no-snapshot` + # leaves boot cold either way. + # # The dev servers are the thing under test: this is how the PR's Repack # change reaches them (workspace dist/ is gitignored). - name: Build workspace packages @@ -162,6 +154,7 @@ jobs: # starting with '#', so no multi-line construct (no `for`/`if`/here-doc # blocks) and no comments may be relied on. Keep each check one line. script: | + adb shell settings put global hide_error_dialogs 1 adb reverse tcp:8081 tcp:8081 adb reverse tcp:8082 tcp:8082 adb reverse --list From 354042c63b864daf93c95975db9aef8a37daf76f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Wed, 14 Oct 2026 15:16:01 +0200 Subject: [PATCH 04/13] fix: v4 --- .github/workflows/e2e.yml | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 06212366c..b70053bfd 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -70,14 +70,10 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - # No caching, deliberately, until this lane is reliably green. Neither the - # Gradle nor the AVD cache had ever been written or hit — `actions/cache` - # only saves on a green run and every run so far was red — so deleting them - # costs no time and removes a variable: every run is then explainable by the - # workflow you can see, with no dependence on state left by an earlier run. - # Re-add as one change once green. The AVD cache is only worth having - # together with snapshot boot, since the emulator's default `-no-snapshot` - # leaves boot cold either way. + # No caching, deliberately, until this lane is reliably green. Neither cache + # had ever been written or hit — `actions/cache` only saves on a green run + # and every run so far was red — so deleting them costs no time and removes + # a variable: nothing here depends on state an earlier run left behind. # # The dev servers are the thing under test: this is how the PR's Repack # change reaches them (workspace dist/ is gitignored). @@ -154,6 +150,11 @@ jobs: # starting with '#', so no multi-line construct (no `for`/`if`/here-doc # blocks) and no comments may be relied on. Keep each check one line. script: | + free -m + ps -eo rss,comm --sort=-rss | head -n 8 + uptime + adb logcat -d -v time > /tmp/logcat.txt 2>&1 + nohup adb logcat -v time >> /tmp/logcat.txt 2>&1 & adb shell settings put global hide_error_dialogs 1 adb reverse tcp:8081 tcp:8081 adb reverse tcp:8082 tcp:8082 @@ -194,5 +195,25 @@ jobs: if: ${{ !cancelled() }} run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log + # The only step whose purpose is to find the cause rather than prove the + # symptom. Runs 38531417287 and 38557739117 both froze the same way — every + # test timed out against a 12-node screen tree that turned out to be + # Android's own "Pixel Launcher isn't responding" dialog — and nothing in + # the server logs, the traces or the screenshots says why the launcher + # stopped responding. logcat is where the system writes that verdict: the + # ANRing process, the reason, and the load average and CPU split at the + # moment of the freeze. Without it every next step is a guess at cores or + # API level or profile; with it, the fix has a name. + - name: Show the ANR report + if: ${{ failure() }} + run: | + test -s /tmp/logcat.txt || { echo 'no logcat captured'; exit 0; } + echo '### ANR / crash lines' + grep -iE "ANR in|Reason:|not responding|FATAL EXCEPTION|Force finishing|am_anr|LOW MEMORY|lowmemorykiller" /tmp/logcat.txt | head -n 40 + echo '### CPU and memory at the freeze' + grep -A 12 -m 1 "ANR in" /tmp/logcat.txt || true + echo '### last 40 lines' + tail -n 40 /tmp/logcat.txt + # iOS is Phase 1b: needs an xcodebuild lane, a pinned simulator runtime and a # ~/.agent-device cache, none measured yet. From 46a30bc658c5f9f4d3a933bee70da7001c2ee673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Wed, 14 Oct 2026 16:50:07 +0200 Subject: [PATCH 05/13] fix: v5 --- .github/workflows/e2e.yml | 46 ++++++++++--------- .../configs/rspack.host-app.mts | 5 ++ .../configs/webpack.host-app.mts | 5 ++ apps/tester-federation-v2/src/host/index.js | 14 +++++- 4 files changed, 47 insertions(+), 23 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b70053bfd..3623dffd0 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -88,8 +88,17 @@ jobs: # react-native-test-app picks its bundle source once at app start and # never retries, so an app launched while 8081 is still booting shows an # alert no later tap dismisses. + # E2E=true reaches DefinePlugin in the host configs and compiles the + # bundle with warning UI muted (LogBox.ignoreAllLogs in src/host/index.js). + # Needed because a warning toast is drawn over the bottom of the app and + # absorbs taps meant for controls there — it killed the Gallery test on + # CI's smaller screen, which has everything ~15% closer. Red error + # screens are NOT muted, so a broken change still fails loudly. Local + # dev without this env keeps every warning. - name: Start dev servers working-directory: apps/tester-federation-v2 + env: + E2E: 'true' run: | nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & disown @@ -150,11 +159,6 @@ jobs: # starting with '#', so no multi-line construct (no `for`/`if`/here-doc # blocks) and no comments may be relied on. Keep each check one line. script: | - free -m - ps -eo rss,comm --sort=-rss | head -n 8 - uptime - adb logcat -d -v time > /tmp/logcat.txt 2>&1 - nohup adb logcat -v time >> /tmp/logcat.txt 2>&1 & adb shell settings put global hide_error_dialogs 1 adb reverse tcp:8081 tcp:8081 adb reverse tcp:8082 tcp:8082 @@ -162,7 +166,7 @@ jobs: adb reverse --list | grep -q 'tcp:8081' || { echo '::error::adb reverse missing for tcp:8081'; exit 1; } adb reverse --list | grep -q 'tcp:8082' || { echo '::error::adb reverse missing for tcp:8082'; exit 1; } adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk - pnpm exec e2e run --target android --reporter list,junit + pnpm exec e2e run --target android --reporter list,junit; ec=$?; adb logcat -d -v time > .e2e/logcat.txt 2>&1; exit $ec # !cancelled() keeps evidence from a test that failed then passed on # retry. include-hidden-files is the load-bearing line: upload-artifact @@ -195,25 +199,23 @@ jobs: if: ${{ !cancelled() }} run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log - # The only step whose purpose is to find the cause rather than prove the - # symptom. Runs 38531417287 and 38557739117 both froze the same way — every - # test timed out against a 12-node screen tree that turned out to be - # Android's own "Pixel Launcher isn't responding" dialog — and nothing in - # the server logs, the traces or the screenshots says why the launcher - # stopped responding. logcat is where the system writes that verdict: the - # ANRing process, the reason, and the load average and CPU split at the - # moment of the freeze. Without it every next step is a guess at cores or - # API level or profile; with it, the fix has a name. - - name: Show the ANR report + # Captured inside the emulator step (a background `adb logcat` there dies + # with the per-line shell the action spawns), written to `.e2e/logcat.txt` + # so it rides up with the artifact. On a red run this is the app's own + # voice: ReactNativeJS lines say what the MF runtime did when the remote + # was requested, and `ActivityManager` lines say whether anything ever + # asked to navigate. + - name: Show device log on failure if: ${{ failure() }} run: | - test -s /tmp/logcat.txt || { echo 'no logcat captured'; exit 0; } + f=apps/tester-federation-v2/.e2e/logcat.txt + test -s $f || { echo 'no logcat captured'; exit 0; } + echo '### app console (ReactNativeJS)' + grep "ReactNativeJS" $f | tail -n 40 echo '### ANR / crash lines' - grep -iE "ANR in|Reason:|not responding|FATAL EXCEPTION|Force finishing|am_anr|LOW MEMORY|lowmemorykiller" /tmp/logcat.txt | head -n 40 - echo '### CPU and memory at the freeze' - grep -A 12 -m 1 "ANR in" /tmp/logcat.txt || true - echo '### last 40 lines' - tail -n 40 /tmp/logcat.txt + grep -iE "ANR in|Reason:|not responding|FATAL EXCEPTION|Force finishing|am_anr|lowmemorykiller" $f | tail -n 30 + echo '### app lifecycle' + grep -E "ActivityManager.*federationV2|Displayed com.tester" $f | tail -n 15 # iOS is Phase 1b: needs an xcodebuild lane, a pinned simulator runtime and a # ~/.agent-device cache, none measured yet. diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mts b/apps/tester-federation-v2/configs/rspack.host-app.mts index 43fedabe2..73550f341 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mts +++ b/apps/tester-federation-v2/configs/rspack.host-app.mts @@ -99,6 +99,11 @@ export default Repack.defineRspackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', + // read by src/host/index.js — see the comment there for why e2e + // mutes warning UI; must stay defined in *both* host configs, the + // webpack and rspack builds share the entry and a missing global is + // a startup ReferenceError + __E2E__: process.env.E2E === 'true', }), ], }; diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mts b/apps/tester-federation-v2/configs/webpack.host-app.mts index 32dc1ac25..90cafe69b 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mts +++ b/apps/tester-federation-v2/configs/webpack.host-app.mts @@ -95,6 +95,11 @@ export default Repack.defineWebpackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', + // read by src/host/index.js — see the comment there for why e2e + // mutes warning UI; must stay defined in *both* host configs, the + // webpack and rspack builds share the entry and a missing global is + // a startup ReferenceError + __E2E__: process.env.E2E === 'true', }), ], }; diff --git a/apps/tester-federation-v2/src/host/index.js b/apps/tester-federation-v2/src/host/index.js index 61610d410..4ed8d8c35 100644 --- a/apps/tester-federation-v2/src/host/index.js +++ b/apps/tester-federation-v2/src/host/index.js @@ -1,11 +1,23 @@ import { ScriptManager } from '@callstack/repack/client'; import { preloadRemote } from '@module-federation/runtime'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { AppRegistry } from 'react-native'; +import { AppRegistry, LogBox } from 'react-native'; import { components } from '../../app.json'; import App from './App'; +// Set via DefinePlugin when the dev servers run with E2E=true (see CI's +// e2e.yml). Warning toasts are part of the app's own view tree and sit on +// top of the bottom of the screen, so a logged warning can silently absorb a +// tap meant for a control there — this cost the Gallery e2e test several CI +// runs (agent_context/e2e-ci-oct2026/design.md had flagged exactly this +// risk). Errors are NOT ignored: red screens still render, so a broken +// change still fails loudly. No flag, no muting: normal development keeps +// every warning. +if (__E2E__) { + LogBox.ignoreAllLogs(true); +} + if (__WITH_PRELOAD__) { // enable caching of scripts in the AsyncStorage ScriptManager.shared.setStorage(AsyncStorage); From 2ffaa0755a83c6686377507061692f0d97565f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Thu, 15 Oct 2026 11:14:44 +0200 Subject: [PATCH 06/13] feat: trim comments --- .github/workflows/e2e.yml | 122 +----------------- .../configs/rspack.host-app.mts | 4 - .../configs/webpack.host-app.mts | 4 - 3 files changed, 6 insertions(+), 124 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 3623dffd0..a97a37008 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,12 +1,5 @@ name: e2e -# Module Federation v2 e2e on a real Android emulator, mirroring the local flow: -# start both dev servers, build the app, run the tests on a booted device. -# -# `pull_request_target` runs workflow code from the base revision, so a -# contributor cannot change what executes; the PR head is checked out -# explicitly below. The job reads no secrets (every test is deterministic) and -# the token is pinned to `contents: read`. on: pull_request_target: types: [opened, reopened, synchronize, labeled, unlabeled] @@ -14,37 +7,27 @@ on: branches: [main] workflow_dispatch: -# An emulator run is too expensive to let superseded commits queue up behind it. +# one run per PR; a new push cancels the older one concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +# keep it read-only permissions: contents: read jobs: android: name: e2e (Android) - # Same-repo PRs run unconditionally; a fork PR runs while the `e2e` label - # is on it (the label does not exist in repo settings yet). No workflow-level - # `paths:` filter — a path-filtered required check is never created and the - # merge hangs forever. + # same-repo PRs - always run; forks - run only with the `e2e` label if: >- github.event_name != 'pull_request_target' || github.event.pull_request.head.repo.full_name == github.repository || contains(github.event.pull_request.labels.*.name, 'e2e') runs-on: ubuntu-latest timeout-minutes: 45 - # NOT continue-on-error. A job that cannot fail reports green over red - # tests: run 38408377768 concluded "success" with the Gallery test failing. - # Still not a *required* check — branch protection is a separate decision - # and the suite has no stability history — but "not required" and "cannot - # fail" are independent switches, and only the first one is set here. steps: - # pull_request_target checks out the base by default, which would test - # the wrong code; every other trigger has no PR context and falls - # through to github.sha. - name: Checkout pull request head uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 with: @@ -53,8 +36,6 @@ jobs: - name: Setup workspace uses: ./.github/actions/setup-pnpm-node with: - # e2e requires Node ^22.22.3 || >=24.8.0; exact pin so the floor - # cannot drift. node-version: '24.18.0' - name: Setup Java @@ -63,38 +44,16 @@ jobs: distribution: temurin java-version: '17' - # Required by android-emulator-runner on GitHub-hosted runners. - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - # No caching, deliberately, until this lane is reliably green. Neither cache - # had ever been written or hit — `actions/cache` only saves on a green run - # and every run so far was red — so deleting them costs no time and removes - # a variable: nothing here depends on state an earlier run left behind. - # - # The dev servers are the thing under test: this is how the PR's Repack - # change reaches them (workspace dist/ is gitignored). - name: Build workspace packages run: pnpm --filter tester-federation-v2... build - # Servers before Gradle so their cold compile overlaps the build and the - # emulator boot — but "listening" is not "compiled", so the overlap only - # happens if something actually asks for a bundle (see the warm step). - # nohup/disown because the action runs each script line in its own shell - # and these must outlive this step. The /status wait is not ceremony: - # react-native-test-app picks its bundle source once at app start and - # never retries, so an app launched while 8081 is still booting shows an - # alert no later tap dismisses. - # E2E=true reaches DefinePlugin in the host configs and compiles the - # bundle with warning UI muted (LogBox.ignoreAllLogs in src/host/index.js). - # Needed because a warning toast is drawn over the bottom of the app and - # absorbs taps meant for controls there — it killed the Gallery test on - # CI's smaller screen, which has everything ~15% closer. Red error - # screens are NOT muted, so a broken change still fails loudly. Local - # dev without this env keeps every warning. + # nohup/disown: the runner kills step processes at the end. /status wait: RNTA picks its bundle source once, never retries - name: Start dev servers working-directory: apps/tester-federation-v2 env: @@ -106,28 +65,7 @@ jobs: disown timeout 180 sh -c 'until [ "$(curl -sf localhost:8081/status)" = "packager-status:running" ] && [ "$(curl -sf localhost:8082/status)" = "packager-status:running" ]; do sleep 2; done' - # /status answers as soon as the server *listens* (createServer.ts - # registers it as a static reply — it never touches the compiler), and - # Re.Pack builds a platform lazily, on that platform's first asset - # request: Compiler.ts `getAsset` -> `activatePlatform`, with a `watchRun` - # hook that parks the initial build until then. So the previous step's - # wait started the overlap it was meant to have but never achieved: in run - # 38408377768 both servers listened at 06:09:13 and the host did not - # compile until 06:16:55, and 8082 never compiled at all — the device - # never sent it a single request, which is why the Gallery test hung. - # These two fetches are what make the overlap real, and they move 8082's - # cold build off the device's critical path, where it currently lands - # *after* a tap and gets killed when the step ends. - # - # The paths are RNTA's dev-bundle shape (verified against a live 8082: - # `/android/index.bundle?platform=android` -> 200 + "Compiled android", - # while the Metro-style `/index.android.bundle` 404s). The compile is - # keyed per platform, so any accepted filename triggers it; the params - # mirror what the app itself sends. - # - # A cold miss takes seconds (host: 10.5s, warm cache). 300s of budget, and - # -f so a server that 404s or dies fails the step here rather than - # presenting as a test failure 15 minutes later. + # ask for the bundles now so the servers compile them — they only build on first request - name: Warm both compilers working-directory: apps/tester-federation-v2 run: | @@ -139,13 +77,7 @@ jobs: working-directory: apps/tester-federation-v2/android run: ./gradlew :app:assembleDebug -PreactNativeArchitectures=x86_64 - # The reverses are load-bearing for 8082: the MiniApp remote URL is baked - # into the bundle as http://localhost:8082/..., and the device's localhost - # is the device itself (8081 needs no reverse — the app reaches it via - # 10.0.2.2). Proven locally: with reverses wiped, exactly the Gallery - # test fails. google_apis, never google_apis_playstore — the Play image - # could not launch the app in earlier testing (unconfirmed mechanism; do - # not switch without verifying). + # expose dev servers to the emulator, install the APK, run the tests - name: Run e2e on an emulator uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # tag=v2.38.0 with: @@ -154,10 +86,6 @@ jobs: arch: x86_64 profile: pixel_7 working-directory: apps/tester-federation-v2 - # Every line here is run in its own `sh -c`: the action's - # script-parser.ts splits this input on newlines and drops lines - # starting with '#', so no multi-line construct (no `for`/`if`/here-doc - # blocks) and no comments may be relied on. Keep each check one line. script: | adb shell settings put global hide_error_dialogs 1 adb reverse tcp:8081 tcp:8081 @@ -168,17 +96,6 @@ jobs: adb install -r -g android/app/build/outputs/apk/debug/app-debug.apk pnpm exec e2e run --target android --reporter list,junit; ec=$?; adb logcat -d -v time > .e2e/logcat.txt 2>&1; exit $ec - # !cancelled() keeps evidence from a test that failed then passed on - # retry. include-hidden-files is the load-bearing line: upload-artifact - # v7.0.1 passes `excludeHiddenFiles: !includeHiddenFiles` into - # @actions/glob and the input defaults to false, so a path whose basename - # starts with a dot — `.e2e` — matches zero files. Run 38408377768 then - # reported the step a *success* having uploaded nothing (`total_count: 0` - # artifacts, one `##[warning]No files were found`), and the trace and - # screenshots that would have said what was on screen were gone. Verified - # by experiment against @actions/glob, not inferred. if-no-files-found: - # error so a missing evidence directory is a build failure, never a - # warning that scrolls past again. - name: Upload e2e artifacts if: ${{ !cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 @@ -189,33 +106,6 @@ jobs: if-no-files-found: error retention-days: 7 - # The nohup'd server logs are otherwise invisible, and they are the one - # diagnostic that survives on its own (they live outside `.e2e`, so they - # arrive even when the upload above does not). On a red run read this step - # first: "Compiled android" under each port is the whole story of whether - # the device ever reached that server. !cancelled() rather than failure() - # so a cancelled run keeps them too. - name: Show dev server logs if: ${{ !cancelled() }} run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log - - # Captured inside the emulator step (a background `adb logcat` there dies - # with the per-line shell the action spawns), written to `.e2e/logcat.txt` - # so it rides up with the artifact. On a red run this is the app's own - # voice: ReactNativeJS lines say what the MF runtime did when the remote - # was requested, and `ActivityManager` lines say whether anything ever - # asked to navigate. - - name: Show device log on failure - if: ${{ failure() }} - run: | - f=apps/tester-federation-v2/.e2e/logcat.txt - test -s $f || { echo 'no logcat captured'; exit 0; } - echo '### app console (ReactNativeJS)' - grep "ReactNativeJS" $f | tail -n 40 - echo '### ANR / crash lines' - grep -iE "ANR in|Reason:|not responding|FATAL EXCEPTION|Force finishing|am_anr|lowmemorykiller" $f | tail -n 30 - echo '### app lifecycle' - grep -E "ActivityManager.*federationV2|Displayed com.tester" $f | tail -n 15 - - # iOS is Phase 1b: needs an xcodebuild lane, a pinned simulator runtime and a - # ~/.agent-device cache, none measured yet. diff --git a/apps/tester-federation-v2/configs/rspack.host-app.mts b/apps/tester-federation-v2/configs/rspack.host-app.mts index 73550f341..3bf821f2f 100644 --- a/apps/tester-federation-v2/configs/rspack.host-app.mts +++ b/apps/tester-federation-v2/configs/rspack.host-app.mts @@ -99,10 +99,6 @@ export default Repack.defineRspackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', - // read by src/host/index.js — see the comment there for why e2e - // mutes warning UI; must stay defined in *both* host configs, the - // webpack and rspack builds share the entry and a missing global is - // a startup ReferenceError __E2E__: process.env.E2E === 'true', }), ], diff --git a/apps/tester-federation-v2/configs/webpack.host-app.mts b/apps/tester-federation-v2/configs/webpack.host-app.mts index 90cafe69b..0081f868d 100644 --- a/apps/tester-federation-v2/configs/webpack.host-app.mts +++ b/apps/tester-federation-v2/configs/webpack.host-app.mts @@ -95,10 +95,6 @@ export default Repack.defineWebpackConfig((env) => { __WITH_PRELOAD__: process.env.WITH_PRELOAD === 'true' || process.env.WITH_PRELOAD === '1', - // read by src/host/index.js — see the comment there for why e2e - // mutes warning UI; must stay defined in *both* host configs, the - // webpack and rspack builds share the entry and a missing global is - // a startup ReferenceError __E2E__: process.env.E2E === 'true', }), ], From 62a774bd6453aabaea95c72656ba1f2c08152685 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Thu, 15 Oct 2026 12:59:27 +0200 Subject: [PATCH 07/13] feat: ios v1 --- .github/workflows/e2e.yml | 72 +++++++++++++++++++++++++ apps/tester-federation-v2/e2e.config.ts | 5 ++ 2 files changed, 77 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index a97a37008..ee9191443 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -109,3 +109,75 @@ jobs: - name: Show dev server logs if: ${{ !cancelled() }} run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log + + # dispatch + main only for now — no PR triggers until this lane has greens + ios: + name: e2e (iOS) + if: github.event_name != 'pull_request_target' + runs-on: macos-15 + timeout-minutes: 45 + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.2.2 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Setup workspace + uses: ./.github/actions/setup-pnpm-node + with: + node-version: '24.18.0' + + - name: Build workspace packages + run: pnpm --filter tester-federation-v2... build + + # like the android job, minus timeout (no such command on macOS) — the retry counter bounds it + - name: Start dev servers + working-directory: apps/tester-federation-v2 + env: + E2E: 'true' + run: | + nohup pnpm start:hostapp > /tmp/hostapp.log 2>&1 & + disown + nohup pnpm start:miniapp > /tmp/miniapp.log 2>&1 & + disown + i=0; until curl -sf localhost:8081/status | grep -q packager-status:running && curl -sf localhost:8082/status | grep -q packager-status:running; do sleep 2; i=$((i+1)); [ "$i" -ge 90 ] && exit 1; done + + # same trick as android, iOS bundle URLs; the simulator shares localhost so no adb reverse + - name: Warm both compilers + working-directory: apps/tester-federation-v2 + run: | + curl -sf --max-time 300 -o /dev/null 'http://localhost:8081/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' + curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' + echo 'Both bundles served; compilers are warm.' + + - name: Boot simulator + run: | + xcrun simctl create e2e "iPhone 16" latest + xcrun simctl boot e2e + xcrun simctl bootstatus e2e -b + + # pod install generates the Xcode project the workspace points at + - name: Build iOS app + working-directory: apps/tester-federation-v2/ios + run: pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build CODE_SIGNING_ALLOWED=NO build + + - name: Run e2e + working-directory: apps/tester-federation-v2 + run: | + xcrun simctl install booted ios/build/Build/Products/Debug-iphonesimulator/*.app + pnpm exec e2e run --target ios --reporter list,junit + + - name: Upload e2e artifacts + if: ${{ !cancelled() }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # tag=v7.0.1 + with: + name: e2e-ios + path: apps/tester-federation-v2/.e2e + include-hidden-files: true + if-no-files-found: error + retention-days: 7 + + - name: Show dev server logs + if: ${{ !cancelled() }} + run: tail -n 80 /tmp/hostapp.log /tmp/miniapp.log diff --git a/apps/tester-federation-v2/e2e.config.ts b/apps/tester-federation-v2/e2e.config.ts index d25c5f722..1848abf52 100644 --- a/apps/tester-federation-v2/e2e.config.ts +++ b/apps/tester-federation-v2/e2e.config.ts @@ -9,5 +9,10 @@ export default { engine: mobile({ platform: 'android' }), app: { bundleId: 'com.tester.federationV2' }, }, + { + name: 'ios', + engine: mobile({ platform: 'ios' }), + app: { bundleId: 'com.tester.federationV2' }, + }, ], } satisfies E2EConfig; From f97b2b2a24f506de6180e8b42db9b457526d814a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Thu, 15 Oct 2026 13:12:56 +0200 Subject: [PATCH 08/13] feat: ios v2 --- .github/workflows/e2e.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ee9191443..8249b3f3d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -131,7 +131,7 @@ jobs: - name: Build workspace packages run: pnpm --filter tester-federation-v2... build - # like the android job, minus timeout (no such command on macOS) — the retry counter bounds it + # same as android, but macOS has no `timeout` — curl retries instead - name: Start dev servers working-directory: apps/tester-federation-v2 env: @@ -141,7 +141,7 @@ jobs: disown nohup pnpm start:miniapp > /tmp/miniapp.log 2>&1 & disown - i=0; until curl -sf localhost:8081/status | grep -q packager-status:running && curl -sf localhost:8082/status | grep -q packager-status:running; do sleep 2; i=$((i+1)); [ "$i" -ge 90 ] && exit 1; done + for p in 8081 8082; do curl -sf --retry 90 --retry-delay 2 --retry-all-errors --retry-connrefused -o /dev/null "http://localhost:$p/status"; done # same trick as android, iOS bundle URLs; the simulator shares localhost so no adb reverse - name: Warm both compilers From fe5458ed9b8b1c44d7f4d3528de3c7aee57a10b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Thu, 15 Oct 2026 13:28:31 +0200 Subject: [PATCH 09/13] feat: ios v3 --- .github/workflows/e2e.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 8249b3f3d..f26906797 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -151,9 +151,12 @@ jobs: curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' echo 'Both bundles served; compilers are warm.' + # pick the newest iOS runtime the runner image actually ships - name: Boot simulator run: | - xcrun simctl create e2e "iPhone 16" latest + RT=$(xcrun simctl list runtimes -j | jq -r '.runtimes | map(select(.isAvailable and (.identifier | startswith("com.apple.CoreSimulator.SimRuntime.iOS-")))) | sort_by(.version) | last | .identifier') + echo "using $RT" + xcrun simctl create e2e "iPhone 16" "$RT" xcrun simctl boot e2e xcrun simctl bootstatus e2e -b From 297f42f3d23f8b9afc97a08676a6efb5c796d3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Thu, 15 Oct 2026 14:12:55 +0200 Subject: [PATCH 10/13] feat: ios v4 --- .github/workflows/e2e.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f26906797..09c7cc649 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -151,11 +151,12 @@ jobs: curl -sf --max-time 300 -o /dev/null 'http://localhost:8082/index.bundle?platform=ios&dev=true&lazy=true&minify=false&modulesOnly=false&runModule=true&app=com.tester.federationV2' echo 'Both bundles served; compilers are warm.' - # pick the newest iOS runtime the runner image actually ships + # match the runtime to the default Xcode's SDK (16.4 → iOS 18.x), not just newest available - name: Boot simulator run: | - RT=$(xcrun simctl list runtimes -j | jq -r '.runtimes | map(select(.isAvailable and (.identifier | startswith("com.apple.CoreSimulator.SimRuntime.iOS-")))) | sort_by(.version) | last | .identifier') + RT=$(xcrun simctl list runtimes -j | jq -r '.runtimes | map(select(.isAvailable and (.identifier | startswith("com.apple.CoreSimulator.SimRuntime.iOS-18-")))) | sort_by(.version) | last | .identifier') echo "using $RT" + [ "$RT" != "null" ] && [ -n "$RT" ] xcrun simctl create e2e "iPhone 16" "$RT" xcrun simctl boot e2e xcrun simctl bootstatus e2e -b From d6fa62ece54cbad1edb96f21b47797e3874195da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Thu, 15 Oct 2026 16:02:31 +0200 Subject: [PATCH 11/13] feat: ios v5 --- .github/workflows/e2e.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 09c7cc649..b46130eed 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -131,7 +131,7 @@ jobs: - name: Build workspace packages run: pnpm --filter tester-federation-v2... build - # same as android, but macOS has no `timeout` — curl retries instead + # start both dev servers in the background, wait until both answer /status - name: Start dev servers working-directory: apps/tester-federation-v2 env: @@ -143,7 +143,7 @@ jobs: disown for p in 8081 8082; do curl -sf --retry 90 --retry-delay 2 --retry-all-errors --retry-connrefused -o /dev/null "http://localhost:$p/status"; done - # same trick as android, iOS bundle URLs; the simulator shares localhost so no adb reverse + # ask for the iOS bundles now so the servers compile them — they only build on first request - name: Warm both compilers working-directory: apps/tester-federation-v2 run: | From 6a4ce4e2bc596a5e192db4be9cdf52aecb4a6bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Fri, 16 Oct 2026 11:10:15 +0200 Subject: [PATCH 12/13] fix: cut build times --- .github/workflows/e2e.yml | 2 +- apps/tester-federation-v2/android/gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b46130eed..e7193d6ff 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -164,7 +164,7 @@ jobs: # pod install generates the Xcode project the workspace points at - name: Build iOS app working-directory: apps/tester-federation-v2/ios - run: pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build CODE_SIGNING_ALLOWED=NO build + run: pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -destination 'generic/platform=iOS Simulator' -arch arm64 -derivedDataPath build CODE_SIGNING_ALLOWED=NO build - name: Run e2e working-directory: apps/tester-federation-v2 diff --git a/apps/tester-federation-v2/android/gradle.properties b/apps/tester-federation-v2/android/gradle.properties index 3b46be1a2..4bc726719 100644 --- a/apps/tester-federation-v2/android/gradle.properties +++ b/apps/tester-federation-v2/android/gradle.properties @@ -18,7 +18,7 @@ org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryEr # section on Gradle build performance: # https://docs.gradle.org/current/userguide/performance.html#parallel_execution. # Default is `false`. -#org.gradle.parallel=true +org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK From 440748fa35adcbb44b19ca5cf916c1c334fdbc10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Fri, 16 Oct 2026 11:28:40 +0200 Subject: [PATCH 13/13] fix: move param --- .github/workflows/e2e.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e7193d6ff..44e3ba0bc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -162,9 +162,11 @@ jobs: xcrun simctl bootstatus e2e -b # pod install generates the Xcode project the workspace points at + # ARCHS=arm64: the generic simulator destination otherwise builds arm64 + x86_64 on an arm64-only runner. + # It must be a setting override — the runner's Xcode 16.4 rejects `-arch` alongside the destination. - name: Build iOS app working-directory: apps/tester-federation-v2/ios - run: pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -destination 'generic/platform=iOS Simulator' -arch arm64 -derivedDataPath build CODE_SIGNING_ALLOWED=NO build + run: pod install && xcodebuild -workspace ModuleFederationTesterV2.xcworkspace -scheme ReactTestApp -configuration Debug -destination 'generic/platform=iOS Simulator' -derivedDataPath build CODE_SIGNING_ALLOWED=NO ARCHS=arm64 build - name: Run e2e working-directory: apps/tester-federation-v2