Skip to content

Commit f12128c

Browse files
Build React Native iOS E2E IPA artifact
Assisted-By: devx/6c1e3ad5-96c8-4972-b087-da7ff7b195c3
1 parent 79b5af7 commit f12128c

4 files changed

Lines changed: 227 additions & 6 deletions

File tree

e2e/BITRISE.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ The non-secret E2E defaults are defined in `e2e/bitrise.yml` under `app.envs`. D
5959
| `E2E_BROWSERSTACK_TIMEOUT_SECONDS` | `1800` | BrowserStack build timeout. |
6060
| `E2E_BROWSERSTACK_POLL_SECONDS` | `30` | BrowserStack status polling interval. |
6161
| `E2E_IOS_EXPORT_METHOD` | `development` | Export method for the React Native iOS IPA. |
62+
| `E2E_IOS_BUNDLE_ID` | `com.shopify.checkoutkit.reactnativedemo` | Bundle identifier used for iOS archive and export signing. |
63+
| `E2E_IOS_DEVELOPMENT_TEAM` | `A7XGC83MZE` | Apple development team used for iOS archive signing. |
64+
| `E2E_IOS_CODE_SIGN_IDENTITY` | `Apple Development` | Code signing identity used for iOS archive and export signing. |
65+
| `E2E_IOS_PROVISIONING_PROFILE_SPECIFIER` | `bitrise-checkout-kit-e2e` | Provisioning profile specifier installed by Bitrise and passed to `xcodebuild`. |
6266
| `E2E_ANDROID_COMMAND_TIMEOUT_SECONDS` | `1800` | Per-command timeout for React Native Android artifact commands. |
67+
| `E2E_IOS_COMMAND_TIMEOUT_SECONDS` | `1800` | Per-command timeout for React Native iOS dependency, CocoaPods, archive, and export commands. |
6368
| `E2E_PACKAGE_COMMAND_TIMEOUT_SECONDS` | `300` | Per-command timeout for package-suite matrix and suite commands. |
6469
| `E2E_RUBY_INSTALL_TIMEOUT_SECONDS` | `1800` | Timeout for installing the exact repository Ruby version from `.ruby-version`. |
6570

@@ -87,7 +92,11 @@ Configure the Bitrise app to run the `e2e` pipeline for pull requests once the s
8792

8893
## Code signing
8994

90-
React Native iOS IPA generation is added in a later phase and will require Bitrise iOS code signing setup for the sample app.
95+
React Native iOS IPA generation uses Bitrise's certificate and profile installer before running `xcodebuild archive` and `xcodebuild -exportArchive`.
96+
97+
The React Native iOS artifact workflow overrides the default Linux stack and runs on `osx-xcode-26.0.x-edge` with `g2.mac.4large`, because it requires Xcode and iOS signing. Its cache keys are prefixed with `rn-ios-macos-` so macOS caches cannot restore Linux-built dependencies.
98+
99+
Upload the required signing certificate and provisioning profile for the React Native sample app to the Bitrise app before running the iOS artifact workflow. The default provisioning profile specifier is `bitrise-checkout-kit-e2e`; update `E2E_IOS_PROVISIONING_PROFILE_SPECIFIER` in `e2e/bitrise.yml` if the Bitrise-installed profile uses a different name.
91100

92101
## Caching
93102

e2e/bitrise.yml

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ app:
1515
- E2E_BROWSERSTACK_TIMEOUT_SECONDS: "1800"
1616
- E2E_BROWSERSTACK_POLL_SECONDS: "30"
1717
- E2E_IOS_EXPORT_METHOD: development
18+
- E2E_IOS_BUNDLE_ID: com.shopify.checkoutkit.reactnativedemo
19+
- E2E_IOS_DEVELOPMENT_TEAM: A7XGC83MZE
20+
- E2E_IOS_CODE_SIGN_IDENTITY: Apple Development
21+
- E2E_IOS_PROVISIONING_PROFILE_SPECIFIER: bitrise-checkout-kit-e2e
1822
- E2E_ANDROID_COMMAND_TIMEOUT_SECONDS: "1800"
23+
- E2E_IOS_COMMAND_TIMEOUT_SECONDS: "1800"
1924
- E2E_PACKAGE_COMMAND_TIMEOUT_SECONDS: "300"
2025
- E2E_RUBY_INSTALL_TIMEOUT_SECONDS: "1800"
2126

