Skip to content
Open
164 changes: 164 additions & 0 deletions .github/workflows/main-pipeline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
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_ios: ${{ steps.filter.outputs.e2e_ios }}
steps:
- uses: namespacelabs/nscloud-checkout-action@938f5d2d403d6224d9a0c0dc559b1dae09c2ede4 # v8.1.1

Expand Down Expand Up @@ -125,6 +126,14 @@ jobs:
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'
# iOS native implementation E2E coverage scope.
e2e_ios:
- 'implementations/ios-sdk/**'
- 'lib/mocks/**'
- 'packages/ios/**'
- 'package.json'
- 'pnpm-lock.yaml'
- '.github/workflows/main-pipeline.yaml'

setup:
name: 🛠️ pnpm install
Expand Down Expand Up @@ -650,6 +659,57 @@ jobs:
if-no-files-found: error
retention-days: 1

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 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

e2e-react-native-android:
name: 📱 E2E React Native Android (shard ${{ matrix.shard }}/2)
runs-on: namespace-profile-linux-16-vcpu-32-gb-ram-optimal
Expand Down Expand Up @@ -808,3 +868,107 @@ jobs:
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'
# Smoke mode for the first PR — restrict to one test class per scheme.
# Remove this env var in a follow-up PR to enable the full suite.
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
10 changes: 10 additions & 0 deletions implementations/ios-sdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@implementation/ios-sdk",
"version": "0.0.0",
"private": true,
"scripts": {
"xcodegen": "xcodegen generate",
"test:e2e:ios:build:release": "set -o pipefail && pnpm run xcodegen && xcodebuild build-for-testing -project OptimizationApp.xcodeproj -scheme OptimizationAppSwiftUI -configuration Release -destination 'generic/platform=iOS Simulator' -derivedDataPath /tmp/optimization-ios-derived-data CODE_SIGNING_ALLOWED=NO COMPILER_INDEX_STORE_ENABLE=NO | xcbeautify && xcodebuild build-for-testing -project OptimizationApp.xcodeproj -scheme OptimizationAppUIKit -configuration Release -destination 'generic/platform=iOS Simulator' -derivedDataPath /tmp/optimization-ios-derived-data CODE_SIGNING_ALLOWED=NO COMPILER_INDEX_STORE_ENABLE=NO | xcbeautify",
"test:e2e:ios:run:release": "set -o pipefail && xcodebuild test-without-building -xctestrun \"$(ls /tmp/optimization-ios-derived-data/Build/Products/OptimizationApp${IOS_SCHEME}_*.xctestrun | head -1)\" -destination \"platform=iOS Simulator,name=${IOS_SIM_NAME:-iPhone 16},OS=${IOS_SIM_OS:-latest}\" -resultBundlePath /tmp/optimization-ios-derived-data/Test-${IOS_SCHEME}.xcresult ${IOS_ONLY_TESTING:+-only-testing:${IOS_ONLY_TESTING}} | xcbeautify"
}
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"format:check": "prettier . --check",
"format:fix": "prettier . --check --write",
"implementation:install": "pnpm run build:pkgs && pnpm run implementation:run -- --all -- implementation:install",
"implementation:ios-sdk": "pnpm run implementation:run -- ios-sdk",
"implementation:node-sdk": "pnpm run implementation:run -- node-sdk",
"implementation:node-sdk+web-sdk": "pnpm run implementation:run -- node-sdk+web-sdk",
"implementation:react-native-sdk": "pnpm run implementation:run -- react-native-sdk",
Expand All @@ -38,13 +39,15 @@
"prepare": "husky",
"serve:mocks": "pnpm --dir lib/mocks serve",
"setup:e2e": "pnpm run build:pkgs && pnpm run implementation:run -- --all -- implementation:install && pnpm run playwright:install && pnpm run playwright:install-deps",
"setup:e2e:ios-sdk": "pnpm run implementation:ios-sdk -- xcodegen",
"setup:e2e:node-sdk": "pnpm run build:pkgs && pnpm run implementation:run -- node-sdk implementation:install && pnpm run implementation:run -- node-sdk implementation:setup:e2e",
"setup:e2e:node-sdk+web-sdk": "pnpm run build:pkgs && pnpm run implementation:run -- node-sdk+web-sdk implementation:install && pnpm run implementation:run -- node-sdk+web-sdk implementation:setup:e2e",
"setup:e2e:react-native-sdk": "pnpm run build:pkgs && pnpm run implementation:run -- react-native-sdk implementation:install && pnpm run implementation:run -- react-native-sdk implementation:setup:e2e",
"setup:e2e:react-web-sdk": "pnpm run build:pkgs && pnpm run implementation:run -- react-web-sdk implementation:install && pnpm run implementation:run -- react-web-sdk implementation:setup:e2e",
"setup:e2e:web-sdk_react": "pnpm run build:pkgs && pnpm run implementation:run -- web-sdk_react implementation:install && pnpm run implementation:run -- web-sdk_react implementation:setup:e2e",
"setup:e2e:web-sdk": "pnpm run build:pkgs && pnpm run implementation:run -- web-sdk implementation:install && pnpm run implementation:run -- web-sdk implementation:setup:e2e",
"test:e2e": "pnpm run setup:e2e && pnpm run implementation:run -- --all -- implementation:test:e2e:run",
"test:e2e:ios-sdk": "pnpm run setup:e2e:ios-sdk && pnpm run implementation:ios-sdk -- test:e2e:ios:build:release && IOS_SCHEME=SwiftUI pnpm run implementation:ios-sdk -- test:e2e:ios:run:release && IOS_SCHEME=UIKit pnpm run implementation:ios-sdk -- test:e2e:ios:run:release",
"test:e2e:node-sdk": "pnpm run setup:e2e:node-sdk && pnpm run implementation:run -- node-sdk implementation:test:e2e:run",
"test:e2e:node-sdk+web-sdk": "pnpm run setup:e2e:node-sdk+web-sdk && pnpm run implementation:run -- node-sdk+web-sdk implementation:test:e2e:run",
"test:e2e:react-native-sdk": "pnpm run setup:e2e:react-native-sdk && pnpm run implementation:run -- react-native-sdk implementation:test:e2e:run",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ enum NativePolyfills {
}

private static func registerNativeSetTimeout(in context: JSContext, timerStore: TimerStore) {
weak let weakContext = context
weak var weakContext = context
let nativeSetTimeout: @convention(block) (Int, Int) -> Void = { timerId, delayMs in
let workItem = DispatchWorkItem {
guard let ctx = weakContext else { return }
Expand Down Expand Up @@ -107,7 +107,7 @@ enum NativePolyfills {
}

private static func registerNativeFetch(in context: JSContext) {
weak let weakContext = context
weak var weakContext = context
let nativeFetch: @convention(block) (String, String, String, JSValue, Int) -> Void = {
urlString, method, headersJSON, bodyValue, callbackId in

Expand Down
Loading