Skip to content

Commit b459f61

Browse files
authored
Merge pull request #346 from immutable/feature/dx-3448-ios-browserstack
[SDX-3448] iOS browserstack tests on CICD
2 parents f188bf6 + f3ee6e2 commit b459f61

File tree

5 files changed

+187
-9
lines changed

5 files changed

+187
-9
lines changed

.github/workflows/ui-tests.yml

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ jobs:
2020
include:
2121
- targetPlatform: StandaloneOSX
2222
buildMethod: MacBuilder.BuildForAltTester
23-
buildPath: MacOS
23+
buildPath: sample/Builds/MacOS
2424
- targetPlatform: StandaloneWindows64
2525
buildMethod: WindowsBuilder.BuildForAltTester
26-
buildPath: Windows64
26+
buildPath: sample/Builds/Windows64
2727
- targetPlatform: Android
2828
buildMethod: MobileBuilder.BuildForAltTester
29-
buildPath: Android
29+
buildPath: sample/Builds/Android
3030
steps:
3131
- uses: actions/checkout@v3
3232
with:
@@ -47,15 +47,14 @@ jobs:
4747
with:
4848
targetPlatform: ${{ matrix.targetPlatform }}
4949
projectPath: sample
50-
buildMethod: ${{ matrix.buildMethod }}
51-
customParameters: -logFile logFile.log -quit -batchmode
5250
- name: List build directory
53-
run: ls -R sample/Builds/
51+
run: ls -R ./
5452
- name: Upload artifact
5553
uses: actions/upload-artifact@v4
54+
if: always()
5655
with:
5756
name: Build-${{ matrix.targetPlatform }}
58-
path: sample/Builds/${{ matrix.buildPath }}
57+
path: ${{ matrix.buildPath }}
5958
test:
6059
name: Run ${{ matrix.targetPlatform }} UI tests 🧪
6160
needs: build
@@ -70,7 +69,10 @@ jobs:
7069
test_script: test_windows.ps1
7170
- targetPlatform: Android
7271
runs-on: [ self-hosted, macOS ]
73-
test_script: browserstack-sdk pytest -s ./test/test_android.py
72+
test_script: browserstack-sdk pytest -s ./test/test_android.py --browserstack.config "browserstack.android.yml"
73+
- targetPlatform: iOS
74+
runs-on: [ self-hosted, macOS ]
75+
test_script: browserstack-sdk pytest -s ./test/test_ios.py --browserstack.config "browserstack.ios.yml"
7476
concurrency:
7577
group: test-${{ matrix.targetPlatform }}
7678
runs-on: ${{ matrix.runs-on }}

sample/Tests/Payload.ipa

34.5 MB
Binary file not shown.

sample/Tests/browserstack.yml renamed to sample/Tests/browserstack.android.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# Set 'projectName' to the name of your project. Example, Marketing Website
1414
projectName: Unity Sample App
1515
# Set `buildName` as the name of the job / testsuite being run
16-
buildName: browserstack build
16+
buildName: Android build
1717
# `buildIdentifier` is a unique id to differentiate every execution that gets appended to
1818
# buildName. Choose your buildIdentifier format from the available expressions:
1919
# ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution

sample/Tests/browserstack.ios.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# =============================
2+
# Set BrowserStack Credentials
3+
# =============================
4+
# Add your BrowserStack userName and acccessKey here or set BROWSERSTACK_USERNAME and
5+
# BROWSERSTACK_ACCESS_KEY as env variables
6+
#userName: BROWSERSTACK_USERNAME
7+
#accessKey: BROWSERSTACK_ACCESS_KEY
8+
9+
# ======================
10+
# BrowserStack Reporting
11+
# ======================
12+
# The following capabilities are used to set up reporting on BrowserStack:
13+
# Set 'projectName' to the name of your project. Example, Marketing Website
14+
projectName: Unity Sample App
15+
# Set `buildName` as the name of the job / testsuite being run
16+
buildName: iOS build
17+
# `buildIdentifier` is a unique id to differentiate every execution that gets appended to
18+
# buildName. Choose your buildIdentifier format from the available expressions:
19+
# ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution
20+
# ${DATE_TIME}: Generates a Timestamp with every execution. Eg. 05-Nov-19:30
21+
# Read more about buildIdentifiers here -> https://www.browserstack.com/docs/automate/selenium/organize-tests
22+
buildIdentifier: '#${BUILD_NUMBER}' # Supports strings along with either/both ${expression}
23+
framework: pytest
24+
source: pytest-browserstack:sample-sdk:v1.0
25+
26+
# Set `app` to define the app that is to be used for testing.
27+
# It can either take the id of any uploaded app or the path of the app directly.
28+
#app: ./WikipediaSample.apk
29+
app: ./Payload.ipa #For running local tests
30+
31+
# =======================================
32+
# Platforms (Browsers / Devices to test)
33+
# =======================================
34+
# Platforms object contains all the browser / device combinations you want to test on.
35+
# Entire list available here -> (https://www.browserstack.com/list-of-browsers-and-platforms/automate)
36+
37+
platforms:
38+
- platformName: ios
39+
deviceName: iPhone 14 Pro Max
40+
platformVersion: 16
41+
42+
# =======================
43+
# Parallels per Platform
44+
# =======================
45+
# The number of parallel threads to be used for each platform set.
46+
# BrowserStack's SDK runner will select the best strategy based on the configured value
47+
#
48+
# Example 1 - If you have configured 3 platforms and set `parallelsPerPlatform` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack
49+
#
50+
# Example 2 - If you have configured 1 platform and set `parallelsPerPlatform` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack
51+
parallelsPerPlatform: 1
52+
53+
# ==========================================
54+
# BrowserStack Local
55+
# (For localhost, staging/private websites)
56+
# ==========================================
57+
# Set browserStackLocal to true if your website under test is not accessible publicly over the internet
58+
# Learn more about how BrowserStack Local works here -> https://www.browserstack.com/docs/automate/selenium/local-testing-introduction
59+
browserstackLocal: true # <boolean> (Default false)
60+
browserStackLocalOptions:
61+
#Options to be passed to BrowserStack local in-case of advanced configurations
62+
# localIdentifier: # <string> (Default: null) Needed if you need to run multiple instances of local.
63+
forceLocal: true # <boolean> (Default: false) Set to true if you need to resolve all your traffic via BrowserStack Local tunnel.
64+
# Entire list of arguments available here -> https://www.browserstack.com/docs/automate/selenium/manage-incoming-connections
65+
66+
# ===================
67+
# Debugging features
68+
# ===================
69+
debug: false # <boolean> # Set to true if you need screenshots for every selenium command ran
70+
networkLogs: false # <boolean> Set to true to enable HAR logs capturing
71+
consoleLogs: errors # <string> Remote browser's console debug levels to be printed (Default: errors)
72+
# Available options are `disable`, `errors`, `warnings`, `info`, `verbose` (Default: errors)
73+
acceptInsecureCerts: true