@@ -74,28 +79,132 @@ workflows:
7479
$BITRISE_DEPLOY_DIR/e2e-matrix.json:E2E_MATRIX_JSON
7580
7681
e2e-build-react-native-ios:
82+
meta:
83+
bitrise.io:
84+
stack: osx-xcode-26.0.x-edge
85+
machine_type_id: g2.mac.4large
7786
steps:
7887
- git-clone@8: {}
7988
- restore-cache@3:
8089
inputs:
8190
- key: |-
82-
rn-ios-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }}
91+
e2e-ruby-macos-{{ checksum ".ruby-version" }}
92+
- certificate-and-profile-installer@1: {}
93+
- restore-cache@3:
94+
inputs:
95+
- key: |-
96+
rn-ios-macos-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }}
8397
- script@1:
84-
title: Create React Native iOS artifact placeholder
98+
title: Build React Native iOS IPA artifact
8599
inputs:
86100
- content: |-
87101
set -euo pipefail
102+
source e2e/scripts/bitrise_ci_helpers
103+
e2e_prepare_ruby
104+
e2e_log "Enabling Corepack"
105+
corepack enable
106+
e2e_log "Checking storefront configuration secrets"
107+
: "${STOREFRONT_DOMAIN:?STOREFRONT_DOMAIN is required. Check https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/workflow_editor#!/secrets and enable Expose for pull requests.}"
108+
: "${STOREFRONT_ACCESS_TOKEN:?STOREFRONT_ACCESS_TOKEN is required. Check https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/workflow_editor#!/secrets and enable Expose for pull requests.}"
109+
e2e_log "Configuring storefront environment"
110+
./scripts/setup_storefront_env --skip-optional-prompts
111+
e2e_log "Installing React Native dependencies"
112+
cd platforms/react-native
113+
e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" pnpm install --frozen-lockfile
114+
cd sample
115+
e2e_log "Installing iOS Ruby dependencies"
116+
e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" bundle install
117+
cd ios
118+
e2e_log "Installing CocoaPods dependencies"
119+
e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" bundle exec pod install --deployment --repo-update
88120
mkdir -p "$BITRISE_DEPLOY_DIR/e2e"
89-
echo "Phase 2 placeholder for React Native iOS IPA" > "$BITRISE_DEPLOY_DIR/e2e/react-native-ios.ipa"
90-
envman add --key E2E_REACT_NATIVE_IOS_APP_PATH --value "$BITRISE_DEPLOY_DIR/e2e/react-native-ios.ipa"
121+
archive_path="$BITRISE_DEPLOY_DIR/e2e/CheckoutKitReactNativeDemo.xcarchive"
122+
export_path="$BITRISE_DEPLOY_DIR/e2e/react-native-ios-export"
123+
export_options="$BITRISE_DEPLOY_DIR/e2e/ExportOptions.plist"
124+
export_method="${E2E_IOS_EXPORT_METHOD:-development}"
125+
ios_bundle_id="${E2E_IOS_BUNDLE_ID:-com.shopify.checkoutkit.reactnativedemo}"
126+
ios_development_team="${E2E_IOS_DEVELOPMENT_TEAM:-A7XGC83MZE}"
127+
ios_code_sign_identity="${E2E_IOS_CODE_SIGN_IDENTITY:-Apple Development}"
128+
ios_profile_specifier="${E2E_IOS_PROVISIONING_PROFILE_SPECIFIER:-bitrise-checkout-kit-e2e}"
129+
cat > "$export_options" <<PLIST
130+
<?xml version="1.0" encoding="UTF-8"?>
131+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
132+
<plist version="1.0">
133+
<dict>
134+
<key>method</key>
135+
<string>$export_method</string>
136+
<key>signingStyle</key>
137+
<string>manual</string>
138+
<key>teamID</key>
139+
<string>$ios_development_team</string>
140+
<key>signingCertificate</key>
141+
<string>$ios_code_sign_identity</string>
142+
<key>provisioningProfiles</key>
143+
<dict>
144+
<key>$ios_bundle_id</key>
145+
<string>$ios_profile_specifier</string>
146+
</dict>
147+
<key>stripSwiftSymbols</key>
148+
<true/>
149+
<key>compileBitcode</key>
150+
<false/>
151+
</dict>
152+
</plist>
153+
PLIST
154+
e2e_log "Archiving React Native iOS app"
155+
if ! e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" xcodebuild archive \
156+
-workspace CheckoutKitReactNativeDemo.xcworkspace \
157+
-scheme CheckoutKitReactNativeDemo \
158+
-configuration Release \
159+
-sdk iphoneos \
160+
-destination generic/platform=iOS \
161+
-archivePath "$archive_path" \
162+
-skipPackagePluginValidation \
163+
"CODE_SIGN_STYLE=Manual" \
164+
"DEVELOPMENT_TEAM=$ios_development_team" \
165+
"CODE_SIGN_IDENTITY=$ios_code_sign_identity" \
166+
"PROVISIONING_PROFILE_SPECIFIER=$ios_profile_specifier"; then
167+
e2e_log "iOS archive failed"
168+
echo "Check Bitrise code signing assets for bundle id ${ios_bundle_id} and team ${ios_development_team}." >&2
169+
echo "Expected provisioning profile specifier: ${ios_profile_specifier}." >&2
170+
echo "Upload an Apple Development certificate and matching development provisioning profile at https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/workflow_editor#!/code_signing." >&2
171+
echo "The e2e-build-react-native-ios workflow runs certificate-and-profile-installer before xcodebuild." >&2
172+
exit 1
173+
fi
174+
e2e_log "Exporting React Native iOS IPA"
175+
if ! e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" xcodebuild -exportArchive \
176+
-archivePath "$archive_path" \
177+
-exportPath "$export_path" \
178+
-exportOptionsPlist "$export_options"; then
179+
e2e_log "iOS IPA export failed"
180+
echo "Check that the installed provisioning profile matches export method ${export_method}." >&2
181+
echo "Export options plist: ${export_options}" >&2
182+
exit 1
183+
fi
184+
ios_ipa="$(find "$export_path" -name "*.ipa" -print -quit)"
185+
if [ -z "$ios_ipa" ] || [ ! -f "$ios_ipa" ]; then
186+
e2e_log "iOS IPA export did not create an IPA"
187+
find "$export_path" -maxdepth 3 -print >&2 || true
188+
exit 1
189+
fi
190+
e2e_log "Publishing iOS IPA path"
191+
envman add --key E2E_REACT_NATIVE_IOS_APP_PATH --value "$ios_ipa"
192+
- save-cache@1:
193+
is_always_run: true
194+
inputs:
195+
- key: |-
196+
e2e-ruby-macos-{{ checksum ".ruby-version" }}
197+
- paths: |-
198+
~/.rbenv/versions
91199
- deploy-to-bitrise-io@2:
200+
is_always_run: false
92201
inputs:
93202
- pipeline_intermediate_files: |-
94203
$E2E_REACT_NATIVE_IOS_APP_PATH:E2E_REACT_NATIVE_IOS_APP_PATH
95204
- save-cache@1:
96205
inputs:
97206
- key: |-
98-
rn-ios-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }}
207+
rn-ios-macos-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }}
99208
- paths: |-
100209
platforms/react-native/node_modules
101210
platforms/react-native/sample/ios/Pods

