Skip to content

Commit bbe08fa

Browse files
committed
First set of E2E tests on iOS
1 parent 10ba438 commit bbe08fa

16 files changed

+141
-104
lines changed

.github/workflows/nightly-tests-mobile.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,18 @@ jobs:
4747
project-name: 'Thunderbird Appointment'
4848
build-name: 'TB Appointment Nightly Tests (Mobile): BUILD_INFO'
4949

50-
- name: Run E2E Tests on Production on Browserstack (Mobile)
50+
- name: Prod E2E Tests on Android Chrome
5151
# don't send GHA failure email if any of the E2E tests fail, can be annoying (I check the jobs each day in BrowserStack)
5252
continue-on-error: true
5353
run: |
5454
cd ./test/e2e
5555
cp .env.prod.example .env
5656
npm run prod-nightly-tests-mobile-browserstack-android-chrome
57+
58+
- name: Prod E2E Tests on iOS Safari
59+
# don't send GHA failure email if any of the E2E tests fail, can be annoying (I check the jobs each day in BrowserStack)
60+
continue-on-error: true
61+
run: |
62+
cd ./test/e2e
63+
cp .env.prod.example .env
64+
npm run prod-nightly-tests-mobile-browserstack-ios-safari

backend/test/unit/test_schedule_availability.py

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55

66
class TestScheduleAvailability:
7-
87
def test_empty_availability_is_valid(self):
98
# Test empty availability is valid
109
schedule = schemas.ScheduleValidationIn(
@@ -17,7 +16,6 @@ def test_empty_availability_is_valid(self):
1716
)
1817
assert all_availability_is_valid(schedule)
1918

20-
2119
def test_all_availability_is_valid(self):
2220
# Test already sorted availabilities
2321
schedule = schemas.ScheduleValidationIn(
@@ -37,7 +35,7 @@ def test_all_availability_is_valid(self):
3735
schemas.AvailabilityValidationIn(
3836
schedule_id=1, day_of_week=2, start_time=time(9, 0), end_time=time(10, 0)
3937
),
40-
]
38+
],
4139
)
4240
assert all_availability_is_valid(schedule)
4341

@@ -49,16 +47,13 @@ def test_all_availability_is_valid(self):
4947
schemas.AvailabilityValidationIn(
5048
schedule_id=1, day_of_week=1, start_time=time(10, 0), end_time=time(11, 0)
5149
),
52-
schemas.AvailabilityValidationIn(
53-
schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(10, 0)
54-
),
50+
schemas.AvailabilityValidationIn(schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(10, 0)),
5551
schemas.AvailabilityValidationIn(
5652
schedule_id=1, day_of_week=1, start_time=time(17, 0), end_time=time(18, 0)
5753
),
5854
]
5955
assert all_availability_is_valid(schedule)
6056

61-
6257
def test_all_availability_is_invalid(self):
6358
# Test overlapping end-start times
6459
schedule = schemas.ScheduleValidationIn(
@@ -75,15 +70,13 @@ def test_all_availability_is_invalid(self):
7570
schemas.AvailabilityValidationIn(
7671
schedule_id=1, day_of_week=1, start_time=time(10, 0), end_time=time(12, 0)
7772
),
78-
]
73+
],
7974
)
8075
assert not all_availability_is_valid(schedule)
8176