sample/Tests/test/test_ios.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import sys
2+
import time
3+
import unittest
4+
from pathlib import Path
5+
6+
from appium import webdriver
7+
from appium.options.ios import XCUITestOptions
8+
from appium.webdriver.common.appiumby import AppiumBy
9+
from appium.webdriver.webdriver import WebDriver
10+
from selenium.webdriver.support.ui import WebDriverWait
11+
from selenium.webdriver.support import expected_conditions as EC
12+
13+
from alttester import AltDriver, By
14+
15+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / 'src'))
16+
from fetch_otp import EMAIL, fetch_code
17+
18+
# To run this test on an actual Android device: appium --base-path /wd/hub --allow-insecure chromedriver_autodownload
19+
class TestBase(unittest.TestCase):
20+
altdriver = None
21+
appium_driver = None
22+
23+
@classmethod
24+
def setUpClass(cls):
25+
# https://appium.github.io/appium-xcuitest-driver/latest/preparation/real-device-config/
26+
options = XCUITestOptions()
27+
options.app = "./Payload.ipa"
28+
options.show_xcode_log = True
29+
options.xcode_org_id = "APPLE_TEAM_ID" # Replace with Apple Team ID
30+
options.auto_accept_alerts = True
31+
32+
cls.appium_driver = webdriver.Remote('https://hub-cloud.browserstack.com/wd/hub/', options=options)
33+
34+
time.sleep(10)
35+
cls.altdriver = AltDriver()
36+
37+
@classmethod
38+
def tearDownClass(cls):
39+
print("\nEnding")
40+
cls.altdriver.stop()
41+
cls.appium_driver.quit()
42+
43+
def test_1_pkce_login(self):
44+
# Select use PKCE auth
45+
self.altdriver.find_object(By.NAME, "PKCE").tap()
46+
47+
# Wait for unauthenticated screen
48+
self.altdriver.wait_for_current_scene_to_be("UnauthenticatedScene")
49+
50+
# Login
51+
loginBtn = self.altdriver.wait_for_object(By.NAME, "LoginBtn")
52+
loginBtn.tap()
53+
54+
driver = self.appium_driver
55+
56+
# Wait for the ASWebAuthenticationSession context to appear
57+
WebDriverWait(driver, 30).until(lambda d: len(d.contexts) > 2)
58+
contexts = driver.contexts
59+
60+
target_context = None
61+
62+
# Since it's unclear which WebView context contains the email field on iOS,
63+
# we need to iterate through each context to identify the correct one.
64+
for context in contexts:
65+
if context == "NATIVE_APP":
66+
continue
67+
68+
driver.switch_to.context(context)
69+
70+
try:
71+
# Attempt to find the email input field
72+
email_field = WebDriverWait(driver, 5).until(
73+
EC.presence_of_element_located((AppiumBy.XPATH, "//input[@name='address']"))
74+
)
75+
# Found email
76+
target_context = context
77+
78+
email_field.send_keys("[email protected]")
79+
submit_button = driver.find_element(by=AppiumBy.XPATH, value="//form/div/div/div[2]/button")
80+
submit_button.click()
81+
82+
time.sleep(10) # Wait for OTP
83+
84+
code = fetch_gmail_code()
85+
assert code, "Failed to fetch OTP from Gmail"
86+
print(f"Successfully fetched OTP: {code}")
87+
88+
# Unlike on Android, each digit must be entered into a separate input field on iOS.
89+
for i, digit in enumerate(code):
90+
otp_field = driver.find_element(by=AppiumBy.XPATH, value=f"//div[@id='passwordless_container']/div[{i + 1}]/input")
91+
otp_field.send_keys(digit)
92+
93+
# Wait for authenticated screen
94+
self.altdriver.wait_for_current_scene_to_be("AuthenticatedScene")
95+
96+
break
97+
except:
98+
# If the field is not found, continue to the next context
99+
print(f"Email field not found in context: {context}")
100+
101+
# If target context was not found, raise an error
102+
if not target_context:
103+
raise Exception("Could not find the email field in any webview context.")

0 commit comments

Comments
 (0)