e2e/test/bitrise_pipeline_test.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ def test_bitrise_config_defines_non_secret_e2e_defaults
128128
assert_equal "1800", values.fetch("E2E_BROWSERSTACK_TIMEOUT_SECONDS")
129129
assert_equal "30", values.fetch("E2E_BROWSERSTACK_POLL_SECONDS")
130130
assert_equal "development", values.fetch("E2E_IOS_EXPORT_METHOD")
131+
assert_equal "com.shopify.checkoutkit.reactnativedemo", values.fetch("E2E_IOS_BUNDLE_ID")
132+
assert_equal "A7XGC83MZE", values.fetch("E2E_IOS_DEVELOPMENT_TEAM")
133+
assert_equal "Apple Development", values.fetch("E2E_IOS_CODE_SIGN_IDENTITY")
134+
assert_equal "bitrise-checkout-kit-e2e", values.fetch("E2E_IOS_PROVISIONING_PROFILE_SPECIFIER")
131135
assert_equal "300", values.fetch("E2E_PACKAGE_COMMAND_TIMEOUT_SECONDS")
132136
assert_equal "1800", values.fetch("E2E_RUBY_INSTALL_TIMEOUT_SECONDS")
133137
end
@@ -204,6 +208,96 @@ def test_react_native_android_workflow_does_not_use_local_native_sdk_overrides
204208
refute_includes script, "--local"
205209
end
206210

