Skip to content

Commit 1008d4b

Browse files
committed
ci: add E2E workflow and iOS device build
1 parent db420a7 commit 1008d4b

3 files changed

Lines changed: 303 additions & 0 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: "Create demo .env"
2+
description: "Writes the Unity demo app's .env file used by E2E builds"
3+
inputs:
4+
onesignal-app-id:
5+
description: "OneSignal App ID for the demo app"
6+
required: true
7+
onesignal-api-key:
8+
description: "OneSignal REST API key for the demo app"
9+
required: true
10+
e2e-mode:
11+
description: "Whether to enable E2E_MODE in the demo app"
12+
required: false
13+
default: "true"
14+
runs:
15+
using: "composite"
16+
steps:
17+
- name: Write .env
18+
working-directory: examples/demo
19+
shell: bash
20+
run: |
21+
{
22+
echo "ONESIGNAL_APP_ID=${{ inputs.onesignal-app-id }}"
23+
echo "ONESIGNAL_API_KEY=${{ inputs.onesignal-api-key }}"
24+
echo "E2E_MODE=${{ inputs.e2e-mode }}"
25+
} > .env

.github/workflows/e2e.yml

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
name: E2E Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- rel/**
7+
workflow_dispatch:
8+
inputs:
9+
platform:
10+
description: "Platform to test"
11+
required: true
12+
default: "both"
13+
type: choice
14+
options:
15+
- android
16+
- ios
17+
- both
18+
19+
permissions:
20+
contents: read
21+
22+
concurrency:
23+
group: ${{ github.workflow }}-${{ github.ref }}
24+
cancel-in-progress: true
25+
26+
jobs:
27+
build-android:
28+
if: >-
29+
github.event_name == 'push' ||
30+
github.event.inputs.platform == 'android' ||
31+
github.event.inputs.platform == 'both'
32+
runs-on: ubuntu-latest
33+
steps:
34+
- name: Checkout
35+
uses: actions/checkout@v6
36+
with:
37+
lfs: true
38+
39+
- name: Create demo .env
40+
uses: ./.github/actions/create-demo-env
41+
with:
42+
onesignal-app-id: ${{ vars.APPIUM_ONESIGNAL_APP_ID }}
43+
onesignal-api-key: ${{ secrets.APPIUM_ONESIGNAL_API_KEY }}
44+
45+
- name: Resolve OneSignal Android SDK version
46+
id: android-sdk-version
47+
run: |
48+
VERSION=$(grep -oE 'com\.onesignal:OneSignal:[^"]+' com.onesignal.unity.android/Editor/OneSignalAndroidDependencies.xml | head -n1 | cut -d: -f3)
49+
if [ -z "$VERSION" ]; then
50+
echo "::error::Could not parse OneSignal Android SDK version from OneSignalAndroidDependencies.xml"
51+
exit 1
52+
fi
53+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
54+
55+
- name: Wait for OneSignal Android SDK on Maven Central
56+
uses: OneSignal/sdk-shared/.github/actions/wait-for-maven-artifact@main
57+
with:
58+
version: ${{ steps.android-sdk-version.outputs.version }}
59+
60+
- name: Cache Unity Library
61+
uses: actions/cache@v5
62+
with:
63+
path: examples/demo/Library
64+
key: unity-library-android-${{ hashFiles('examples/demo/Packages/manifest.json', 'examples/demo/ProjectSettings/ProjectVersion.txt') }}
65+
restore-keys: unity-library-android-
66+
67+
- name: Build APK
68+
uses: game-ci/unity-builder@v4
69+
env:
70+
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
71+
UNITY_EMAIL: ${{ secrets.UNITY_USERNAME }}
72+
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
73+
with:
74+
projectPath: examples/demo
75+
unityVersion: auto
76+
targetPlatform: Android
77+
buildMethod: BuildScript.BuildAndroidEmulator
78+
allowDirtyBuild: true
79+
80+
- name: Upload APK
81+
uses: actions/upload-artifact@v7
82+
with:
83+
name: demo-apk
84+
path: examples/demo/Build/Android/onesignal-demo.apk
85+
retention-days: 1
86+
compression-level: 0
87+
88+
build-ios:
89+
if: >-
90+
github.event_name == 'push' ||
91+
github.event.inputs.platform == 'ios' ||
92+
github.event.inputs.platform == 'both'
93+
runs-on: macos-latest
94+
steps:
95+
- name: Checkout
96+
uses: actions/checkout@v6
97+
with:
98+
lfs: true
99+
100+
- name: Create demo .env
101+
uses: ./.github/actions/create-demo-env
102+
with:
103+
onesignal-app-id: ${{ vars.APPIUM_ONESIGNAL_APP_ID }}
104+
onesignal-api-key: ${{ secrets.APPIUM_ONESIGNAL_API_KEY }}
105+
106+
- name: Cache Unity Library
107+
uses: actions/cache@v5
108+
with:
109+
path: examples/demo/Library
110+
key: unity-library-ios-${{ hashFiles('examples/demo/Packages/manifest.json', 'examples/demo/ProjectSettings/ProjectVersion.txt') }}
111+
restore-keys: unity-library-ios-
112+
113+
- name: Cache Xcode DerivedData
114+
uses: actions/cache@v5
115+
with:
116+
path: examples/demo/Build/iOS-DerivedData
117+
key: deriveddata-${{ runner.os }}-${{ hashFiles('examples/demo/Build/iOS/Unity-iPhone.xcodeproj/project.pbxproj', 'examples/demo/Build/iOS/Podfile') }}
118+
restore-keys: deriveddata-${{ runner.os }}-
119+
120+
- name: Export Xcode project from Unity
121+
uses: game-ci/unity-builder@v4
122+
env:
123+
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
124+
UNITY_EMAIL: ${{ secrets.UNITY_USERNAME }}
125+
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
126+
with:
127+
projectPath: examples/demo
128+
unityVersion: auto
129+
targetPlatform: iOS
130+
buildMethod: BuildScript.BuildiOSDevice
131+
allowDirtyBuild: true
132+
cacheUnityInstallationOnMac: true
133+
134+
- name: Pod install
135+
working-directory: examples/demo/Build/iOS
136+
run: |
137+
if [ -f Podfile ]; then
138+
pod install --repo-update
139+
else
140+
echo "No Podfile in Unity-exported Xcode project; skipping pod install"
141+
fi
142+
143+
- name: Set up iOS codesigning
144+
uses: OneSignal/sdk-shared/.github/actions/setup-ios-demo-codesigning@main
145+
with:
146+
p12-base64: ${{ secrets.APPIUM_IOS_DEV_CERT_P12_BASE64 }}
147+
p12-password: ${{ secrets.APPIUM_IOS_DEV_CERT_PASSWORD }}
148+
asc-key-id: ${{ secrets.APPIUM_APP_STORE_CONNECT_KEY_ID }}
149+
asc-issuer-id: ${{ secrets.APPIUM_APP_STORE_CONNECT_ISSUER_ID }}
150+
asc-private-key: ${{ secrets.APPIUM_APP_STORE_CONNECT_PRIVATE_KEY }}
151+
152+
- name: Build signed IPA
153+
working-directory: examples/demo/Build/iOS
154+
# Unity always names the generated Xcode project Unity-iPhone, even
155+
# though the demo's product name is "OneSignal Demo". After
156+
# `pod install` an Unity-iPhone.xcworkspace exists and must be used;
157+
# without CocoaPods we fall back to the .xcodeproj. The
158+
# SigningPostProcessor already pinned DEVELOPMENT_TEAM and the NSE /
159+
# Live Activity bundle IDs to match ExportOptions.plist.
160+
run: |
161+
if [ -d Unity-iPhone.xcworkspace ]; then
162+
CONTAINER_ARGS=(-workspace Unity-iPhone.xcworkspace)
163+
else
164+
CONTAINER_ARGS=(-project Unity-iPhone.xcodeproj)
165+
fi
166+
xcodebuild archive \
167+
"${CONTAINER_ARGS[@]}" \
168+
-scheme Unity-iPhone \
169+
-configuration Release \
170+
-sdk iphoneos \
171+
-destination 'generic/platform=iOS' \
172+
-archivePath build/App.xcarchive \
173+
-derivedDataPath ../iOS-DerivedData \
174+
-quiet \
175+
-hideShellScriptEnvironment \
176+
CODE_SIGN_STYLE=Manual \
177+
COMPILER_INDEX_STORE_ENABLE=NO
178+
xcodebuild -exportArchive \
179+
-archivePath build/App.xcarchive \
180+
-exportOptionsPlist "$GITHUB_WORKSPACE/examples/demo/iOS/ExportOptions.plist" \
181+
-exportPath build/ipa \
182+
-quiet
183+
184+
- name: Locate and stage IPA
185+
id: ipa
186+
working-directory: examples/demo
187+
run: |
188+
IPA=$(ls Build/iOS/build/ipa/*.ipa | head -n1)
189+
if [ -z "$IPA" ]; then
190+
echo "::error::No IPA produced by xcodebuild -exportArchive"
191+
exit 1
192+
fi
193+
cp "$IPA" demo.ipa
194+
echo "path=examples/demo/demo.ipa" >> "$GITHUB_OUTPUT"
195+
196+
- name: Verify aps-environment in IPA
197+
working-directory: examples/demo
198+
run: |
199+
unzip -oq demo.ipa -d /tmp/ipa
200+
APP=$(ls -d /tmp/ipa/Payload/*.app | head -n1)
201+
codesign -d --entitlements - "$APP" 2>&1 | tee /tmp/entitlements.txt
202+
if ! grep -q 'aps-environment' /tmp/entitlements.txt; then
203+
echo "::error::Built IPA is missing aps-environment entitlement; push subscription will not work"
204+
exit 1
205+
fi
206+
207+
- name: Upload IPA
208+
uses: actions/upload-artifact@v7
209+
with:
210+
name: demo-ipa
211+
path: ${{ steps.ipa.outputs.path }}
212+
retention-days: 1
213+
compression-level: 0
214+
215+
e2e-android:
216+
needs: build-android
217+
uses: OneSignal/sdk-shared/.github/workflows/appium-e2e.yml@main
218+
secrets: inherit
219+
with:
220+
platform: android
221+
app-artifact: demo-apk
222+
app-filename: onesignal-demo.apk
223+
sdk-type: unity
224+
build-name: unity-android-${{ github.ref_name }}-${{ github.run_number }}
225+
226+
e2e-ios:
227+
needs: build-ios
228+
uses: OneSignal/sdk-shared/.github/workflows/appium-e2e.yml@main
229+
secrets: inherit
230+
with:
231+
platform: ios
232+
app-artifact: demo-ipa
233+
app-filename: demo.ipa
234+
sdk-type: unity
235+
build-name: unity-ios-${{ github.ref_name }}-${{ github.run_number }}

examples/demo/Assets/Scripts/Editor/BuildScript.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,49 @@ public static void BuildiOSSimulator()
110110
HandleReport(report, IOSOutputDir);
111111
}
112112

113+
/// <summary>
114+
/// Builds an Xcode project targeting physical iOS devices, used by CI to
115+
/// produce a signed IPA for BrowserStack. The OneSignal SDK and demo
116+
/// post-processors add push capabilities, the NSE / Live Activity widget
117+
/// targets, and pin DEVELOPMENT_TEAM + aps-environment=development so the
118+
/// archive step can sign with the Appium provisioning profiles in
119+
/// ExportOptions.plist.
120+
/// </summary>
121+
public static void BuildiOSDevice()
122+
{
123+
Directory.CreateDirectory(IOSOutputDir);
124+
125+
PlayerSettings.SetScriptingBackend(NamedBuildTarget.iOS, ScriptingImplementation.IL2CPP);
126+
PlayerSettings.iOS.sdkVersion = iOSSdkVersion.DeviceSDK;
127+
128+
Debug.Log(
129+
$"[BuildScript] iOS sdk={PlayerSettings.iOS.sdkVersion} backend={PlayerSettings.GetScriptingBackend(NamedBuildTarget.iOS)}"
130+
);
131+
132+
PlayerSettings.SetIl2CppCodeGeneration(
133+
NamedBuildTarget.iOS,
134+
Il2CppCodeGeneration.OptimizeSize
135+
);
136+
PlayerSettings.SetManagedStrippingLevel(NamedBuildTarget.iOS, ManagedStrippingLevel.High);
137+
PlayerSettings.stripEngineCode = true;
138+
139+
PlayerSettings.SetIl2CppCompilerConfiguration(
140+
NamedBuildTarget.iOS,
141+
Il2CppCompilerConfiguration.Release
142+
);
143+
144+
var options = new BuildPlayerOptions
145+
{
146+
scenes = GetScenes(),
147+
locationPathName = IOSOutputDir,
148+
target = BuildTarget.iOS,
149+
options = BuildOptions.None,
150+
};
151+
152+
var report = BuildPipeline.BuildPlayer(options);
153+
HandleReport(report, IOSOutputDir);
154+
}
155+
113156
private static string[] GetScenes()
114157
{
115158
var scenes = EditorBuildSettings.scenes;

0 commit comments

Comments
 (0)