From 611d68373327d8a0042343a6a5ebd6676ef1d893 Mon Sep 17 00:00:00 2001 From: rwood-moz Date: Tue, 17 Dec 2024 12:34:30 -0500 Subject: [PATCH] Expand prod sanity test --- .github/workflows/deploy-production.yml | 1 + .github/workflows/nightly-tests.yml | 1 + test/e2e/.env.example | 7 ++++ test/e2e/README.md | 8 +++-- test/e2e/browserstack.yml | 4 +++ test/e2e/const/constants.ts | 7 +++- test/e2e/pages/booking-page.ts | 24 +++++++++++++ test/e2e/pages/dashboard-page.ts | 3 +- test/e2e/tests/book-appointment.spec.ts | 45 +++++++++++++++++++++++++ test/e2e/tests/sign-in.spec.ts | 32 +++++++++--------- test/e2e/tests/splashscreen.spec.ts | 10 +++--- 11 files changed, 116 insertions(+), 26 deletions(-) create mode 100644 test/e2e/pages/booking-page.ts create mode 100644 test/e2e/tests/book-appointment.spec.ts diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index 9a220daf9..4131244cb 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -242,6 +242,7 @@ jobs: env: APPT_PROD_LOGIN_EMAIL: ${{ secrets.E2E_APPT_PROD_LOGIN_EMAIL }} APPT_PROD_LOGIN_PWORD: ${{ secrets.E2E_APPT_PROD_LOGIN_PASSWORD }} + CI: true steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml index eb7f02a9d..bd01193d3 100644 --- a/.github/workflows/nightly-tests.yml +++ b/.github/workflows/nightly-tests.yml @@ -22,6 +22,7 @@ jobs: env: APPT_PROD_LOGIN_EMAIL: ${{ secrets.E2E_APPT_PROD_LOGIN_EMAIL }} APPT_PROD_LOGIN_PWORD: ${{ secrets.E2E_APPT_PROD_LOGIN_PASSWORD }} + CI: false steps: - uses: actions/checkout@v4 diff --git a/test/e2e/.env.example b/test/e2e/.env.example index 5d35ca009..f7322d57c 100644 --- a/test/e2e/.env.example +++ b/test/e2e/.env.example @@ -6,3 +6,10 @@ APPT_PROD_URL=https://appointment.day/ # Production sign-in (FxA) credentials APPT_PROD_LOGIN_EMAIL= APPT_PROD_LOGIN_PWORD= + +# Appointment user display name (settings => account) for above user +APPT_PROD_ACCT_DISPLAY_NAME= + +# Production booking share links for the existing user above +APPT_PROD_SHARE_LINK_SHORT= +APPT_PROD_SHARE_LINK_LONG= diff --git a/test/e2e/README.md b/test/e2e/README.md index b15d09ef8..90d692744 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -23,17 +23,20 @@ npx playwright install ## Running Locally -The E2E tests require credentials for an existing Appointment (FxA) account and reads these from your local env vars. First copy over the provided `.example.env` to a local `.env`: +The E2E tests require credentials for an existing Appointment (FxA) account and reads these from your local env vars. +The tests also require the Appointment account display name associated with those given user credentials. The Display name +is found in Appointment => Settings => Account => Display name. First copy over the provided `.example.env` to a local `.env`: ```bash cd test/e2e cp .env.example .env ``` -Then edit your local `.env` file and provide the credentials for your Appointment test account: +Then edit your local `.env` file and provide the following values: ```dotenv APPT_PROD_LOGIN_EMAIL= APPT_PROD_LOGIN_PWORD= +APPT_PROD_ACCT_DISPLAY_NAME= ``` To run the production sanity test headless (still in `test/e2e`): @@ -65,6 +68,7 @@ Once you have credentials for an existing Appointemnt test account, edit your lo ```dotenv APPT_PROD_LOGIN_EMAIL= APPT_PROD_LOGIN_PWORD= +APPT_PROD_ACCT_DISPLAY_NAME= ``` Also in order to run on BrowserStack you need to provide your BrowserStack credentials. Sign into your BrowserStack account and navigate to your `User Profile` and find your auth username and access key. In your local terminal export the following env vars to set the BrowserStack credentials that the tests will use: diff --git a/test/e2e/browserstack.yml b/test/e2e/browserstack.yml index a9e3199de..c37dba25e 100644 --- a/test/e2e/browserstack.yml +++ b/test/e2e/browserstack.yml @@ -29,10 +29,14 @@ platforms: osVersion: Sequoia browserName: playwright-firefox browserVersion: latest + playwrightConfigOptions: + name: Firefox-OSX - os: OS X osVersion: Sequoia browserName: playwright-chromium browserVersion: latest + playwrightConfigOptions: + name: Chromium-OSX # ======================= # Parallels per Platform diff --git a/test/e2e/const/constants.ts b/test/e2e/const/constants.ts index 9f6e1a688..2f792a272 100644 --- a/test/e2e/const/constants.ts +++ b/test/e2e/const/constants.ts @@ -1,10 +1,15 @@ // appointment urls export const APPT_PROD_URL = process.env.APPT_PROD_URL; +export const PROD_SHARE_LINK_SHORT = process.env.APPT_PROD_SHARE_LINK_SHORT; +export const PROD_SHARE_LINK_LONG = process.env.APPT_PROD_SHARE_LINK_LONG; // page titles export const APPT_PAGE_TITLE = 'Thunderbird Appointment'; export const FXA_PAGE_TITLE = 'Mozilla accounts'; -// production sign-in credentials +// production sign-in credentials and corresponding account display name export const PROD_LOGIN_EMAIL = process.env.APPT_PROD_LOGIN_EMAIL; export const PROD_LOGIN_PWORD = process.env.APPT_PROD_LOGIN_PWORD; + +// appointment user display name (settings => account) for above user +export const PROD_ACCT_DISPLAY_NAME = process.env.APPT_PROD_ACCT_DISPLAY_NAME; diff --git a/test/e2e/pages/booking-page.ts b/test/e2e/pages/booking-page.ts new file mode 100644 index 000000000..7c142ebb6 --- /dev/null +++ b/test/e2e/pages/booking-page.ts @@ -0,0 +1,24 @@ +import { type Page, type Locator } from '@playwright/test'; + +export class BookingPage { + readonly page: Page; + readonly titleText: Locator; + readonly invitingText: Locator; + readonly confirmButton: Locator; + readonly bookingCalendar: Locator; + readonly calendarHeader: Locator; + + constructor(page: Page) { + this.page = page; + this.titleText = page.getByTestId('booking-view-title-text'); + this.invitingText = page.getByTestId('booking-view-inviting-you-text'); + this.bookingCalendar = page.getByTestId('booking-view-calendar-div'); + this.confirmButton = page.getByTestId('booking-view-confirm-selection-button'); + this.calendarHeader = page.locator('.calendar-header__period-name'); + } + + async gotoBookingPage(bookingPageURL: string) { + await this.page.goto(bookingPageURL); + await this.page.waitForLoadState('domcontentloaded'); + } +} diff --git a/test/e2e/pages/dashboard-page.ts b/test/e2e/pages/dashboard-page.ts index 71f9ffaca..9e80e2d82 100644 --- a/test/e2e/pages/dashboard-page.ts +++ b/test/e2e/pages/dashboard-page.ts @@ -1,5 +1,4 @@ -import { expect, type Page, type Locator } from '@playwright/test'; -import exp from 'constants'; +import { type Page, type Locator } from '@playwright/test'; export class DashboardPage { readonly page: Page; diff --git a/test/e2e/tests/book-appointment.spec.ts b/test/e2e/tests/book-appointment.spec.ts new file mode 100644 index 000000000..3a68cd080 --- /dev/null +++ b/test/e2e/tests/book-appointment.spec.ts @@ -0,0 +1,45 @@ +import { test, expect } from '@playwright/test'; +import { BookingPage } from '../pages/booking-page'; +import { PROD_SHARE_LINK_SHORT, PROD_SHARE_LINK_LONG, PROD_ACCT_DISPLAY_NAME } from '../const/constants'; + +let bookingPage: BookingPage; + +// verify booking page loaded successfully +const verifyBookingPageLoaded = async () => { + await expect(bookingPage.titleText).toBeVisible({ timeout: 30_000 }); + await expect(bookingPage.titleText).toContainText(String(PROD_ACCT_DISPLAY_NAME)); + await expect(bookingPage.invitingText).toBeVisible(); + await expect(bookingPage.invitingText).toContainText(String(PROD_ACCT_DISPLAY_NAME)); + await expect(bookingPage.bookingCalendar).toBeVisible(); + // calendar header should contain current MMM YYYY + let today = new Date(); + let curMonth = today.toLocaleString('default', { month: 'short' }); + var curYear = String(today.getFullYear()); + await expect(bookingPage.calendarHeader).toHaveText(`${curMonth} ${curYear}`); + // confirm button is disabled by default until a slot is selected + await expect(bookingPage.confirmButton).toBeDisabled(); +} + +test.beforeEach(async ({ page }) => { + bookingPage = new BookingPage(page); + console.log(`*rw* CI env: ${process.env.CI}.`); +}); + +// verify we are able to book an appointment using existing user's share link +test.describe('book an appointment', { + tag: '@prod-sanity' +}, () => { + test('able to access booking page via short link', async ({ page }) => { + await bookingPage.gotoBookingPage(String(PROD_SHARE_LINK_SHORT)); + await verifyBookingPageLoaded(); + }); + + test('able to access booking page via long link', async ({ page }) => { + await bookingPage.gotoBookingPage(String(PROD_SHARE_LINK_LONG)); + await verifyBookingPageLoaded(); + }); + + test('able to request a booking', async ({ page }) => { + await bookingPage.gotoBookingPage(String(PROD_SHARE_LINK_SHORT)); + }); +}); diff --git a/test/e2e/tests/sign-in.spec.ts b/test/e2e/tests/sign-in.spec.ts index 7526417f0..39516c67f 100644 --- a/test/e2e/tests/sign-in.spec.ts +++ b/test/e2e/tests/sign-in.spec.ts @@ -1,19 +1,19 @@ import { test, expect } from '@playwright/test'; import { SplashscreenPage } from '../pages/splashscreen-page'; import { FxAPage } from '../pages/fxa-page'; -import { FXA_PAGE_TITLE, APPT_PAGE_TITLE } from '../const/constants'; +import { APPT_PAGE_TITLE } from '../const/constants'; import { DashboardPage } from '../pages/dashboard-page'; -let splashscreen: SplashscreenPage; -let fxa_sign_in: FxAPage; -let dashboard_page: DashboardPage; +let splashscreenPage: SplashscreenPage; +let signInPage: FxAPage; +let dashboardPage: DashboardPage; test.beforeEach(async ({ page }) => { // navigate to the main appointment page (splashscreen) - splashscreen = new SplashscreenPage(page); - fxa_sign_in = new FxAPage(page); - dashboard_page = new DashboardPage(page); - await splashscreen.gotoProd(); + splashscreenPage = new SplashscreenPage(page); + signInPage = new FxAPage(page); + dashboardPage = new DashboardPage(page); + await splashscreenPage.gotoProd(); }); // verify we are able to sign-in @@ -21,19 +21,19 @@ test.describe('sign-in', { tag: '@prod-sanity' }, () => { test('able to sign-in', async ({ page }) => { - await splashscreen.getToFxA(); + await splashscreenPage.getToFxA(); - await expect(fxa_sign_in.signInHeaderText).toBeVisible({ timeout: 30_000 }); // generous time for fxa to appear - await expect(fxa_sign_in.userAvatar).toBeVisible({ timeout: 30_000}); - await expect(fxa_sign_in.signInButton).toBeVisible(); + await expect(signInPage.signInHeaderText).toBeVisible({ timeout: 30_000 }); // generous time for fxa to appear + await expect(signInPage.userAvatar).toBeVisible({ timeout: 30_000}); + await expect(signInPage.signInButton).toBeVisible(); - await fxa_sign_in.signIn(); + await signInPage.signIn(); await page.waitForLoadState('domcontentloaded'); await expect(page).toHaveTitle(APPT_PAGE_TITLE, { timeout: 30_000 }); // give generous time for fxa sign-in - await expect(dashboard_page.userMenuAvatar).toBeVisible({ timeout: 30_000 }); - await expect(dashboard_page.navBarDashboardBtn).toBeVisible({ timeout: 30_000 }); - await expect(dashboard_page.shareMyLink).toBeVisible({ timeout: 30_000 }); + await expect(dashboardPage.userMenuAvatar).toBeVisible({ timeout: 30_000 }); + await expect(dashboardPage.navBarDashboardBtn).toBeVisible({ timeout: 30_000 }); + await expect(dashboardPage.shareMyLink).toBeVisible({ timeout: 30_000 }); }); }); diff --git a/test/e2e/tests/splashscreen.spec.ts b/test/e2e/tests/splashscreen.spec.ts index 010e2fd4b..0c05b9c7d 100644 --- a/test/e2e/tests/splashscreen.spec.ts +++ b/test/e2e/tests/splashscreen.spec.ts @@ -2,12 +2,12 @@ import { test, expect } from '@playwright/test'; import { SplashscreenPage } from '../pages/splashscreen-page'; import { APPT_PAGE_TITLE } from '../const/constants'; -let splashscreen: SplashscreenPage; +let splashscreenPage: SplashscreenPage; test.beforeEach(async ({ page }) => { // navigate to the main appointment page (splashscreen) - splashscreen = new SplashscreenPage(page); - await splashscreen.gotoProd(); + splashscreenPage = new SplashscreenPage(page); + await splashscreenPage.gotoProd(); }); // verify main appointment splash screen appears correctly @@ -16,7 +16,7 @@ test.describe('splash screen', { }, () => { test('appears correctly', async ({ page }) => { await expect(page).toHaveTitle(APPT_PAGE_TITLE); - await expect(splashscreen.loginBtn).toBeVisible(); - await expect(splashscreen.signUpBetaBtn).toBeVisible(); + await expect(splashscreenPage.loginBtn).toBeVisible(); + await expect(splashscreenPage.signUpBetaBtn).toBeVisible(); }); });