8277
# Test completely overlapping slots
8378
schedule.availabilities = [
84-
schemas.AvailabilityValidationIn(
85-
schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(12, 0)
86-
),
79+
schemas.AvailabilityValidationIn(schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(12, 0)),
8780
schemas.AvailabilityValidationIn(
8881
schedule_id=1, day_of_week=1, start_time=time(10, 0), end_time=time(11, 0)
8982
),
@@ -92,9 +85,7 @@ def test_all_availability_is_invalid(self):
9285

9386
# Test slots with invalid start/end time
9487
schedule.availabilities = [
95-
schemas.AvailabilityValidationIn(
96-
schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(12, 0)
97-
),
88+
schemas.AvailabilityValidationIn(schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(12, 0)),
9889
schemas.AvailabilityValidationIn(
9990
schedule_id=1, day_of_week=1, start_time=time(14, 0), end_time=time(13, 0)
10091
),
@@ -103,8 +94,6 @@ def test_all_availability_is_invalid(self):
10394

10495
# Test slots that are too small for the defined duration
10596
schedule.availabilities = [
106-
schemas.AvailabilityValidationIn(
107-
schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(9, 15)
108-
),
97+
schemas.AvailabilityValidationIn(schedule_id=1, day_of_week=1, start_time=time(9, 0), end_time=time(9, 15)),
10998
]
11099
assert not all_availability_is_valid(schedule)

test/e2e/README.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -175,28 +175,28 @@ export BROWSERSTACK_USERNAME=<your-browserstack-user-name>
175175
export BROWSERSTACK_ACCESS_KEY=<your-browserstack-access-key>
176176
```
177177

178-
To run the E2E tests on BrowserStack (still in `test/e2e`):
178+
To run the E2E tests on Firefox Desktop on BrowserStack (still in `test/e2e`):
179179

180180
```bash
181-
npm run e2e-test-browserstack
181+
npm run e2e-test-browserstack-firefox
182182
```
183183

184-
To run the production sanity test on BrowserStack (still in `test/e2e`):
184+
To run the production sanity test on Firefox Desktop on BrowserStack (still in `test/e2e`):
185185

186186
```bash
187-
npm run prod-sanity-test-browserstack
187+
npm run prod-sanity-test-browserstack-firefox
188188
```
189189

190-
To run the E2E tests on mobile devices on BrowserStack (still in `test/e2e`):
190+
To run the E2E tests on Android Chrome on a real Google Pixel device on BrowserStack (still in `test/e2e`):
191191

192192
```bash
193-
npm run e2e-test-mobile-browserstack
193+
npm run e2e-tests-mobile-browserstack-android-chrome
194194
```
195195

196-
To run the nightly test suite on real mobile devices in BrowserStack (still in `test/e2e`):
196+
To run the E2E tests on iOS Safari on a real iPhone device on BrowserStack (still in `test/e2e`):
197197

198198
```bash
199-
npm run prod-nightly-tests-mobile-browserstack-gha
199+
npm run e2e-tests-mobile-browserstack-ios-safari
200200
```
201201

202202
After the tests finish in your local console you'll see a link to the BrowserStack test session; when signed into your BrowserStack account you'll be able to use that link to see the test session results including video playback.

test/e2e/browserstack-mobile-nightly.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,20 @@ platforms:
2929
osVersion: 16.0
3030
deviceName: Google Pixel 10
3131
playwrightConfigOptions:
32-
name: Chrome-GooglePixel10
32+
name: android-chrome
3333
chrome:
3434
chromeOptions:
3535
prefs:
3636
credentials_enable_service: false
3737
profile:
3838
password_manager_enabled: false
3939

40+
- browserName: safari
41+
osVersion: 26
42+
deviceName: iPhone 17
43+
playwrightConfigOptions:
44+
name: ios-safari
45+
4046
# =======================
4147
# Parallels per Platform
4248
# =======================

test/e2e/browserstack-mobile.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,20 @@ platforms:
2929
osVersion: 16.0
3030
deviceName: Google Pixel 10
3131
playwrightConfigOptions:
32-
name: Chrome-GooglePixel10
32+
name: android-chrome
3333
chrome:
3434
chromeOptions:
3535
prefs:
3636
credentials_enable_service: false
3737
profile:
3838
password_manager_enabled: false
3939

40+
- browserName: safari
41+
osVersion: 26
42+
deviceName: iPhone 17
43+
playwrightConfigOptions:
44+
name: ios-safari
45+
4046
# =======================
4147
# Parallels per Platform
4248
# =======================

test/e2e/package-lock.json

Lines changed: 8 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/e2e/package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
"scripts": {
66
"e2e-test": "npx playwright test --grep e2e-suite --project=firefox",
77
"e2e-test-headed": "npx playwright test --grep e2e-suite --project=firefox --headed",
8-
"e2e-test-browserstack-firefox": "npx browserstack-node-sdk playwright test --grep e2e-suite --project=Firefox-OSX --browserstack.buildName 'Appointment E2E Tests Firefox Desktop' --browserstack.config 'browserstack-desktop.yml'",
8+
"e2e-test-browserstack-firefox": "npx browserstack-node-sdk playwright test --grep e2e-suite --project=Firefox-OSX --max-failures=2 --browserstack.buildName 'Appointment E2E Tests Firefox Desktop' --browserstack.config 'browserstack-desktop.yml'",
9+
"e2e-tests-mobile-browserstack-android-chrome": "npx browserstack-node-sdk playwright test --grep e2e-mobile-suite --project=android-chrome --max-failures=2 --browserstack.buildName 'Appointment E2E Tests Android Chrome' --browserstack.config 'browserstack-mobile.yml'",
10+
"e2e-tests-mobile-browserstack-ios-safari": "npx browserstack-node-sdk playwright test --grep e2e-mobile-suite --project=ios-safari --max-failures=2 --browserstack.buildName 'Appointment E2E Tests iOS Chrome' --browserstack.config 'browserstack-mobile.yml'",
911
"prod-sanity-test": "npx playwright test --grep prod-sanity --project=firefox",
1012
"prod-sanity-test-headed": "npx playwright test --grep prod-sanity --project=firefox --headed",
11-
"prod-sanity-test-browserstack-firefox": "npx browserstack-node-sdk playwright test --grep prod-sanity --project=Firefox-OSX --browserstack.buildName 'Appointment Production Sanity Test Firefox Desktop' --browserstack.config 'browserstack-desktop.yml'",
13+
"prod-sanity-test-browserstack-firefox": "npx browserstack-node-sdk playwright test --grep prod-sanity --project=Firefox-OSX --max-failures=2 --browserstack.buildName 'Appointment Production Sanity Test Firefox Desktop' --browserstack.config 'browserstack-desktop.yml'",
1214
"prod-nightly-tests-browserstack-firefox": "npx browserstack-node-sdk playwright test --grep prod-nightly --project=Firefox-OSX --browserstack.buildName 'Appointment Nightly Tests Firefox Desktop' --browserstack.config 'browserstack-desktop-nightly.yml'",
1315
"prod-nightly-tests-browserstack-safari": "npx browserstack-node-sdk playwright test --grep prod-nightly --project=Safari-OSX --browserstack.buildName 'Appointment Nightly Tests Safari Desktop' --browserstack.config 'browserstack-desktop-nightly.yml'",
1416
"prod-nightly-tests-browserstack-chromium": "npx browserstack-node-sdk playwright test --grep prod-nightly --project=Chromium-Win11 --browserstack.buildName 'Appointment Nightly Tests Chromium Desktop' --browserstack.config 'browserstack-desktop-nightly.yml'",
15-
"prod-nightly-tests-mobile-browserstack-android-chrome": "npx browserstack-node-sdk playwright test --grep prod-mobile-nightly --project=Chrome-GooglePixel10 --browserstack.buildName 'Appointment Nightly Tests Android Chrome' --browserstack.config 'browserstack-mobile-nightly.yml'",
17+
"prod-nightly-tests-mobile-browserstack-android-chrome": "npx browserstack-node-sdk playwright test --grep prod-mobile-nightly --project=android-chrome --browserstack.buildName 'Appointment Nightly Tests Android Chrome' --browserstack.config 'browserstack-mobile-nightly.yml'",
18+
"prod-nightly-tests-mobile-browserstack-ios-safari": "npx browserstack-node-sdk playwright test --grep prod-mobile-nightly --project=ios-safari --browserstack.buildName 'Appointment Nightly Tests iOS Chrome' --browserstack.config 'browserstack-mobile-nightly.yml'",
1619
"postinstall": "npm update browserstack-node-sdk"
1720
},
1821
"keywords": [],

test/e2e/pages/settings-page.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@ import {
55
APPT_HTML_DARK_MODE_CLASS,
66
TIMEOUT_1_SECOND,
77
TIMEOUT_2_SECONDS,
8-
TIMEOUT_3_SECONDS,
98
TIMEOUT_5_SECONDS,
109
TIMEOUT_30_SECONDS,
1110
APPT_LANGUAGE_SETTING_EN,
1211
} from '../const/constants';
12+
import test from 'node:test';
1313

1414

1515
export class SettingsPage {
1616
readonly page: Page;
17+
readonly testPlatform: string;
1718
readonly accountSettingsBtn: Locator;
1819
readonly connectedAppsBtn: Locator;
1920
readonly preferencesBtn: Locator;
@@ -49,8 +50,9 @@ export class SettingsPage {
4950
readonly revertBtn: Locator;
5051

5152

52-
constructor(page: Page) {
53+
constructor(page: Page, testPlatform: string = 'desktop') {
5354
this.page = page;
55+
this.testPlatform = testPlatform;
5456

5557
// main settings view
5658
this.settingsHeaderEN = this.page.getByRole('main').getByText('Settings', { exact: true });
@@ -94,13 +96,22 @@ export class SettingsPage {
9496
this.defaultCalendarConnectedCbox = this.page.locator('div').filter({ hasText: /^Default*/ }).getByTestId('checkbox-input');
9597
}
9698

99+
/**
100+
* Scroll the given element into view. The reason why we do this here is because playright doesn't yet supported this on ios.
101+
*/
102+
async scrollIntoView(targetElement: Locator, timeout: number = 10000) {
103+
if (!this.testPlatform.includes('ios')) {
104+
await targetElement.scrollIntoViewIfNeeded({ timeout: timeout });
105+
}
106+
}
107+
97108
/**
98109
* Navigate to settings, account settings section
99110
*/
100111
async gotoAccountSettings() {
101112
await this.page.goto(APPT_SETTINGS_PAGE);
102113
await this.page.waitForTimeout(TIMEOUT_5_SECONDS);
103-
await this.accountSettingsBtn.scrollIntoViewIfNeeded();
114+
await this.scrollIntoView(this.accountSettingsBtn);
104115
await this.accountSettingsBtn.click();
105116
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
106117
}
@@ -111,7 +122,7 @@ export class SettingsPage {
111122
async gotoPreferencesSettings() {
112123
await this.page.goto(APPT_SETTINGS_PAGE);
113124
await this.page.waitForTimeout(TIMEOUT_5_SECONDS);
114-
await this.preferencesBtn.scrollIntoViewIfNeeded({ timeout: TIMEOUT_30_SECONDS });
125+
await this.scrollIntoView(this.preferencesBtn, TIMEOUT_30_SECONDS);
115126
await this.preferencesBtn.click();
116127
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
117128
}
@@ -122,7 +133,7 @@ export class SettingsPage {
122133
async gotoConnectedAppSettings() {
123134
await this.page.goto(APPT_SETTINGS_PAGE);
124135
await this.page.waitForTimeout(TIMEOUT_5_SECONDS);
125-
await this.connectedAppsBtn.scrollIntoViewIfNeeded();
136+
await this.scrollIntoView(this.connectedAppsBtn);
126137
await this.connectedAppsBtn.click();
127138
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
128139
}
@@ -132,11 +143,11 @@ export class SettingsPage {
132143
*/
133144
async changeDefaultTimezoneSetting(timezone: string) {
134145
await this.defaultTimeZoneSelect.waitFor( { timeout: TIMEOUT_30_SECONDS });
135-
await this.defaultTimeZoneSelect.scrollIntoViewIfNeeded();
146+
await this.scrollIntoView(this.defaultTimeZoneSelect);
136147
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
137148
await this.defaultTimeZoneSelect.selectOption(timezone, { timeout: TIMEOUT_30_SECONDS });
138149
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
139-
await this.saveBtnEN.scrollIntoViewIfNeeded();
150+
await this.scrollIntoView(this.saveBtnEN);
140151
await this.saveBtnEN.click();
141152
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
142153
await expect(this.savedSuccessfullyTextEN).toBeVisible();
@@ -146,17 +157,17 @@ export class SettingsPage {
146157
* Change the language setting
147158
*/
148159
async changeLanguageSetting(currentLanguage: string, newLanguage: string) {
149-
await this.languageSelect.scrollIntoViewIfNeeded();
160+
await this.scrollIntoView(this.languageSelect);
150161
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
151162
await this.languageSelect.selectOption(newLanguage, { timeout: TIMEOUT_30_SECONDS });
152163
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
153164
if (currentLanguage == APPT_LANGUAGE_SETTING_EN) {
154-
await this.saveBtnEN.scrollIntoViewIfNeeded();
165+
await this.scrollIntoView(this.saveBtnEN);
155166
await this.saveBtnEN.click();
156167
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
157168
await expect(this.savedSuccessfullyTextDE).toBeVisible();
158169
} else {
159-
await this.saveBtnDE.scrollIntoViewIfNeeded();
170+
await this.scrollIntoView(this.saveBtnDE);
160171
await this.saveBtnDE.click();
161172
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
162173
await expect(this.savedSuccessfullyTextEN).toBeVisible();
@@ -169,7 +180,7 @@ export class SettingsPage {
169180
*/
170181
async changeThemeSetting(theme: string) {
171182
await this.themeSelect.waitFor({ timeout: TIMEOUT_30_SECONDS });
172-
await this.themeSelect.scrollIntoViewIfNeeded();
183+
await this.scrollIntoView(this.themeSelect);
173184
await this.themeSelect.selectOption(theme);
174185
await this.page.waitForTimeout(TIMEOUT_2_SECONDS);
175186
await this.saveBtnEN.click();
@@ -199,7 +210,7 @@ export class SettingsPage {
199210
await this.startOfWeekSundayBtn.click({ timeout: TIMEOUT_30_SECONDS });
200211
}
201212
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
202-
await this.saveBtnEN.scrollIntoViewIfNeeded();
213+
await this.scrollIntoView(this.saveBtnEN);
203214
await this.saveBtnEN.click();
204215
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
205216
await expect(this.savedSuccessfullyTextEN).toBeVisible();

0 commit comments

Comments
 (0)