Skip to content

Commit 660e5a8

Browse files
authored
Fix production sanity test end-of-month issues (#846)
1 parent 4677448 commit 660e5a8

File tree

4 files changed

+66
-43
lines changed

4 files changed

+66
-43
lines changed

test/e2e/const/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const APPT_PROD_URL = String(process.env.APPT_PROD_URL);
33
export const APPT_PROD_MY_SHARE_LINK = String(process.env.APPT_PROD_MY_SHARE_LINK);
44
export const APPT_PROD_SHORT_SHARE_LINK_PREFIX = String(process.env.APPT_PROD_SHORT_SHARE_LINK_PREFIX);
55
export const APPT_PROD_LONG_SHARE_LINK_PREFIX = String(process.env.APPT_PROD_LONG_SHARE_LINK_PREFIX);
6+
export const APPT_PROD_PENDING_BOOKINGS_PAGE = `${process.env.APPT_PROD_URL}bookings/pending`;
67

78
// page titles
89
export const APPT_PAGE_TITLE = 'Thunderbird Appointment';

test/e2e/pages/dashboard-page.ts

Lines changed: 34 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { expect, type Page, type Locator } from '@playwright/test';
2+
import { APPT_PROD_PENDING_BOOKINGS_PAGE } from '../const/constants';
3+
24

35
export class DashboardPage {
46
readonly page: Page;
@@ -7,6 +9,10 @@ export class DashboardPage {
79
readonly logOutMenuItem: Locator;
810
readonly shareMyLink: Locator;
911
readonly nextMonthArrow: Locator;
12+
readonly pendingBookingsPageHeader: Locator;
13+
readonly pendingBookingsFilterSelect: Locator;
14+
readonly allFutureBookingsOptionText: string = 'All future bookings';
15+
readonly apptsFilterInput: Locator;
1016

1117
constructor(page: Page) {
1218
this.page = page;
@@ -15,56 +21,47 @@ export class DashboardPage {
1521
this.logOutMenuItem = this.page.getByTestId('user-nav-logout-menu');
1622
this.shareMyLink = this.page.getByTestId('dashboard-share-quick-link-btn');
1723
this.nextMonthArrow = this.page.locator('[data-icon="chevron-right"]');
24+
this.pendingBookingsPageHeader = this.page.getByText('Bookings');
25+
this.pendingBookingsFilterSelect = this.page.getByTestId('bookings-filter-select');
26+
this. apptsFilterInput = this.page.getByPlaceholder('Search bookings');
1827
}
1928

2029
/**
21-
* With the booking page week view already displayed, go forward to the next week.
30+
* Navigate to the pending bookings page and display all future pending bookings
2231
*/
23-
async goForwardOneMonth() {
24-
console.log('skipping ahead to the next calendar month');
25-
await this.nextMonthArrow.click();
32+
async gotoPendingBookings() {
33+
await this.page.goto(APPT_PROD_PENDING_BOOKINGS_PAGE);
2634
await this.page.waitForLoadState('domcontentloaded');
27-
await expect(this.shareMyLink).toBeVisible({ timeout: 30_000 });
35+
// ensure all future bookings are displayed
36+
await this.pendingBookingsFilterSelect.selectOption(this.allFutureBookingsOptionText, { timeout: 60_000 });
2837
}
2938

39+
/**
40+
* With pending bookings list displayed, enter a filter string to narrow down the list
41+
*/
42+
async filterPendingBookings(filterString: string) {
43+
await this.apptsFilterInput.fill(filterString);
44+
}
45+
3046
/**
3147
* Given a requested booking's time slot reference, verify that a corresponding hold event
32-
* was created in the host calendar dashboard month view.
33-
* @param requestedBookingTimeSlotRef String containing the requested booking time slot ref
34-
* taken from the DOM on the share link page at the time when the slot was chosen. Will be in
35-
* the format of: 'event-2025-01-14 14:30'.
48+
* exists in the host account's list of future pending bookings
3649
* @param hostUserDisplayName String containing the host account's user display name
3750
* @param requsterName String containing the name of the requester (provided at booking request)
51+
* @param slotDate String containing date of the requested slot (format e.g: 'February 7, 2025')
52+
* @param slotTime String containg the time of the requested slot (format e.g: '03:30 PM')
3853
*/
39-
async verifyHoldEventCreated(requestedBookingTimeSlotRef: string, hostUserDisplayName: string, requsterName: string) {
40-
// on the host calendar view, hold appointment dom elements contain ids that look like this:
41-
// 'calendar-month__event-HOLD: Appointment - tbautomation1 and Automated-Test-Bot2025-01-09'
42-
// in this case 'tbautomation1' is the appointment host user who shares the booking link; and
43-
// `Automated-Test-Bot` is the booking requester's name. First build a search string to match.
44-
const eventDate = requestedBookingTimeSlotRef.substring(6, 16); // i.e. '2025-01-14'
45-
const eventSearchId = `calendar-month__event-HOLD: Appointment - ${hostUserDisplayName} and ${requsterName}${eventDate}`;
46-
console.log(`searching for calendar event with dom element id: ${eventSearchId}`);
47-
48-
// todo: the hold event dom element id only contains the event date and not time slot so if we
49-
// search we will get all events on that date for the given users but won't be able to tell if
50-
// hold event was created for the correct time slot; for now just ensure at lest one hold event
51-
// was created on the booking request date, but update this later (see github issue #820).
54+
async verifyHoldEventCreated(hostUserDisplayName: string, requsterName: string, slotDate: string, slotTime: string) {
55+
// switch to the 'bookings' tab and display all future pending bookings; use the URL instead of UI
56+
await this.gotoPendingBookings();
5257

53-
// check if the hold event is found on current month view
54-
const holdEvent: Locator = this.page.locator(`id=${eventSearchId}`);
55-
const firstHoldEvent: Locator = holdEvent.first();
56-
var eventText = await firstHoldEvent.innerText();
57-
if (!eventText) {
58-
// hold event not found in current month view so skip ahead to the next month and check again
59-
console.log('no matching hold event found in current month');
60-
await this.goForwardOneMonth();
61-
eventText = await firstHoldEvent.innerText();
62-
expect(eventText.length, 'matching hold event was not found on host calendar!').toBeGreaterThan(4);
63-
}
58+
// with all future pending bookings now displayed, filter by appointments for our host user and requester
59+
const eventFilter = `HOLD: Appointment - ${hostUserDisplayName} and ${requsterName}`;
60+
await this.filterPendingBookings(eventFilter);
6461

65-
// at least one matching hold event found
66-
console.log(`matching hold event found, has text: ${eventText}`);
67-
const expHoldEventText = `HOLD: Appointment - ${hostUserDisplayName} and ${requsterName}`;
68-
expect(eventText).toContain(expHoldEventText);
62+
// now we have a list of future pending appointments for our host and requster; now ensure one
63+
// of them matches the slot that was selected by the test
64+
const apptLocator = this.page.getByRole('cell', { name: `${slotDate}` }).locator('div', { hasText: `${slotTime} to`});
65+
await expect(apptLocator).toBeVisible( { timeout: 60_000 });
6966
}
7067
}

test/e2e/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default defineConfig({
1818
/* Fail the build on CI if you accidentally left test.only in the source code. */
1919
forbidOnly: !!process.env.CI,
2020
/* Retry on CI only */
21-
retries: process.env.CI ? 2 : 0,
21+
retries: process.env.CI ? 1 : 0,
2222
/* Opt out of parallel tests on CI. */
2323
workers: process.env.CI ? 1 : 1, // actualy don't run in parallel locally either, for now
2424
// Tests will timeout if still running after this time (ms)

test/e2e/tests/book-appointment.spec.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,35 @@ const verifyBookingPageLoaded = async () => {
1414
await expect(bookingPage.invitingText).toBeVisible();
1515
await expect(bookingPage.invitingText).toContainText(PROD_DISPLAY_NAME);
1616
await expect(bookingPage.bookingCalendar).toBeVisible();
17-
// calendar header should contain current MMM YYYY
17+
18+
// verify calendar header
1819
const today: Date = new Date();
1920
const curMonth: string = today.toLocaleString('default', { month: 'short' });
2021
const curYear: string = String(today.getFullYear());
21-
await expect(bookingPage.calendarHeader).toHaveText(`${curMonth} ${curYear}`);
22-
// confirm button is disabled by default until a slot is selected
22+
23+
// by default you can only book slots 1-14 days in the future; if it's near the end of the
24+
// month then there's a chance there are no slots availble to be booked; the booking request
25+
// page always shows the month that has the first available time slot; so the month displayed
26+
// may be the current month or one month in the future (and perhaps next year if January)
27+
28+
// the header may contain either the current month or the next month
29+
today.setMonth(today.getMonth() + 1, 1);
30+
const nextMonth = today.toLocaleString('default', { month: 'short' });
31+
const monthRegex = new RegExp(String.raw`${curMonth}|${nextMonth}`);
32+
await expect(bookingPage.calendarHeader).toContainText(monthRegex);
33+
34+
// if the current month is Dec then the displayed month might be Jan, which means in that case
35+
// the year value might be this year or next year; for other months it's safe to check for
36+
// the current year only because the test may book appointments in current or +1 month only
37+
if (curMonth == 'Dec') {
38+
const nextYear = curYear + 1;
39+
var yearRegex = new RegExp(String.raw`... ${curYear}|${nextYear}`);
40+
} else {
41+
var yearRegex = new RegExp(String.raw`... ${curYear}`);
42+
}
43+
await expect(bookingPage.calendarHeader).toContainText(yearRegex);
44+
45+
// also the confirm button is disabled by default until a slot is selected
2346
await expect(bookingPage.confirmBtn).toBeDisabled();
2447
}
2548

@@ -85,7 +108,9 @@ test.describe('book an appointment', {
85108
// navigate to and sign into appointment (host account whom we requested a booking with/owns the share link)
86109
await navigateToAppointmentProdAndSignIn(page);
87110

88-
// now verify a 'hold' now exists on the host calendar at the time slot that was requested
89-
await dashboardPage.verifyHoldEventCreated(selectedSlot, PROD_DISPLAY_NAME, APPT_BOOKING_REQUESTER_NAME);
111+
// now verify a corresponding pending booking was created on the host account's list of pending bookings
112+
// (drop the day of the week from our time slot string as this function just needs the month, day, and year)
113+
const expMonthDayYear = expDateStr.substring(expDateStr.indexOf(',') + 2);
114+
await dashboardPage.verifyHoldEventCreated(PROD_DISPLAY_NAME, APPT_BOOKING_REQUESTER_NAME, expMonthDayYear, expTimeStr);
90115
});
91116
});

0 commit comments

Comments
 (0)