diff --git a/.github/workflows/harness-ios-simcam.yml b/.github/workflows/harness-ios-simcam.yml new file mode 100644 index 0000000000..d63199de07 --- /dev/null +++ b/.github/workflows/harness-ios-simcam.yml @@ -0,0 +1,124 @@ +name: Harness iOS Simulator (SimCam) + +permissions: + contents: read + +concurrency: + group: harness-ios-simcam-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - ".github/workflows/harness-ios-simcam.yml" + - "apps/simple-camera/**" + - "packages/react-native-vision-camera/**" + - "packages/react-native-vision-camera-barcode-scanner/**" + - "packages/react-native-vision-camera-location/**" + - "packages/react-native-vision-camera-resizer/**" + - "packages/react-native-vision-camera-skia/**" + - "packages/react-native-vision-camera-worklets/**" + - "bun.lock" + - "package.json" + - "patches/**" + pull_request: + paths: + - ".github/workflows/harness-ios-simcam.yml" + - "apps/simple-camera/**" + - "packages/react-native-vision-camera/**" + - "packages/react-native-vision-camera-barcode-scanner/**" + - "packages/react-native-vision-camera-location/**" + - "packages/react-native-vision-camera-resizer/**" + - "packages/react-native-vision-camera-skia/**" + - "packages/react-native-vision-camera-worklets/**" + - "bun.lock" + - "package.json" + - "patches/**" + +env: + HARNESS_PROJECT_ROOT: apps/simple-camera + HARNESS_APP_PATH: ios/build/harness/DerivedData/Build/Products/Debug-iphonesimulator/SimpleCamera.app + HARNESS_IOS_BUNDLE_ID: com.margelo.nitro.camera.example.simple + HARNESS_IOS_DEVICE_MODE: simulator + HARNESS_IOS_SIMULATOR: iPhone 17 Pro + HARNESS_IOS_SIMULATOR_VERSION: "26.5" + HARNESS_IOS_METRO_HOST: 127.0.0.1 + HARNESS_METRO_BIND_HOST: 127.0.0.1 + HARNESS_SIMCAM: "1" + SIMCAM_SOURCE_IMAGE: apps/simple-camera/src/assets/simcam-harness-source.png + DERIVED_DATA_PATH: build/harness/DerivedData + +jobs: + test-ios-simcam: + name: Test iOS Simulator With SimCam + runs-on: macos-26 + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: oven-sh/setup-bun@v2 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "24" + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "3.4.9" + bundler-cache: true + working-directory: apps/simple-camera + + - name: Install JavaScript dependencies + run: bun install --frozen-lockfile + + - name: Build packages + run: bun run build + + - name: Install Skia binaries + run: npx install-skia + + - name: Install CocoaPods + working-directory: apps/simple-camera + run: bundle exec pod install --project-directory=ios + + - name: Configure SimCam + env: + SIMCAM_LICENSE_TOKEN: ${{ secrets.SIMCAM_LICENSE_TOKEN }} + run: | + brew install --cask --appdir="$HOME/Applications" software-mansion/tap/simcam + simcamctl license install-token "$SIMCAM_LICENSE_TOKEN" + simcamctl set-source --image "$GITHUB_WORKSPACE/$SIMCAM_SOURCE_IMAGE" --fill + + - name: Build iOS app + working-directory: apps/simple-camera/ios + run: | + set -euo pipefail + xcodebuild \ + -workspace SimpleCamera.xcworkspace \ + -scheme SimpleCamera \ + -configuration Debug \ + -destination "generic/platform=iOS Simulator" \ + -derivedDataPath "$DERIVED_DATA_PATH" \ + build + + - name: Run Harness tests + working-directory: apps/simple-camera + run: ../../node_modules/.bin/react-native-harness --harnessRunner ios --no-watchman --verbose --forceExit + + - name: Upload Harness logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: harness-ios-simcam-logs + path: | + apps/simple-camera/.harness/logs + apps/simple-camera/ios/build/harness/DerivedData/Logs + if-no-files-found: ignore + retention-days: 7 diff --git a/apps/simple-camera/__tests__/visioncamera.video.harness.ts b/apps/simple-camera/__tests__/visioncamera.video.harness.ts index 0fb3cd6175..8742200670 100644 --- a/apps/simple-camera/__tests__/visioncamera.video.harness.ts +++ b/apps/simple-camera/__tests__/visioncamera.video.harness.ts @@ -18,6 +18,14 @@ import { deferred, withTimeout } from './test-utils' const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) +const isSimCamHarnessRun = + ( + globalThis as { + process?: { env?: { HARNESS_SIMCAM?: string } } + } + ).process?.env?.HARNESS_SIMCAM?.trim() === '1' +const itWithAudio = isSimCamHarnessRun ? it.skip : it + describe('VisionCamera - Video', () => { let factory: CameraDeviceFactory let backDevice: CameraDevice @@ -75,7 +83,7 @@ describe('VisionCamera - Video', () => { } }) - it('records with audio enabled', async () => { + itWithAudio('records with audio enabled', async () => { const session = await VisionCamera.createCameraSession(false) const videoOutput = VisionCamera.createVideoOutput({ targetResolution: CommonResolutions.HD_16_9, diff --git a/apps/simple-camera/rn-harness.config.mjs b/apps/simple-camera/rn-harness.config.mjs index a5d9f61598..3dfcbc900b 100644 --- a/apps/simple-camera/rn-harness.config.mjs +++ b/apps/simple-camera/rn-harness.config.mjs @@ -34,6 +34,7 @@ const iosSimulatorName = process.env.HARNESS_IOS_SIMULATOR ?? 'iPhone 16 Pro' const iosSimulatorVersion = process.env.HARNESS_IOS_SIMULATOR_VERSION ?? '18.5' const iosPhysicalDeviceIdentifier = process.env.HARNESS_IOS_DEVICE_ID?.trim() || 'iPhone' +const iosDeviceMode = process.env.HARNESS_IOS_DEVICE_MODE?.trim().toLowerCase() const iosMetroHostInput = process.env.HARNESS_IOS_METRO_HOST?.trim() ?? '' const iosMetroPort = process.env.HARNESS_IOS_METRO_PORT ?? '8081' @@ -85,13 +86,16 @@ const androidDevice = useEmulator }) : physicalAndroidDevice(androidPhysicalManufacturer, androidPhysicalModel) -const iosDevice = isCI - ? applePhysicalDevice(iosPhysicalDeviceIdentifier, { +const useIosSimulator = + iosDeviceMode === 'simulator' || (iosDeviceMode == null && !isCI) + +const iosDevice = useIosSimulator + ? appleSimulator(iosSimulatorName, iosSimulatorVersion) + : applePhysicalDevice(iosPhysicalDeviceIdentifier, { codeSign: { teamId: 'TheTeamHereDoesntMatterOnCiButWeHaveToPassItStillIthink', }, }) - : appleSimulator(iosSimulatorName, iosSimulatorVersion) const config = { entryPoint: './index.js', diff --git a/apps/simple-camera/src/assets/simcam-harness-source.png b/apps/simple-camera/src/assets/simcam-harness-source.png new file mode 100644 index 0000000000..aa6814c0a5 Binary files /dev/null and b/apps/simple-camera/src/assets/simcam-harness-source.png differ