From 8b8a711849699041b1346e5decbb2c2b775c169b Mon Sep 17 00:00:00 2001 From: rwood-moz Date: Fri, 31 Jan 2025 11:02:55 -0500 Subject: [PATCH] Fix production sanity test end-of-month issues --- test/e2e/const/constants.ts | 1 + test/e2e/pages/dashboard-page.ts | 71 ++++++++++++------------- test/e2e/playwright.config.ts | 2 +- test/e2e/tests/book-appointment.spec.ts | 35 ++++++++++-- 4 files changed, 66 insertions(+), 43 deletions(-) diff --git a/test/e2e/const/constants.ts b/test/e2e/const/constants.ts index 452fbb30..e095a78b 100644 --- a/test/e2e/const/constants.ts +++ b/test/e2e/const/constants.ts @@ -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'; diff --git a/test/e2e/pages/dashboard-page.ts b/test/e2e/pages/dashboard-page.ts index 5877e5db..74859791 100644 --- a/test/e2e/pages/dashboard-page.ts +++ b/test/e2e/pages/dashboard-page.ts @@ -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; @@ -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; @@ -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 }); } } diff --git a/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index 4ad271a1..eec6369e 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -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) diff --git a/test/e2e/tests/book-appointment.spec.ts b/test/e2e/tests/book-appointment.spec.ts index f5e51841..f6bab443 100644 --- a/test/e2e/tests/book-appointment.spec.ts +++ b/test/e2e/tests/book-appointment.spec.ts @@ -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(); } @@ -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); }); });