Sets up the core infrastructure for Playwright-based automated testing, including: - Docker support for consistent environments - Azure Pipelines configuration for CI/CD - Page Object Model structure for maintainable tests - Test data, validation, and rule engine implementation - Test configuration for alerts and other scenarios
166 lines
No EOL
7.5 KiB
TypeScript
166 lines
No EOL
7.5 KiB
TypeScript
import { expect, type Locator, type Page } from '@playwright/test';
|
|
import { BasePage } from './BasePage';
|
|
import { IAppointmentDetails } from '@business-logic/types/CustomerDetails';
|
|
import { AppointmentType } from '@business-logic/types/Enums';
|
|
import { AddressForm } from './forms/AddressForm';
|
|
import { faker } from '@faker-js/faker';
|
|
import { step } from '@business-logic/types/Step';
|
|
import { ITestData } from '@business-logic/types/ITestData';
|
|
|
|
export class ServiceLocationPage extends BasePage {
|
|
readonly page: Page;
|
|
|
|
readonly addressForm: AddressForm;
|
|
|
|
// Initial selection
|
|
readonly inShopButton: Locator;
|
|
readonly mobileButton: Locator;
|
|
readonly dropOffButton: Locator;
|
|
readonly RecalWarningMessage1: Locator;
|
|
readonly RecalWarningMessage2: Locator;
|
|
readonly militaryWarningMessage: Locator;
|
|
|
|
// For in-shop and drop off
|
|
readonly selectAShopOptions: Locator;
|
|
readonly firstAppointmentButton: Locator;
|
|
readonly changeZipButton: Locator;
|
|
readonly updateZipTextBox: Locator;
|
|
readonly saveZipButton: Locator;
|
|
|
|
// For mobile
|
|
readonly enterServiceAddressButton: Locator;
|
|
readonly serviceAddressTextBox: Locator;
|
|
readonly aptNumberTextBox: Locator;
|
|
readonly cityTextBox: Locator;
|
|
readonly stateDropDown: Locator;
|
|
readonly zipCodeTextBox: Locator;
|
|
readonly vehicleProtectedYesButton: Locator;
|
|
readonly vehicleProtectedNoButton: Locator;
|
|
readonly saveAddressButton: Locator;
|
|
readonly repeatedClicksModalCloseButton: Locator;
|
|
|
|
url = process.env['BASE_URL']! + '/fmg/?fmgPage=service-location';
|
|
|
|
constructor(page: Page) {
|
|
super(page);
|
|
this.page = page;
|
|
|
|
this.addressForm = new AddressForm(page);
|
|
|
|
// Initial selection
|
|
this.inShopButton = this.page.getByText(/In-shop/);
|
|
this.mobileButton = this.page.getByText(/Mobile/);
|
|
this.dropOffButton = this.page.getByText(/Drop-off/);
|
|
this.RecalWarningMessage1 = this.page.getByText(/We're not able to provide mobile service/);
|
|
this.RecalWarningMessage2 = this.page.getByText(/advanced safety system recalibration needs to be done in our shop./);
|
|
this.militaryWarningMessage = this.page.locator('[class*="widget-name-AlertMilitaryBaseZipWidget"]');
|
|
|
|
// For in-shop and drop off
|
|
this.selectAShopOptions = this.page.locator('[class="shop-question"]');
|
|
this.firstAppointmentButton = this.page.locator('fieldset:has(legend#chooseShop)').locator('label').first();
|
|
this.changeZipButton = this.page.locator('a:has(span.sr-only:has-text("edit zip code"))');
|
|
this.updateZipTextBox = this.page.locator('#serviceZipCode');
|
|
this.saveZipButton = this.page.getByText('Save ZIP code', { exact: true });
|
|
|
|
|
|
// For mobile
|
|
this.enterServiceAddressButton = this.page.getByRole('link', { name: 'Enter your service address' });
|
|
this.serviceAddressTextBox = this.page.getByRole('textbox', { name: 'Street Address' });
|
|
this.aptNumberTextBox = this.page.getByRole('textbox', { name: 'Apt. number'});
|
|
this.cityTextBox = this.page.getByRole('textbox', { name: 'City' });
|
|
this.stateDropDown = this.page.getByRole('combobox', { name: 'State' });
|
|
this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'Zip code' });
|
|
this.vehicleProtectedYesButton = this.page.locator('label').filter({ hasText: 'Yes' }).locator('div');
|
|
this.vehicleProtectedNoButton = this.page.locator('label').filter({ hasText: 'No' }).locator('div');
|
|
this.saveAddressButton = this.page.getByRole('button', { name: 'Continue' })
|
|
this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']');
|
|
}
|
|
|
|
async selectLocation(appointmentDetails: IAppointmentDetails){
|
|
|
|
switch(appointmentDetails.serviceLocation) {
|
|
case AppointmentType.Mobile:
|
|
await this.scheduleMobile(appointmentDetails);
|
|
break;
|
|
case AppointmentType.InShop:
|
|
await this.scheduleInShop(appointmentDetails);
|
|
break;
|
|
case AppointmentType.DropOff:
|
|
await this.scheduleDropOff(appointmentDetails);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
async scheduleInShop(appointmentDetails?: IAppointmentDetails) {
|
|
await this.inShopButton.click();
|
|
if (appointmentDetails && appointmentDetails.shopAddress) {
|
|
const zipCodeMatch = appointmentDetails.shopAddress.match(/\b\d{5}$/);
|
|
if (zipCodeMatch) {
|
|
const zipCode = zipCodeMatch[0];
|
|
// Enter the ZIP code into the updateZipTextBox
|
|
await this.changeZipButton.click();
|
|
await this.page.waitForTimeout(500);
|
|
await this.updateZipTextBox.fill(zipCode);
|
|
await this.saveZipButton.click();
|
|
}
|
|
await this.inShopButton.click();
|
|
await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).scrollIntoViewIfNeeded().then(() => this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).click());
|
|
} else {
|
|
await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => {
|
|
await this.firstAppointmentButton.click();
|
|
if (appointmentDetails) {
|
|
appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async scheduleMobile(appointmentDetails: IAppointmentDetails){
|
|
if (appointmentDetails.serviceAddress) {
|
|
await this.mobileButton.click();
|
|
await this.enterServiceAddressButton.click();
|
|
await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! });
|
|
if (await this.repeatedClicksModalCloseButton.isVisible()) {
|
|
await this.repeatedClicksModalCloseButton.click();
|
|
}
|
|
if (faker.datatype.boolean()) {
|
|
await this.vehicleProtectedYesButton.check();
|
|
} else {
|
|
await this.vehicleProtectedNoButton.check();
|
|
}
|
|
await this.saveAddressButton.click();
|
|
} else {
|
|
console.error('ServiceLocationPage >> Please supply an address')
|
|
}
|
|
}
|
|
|
|
async scheduleDropOff(appointmentDetails?: IAppointmentDetails){
|
|
await this.dropOffButton.click();
|
|
if (appointmentDetails && appointmentDetails.shopAddress) {
|
|
await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check();
|
|
} else {
|
|
// await this.firstAppointmentButton.click();
|
|
// await this.clickWithRetry(this.firstAppointmentButton, this.page);
|
|
await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => {
|
|
await this.firstAppointmentButton.click();
|
|
if (appointmentDetails) {
|
|
appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async validateRecalWarning(){
|
|
await expect(this.RecalWarningMessage1).toBeVisible();
|
|
await expect(this.RecalWarningMessage2).toBeVisible();
|
|
|
|
}
|
|
|
|
@step("ServiceLocationPage >> Select service location: ")
|
|
async handleServiceLocationPage(testData: Partial<ITestData>) {
|
|
const { appointmentDetails } = testData;
|
|
await this.selectLocation(appointmentDetails!);
|
|
await this.nextPage();
|
|
}
|
|
} |