Skip to content

Commit ed40185

Browse files
committed
Port the book appointment E2E test to mobile
1 parent 74799ac commit ed40185

File tree

4 files changed

+107
-7
lines changed

4 files changed

+107
-7
lines changed

frontend/src/views/BookerView/components/SlotSelectionAside.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,11 @@ const bookEvent = async () => {
144144
</div>
145145

146146
<form ref="userInfoForm" class="user-info-form" @submit.prevent @keyup.enter="bookEvent">
147-
<text-input required name="booker-view-user-name" v-model="guestUserName">
147+
<text-input required name="booker-view-user-name" v-model="guestUserName" data-testid="booking-view-name-input">
148148
{{ t('label.name') }}
149149
</text-input>
150150

151-
<text-input required type="email" name="booker-view-user-email" v-model="guestUserEmail">
151+
<text-input required type="email" name="booker-view-user-email" v-model="guestUserEmail" data-testid="booking-view-email-input">
152152
{{ t('label.email') }}
153153
</text-input>
154154

test/e2e/pages/booking-page.ts

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

55
export class BookingPage {
66
readonly page: Page;
7+
readonly testPlatform: String;
78
readonly titleText: Locator;
89
readonly bookATimeToMeetText: Locator;
910
readonly selectTimeSlotText: Locator;
@@ -29,8 +30,10 @@ export class BookingPage {
2930
readonly bookApptPage630PMSlot: Locator;
3031
readonly bookApptPage15MinSlot: Locator;
3132

32-
constructor(page: Page) {
33+
constructor(page: Page, testPlatform: string = 'desktop') {
3334
this.page = page;
35+
this.testPlatform = testPlatform;
36+
3437
this.titleText = this.page.getByTestId('booking-view-title-text');
3538
this.bookATimeToMeetText = this.page.getByTestId('booking-view-book-a-time-to-meet-with-text');
3639
this.selectTimeSlotText = this.page.getByText('Select an open time slot from the calendar');
@@ -42,11 +45,11 @@ export class BookingPage {
4245
this.bookingCalendarHdrFri = this.page.getByText('FRI', { exact: true });
4346
this.bookingCalendarHdrSat = this.page.getByText('SAT', { exact: true });
4447
this.bookingWeekPickerBtn = this.page.locator('.week-picker-button');
45-
this.bookApptBtn = this.page.getByTestId('booking-view-confirm-selection-button');
4648
this.nextWeekArrow = this.page.getByRole('button', { name: 'Next week' });
4749
this.availableBookingSlot = this.page.locator('.selectable-slot', { hasNotText: 'Busy'});
48-
this.bookSelectionNameInput = this.page.getByPlaceholder('First and last name');
49-
this.bookSelectionEmailInput = this.page.getByPlaceholder('[email protected]');
50+
this.bookSelectionNameInput = this.page.getByTestId('booking-view-name-input');
51+
this.bookSelectionEmailInput = this.page.getByTestId('booking-view-email-input');
52+
this.bookApptBtn = this.page.getByTestId('booking-view-confirm-selection-button');
5053
this.bookingConfirmedTitleText = this.page.getByText('Booking confirmed');
5154
this.requestSentAvailabilityText = this.page.getByText("'s Availability");
5255
this.requestSentCloseBtn = this.page.getByRole('button', { name: 'Close' });
@@ -86,6 +89,15 @@ export class BookingPage {
8689
await expect(this.selectTimeSlotText).toBeVisible( { timeout: TIMEOUT_30_SECONDS });
8790
}
8891

92+
/**
93+
* Scroll the given element into view. The reason why we do this here is because playright doesn't yet supported this on ios.
94+
*/
95+
async scrollIntoView(targetElement: Locator, timeout: number = 10000) {
96+
if (!this.testPlatform.includes('ios')) {
97+
await targetElement.scrollIntoViewIfNeeded({ timeout: timeout });
98+
}
99+
}
100+
89101
/**
90102
* With the booking page week view already displayed, go forward to the next week.
91103
*/
@@ -139,6 +151,7 @@ export class BookingPage {
139151
* @param bookerEmail String to fill in as the booking requester's email
140152
*/
141153
async finishBooking(bookerName: string, bookerEmail: string) {
154+
await this.scrollIntoView(this.bookSelectionNameInput);
142155
await this.bookSelectionNameInput.fill(bookerName);
143156
await this.bookSelectionEmailInput.fill(bookerEmail);
144157
await this.bookApptBtn.click();
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { test, expect } from '@playwright/test';
2+
import { BookingPage } from '../../pages/booking-page';
3+
import { DashboardPage } from '../../pages/dashboard-page';
4+
import { ensureWeAreSignedIn } from '../../utils/utils';
5+
6+
import {
7+
APPT_DISPLAY_NAME,
8+
PLAYWRIGHT_TAG_PROD_MOBILE_NIGHTLY,
9+
PLAYWRIGHT_TAG_E2E_SUITE_MOBILE,
10+
APPT_TIMEZONE_SETTING_PRIMARY,
11+
APPT_BOOKEE_NAME,
12+
APPT_BOOKEE_EMAIL,
13+
TIMEOUT_3_SECONDS,
14+
TIMEOUT_10_SECONDS,
15+
TIMEOUT_30_SECONDS,
16+
TIMEOUT_60_SECONDS,
17+
} from '../../const/constants';
18+
19+
var bookingPage: BookingPage;
20+
var dashboardPage: DashboardPage;
21+
22+
test.beforeEach(async ({ page }, testInfo) => {
23+
bookingPage = new BookingPage(page, testInfo.project.name); // i.e. 'ios-safari'
24+
dashboardPage = new DashboardPage(page);
25+
});
26+
27+
// the APPT_TIMEZONE_SETTING_PRIMARY is set to the local timezone where the test is running; that way
28+
// the setting in Appointment will also match what is displayed in the book appoitment page, since
29+
// the book appoitment page uses the local timezone and not a setting; in CI the tests run in BrowserStack
30+
// which is located in a different timezone than when running the tests locally, so must be dynamic
31+
test.use({
32+
timezoneId: APPT_TIMEZONE_SETTING_PRIMARY,
33+
});
34+
35+
test.describe('book an appointment on mobile browser', () => {
36+
37+
test('able to request a booking on mobile browser', {
38+
tag: [PLAYWRIGHT_TAG_E2E_SUITE_MOBILE, PLAYWRIGHT_TAG_PROD_MOBILE_NIGHTLY],
39+
}, async ({ page }) => {
40+
// in order to ensure we find an available slot we can click on, first switch to week view URL
41+
await bookingPage.gotoBookingPageWeekView();
42+
await expect(bookingPage.titleText).toBeVisible({ timeout: TIMEOUT_30_SECONDS });
43+
44+
// now select an available booking time slot
45+
const selectedSlot: string|null = await bookingPage.selectAvailableBookingSlot(APPT_DISPLAY_NAME);
46+
console.log(`selected appointment time slot: ${selectedSlot}`);
47+
48+
// now provide the bookee details for our selected time slot; when signed into Appointment this
49+
// info is provided automatically however for this test we are selecting a time slot without being
50+
// signed in / without being an Appointment user; so we must specify the bookee name and email
51+
await bookingPage.finishBooking(APPT_BOOKEE_NAME, APPT_BOOKEE_EMAIL);
52+
53+
// now we have an availble booking time slot selected, and bookee details provided, we can click the book button
54+
await bookingPage.scrollIntoView(bookingPage.bookApptBtn);
55+
await bookingPage.bookApptBtn.click();
56+
57+
// verify booking request sent pop-up
58+
await expect(bookingPage.bookingConfirmedTitleText.first()).toBeVisible({ timeout: TIMEOUT_60_SECONDS });
59+
await bookingPage.bookingConfirmedTitleText.scrollIntoViewIfNeeded();
60+
61+
// booking request sent dialog should display the correct time slot that was requested
62+
// our requested time slot is stored in this format, as example: 'event-2025-01-14 14:30'
63+
// the dialog reports the slot in this format, as example: 'January 14, 2025' with start time
64+
// on next line; convert our selected slot value to same format as displayed so we can verify
65+
const expDateStr = await bookingPage.getDateFromSlotString(selectedSlot);
66+
const expTimeStr = await bookingPage.getTimeFromSlotString(selectedSlot);
67+
68+
// now verify the correct date/time is dispalyed on the booking request sent pop-up
69+
await bookingPage.verifyRequestedSlotTextDisplayed(expDateStr, expTimeStr);
70+
71+
// now verify a corresponding pending booking was created on the host account's list of pending bookings
72+
// wait N seconds for the appointment dashboard to update, sometimes the test is so fast when it
73+
// switches back to the dashboard the new pending appointment hasn't been added/displayed yet
74+
await page.waitForTimeout(TIMEOUT_10_SECONDS);
75+
await ensureWeAreSignedIn(page);
76+
await dashboardPage.verifyEventCreated(expDateStr, expTimeStr);
77+
78+
// also go back to main dashboard and check that pending requests link now appears
79+
await dashboardPage.gotoToDashboardMonthView();
80+
await expect(dashboardPage.pendingBookingRequestsLink).toBeVisible();
81+
82+
// click the pending requests link and verify it navigates to the correct URL
83+
await dashboardPage.pendingBookingRequestsLink.click();
84+
await page.waitForTimeout(TIMEOUT_3_SECONDS);
85+
await expect(page).toHaveURL(/.*\/bookings\?unconfirmed=true/);
86+
});
87+
});

test/e2e/tests/mobile/settings-account.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ test.describe('account settings on mobile browser', {
2828
settingsPage = new SettingsPage(page, testInfo.project.name); // i.e. 'ios-safari'
2929
dashboardPage = new DashboardPage(page);
3030
availabilityPage = new AvailabilityPage(page);
31-
bookApptPage = new BookingPage(page);
31+
bookApptPage = new BookingPage(page, testInfo.project.name); // i.e. 'ios-safari'
3232

3333
// mobile browsers don't support saving auth storage state so must sign in before each test
3434
// send in the playright test project name i.e. safari-ios because some mobile platforms differ

0 commit comments

Comments
 (0)