211+
def test_react_native_ios_workflow_uses_macos_xcode_resources
212+
workflow = load_bitrise_config.fetch("workflows").fetch("e2e-build-react-native-ios")
213+
meta = workflow.fetch("meta").fetch("bitrise.io")
214+
215+
assert_equal "osx-xcode-26.0.x-edge", meta.fetch("stack")
216+
assert_equal "g2.mac.4large", meta.fetch("machine_type_id")
217+
end
218+
219+
def test_react_native_ios_workflow_caches_macos_ruby
220+
restore_cache = workflow_step(load_bitrise_config, "e2e-build-react-native-ios", "restore-cache", key_prefix: "e2e-ruby-macos-")
221+
save_cache = workflow_step(load_bitrise_config, "e2e-build-react-native-ios", "save-cache", key_prefix: "e2e-ruby-macos-")
222+
223+
assert_includes cache_input(restore_cache, "key"), '{{ checksum ".ruby-version" }}'
224+
assert_includes cache_input(save_cache, "key"), '{{ checksum ".ruby-version" }}'
225+
assert_equal true, save_cache.fetch("is_always_run")
226+
assert_includes cache_input(save_cache, "paths"), "~/.rbenv/versions"
227+
end
228+
229+
def test_react_native_ios_workflow_builds_real_ipa_artifact
230+
config = load_bitrise_config
231+
script = workflow_script("e2e-build-react-native-ios")
232+
233+
assert_equal "1800", app_env_values.fetch("E2E_IOS_COMMAND_TIMEOUT_SECONDS")
234+
assert_workflow_has_step(config, "e2e-build-react-native-ios", "certificate-and-profile-installer")
235+
assert_includes script, "source e2e/scripts/bitrise_ci_helpers"
236+
assert_includes script, "e2e_prepare_ruby"
237+
assert_includes script, 'e2e_log "Checking storefront configuration secrets"'
238+
assert_includes script, 'STOREFRONT_DOMAIN is required'
239+
assert_includes script, 'STOREFRONT_ACCESS_TOKEN is required'
240+
assert_includes script, 'workflow_editor#!/secrets'
241+
assert_includes script, 'Expose for pull requests'
242+
assert_includes script, 'e2e_log "Configuring storefront environment"'
243+
assert_includes script, 'e2e_log "Installing React Native dependencies"'
244+
assert_includes script, 'e2e_log "Installing iOS Ruby dependencies"'
245+
assert_includes script, 'e2e_log "Installing CocoaPods dependencies"'
246+
assert_includes script, 'ios_bundle_id="${E2E_IOS_BUNDLE_ID:-com.shopify.checkoutkit.reactnativedemo}"'
247+
assert_includes script, 'ios_development_team="${E2E_IOS_DEVELOPMENT_TEAM:-A7XGC83MZE}"'
248+
assert_includes script, 'ios_code_sign_identity="${E2E_IOS_CODE_SIGN_IDENTITY:-Apple Development}"'
249+
assert_includes script, 'ios_profile_specifier="${E2E_IOS_PROVISIONING_PROFILE_SPECIFIER:-bitrise-checkout-kit-e2e}"'
250+
assert_includes script, '<key>signingStyle</key>'
251+
assert_includes script, '<string>manual</string>'
252+
assert_includes script, '<key>teamID</key>'
253+
assert_includes script, '<string>$ios_development_team</string>'
254+
assert_includes script, '<key>provisioningProfiles</key>'
255+
assert_includes script, '<key>$ios_bundle_id</key>'
256+
assert_includes script, '<string>$ios_profile_specifier</string>'
257+
assert_includes script, 'e2e_log "Archiving React Native iOS app"'
258+
assert_includes script, 'CODE_SIGN_STYLE=Manual'
259+
assert_includes script, 'DEVELOPMENT_TEAM=$ios_development_team'
260+
assert_includes script, 'CODE_SIGN_IDENTITY=$ios_code_sign_identity'
261+
assert_includes script, 'PROVISIONING_PROFILE_SPECIFIER=$ios_profile_specifier'
262+
assert_includes script, 'e2e_log "iOS archive failed"'
263+
assert_includes script, 'com.shopify.checkoutkit.reactnativedemo'
264+
assert_includes script, 'A7XGC83MZE'
265+
assert_includes script, 'workflow_editor#!/code_signing'
266+
assert_includes script, 'certificate-and-profile-installer'
267+
assert_includes script, 'e2e_log "Exporting React Native iOS IPA"'
268+
assert_includes script, 'e2e_log "iOS IPA export failed"'
269+
assert_includes script, 'find "$export_path"'
270+
assert_includes script, 'e2e_log "Publishing iOS IPA path"'
271+
assert_includes script, "corepack enable"
272+
assert_includes script, "cd platforms/react-native"
273+
assert_command_order script, "cd platforms/react-native", "pnpm install --frozen-lockfile"
274+
assert_includes script, "pnpm install --frozen-lockfile"
275+
assert_includes script, "./scripts/setup_storefront_env --skip-optional-prompts"
276+
assert_includes script, 'e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" pnpm install --frozen-lockfile'
277+
assert_includes script, 'e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" bundle install'
278+
assert_includes script, 'e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" bundle exec pod install --deployment --repo-update'
279+
assert_includes script, 'e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" xcodebuild archive'
280+
assert_includes script, 'e2e_run_with_timeout "${E2E_IOS_COMMAND_TIMEOUT_SECONDS:-1800}" xcodebuild -exportArchive'
281+
assert_includes script, "bundle exec pod install --deployment --repo-update"
282+
assert_includes script, "xcodebuild archive"
283+
assert_includes script, "-configuration Release"
284+
assert_includes script, "-sdk iphoneos"
285+
assert_includes script, "generic/platform=iOS"
286+
assert_includes script, "xcodebuild -exportArchive"
287+
assert_includes script, "E2E_REACT_NATIVE_IOS_APP_PATH"
288+
refute_includes script, "Phase 2 placeholder for React Native iOS IPA"
289+
290+
deploy_step = workflow_step(config, "e2e-build-react-native-ios", "deploy-to-bitrise-io")
291+
assert_equal false, deploy_step.fetch("is_always_run")
292+
end
293+
294+
def test_react_native_ios_workflow_does_not_use_local_native_sdk_overrides
295+
script = workflow_script("e2e-build-react-native-ios")
296+
297+
refute_includes script, "USE_LOCAL_SDK"
298+
refute_includes script, "--local"
299+
end
300+
207301
def test_bitrise_setup_docs_exist
208302
assert File.exist?("e2e/BITRISE.md")
209303
end

e2e/test/react_native_sample_e2e_test.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,13 @@ def test_sample_exposes_shared_launch_smoke_ready_marker
99
assert_includes app_source, 'testID="checkout-kit-sample-ready"'
1010
assert_includes app_source, 'accessibilityLabel="checkout-kit-sample-ready"'
1111
end
12+
13+
def test_ios_release_app_loads_bundled_javascript
14+
app_delegate = File.read("platforms/react-native/sample/ios/AppDelegate.swift")
15+
16+
assert_includes app_delegate, "#if DEBUG"
17+
assert_includes app_delegate, 'RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")'
18+
assert_includes app_delegate, '#else'
19+
assert_includes app_delegate, 'Bundle.main.url(forResource: "main", withExtension: "jsbundle")'
20+
end
1221
end

0 commit comments

Comments
 (0)