Skip to content

Commit

Permalink
Fix production sanity test end-of-month issues (#846)
Browse files Browse the repository at this point in the history
  • Loading branch information
rwood-moz authored Feb 4, 2025
1 parent 4677448 commit 660e5a8
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 43 deletions.
1 change: 1 addition & 0 deletions test/e2e/const/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const APPT_PROD_URL = String(process.env.APPT_PROD_URL);
export const APPT_PROD_MY_SHARE_LINK = String(process.env.APPT_PROD_MY_SHARE_LINK);
export const APPT_PROD_SHORT_SHARE_LINK_PREFIX = String(process.env.APPT_PROD_SHORT_SHARE_LINK_PREFIX);
export const APPT_PROD_LONG_SHARE_LINK_PREFIX = String(process.env.APPT_PROD_LONG_SHARE_LINK_PREFIX);
export const APPT_PROD_PENDING_BOOKINGS_PAGE = `${process.env.APPT_PROD_URL}bookings/pending`;

// page titles
export const APPT_PAGE_TITLE = 'Thunderbird Appointment';
Expand Down
71 changes: 34 additions & 37 deletions test/e2e/pages/dashboard-page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { expect, type Page, type Locator } from '@playwright/test';
import { APPT_PROD_PENDING_BOOKINGS_PAGE } from '../const/constants';


export class DashboardPage {
readonly page: Page;
Expand All @@ -7,6 +9,10 @@ export class DashboardPage {
readonly logOutMenuItem: Locator;
readonly shareMyLink: Locator;
readonly nextMonthArrow: Locator;
readonly pendingBookingsPageHeader: Locator;
readonly pendingBookingsFilterSelect: Locator;
readonly allFutureBookingsOptionText: string = 'All future bookings';
readonly apptsFilterInput: Locator;

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

/**
* With the booking page week view already displayed, go forward to the next week.
* Navigate to the pending bookings page and display all future pending bookings
*/
async goForwardOneMonth() {
console.log('skipping ahead to the next calendar month');
await this.nextMonthArrow.click();
async gotoPendingBookings() {
await this.page.goto(APPT_PROD_PENDING_BOOKINGS_PAGE);
await this.page.waitForLoadState('domcontentloaded');
await expect(this.shareMyLink).toBeVisible({ timeout: 30_000 });
// ensure all future bookings are displayed
await this.pendingBookingsFilterSelect.selectOption(this.allFutureBookingsOptionText, { timeout: 60_000 });
}

/**
* With pending bookings list displayed, enter a filter string to narrow down the list
*/
async filterPendingBookings(filterString: string) {
await this.apptsFilterInput.fill(filterString);
}

/**
* Given a requested booking's time slot reference, verify that a corresponding hold event
* was created in the host calendar dashboard month view.
* @param requestedBookingTimeSlotRef String containing the requested booking time slot ref
* taken from the DOM on the share link page at the time when the slot was chosen. Will be in
* the format of: 'event-2025-01-14 14:30'.
* exists in the host account's list of future pending bookings
* @param hostUserDisplayName String containing the host account's user display name
* @param requsterName String containing the name of the requester (provided at booking request)
* @param slotDate String containing date of the requested slot (format e.g: 'February 7, 2025')
* @param slotTime String containg the time of the requested slot (format e.g: '03:30 PM')
*/
async verifyHoldEventCreated(requestedBookingTimeSlotRef: string, hostUserDisplayName: string, requsterName: string) {
// on the host calendar view, hold appointment dom elements contain ids that look like this:
// 'calendar-month__event-HOLD: Appointment - tbautomation1 and Automated-Test-Bot2025-01-09'
// in this case 'tbautomation1' is the appointment host user who shares the booking link; and
// `Automated-Test-Bot` is the booking requester's name. First build a search string to match.
const eventDate = requestedBookingTimeSlotRef.substring(6, 16); // i.e. '2025-01-14'
const eventSearchId = `calendar-month__event-HOLD: Appointment - ${hostUserDisplayName} and ${requsterName}${eventDate}`;
console.log(`searching for calendar event with dom element id: ${eventSearchId}`);

// todo: the hold event dom element id only contains the event date and not time slot so if we
// search we will get all events on that date for the given users but won't be able to tell if
// hold event was created for the correct time slot; for now just ensure at lest one hold event
// was created on the booking request date, but update this later (see github issue #820).
async verifyHoldEventCreated(hostUserDisplayName: string, requsterName: string, slotDate: string, slotTime: string) {
// switch to the 'bookings' tab and display all future pending bookings; use the URL instead of UI
await this.gotoPendingBookings();

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

// at least one matching hold event found
console.log(`matching hold event found, has text: ${eventText}`);
const expHoldEventText = `HOLD: Appointment - ${hostUserDisplayName} and ${requsterName}`;
expect(eventText).toContain(expHoldEventText);
// now we have a list of future pending appointments for our host and requster; now ensure one
// of them matches the slot that was selected by the test
const apptLocator = this.page.getByRole('cell', { name: `${slotDate}` }).locator('div', { hasText: `${slotTime} to`});
await expect(apptLocator).toBeVisible( { timeout: 60_000 });
}
}
2 changes: 1 addition & 1 deletion test/e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default defineConfig({
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
retries: process.env.CI ? 1 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : 1, // actualy don't run in parallel locally either, for now
// Tests will timeout if still running after this time (ms)
Expand Down
35 changes: 30 additions & 5 deletions test/e2e/tests/book-appointment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,35 @@ const verifyBookingPageLoaded = async () => {
await expect(bookingPage.invitingText).toBeVisible();
await expect(bookingPage.invitingText).toContainText(PROD_DISPLAY_NAME);
await expect(bookingPage.bookingCalendar).toBeVisible();
// calendar header should contain current MMM YYYY

// verify calendar header
const today: Date = new Date();
const curMonth: string = today.toLocaleString('default', { month: 'short' });
const curYear: string = String(today.getFullYear());
await expect(bookingPage.calendarHeader).toHaveText(`${curMonth} ${curYear}`);
// confirm button is disabled by default until a slot is selected

// by default you can only book slots 1-14 days in the future; if it's near the end of the
// month then there's a chance there are no slots availble to be booked; the booking request
// page always shows the month that has the first available time slot; so the month displayed
// may be the current month or one month in the future (and perhaps next year if January)

// the header may contain either the current month or the next month
today.setMonth(today.getMonth() + 1, 1);
const nextMonth = today.toLocaleString('default', { month: 'short' });
const monthRegex = new RegExp(String.raw`${curMonth}|${nextMonth}`);
await expect(bookingPage.calendarHeader).toContainText(monthRegex);

// if the current month is Dec then the displayed month might be Jan, which means in that case
// the year value might be this year or next year; for other months it's safe to check for
// the current year only because the test may book appointments in current or +1 month only
if (curMonth == 'Dec') {
const nextYear = curYear + 1;
var yearRegex = new RegExp(String.raw`... ${curYear}|${nextYear}`);
} else {
var yearRegex = new RegExp(String.raw`... ${curYear}`);
}
await expect(bookingPage.calendarHeader).toContainText(yearRegex);

// also the confirm button is disabled by default until a slot is selected
await expect(bookingPage.confirmBtn).toBeDisabled();
}

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

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

0 comments on commit 660e5a8

Please sign in to comment.