import { ICustomerDetails } from 'safelite-playwright-core'; import { Soft } from 'safelite-playwright-core'; import { waitUntil } from 'safelite-playwright-core'; import test, { expect, type Locator, type Page } from '@playwright/test'; import { error } from 'console'; /** * Base class for all page objects. * Provides common functionality for interacting with pages. */ export class BasePage { // Common locators readonly page: Page; readonly continueButton: Locator; readonly backButton: Locator; readonly pageSpinner: Locator; readonly buttonLoadSpin: Locator; readonly hamburgerMenu: Locator; readonly progressBar: Locator; constructor(page: Page){ this.page = page; this.continueButton = page.locator('[id="infoBox"]').getByRole('button'); this.backButton = page.locator('[id="infoBox"]').getByRole('link'); this.pageSpinner = page.getByRole('status'); this.buttonLoadSpin = page.getByRole('alert'); this.hamburgerMenu = this.page.getByRole('button', { name: 'Hamburger Menu (modal window)' }); this.progressBar = this.page.locator('.progress-bar-inner'); } async nextPage() { await waitUntil(async () => {; return (await this.continueButton.getAttribute('aria-disabled')) !== 'true' }); await this.continueButton.click(); await waitUntil(async () => { let loaderElements = await this.page.locator('button .loader, .buy-loader, timeout, .modal-loader').all(); return !(await Promise.any(loaderElements.map(el => el.isVisible())).catch(() => false)); }); } async previousPage() { const startingUrl = this.page.url(); await expect(async () => { const currentUrl = this.page.url(); if (currentUrl === startingUrl) { await this.backButton.click({ timeout: 1000 }); } expect(currentUrl).not.toEqual(startingUrl); }).toPass({ timeout: 240_000 }); } async validateURL(url:string){ await expect(this.pageSpinner).toHaveCount(0, {timeout: 60000}); await this.page.waitForURL(url); } async fillAndValidate(element: Locator, value: string){ await expect(async () => { var text = await element.textContent(); if(text !== value) { await element.clear(); await element.fill(value); } await expect(element).toHaveValue(value); }).toPass(); } async clickWithRetry(element, page) { const timeout = 5000; // milli seconds const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { if (await element.isEnabled()) { await element.click(); await element.keyboard.press('Tab'); return; // Exit loop if click succeeds } } catch (error) { // Ignore error and retry } await page.waitForTimeout(100); // Small delay before retrying } console.log(`Failed to click the the element within ${timeout/1000} seconds` + error); } async logReferralNumber() { let mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')')); let referralNumber = mainLocalStorage.order.referralNumber as number; let referralSequenceNumber = mainLocalStorage.order.referralSequenceNumber as number; if (referralNumber == null) { for (let i = 1; i <= 20; i++) { if (!referralNumber == null) break; await this.page.waitForTimeout(500); mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')')); referralNumber = mainLocalStorage.order.referralNumber as number; referralSequenceNumber = mainLocalStorage.order.referralSequenceNumber as number; } } await test.step(`Referral Number:${referralNumber} Referral Sequence Number:${referralSequenceNumber}`, async () => { console.log(`Referral Number:${referralNumber}`); console.log(`Referral Sequence Number:${referralSequenceNumber}`); }); } async mockScheduleResponseForEarlyBird(customerDetails: ICustomerDetails) { { const apiUrl = `https://digitalapi.${process.env['NODE_ENV']!.replace('sys', 'test').toLowerCase()}.safelite.io/schedule/api/v1/schedule/mobile-time-slots`; await this.page.route(apiUrl, async (route) => { const response = await route.fetch(); const responseBody = await response.json(); responseBody.days.forEach((day: any) => { day.timeSlots.forEach((slot: any) => { if (slot.id.includes("AM")) { slot.offerPremium = true; } }); }); customerDetails.apptDate = responseBody.days.find((day: any) => day.timeSlots.some((slot: any) => slot.offerPremium === true) ).date || undefined; // Mock the response await route.fulfill({ response, body: JSON.stringify(responseBody), }); }); } } async validateProgressBar(progressPercentage: string, timeout: number = 60000) { await waitUntil(async () => { let loaderElements = await this.page.locator('button .loader, .buy-loader, timeout, .modal-loader').all(); return !(await Promise.any(loaderElements.map(el => el.isVisible())).catch(() => false)); }); // Take a screenshot of the page before validating the progress bar await this.page.screenshot({ path: `test-results\\ortoni-data\\progress-bar-${Date.now()}.png`, fullPage: true }); // Wait until the progress bar element is attached and visible const actualProgressPercentage = await this.progressBar.evaluate( async (element) => { await new Promise(resolve => setTimeout(resolve, 200)); return element.style.width || "Not Found"; } ); Soft.expect(actualProgressPercentage).toBe(progressPercentage); console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`); } }