Skip to content

Commit ec388ec

Browse files
authored
Update and add E2E tests for new availability UI (#1209)
1 parent 2d9fc63 commit ec388ec

10 files changed

+434
-84
lines changed

test/e2e/const/constants.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ export const APPT_LONG_SHARE_LINK_PREFIX = String(process.env.APPT_LONG_SHARE_LI
99
export const APPT_PENDING_BOOKINGS_PAGE = String(`${process.env.APPT_URL}bookings?filters=pending&unconfirmed=true`);
1010
export const APPT_BOOKED_BOOKINGS_PAGE = String(`${process.env.APPT_URL}bookings?filters=confirmed`);
1111
export const APPT_SETTINGS_PAGE = String(`${process.env.APPT_URL}settings`);
12-
export const APPT_SETTINGS_ACCOUNT = String(`${process.env.APPT_URL}settings#accountSettings`);
13-
export const APPT_SETTINGS_PREFERENCES = String(`${process.env.APPT_URL}settings#preferences`);
14-
export const APPT_SETTINGS_CONNECTED_APPS = String(`${process.env.APPT_URL}settings#connectedApplications`);
1512
export const APPT_DASHBOARD_HOME_PAGE = String(`${process.env.APPT_URL}dashboard`);
1613
export const APPT_DASHBOARD_MONTH_PAGE = String(`${process.env.APPT_URL}dashboard#month`);
1714
export const APPT_AVAILABILITY_PAGE = String(`${process.env.APPT_URL}availability`);

test/e2e/pages/availability-page.ts

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,25 @@ import {
44
APPT_AVAILABILITY_PAGE,
55
APPT_PENDING_BOOKINGS_PAGE,
66
APPT_BOOKED_BOOKINGS_PAGE,
7-
APPT_DASHBOARD_MONTH_PAGE,
87
TIMEOUT_1_SECOND,
8+
TIMEOUT_2_SECONDS,
99
TIMEOUT_3_SECONDS,
10-
TIMEOUT_60_SECONDS,
10+
APPT_TIMEZONE_SETTING_PRIMARY,
1111
} from '../const/constants';
1212

1313

1414
export class AvailabilityPage {
1515
readonly page: Page;
16-
readonly allFutureBookingsOptionText: string = 'All future bookings';
16+
readonly saveChangesBtn: Locator;
17+
readonly savedSuccessfullyText: Locator;
18+
readonly revertChangesBtn: Locator;
1719
readonly setAvailabilityText: Locator;
20+
readonly bookableToggle: Locator;
21+
readonly bookableToggleContainer: Locator;
22+
readonly timeZoneText: Locator;
23+
readonly editTimeZoneBtn: Locator;
24+
readonly calendarSelect: Locator;
25+
readonly autoConfirmBookingsCheckBox: Locator;
1826
readonly customizePerDayCheckBox: Locator;
1927
readonly allStartTimeInput: Locator;
2028
readonly allEndTimeInput: Locator;
@@ -23,12 +31,35 @@ export class AvailabilityPage {
2331
readonly customStartTime3Input: Locator;
2432
readonly customStartTime4Input: Locator;
2533
readonly customStartTime5Input: Locator;
26-
readonly saveChangesBtn: Locator;
27-
readonly revertChangesBtn: Locator;
34+
readonly minNoticeInput: Locator;
35+
readonly bookingWindowInput: Locator;
36+
readonly bookingPageDetailsHdr: Locator;
37+
readonly bookingPageNameInput: Locator;
38+
readonly bookingPageDescInput: Locator;
39+
readonly bookingPageMtgLinkInput: Locator;
40+
readonly bookingPageMtgDur15MinRadio: Locator;
41+
readonly bookingPageMtgDur30MinRadio: Locator;
42+
readonly bookingPageLinkHdr: Locator;
43+
readonly refreshLinkBtn: Locator;
44+
readonly refreshLinkConfirmTxt: Locator;
45+
readonly refreshLinkConfirmCancelBtn: Locator;
46+
readonly shareYourLinkInput: Locator;
47+
readonly shareLinkCopyBtn: Locator;
2848

2949
constructor(page: Page) {
3050
this.page = page;
51+
this.saveChangesBtn = this.page.getByRole('button', { name: 'Save', exact: true });
52+
this.savedSuccessfullyText = this.page.getByText('Availability saved successfully', { exact: true });
53+
this.revertChangesBtn = this.page.getByRole('button', { name: 'Revert changes', exact: true });
54+
55+
// set your availability section
3156
this.setAvailabilityText = this.page.getByText('Set Your Availability');
57+
this.bookableToggle = this.page.getByTestId('availability-set-availability-toggle');
58+
this.bookableToggleContainer = this.page.getByTitle('Activate schedule');
59+
this.timeZoneText = this.page.getByText(APPT_TIMEZONE_SETTING_PRIMARY);
60+
this.editTimeZoneBtn = this.page.getByRole('button', { name: 'Edit' });
61+
this.calendarSelect = this.page.locator('select[name="calendar"]');
62+
this.autoConfirmBookingsCheckBox = this.page.getByTestId('availability-automatically-confirm-checkbox');
3263
this.customizePerDayCheckBox = this.page.getByRole('checkbox', { name: 'Set custom times for each day'});
3364
this.allStartTimeInput = this.page.locator('#start_time');
3465
this.allEndTimeInput = this.page.locator('#end_time');
@@ -37,16 +68,33 @@ export class AvailabilityPage {
3768
this.customStartTime3Input = this.page.getByTestId('availability-start-time-3-0-input');
3869
this.customStartTime4Input = this.page.getByTestId('availability-start-time-4-0-input');
3970
this.customStartTime5Input = this.page.getByTestId('availability-start-time-5-0-input');
40-
this.saveChangesBtn = this.page.getByTestId('availability-save-changes-btn');
41-
this.revertChangesBtn = this.page.getByRole('button', { name: 'Revert changes' });
71+
this.minNoticeInput = this.page.getByRole('radio', { name: 'instant' });
72+
this.bookingWindowInput = this.page.getByRole('radio', { name: '7 days' });
73+
74+
// booking page details section
75+
this.bookingPageDetailsHdr = this.page.getByRole('heading', { name: 'Booking Page Details' });
76+
this.bookingPageNameInput = this.page.locator('#pageName');
77+
this.bookingPageDescInput = this.page.locator('#pageDescription');
78+
this.bookingPageMtgLinkInput = this.page.locator('#virtualMeetingLink');
79+
this.bookingPageMtgDur15MinRadio = this.page.getByText('15 Min');
80+
this.bookingPageMtgDur30MinRadio = this.page.getByText('30 Min');
81+
82+
// booking page link section
83+
this.bookingPageLinkHdr = this.page.getByRole('heading', { name: 'Booking Page Link' });
84+
this.refreshLinkBtn = this.page.getByRole('button', { name: 'Refresh link' });
85+
this.refreshLinkConfirmTxt = this.page.getByText('Refresh link', { exact: true });
86+
this.refreshLinkConfirmCancelBtn = this.page.getByRole('button', { name: 'Cancel' });
87+
this.shareYourLinkInput = this.page.locator('#shareLink');
88+
this.shareLinkCopyBtn = this.page.getByRole('button', { name: 'Copy', exact: true });
4289
}
4390

4491
/**
45-
* Navigate to the pending bookings page and display all future pending bookings
92+
* Navigate to the availability page
4693
*/
4794
async gotoAvailabilityPage() {
48-
// go to bookings page and set filter in URL to show pending only
95+
// go to availability page, sometimes takes a bit to load all element values
4996
await this.page.goto(APPT_AVAILABILITY_PAGE);
97+
await this.page.waitForTimeout(TIMEOUT_2_SECONDS);
5098
}
5199

52100
/**

test/e2e/pages/booking-page.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export class BookingPage {
1818
readonly requestSentAvailabilityText: Locator;
1919
readonly requestSentCloseBtn: Locator;
2020
readonly eventBookedTitleText: Locator;
21+
readonly scheduleTurnedOffText: Locator;
22+
readonly bookApptPage7AMSlot: Locator;
23+
readonly bookApptPage630PMSlot: Locator;
24+
readonly bookApptPage15MinSlot: Locator;
2125

2226
constructor(page: Page) {
2327
this.page = page;
@@ -35,6 +39,10 @@ export class BookingPage {
3539
this.requestSentAvailabilityText = this.page.getByText("'s Availability");
3640
this.requestSentCloseBtn = this.page.getByRole('button', { name: 'Close' });
3741
this.eventBookedTitleText = this.page.getByText('Event booked!');
42+
this.scheduleTurnedOffText = this.page.getByText('The schedule has been turned off.');
43+
this.bookApptPage7AMSlot = this.page.getByText('07:00 AM - 07:30 AM', { exact: true }).first();
44+
this.bookApptPage630PMSlot = this.page.getByText('06:30 PM - 07:00 PM', { exact: true }).first();
45+
this.bookApptPage15MinSlot = this.page.getByText('09:15 AM - 09:30 AM', { exact: true }).first();
3846
}
3947

4048
/**
@@ -43,6 +51,7 @@ export class BookingPage {
4351
async gotoBookingPageShortUrl() {
4452
// the default share link is a short URL
4553
await this.page.goto(APPT_MY_SHARE_LINK);
54+
await expect(this.confirmBtn).toBeVisible({ timeout: TIMEOUT_30_SECONDS });
4655
}
4756

4857
/**
@@ -53,6 +62,7 @@ export class BookingPage {
5362
const prodShareLinkUser: string = APPT_MY_SHARE_LINK.split(APPT_SHORT_SHARE_LINK_PREFIX)[1];
5463
const longLink: string = `${APPT_LONG_SHARE_LINK_PREFIX}${prodShareLinkUser}`;
5564
await this.page.goto(longLink);
65+
await expect(this.confirmBtn).toBeVisible({ timeout: TIMEOUT_30_SECONDS });
5666
}
5767

5868
/**

test/e2e/pages/settings-page.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type Page, type Locator } from '@playwright/test';
1+
import { type Page, type Locator, expect } from '@playwright/test';
22

33
import {
44
APPT_SETTINGS_PAGE,
@@ -38,6 +38,8 @@ export class SettingsPage {
3838
readonly addGoogleBtn: Locator;
3939
readonly defaultCalendarConnectedCbox: Locator;
4040
readonly saveBtnEN: Locator;
41+
readonly savedSuccessfullyTextEN: Locator;
42+
readonly savedSuccessfullyTextDE: Locator;
4143
readonly saveBtnDE: Locator;
4244
readonly revertBtn: Locator;
4345

@@ -49,6 +51,8 @@ export class SettingsPage {
4951
this.settingsHeaderEN = this.page.getByRole('main').getByText('Settings', { exact: true });
5052
this.settingsHeaderDE = this.page.getByRole('heading', { name: 'Einstellungen' }).first();
5153
this.saveBtnEN = this.page.getByRole('button', { name: 'Save' });
54+
this.savedSuccessfullyTextEN = this.page.getByText('Settings saved successfully', { exact: true });
55+
this.savedSuccessfullyTextDE = this.page.getByText('Einstellungen erfolgreich gespeichert', { exact: true });
5256
this.saveBtnDE = this.page.getByRole('button', { name: 'Speichern' });
5357
this.revertBtn = this.page.getByRole('button', { name: 'Revert changes' });
5458

@@ -126,7 +130,8 @@ export class SettingsPage {
126130
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
127131
await this.saveBtnEN.scrollIntoViewIfNeeded();
128132
await this.saveBtnEN.click();
129-
await this.page.waitForTimeout(TIMEOUT_3_SECONDS); // give a few seconds to be applied
133+
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
134+
await expect(this.savedSuccessfullyTextEN).toBeVisible();
130135
}
131136

132137
/**
@@ -140,11 +145,14 @@ export class SettingsPage {
140145
if (currentLanguage == APPT_LANGUAGE_SETTING_EN) {
141146
await this.saveBtnEN.scrollIntoViewIfNeeded();
142147
await this.saveBtnEN.click();
148+
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
149+
await expect(this.savedSuccessfullyTextDE).toBeVisible();
143150
} else {
144151
await this.saveBtnDE.scrollIntoViewIfNeeded();
145152
await this.saveBtnDE.click();
153+
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
154+
await expect(this.savedSuccessfullyTextEN).toBeVisible();
146155
}
147-
await this.page.waitForTimeout(TIMEOUT_3_SECONDS);
148156
}
149157

150158
/**
@@ -157,7 +165,8 @@ export class SettingsPage {
157165
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
158166
await this.saveBtnEN.scrollIntoViewIfNeeded();
159167
await this.saveBtnEN.click();
160-
await this.page.waitForTimeout(TIMEOUT_3_SECONDS);
168+
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
169+
await expect(this.savedSuccessfullyTextEN).toBeVisible();
161170
}
162171

163172
/**
@@ -183,6 +192,7 @@ export class SettingsPage {
183192
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
184193
await this.saveBtnEN.scrollIntoViewIfNeeded();
185194
await this.saveBtnEN.click();
186-
await this.page.waitForTimeout(TIMEOUT_3_SECONDS); // give a few seconds to be applied
195+
await this.page.waitForTimeout(TIMEOUT_1_SECOND);
196+
await expect(this.savedSuccessfullyTextEN).toBeVisible();
187197
}
188198
}

test/e2e/tests/auth.setup.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { test as setup } from '@playwright/test';
1+
import { test as setup, expect } from '@playwright/test';
2+
import { AvailabilityPage } from '../pages/availability-page';
23
import path from 'path';
34

45
import { navigateToAppointmentAndSignIn, setDefaultUserSettingsLocalStore } from '../utils/utils';
56

67
import {
78
APPT_DASHBOARD_HOME_PAGE,
89
APPT_SETTINGS_PAGE,
10+
TIMEOUT_1_SECOND,
911
TIMEOUT_2_SECONDS,
1012
} from "../const/constants";
1113

@@ -50,4 +52,41 @@ setup('authenticate', async ({ page }) => {
5052

5153
// End of authentication steps.
5254
await page.context().storageState({ path: authFile });
55+
56+
// Now also ensure the test account is bookable (availability panel) before we start
57+
var changesMade = false;
58+
const availabilityPage = new AvailabilityPage(page);
59+
await availabilityPage.gotoAvailabilityPage();
60+
await availabilityPage.bookableToggleContainer.scrollIntoViewIfNeeded();
61+
if (! await availabilityPage.bookableToggle.isChecked()) {
62+
await availabilityPage.bookableToggleContainer.click();
63+
changesMade = true;
64+
}
65+
66+
// And ensure availability start time is 9am, end time 5pm
67+
await availabilityPage.allStartTimeInput.scrollIntoViewIfNeeded();
68+
if (await availabilityPage.allStartTimeInput.inputValue() != '09:00') {
69+
await availabilityPage.allStartTimeInput.fill('09:00');
70+
changesMade = true;
71+
}
72+
73+
if (await availabilityPage.allEndTimeInput.inputValue() != '17:00') {
74+
await availabilityPage.allEndTimeInput.fill('17:00');
75+
changesMade = true;
76+
}
77+
78+
// ensure booking page details meeting duration is 30 min
79+
if (! await availabilityPage.bookingPageMtgDur30MinRadio.isChecked()) {
80+
await availabilityPage.bookingPageMtgDur30MinRadio.scrollIntoViewIfNeeded();
81+
await availabilityPage.bookingPageMtgDur30MinRadio.click();
82+
await page.waitForTimeout(TIMEOUT_1_SECOND);
83+
changesMade = true;
84+
}
85+
86+
// if availability changes were made, save them
87+
if (changesMade) {
88+
await availabilityPage.saveChangesBtn.click();
89+
await page.waitForTimeout(TIMEOUT_1_SECOND);
90+
await expect(availabilityPage.savedSuccessfullyText).toBeVisible();
91+
}
5392
});
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { test, expect, mergeExpects } from '@playwright/test';
2+
import { AvailabilityPage } from '../pages/availability-page';
3+
import { BookingPage } from '../pages/booking-page';
4+
5+
import {
6+
PLAYWRIGHT_TAG_E2E_SUITE,
7+
PLAYWRIGHT_TAG_PROD_NIGHTLY,
8+
APPT_MY_SHARE_LINK,
9+
TIMEOUT_1_SECOND,
10+
TIMEOUT_3_SECONDS,
11+
} from '../const/constants';
12+
13+
let availabilityPage: AvailabilityPage;
14+
let bookApptPage: BookingPage;
15+
16+
test.describe('availability - booking page details', {
17+
tag: [PLAYWRIGHT_TAG_E2E_SUITE, PLAYWRIGHT_TAG_PROD_NIGHTLY],
18+
}, () => {
19+
test.beforeEach(async ({ page }) => {
20+
// note: we are already signed into Appointment with our default settings (via our auth-setup)
21+
// availability panel is displayed open as default
22+
bookApptPage = new BookingPage(page);
23+
availabilityPage = new AvailabilityPage(page);
24+
await availabilityPage.gotoAvailabilityPage();
25+
});
26+
27+
test('able to change booking details', async ({ page }) => {
28+
await availabilityPage.bookingPageDetailsHdr.scrollIntoViewIfNeeded();
29+
await expect(availabilityPage.bookingPageDetailsHdr).toBeVisible();
30+
31+
// change page name (use current date/time value so can verify was changed by this test)
32+
const origPageName = await availabilityPage.bookingPageNameInput.inputValue();
33+
const newPageName = `Page name modified by E2E test at ${Date.now()}`;
34+
await availabilityPage.bookingPageNameInput.fill(newPageName);
35+
36+
// add a page description (again use current date/time in there too so can verify was changed by this test)
37+
const origPageDesc = await availabilityPage.bookingPageDescInput.inputValue();
38+
const newPageDesc = `Page description modified by E2E test at ${Date.now()}`;
39+
await availabilityPage.bookingPageDescInput.fill(newPageDesc);
40+
41+
// able to type in a virtual meeting link
42+
await availabilityPage.bookingPageMtgLinkInput.scrollIntoViewIfNeeded();
43+
await availabilityPage.bookingPageMtgLinkInput.fill(`fake.meeting.link?id=${Date.now()}`);
44+
45+
// change the meeting duration to 15 min
46+
await availabilityPage.bookingPageMtgDur15MinRadio.scrollIntoViewIfNeeded();
47+
await availabilityPage.bookingPageMtgDur15MinRadio.click();
48+
49+
// save the changes
50+
await availabilityPage.saveChangesBtn.click();
51+
await page.waitForTimeout(TIMEOUT_1_SECOND);
52+
53+
// now go to book appointment page (share link) and verify the changes took effect
54+
// we use expect.soft here so that we ensure even on a failure the test will continue
55+
// so that the booking page details will be set back to what they were before
56+
await page.goto(APPT_MY_SHARE_LINK);
57+
await page.waitForTimeout(TIMEOUT_3_SECONDS);
58+
59+
// page name and description
60+
expect.soft(await bookApptPage.titleText.innerText()).toEqual(newPageName);
61+
const pageDescLocator = page.getByText(newPageDesc, { exact: true });
62+
await expect.soft(pageDescLocator).toBeVisible();
63+
64+
// verify a 15 min slot now exists
65+
await expect(bookApptPage.bookApptPage15MinSlot).toBeVisible();
66+
67+
// now go back to booking page settings and change back
68+
await availabilityPage.gotoAvailabilityPage();
69+
await page.waitForTimeout(TIMEOUT_1_SECOND);
70+
await availabilityPage.bookingPageNameInput.fill(origPageName);
71+
await availabilityPage.bookingPageDescInput.fill(origPageDesc);
72+
await availabilityPage.bookingPageMtgLinkInput.clear();
73+
await availabilityPage.bookingPageMtgDur30MinRadio.scrollIntoViewIfNeeded();
74+
await availabilityPage.bookingPageMtgDur30MinRadio.click();
75+
await availabilityPage.saveChangesBtn.click();
76+
await page.waitForTimeout(TIMEOUT_1_SECOND);
77+
});
78+
});

0 commit comments

Comments
 (0)