Skip to content

feat(web-sdk_angular): Web SDK + Angular reference implementation [NT-3465] #1534

feat(web-sdk_angular): Web SDK + Angular reference implementation [NT-3465]

feat(web-sdk_angular): Web SDK + Angular reference implementation [NT-3465] #1534

name: Main Pipeline
permissions:
contents: read
on:
pull_request:
push:
branches:
- main
concurrency:
group: main-pipeline-${{ github.ref }}
cancel-in-progress: true
jobs:
changes:
name: 🔎 Detect Changes
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
outputs:
build: ${{ steps.filter.outputs.build }}
lock: ${{ steps.filter.outputs.lock }}
unit: ${{ steps.filter.outputs.unit }}
e2e_node_sdk: ${{ steps.filter.outputs.e2e_node_sdk }}
e2e_node_sdk_web_sdk: ${{ steps.filter.outputs.e2e_node_sdk_web_sdk }}
e2e_web_sdk: ${{ steps.filter.outputs.e2e_web_sdk }}
e2e_web_sdk_react: ${{ steps.filter.outputs.e2e_web_sdk_react }}
e2e_react_web_sdk: ${{ steps.filter.outputs.e2e_react_web_sdk }}
e2e_react_native_android: ${{ steps.filter.outputs.e2e_react_native_android }}
e2e_android: ${{ steps.filter.outputs.e2e_android }}
e2e_ios: ${{ steps.filter.outputs.e2e_ios }}
swift_package: ${{ steps.filter.outputs.swift_package }}
android_library: ${{ steps.filter.outputs.android_library }}
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
id: filter
with:
predicate-quantifier: every
# Intentional CI policy:
# - Markdown plus docs/documentation changes do not trigger build, unit, native, or E2E jobs.
# - Implementation E2E jobs run only for implementations relevant to changed paths.
# - Skipped E2E jobs due to unmatched filters are expected behavior.
# - Keep this mapping aligned with CONTRIBUTING.md ("E2E Coverage and Environment").
filters: |
build:
- '{lib/**,packages/**,implementations/**,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,tsconfig*.json,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
lock:
- 'pnpm-lock.yaml'
unit:
- '{lib/**,packages/**,**/rstest.config.ts,package.json,pnpm-lock.yaml,pnpm-workspace.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# Node SSR Only implementation E2E coverage scope.
e2e_node_sdk:
- '{implementations/node-sdk/**,lib/**,packages/node/node-sdk/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# Node SSR + Web Vanilla implementation E2E coverage scope.
e2e_node_sdk_web_sdk:
- '{implementations/node-sdk+web-sdk/**,lib/**,packages/node/node-sdk/**,packages/web/web-sdk/**,packages/web/preview-panel/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# Web Vanilla implementation E2E coverage scope.
e2e_web_sdk:
- '{implementations/web-sdk/**,lib/**,packages/web/web-sdk/**,packages/web/preview-panel/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# React + Web SDK implementation E2E coverage scope.
e2e_web_sdk_react:
- '{implementations/web-sdk_react/**,lib/**,packages/web/web-sdk/**,packages/web/preview-panel/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# React Web SDK (optimization-react-web) implementation E2E coverage scope.
e2e_react_web_sdk:
- '{implementations/react-web-sdk/**,lib/**,packages/web/frameworks/react-web-sdk/**,packages/web/web-sdk/**,packages/web/preview-panel/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# React Native Android implementation E2E coverage scope.
e2e_react_native_android:
- '{implementations/react-native-sdk/**,lib/**,packages/react-native-sdk/**,packages/universal/core-sdk/**,packages/universal/api-client/**,packages/universal/api-schemas/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# Android native implementation E2E coverage scope.
e2e_android:
- '{implementations/android-sdk/**,lib/mocks/**,packages/android/**,packages/universal/optimization-js-bridge/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# iOS native implementation E2E coverage scope.
e2e_ios:
- '{implementations/ios-sdk/**,lib/mocks/**,packages/ios/**,packages/universal/optimization-js-bridge/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# Swift Package build/test scope (the package published to optimization.swift).
swift_package:
- '{packages/ios/ContentfulOptimization/**,packages/universal/optimization-js-bridge/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
# Android library scope (the AAR published to Maven Central).
android_library:
- '{packages/android/ContentfulOptimization/**,packages/universal/optimization-js-bridge/**,package.json,pnpm-lock.yaml,.github/workflows/main-pipeline.yaml}'
- '!**/*.@(md|mdx|markdown)'
- '!{docs/**,documentation/**,**/docs/**,**/documentation/**}'
setup:
name: 🛠️ pnpm install
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up pnpm cache (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
license-check:
name: 📄 License Check
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: [setup, changes]
if: needs.changes.outputs.lock == 'true'
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up pnpm cache (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: |
echo "In case of error, please see ./CONTRIBUTING.md"
pnpx license-checker \
--summary \
--production \
--relativeLicensePath \
--onlyAllow 'MIT;Apache-2.0;ISC;BSD-3-Clause;BSD-2-Clause;MIT*;Apache 2.0;Unlicense;Unlicensed;:CC0-1.0;CC-BY-4.0;WTFPL;0BSD;UNLICENSED;Python-2.0;MPL-2.0;CC-BY-3.0;CC0-1.0'
format:
name: 🎨 Format Check
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: setup
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm format:check
build:
name: 📦 Build
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes]
if: needs.changes.outputs.build == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm build:ci
- run: pnpm size:check
- name: Pack SDK tarballs for implementations
run: pnpm run pack:pkgs
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sdk-package-tarballs
path: pkgs/*.tgz
if-no-files-found: error
retention-days: 1
type-check:
name: 🔷 Type Check
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: [setup, changes]
if: needs.changes.outputs.build == 'true'
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm typecheck
lint:
name: 🎨 Lint
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
needs: [setup, changes, build]
if: >-
always() && needs.changes.outputs.build == 'true' && needs.setup.result == 'success' &&
(needs.build.result == 'success' || needs.build.result == 'skipped')
timeout-minutes: 15
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: needs.build.result == 'success'
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm lint
- if: needs.build.result == 'skipped'
run: pnpm build:pkgs
- run: pnpm store prune
- run: pnpm run implementation:install -- --no-frozen-lockfile
- run: pnpm implementation:lint
test-unit:
name: 🧪 Test (${{ matrix.package }})
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes]
if: needs.changes.outputs.unit == 'true'
strategy:
fail-fast: true
matrix:
include:
- package: '@contentful/optimization-api-schemas'
- package: '@contentful/optimization-api-client'
- package: '@contentful/optimization-core'
- package: '@contentful/optimization-node'
- package: '@contentful/optimization-web'
- package: '@contentful/optimization-web-preview-panel'
- package: '@contentful/optimization-react-native'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- run: pnpm --filter ${{ matrix.package }} test:unit
test-android-sdk-unit:
name: 🤖 Test Android SDK (JVM unit)
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes]
if: needs.changes.outputs.e2e_android == 'true'
env:
GRADLE_OPTS: >-
-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.jvmargs=-Xmx4g
-Dkotlin.daemon.jvm.options=-Xmx2g
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: |
mkdir -p "$HOME/.android/sdk" "$HOME/.android/cache"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
gradle
path: |
~/.android/sdk
~/.android/cache
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- name: Run Android SDK JVM unit tests
working-directory: implementations/android-sdk
run: ./gradlew :ContentfulOptimization:testDebugUnitTest
- name: Upload test reports on failure
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ci-results-android-sdk-unit
path: packages/android/ContentfulOptimization/build/reports/tests/
retention-days: 7
e2e-node-sdk:
name: 🖥️ E2E Node SSR Only
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_node_sdk == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/node-sdk/.env.example implementations/node-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:node-sdk -- implementation:install -- --no-frozen-lockfile
- run: pnpm run implementation:node-sdk -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:node-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-node-sdk
path: |
./implementations/node-sdk/playwright-report/
./implementations/node-sdk/test-results/
retention-days: 1
e2e-node-sdk-web-sdk:
name: 🖥️ E2E Node SSR + Web Vanilla
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_node_sdk_web_sdk == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: >
cp implementations/node-sdk+web-sdk/.env.example implementations/node-sdk+web-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run:
pnpm run implementation:node-sdk+web-sdk -- implementation:install -- --no-frozen-lockfile
- run:
pnpm run implementation:node-sdk+web-sdk -- implementation:playwright:install --
--with-deps
- run: pnpm run implementation:node-sdk+web-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-node-sdk+web-sdk
path: |
./implementations/node-sdk+web-sdk/playwright-report/
./implementations/node-sdk+web-sdk/test-results/
retention-days: 1
e2e-web-sdk:
name: 🖥️ E2E Web Vanilla
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_web_sdk == 'true'
steps:
- uses: docker/setup-compose-action@8cccb8c14b6500aaffebff1aa49c502c34d2e5e6 # v2.1.0
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/web-sdk/.env.example implementations/web-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:web-sdk -- implementation:install -- --no-frozen-lockfile
- run: pnpm run implementation:web-sdk -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:web-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-web-sdk
path: |
./implementations/web-sdk/playwright-report/
./implementations/web-sdk/test-results/
retention-days: 1
e2e-web-sdk_react:
name: 🖥️ E2E React + Web SDK
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_web_sdk_react == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/web-sdk_react/.env.example implementations/web-sdk_react/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:web-sdk_react -- implementation:install -- --no-frozen-lockfile
- run:
pnpm run implementation:web-sdk_react -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:web-sdk_react -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-web-sdk_react
path: |
./implementations/web-sdk_react/playwright-report/
./implementations/web-sdk_react/test-results/
retention-days: 1
e2e-react-web-sdk:
name: ⚛️ E2E React Web SDK
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 15
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_react_web_sdk == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- name: Create .env from .env.example
run: cp implementations/react-web-sdk/.env.example implementations/react-web-sdk/.env
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
playwright
apt
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- run: pnpm store prune
- run: pnpm run implementation:react-web-sdk -- implementation:install -- --no-frozen-lockfile
- run:
pnpm run implementation:react-web-sdk -- implementation:playwright:install -- --with-deps
- run: pnpm run implementation:react-web-sdk -- implementation:test:e2e:run
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-react-web-sdk
path: |
./implementations/react-web-sdk/playwright-report/
./implementations/react-web-sdk/test-results/
retention-days: 1
e2e-react-native-android-build:
name: 📱 Build React Native Android APK
runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal
timeout-minutes: 30
needs: [setup, changes, build]
if: needs.changes.outputs.e2e_react_native_android == 'true'
env:
CI: 'true'
GRADLE_OPTS: >-
-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.jvmargs=-Xmx4g
-Dkotlin.daemon.jvm.options=-Xmx2g
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: |
mkdir -p "$HOME/.android/sdk" "$HOME/.android/avd" "$HOME/.android/cache"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
gradle
path: |
~/.android/sdk
~/.android/cache
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- name: Download SDK package tarballs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- name: Prune pnpm store metadata cache
run: pnpm store prune
- name: Install React Native implementation dependencies
run: >
pnpm run implementation:react-native-sdk -- implementation:install -- --no-frozen-lockfile
- name: Create .env from .env.example
run: cp implementations/react-native-sdk/.env.example implementations/react-native-sdk/.env
- name: Build Android app (Detox release)
run: pnpm run implementation:react-native-sdk -- test:e2e:android:build:release
- name: Upload Detox APKs
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: detox-android-apks
path: implementations/react-native-sdk/android/app/build/outputs/apk/
if-no-files-found: error
retention-days: 1
e2e-android-sdk-build:
name: 🤖 Build Android APKs
runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal
timeout-minutes: 30
needs: [setup, changes]
if: needs.changes.outputs.e2e_android == 'true'
env:
CI: 'true'
GRADLE_OPTS: >-
-Dorg.gradle.daemon=false -Dorg.gradle.parallel=true -Dorg.gradle.jvmargs=-Xmx4g
-Dkotlin.daemon.jvm.options=-Xmx2g
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: mkdir -p "$HOME/.android/sdk" "$HOME/.android/cache"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
pnpm
gradle
path: |
~/.android/sdk
~/.android/cache
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- name: Build the JS bridge bundles
run: pnpm --filter @contentful/optimization-js-bridge build
- name: Build Compose and Views APKs
working-directory: implementations/android-sdk
run: ./gradlew :compose:assembleDebug :views:assembleDebug
- name: Collect APK artifacts at stable paths
run: |
mkdir -p android-apks
cp implementations/android-sdk/compose/build/outputs/apk/debug/compose-debug.apk android-apks/
cp implementations/android-sdk/views/build/outputs/apk/debug/views-debug.apk android-apks/
ls -la android-apks/
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: android-apks
path: android-apks/
if-no-files-found: error
retention-days: 1
e2e-android-maestro:
name: 🤖 E2E Android Maestro (${{ matrix.app }})
runs-on: ubuntu-latest
timeout-minutes: 45
needs: [setup, changes, e2e-android-sdk-build]
if: needs.changes.outputs.e2e_android == 'true'
strategy:
fail-fast: false
matrix:
include:
- app: compose
package: com.contentful.optimization.app
apk: compose-debug.apk
- app: views
package: com.contentful.optimization.app.views
apk: views-debug.apk
env:
CI: 'true'
MAESTRO_VERSION: '2.6.0'
MAESTRO_ITERATIONS: '1'
API_LEVEL: '35'
ARCH: x86_64
TARGET: aosp_atd
PROFILE: pixel_7
EMULATOR_OPTIONS:
-no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- 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
if [ ! -e /dev/kvm ]; then
echo "/dev/kvm not found; Android hardware acceleration unavailable." >&2
exit 1
fi
ls -l /dev/kvm
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: android-apks
path: android-apks
- name: AVD cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ env.API_LEVEL }}-${{ env.ARCH }}-${{ env.TARGET }}-${{ env.PROFILE }}
- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0
with:
api-level: ${{ env.API_LEVEL }}
arch: ${{ env.ARCH }}
target: ${{ env.TARGET }}
profile: ${{ env.PROFILE }}
avd-name: test
force-avd-creation: false
emulator-boot-timeout: 600
cores: 3
ram-size: 6144M
disk-size: 8G
disable-animations: false
emulator-options: ${{ env.EMULATOR_OPTIONS }}
script: echo "Generated AVD snapshot for caching."
- name: Start Mock Server
run: |
pnpm --dir lib/mocks serve > /tmp/mock-server.log 2>&1 &
echo $! > /tmp/mock-server.pid
for i in {1..60}; do
if nc -z localhost 8000 2>/dev/null; then
echo "Mock server is ready"
break
fi
echo "Waiting for mock server... ($i/60)"
sleep 1
done
if ! nc -z localhost 8000 2>/dev/null; then
echo "Mock server failed to start:"
cat /tmp/mock-server.log
exit 1
fi
- name: Run Maestro E2E (emulator, warm boot from cached snapshot)
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0
with:
api-level: ${{ env.API_LEVEL }}
arch: ${{ env.ARCH }}
target: ${{ env.TARGET }}
profile: ${{ env.PROFILE }}
avd-name: test
force-avd-creation: false
emulator-boot-timeout: 600
cores: 3
ram-size: 6144M
disk-size: 8G
disable-animations: true
emulator-options: ${{ env.EMULATOR_OPTIONS }} -no-snapshot-save
script: |
echo "Installing Maestro CLI ($MAESTRO_VERSION)..."
curl -Ls "https://get.maestro.mobile.dev" | bash
echo "Installing ${{ matrix.app }} APK (${{ matrix.apk }})..."
echo "Removing any cached install of ${{ matrix.package }} to avoid signature mismatches from the cached AVD..."
adb uninstall ${{ matrix.package }} >/dev/null 2>&1 || true
adb install -r android-apks/${{ matrix.apk }}
echo "Setting up adb reverse (harmless localhost fallback; the apps use 10.0.2.2)..."
adb reverse tcp:8000 tcp:8000 || echo "::warning::adb reverse failed; apps use 10.0.2.2 so continuing"
sleep 3
echo "Verifying the host mock is reachable from the emulator via 10.0.2.2..."
adb shell "for i in 1 2 3 4 5 6 7 8 9 10; do toybox nc -z 10.0.2.2 8000 2>/dev/null && echo 'Mock server reachable via 10.0.2.2' && exit 0; sleep 1; done; echo '::warning::mock 10.0.2.2 verification timed out'"
echo "Starting background logcat capture (diagnostics on failure)..."
adb logcat -c || true
nohup adb logcat -v threadtime > /tmp/android-logcat.txt 2>&1 &
echo "Running Maestro flows for ${{ matrix.package }} ($MAESTRO_ITERATIONS iteration(s); only failed flows are retried)..."
bash implementations/android-sdk/scripts/ci-maestro-run.sh ${{ matrix.package }}
- name: Print debug context on failure
if: failure()
run: |
echo "=== Mock Server Logs ==="
cat /tmp/mock-server.log || echo "No mock server logs found"
echo "=== Maestro test output tree (~/.maestro/tests) ==="
ls -la "$HOME/.maestro/tests" 2>/dev/null || echo "No Maestro test output found"
echo "=== logcat ANR / crash lines ==="
grep -nE "ANR in|FATAL EXCEPTION|not responding|lowmemorykiller|Process .* died|Low on memory" /tmp/android-logcat.txt 2>/dev/null | head -60 || echo "No logcat captured"
- name: Stop Mock Server
if: always()
run: |
kill $(cat /tmp/mock-server.pid) 2>/dev/null || true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: ci-results-android-maestro-${{ matrix.app }}
path: |
~/.maestro/tests/
/tmp/mock-server.log
/tmp/android-logcat.txt
retention-days: 7
e2e-ios-sdk-build:
name: 🍎 Build iOS UI Test Bundles
runs-on: namespace-profile-macos-apple-silicon-arm64-6-cpu-14-gb
timeout-minutes: 30
needs: [setup, changes]
if: needs.changes.outputs.e2e_ios == 'true'
env:
DERIVED_DATA: /tmp/optimization-ios-derived-data
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Install XcodeGen and xcbeautify
run: brew install xcodegen xcbeautify
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
path: |
~/Library/Caches/org.swift.swiftpm
- name: Show toolchain
run: |
xcodebuild -version
xcrun simctl list runtimes | head
- run: pnpm install --prefer-offline --frozen-lockfile
- name: Build the JS bridge
run: pnpm run ios:bridge
- name: Build iOS UI test bundles (SwiftUI + UIKit)
run: pnpm run implementation:ios-sdk -- test:e2e:ios:build:release
- name: Stage Build/Products for artifact
run: |
mkdir -p /tmp/ios-artifact
cp -R "$DERIVED_DATA/Build/Products" /tmp/ios-artifact/Products
ls /tmp/ios-artifact/Products/*.xctestrun
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ios-uitest-bundles
path: /tmp/ios-artifact/
if-no-files-found: error
retention-days: 1
swift-package-build:
name: 🍎 Swift Package Build & Test
runs-on: namespace-profile-macos-apple-silicon-arm64-6-cpu-14-gb
timeout-minutes: 30
needs: [setup, changes]
if: needs.changes.outputs.swift_package == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
path: |
~/Library/Caches/org.swift.swiftpm
- name: Show toolchain
run: |
swift --version
xcodebuild -version
- run: pnpm install --prefer-offline --frozen-lockfile
- name: Build the JS bridge
run: pnpm run ios:bridge
- name: Swift build
run: swift build --package-path packages/ios/ContentfulOptimization
- name: Swift test
run: swift test --package-path packages/ios/ContentfulOptimization
android-library-build:
name: 🤖 Android Library Build & Publish-Local Smoke
runs-on: namespace-profile-linux-8-vcpu-16-gb-ram-optimal
timeout-minutes: 30
needs: [setup, changes]
if: needs.changes.outputs.android_library == 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: mkdir -p "$HOME/.android/sdk"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
path: |
~/.android/sdk
~/.gradle/caches
~/.gradle/wrapper
- run: pnpm install --prefer-offline --frozen-lockfile
- name: Build the JS bridge
run: pnpm --filter @contentful/optimization-js-bridge build
- name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: '17'
- name: Set up Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
# Smoke-test packaging: assemble the AAR + sources/javadoc/POM the release would publish
# (no Central access, no signing) so packaging breaks are caught on PRs.
- name: Verify Maven publishing assembles
working-directory: packages/android/ContentfulOptimization
run: |
./gradlew publishToMavenLocal -Pcontentful.optimization.version=0.0.0-ci \
-Pcontentful.optimization.thirdPartyNoticesFile=THIRD_PARTY_NOTICES.txt \
--no-configuration-cache --no-daemon --console=plain
e2e-react-native-android:
name: 📱 E2E React Native Android (shard ${{ matrix.shard }}/2)
runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal
timeout-minutes: 45
needs: [setup, changes, e2e-react-native-android-build]
if: needs.changes.outputs.e2e_react_native_android == 'true'
strategy:
fail-fast: false
matrix:
shard: [1, 2]
env:
DETOX_AVD_NAME: test
CI: 'true'
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Set Android SDK environment variables
run: |
echo "ANDROID_SDK_ROOT=$HOME/.android/sdk" >> "$GITHUB_ENV"
echo "ANDROID_HOME=$HOME/.android/sdk" >> "$GITHUB_ENV"
- name: Prepare cache directories
run: |
mkdir -p "$HOME/.android/sdk" "$HOME/.android/avd" "$HOME/.android/cache"
- name: Set up caches (Namespace)
uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: |
apt
pnpm
path: |
~/.android/sdk
~/.android/avd
~/.android/cache
- name: Install system dependencies (Android emulator + React Native)
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ca-certificates curl unzip zip git \
python3 python3-pip build-essential \
netcat-openbsd watchman \
cpu-checker \
libgl1 libnss3 libx11-6 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 libxtst6 \
libxi6 libxrender1 libxkbcommon0 libgbm1 libdbus-1-3 libdrm2 libpulse0
sudo apt-get install -y --no-install-recommends libasound2 || sudo apt-get install -y --no-install-recommends libasound2t64
- name: Verify KVM is available
run: |
if [ ! -e /dev/kvm ]; then
echo "/dev/kvm not found; Android hardware acceleration will not work." >&2
exit 1
fi
ls -l /dev/kvm
sudo kvm-ok || true
- name: Setup Java
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Android SDK
uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4.0.1
- name: Install JS dependencies
run: pnpm install --prefer-offline --frozen-lockfile
- name: Download SDK package tarballs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: sdk-package-tarballs
path: pkgs
- name: Prune pnpm store metadata cache
run: pnpm store prune
- name: Install React Native implementation dependencies
run: >
pnpm run implementation:react-native-sdk -- implementation:install -- --no-frozen-lockfile
- name: Create .env from .env.example
run: cp implementations/react-native-sdk/.env.example implementations/react-native-sdk/.env
- name: Download pre-built Detox APKs
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: detox-android-apks
path: implementations/react-native-sdk/android/app/build/outputs/apk/
- name: Start Mock Server
run: |
pnpm --dir lib/mocks serve > /tmp/mock-server.log 2>&1 &
echo $! > /tmp/mock-server.pid
for i in {1..60}; do
if nc -z localhost 8000 2>/dev/null; then
echo "Mock server is ready"
break
fi
echo "Waiting for mock server... ($i/60)"
sleep 1
done
if ! nc -z localhost 8000 2>/dev/null; then
echo "Mock server failed to start:"
cat /tmp/mock-server.log
exit 1
fi
- name: Run Android E2E Tests (emulator)
uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2.37.0
with:
api-level: 36
arch: x86_64
target: google_apis
profile: pixel_tablet
avd-name: test
force-avd-creation: true
emulator-boot-timeout: 600
cores: 6
ram-size: 4096M
disk-size: 8G
disable-animations: true
emulator-options: -no-window -no-audio -no-boot-anim -gpu swiftshader_indirect
script: |
echo "Verifying JAVA_HOME: $JAVA_HOME"
java -version
echo "Setting up adb reverse port forwarding..."
adb reverse tcp:8000 tcp:8000
echo "Running E2E tests..."
pnpm run implementation:react-native-sdk -- test:e2e:android:run:release -- --retries 1 --shard=${{ matrix.shard }}/2
- name: Upload logs on failure
if: failure()
run: |
echo "=== Mock Server Logs ==="
cat /tmp/mock-server.log || echo "No mock server logs found"
- name: Stop Mock Server
if: always()
run: |
kill $(cat /tmp/mock-server.pid) 2>/dev/null || true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: always()
with:
name: ci-results-react-native-android-shard-${{ matrix.shard }}
path: |
implementations/react-native-sdk/.detox/
/tmp/mock-server.log
retention-days: 7
e2e-ios-sdk:
name: 🍎 E2E iOS UI (${{ matrix.scheme }})
runs-on: namespace-profile-macos-apple-silicon-arm64-6-cpu-14-gb
timeout-minutes: 45
needs: [setup, changes, e2e-ios-sdk-build]
if: needs.changes.outputs.e2e_ios == 'true'
strategy:
fail-fast: false
matrix:
include:
- scheme: SwiftUI
- scheme: UIKit
env:
DERIVED_DATA: /tmp/optimization-ios-derived-data
IOS_SCHEME: ${{ matrix.scheme }}
IOS_SIM_NAME: 'iPhone 16'
IOS_SIM_OS: 'latest'
IOS_ONLY_TESTING: OptimizationAppUITests${{ matrix.scheme }}/PreviewPanelTests
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: '.nvmrc'
package-manager-cache: false
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
- name: Install xcbeautify
run: brew install xcbeautify
- uses: namespacelabs/nscloud-cache-action@15799a6b54e5765f85b2aac25b3f0df43ed571c0 # v1.4.3
with:
cache: pnpm
- run: pnpm install --prefer-offline --frozen-lockfile
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: ios-uitest-bundles
path: /tmp/ios-artifact/
- name: Reconstruct DerivedData layout at stable path
run: |
mkdir -p "$DERIVED_DATA/Build"
mv /tmp/ios-artifact/Products "$DERIVED_DATA/Build/Products"
ls "$DERIVED_DATA/Build/Products"
- name: Boot iOS Simulator
run: |
DEVICE_UDID=$(xcrun simctl create "ci-${{ matrix.scheme }}" "$IOS_SIM_NAME")
echo "DEVICE_UDID=$DEVICE_UDID" >> "$GITHUB_ENV"
xcrun simctl boot "$DEVICE_UDID"
xcrun simctl bootstatus "$DEVICE_UDID" -b
- name: Start Mock Server
run: |
pnpm --dir lib/mocks serve > /tmp/mock-server.log 2>&1 &
echo $! > /tmp/mock-server.pid
for i in {1..60}; do
if nc -z localhost 8000 2>/dev/null; then
echo "Mock server is ready"
break
fi
echo "Waiting for mock server... ($i/60)"
sleep 1
done
if ! nc -z localhost 8000 2>/dev/null; then
echo "Mock server failed to start:"
cat /tmp/mock-server.log
exit 1
fi
- name: Verify built xctestrun exists for ${{ matrix.scheme }}
shell: 'bash -eo pipefail {0}'
run: |
shopt -s nullglob
matches=("$DERIVED_DATA"/Build/Products/OptimizationApp"$IOS_SCHEME"_*.xctestrun)
if [ ${#matches[@]} -eq 0 ]; then
echo "No xctestrun found for scheme $IOS_SCHEME under $DERIVED_DATA/Build/Products/" >&2
ls -la "$DERIVED_DATA/Build/Products/" || true
exit 1
fi
printf 'Found xctestrun: %s\n' "${matches[@]}"
- name: Run iOS UI tests (${{ matrix.scheme }})
shell: 'bash -eo pipefail {0}'
run: pnpm run implementation:ios-sdk -- test:e2e:ios:run:release
- name: Stop Mock Server
if: always()
run: kill $(cat /tmp/mock-server.pid) 2>/dev/null || true
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() }}
with:
name: ci-results-ios-${{ matrix.scheme }}
path: |
/tmp/optimization-ios-derived-data/Test-*.xcresult
/tmp/mock-server.log
retention-days: 7