From ebed4b51d7f2c51501839b5000e99d011deac857 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 11 Feb 2026 13:50:26 -0500 Subject: [PATCH 01/80] Fix referral number method by retrieving value from sessionStorage --- playwright-tests/pages/BasePage.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 6d2d5462..26c8dfeb 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -95,16 +95,16 @@ export class BasePage { } 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; + let mainSessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'main\')')); + let referralNumber = mainSessionStorage.order.referralNumber as number; + let referralSequenceNumber = mainSessionStorage.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; + mainSessionStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')')); + referralNumber = mainSessionStorage.order.referralNumber as number; + referralSequenceNumber = mainSessionStorage.order.referralSequenceNumber as number; } } From b5a436d2dec41b39f9764d055167047733f40bf4 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 11 Feb 2026 13:52:28 -0500 Subject: [PATCH 02/80] Add missing header --- playwright-tests/impl/api/CcisApiUtil.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/playwright-tests/impl/api/CcisApiUtil.ts b/playwright-tests/impl/api/CcisApiUtil.ts index d1d2dd87..acc5de7d 100644 --- a/playwright-tests/impl/api/CcisApiUtil.ts +++ b/playwright-tests/impl/api/CcisApiUtil.ts @@ -12,7 +12,8 @@ export default class CcisApiUtil { createFakeResponse(requestBody: IPostSaveFakeResponseRequestBody) { return axios.post(this.postCreateMockPolicyUrl, requestBody, { headers: { - 'X-Mule-Origin-Verify': authToken + 'X-Mule-Origin-Verify': authToken, + 'x-Application-Name': 'CCIS Playwright' } }); } @@ -21,7 +22,8 @@ export default class CcisApiUtil { const deleteUrl = `${this.deleteFakeResponseUrl}/${params.accountNumber}/${params.key}/${params.responseType}`; return axios.delete(deleteUrl, { headers: { - 'X-Mule-Origin-Verify': authToken + 'X-Mule-Origin-Verify': authToken, + 'x-Application-Name': 'CCIS Playwright' } }); } From 50ffb72c7c0a96f7993ce993e6b3412af39715ef Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 12 Feb 2026 16:01:18 -0500 Subject: [PATCH 03/80] Update tag for template --- azure-pipelines-automated-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-automated-testing.yml b/azure-pipelines-automated-testing.yml index c1388bd1..b83a370d 100644 --- a/azure-pipelines-automated-testing.yml +++ b/azure-pipelines-automated-testing.yml @@ -14,7 +14,7 @@ resources: type: github name: Safelite/AzureDevOps endpoint: Safelite - ref: refs/tags/t5.7.40 + ref: refs/tags/t5.7.51 variables: # - group: Digital-Infrastructure From 58b497fcc4db65fe49c44252e0291f45a0243f29 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Fri, 27 Feb 2026 11:23:52 -0500 Subject: [PATCH 04/80] Update locators and logic to validate cart behavior for deductible scenarios --- .../pages/OrderConfirmationPage.ts | 80 ++++++++++++------- 1 file changed, 51 insertions(+), 29 deletions(-) diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 8f30ad8c..1c4a5962 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -3,38 +3,56 @@ import { BasePage } from './BasePage'; import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails'; import { PaymentType, ServicePackage } from '@business-logic/types/Enums'; import { ITestData } from '@business-logic/types/ITestData'; +import { log } from 'console'; export class OrderConfirmationPage extends BasePage { readonly page: Page; + + readonly successCheckmark: Locator; + readonly successHeader: Locator; + readonly serviceText: Locator; + readonly workOrderNumberText: Locator; readonly emailText: Locator; + readonly apptDateText: Locator; - readonly amountDueText: Locator; - readonly viewCartButton: Locator; + //readonly amountDueText: Locator; readonly deductibleText: Locator; readonly subtotalText: Locator; readonly finalAmountDue: Locator; readonly cartServicePackageText: Locator; + readonly VerifiyingCoverageDeductibleText: Locator; + issPageValue = 'order-confirmation'; constructor(page: Page) { super(page); this.page = page; - this.serviceText = this.page.locator('[class="appointment-text text-center lh-base"]'); - this.emailText = this.page.locator('[class="email-confirmation-text"]'); - this.apptDateText = this.page.locator('[class="appointment-date-time text-center mt-4"]'); - this.amountDueText = this.page.getByLabel('expand cart details'); - this.viewCartButton = this.page.locator('#cart-dropdown-head'); - this.deductibleText = this.page.locator('#deductible-value'); + + this.successCheckmark = this.page.locator('img[src*="-checkmark.svg"]'); + this.successHeader = this.page.locator('[class="header-text"]'); + + this.emailText = this.page.locator('[class="subheader-text"]', { hasText: 'Your appointment is on Safelite\'s schedule and your confirmation email is on the way.' }); + this.serviceText = this.page.locator('[class="service-description confirmation-section"]'); + this.workOrderNumberText = this.page.locator('[class="work-order-description"]'); + + this.apptDateText = this.page.locator('[class="appointment-details confirmation-section"]'); + + this.deductibleText = this.page.locator('span#deductible-value, span.deductible-value'); this.subtotalText = this.page.locator("#subtotal-value"); this.finalAmountDue = this.page.locator('#bottom-amount-due-value'); - this.cartServicePackageText = this.page.locator('#cart-service-package'); + this.cartServicePackageText = this.page.locator('.cart-item-list'); + this.VerifiyingCoverageDeductibleText = this.page.locator('span.price cart-item', { hasText: 'Verifying coverage' }); } async validateOrderConfirmationPage(testData: Partial) { // Destructure data we use const { vehicleDetails, customerDetails, servicePackage, isItac, isNoComp, isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy } = testData; + + await expect.soft(this.successHeader).toBeVisible(); + + await this.serviceText.waitFor({ state: "visible" }); await this.logOrderNumber(); @@ -42,64 +60,68 @@ export class OrderConfirmationPage extends BasePage { const serviceTextValue = await this.serviceText.textContent(); const apptDateValue = await this.apptDateText.textContent(); const emailTextValue = await this.emailText.textContent(); - const servicePackageValue = await this.cartServicePackageText.textContent(); - const amountDueValue = await this.amountDueText.textContent(); + const servicePackageValue = (claimDetails!.policyDeductible >= 0) ? await this.cartServicePackageText.textContent(): null; + //const amountDueValue = await this.amountDueText.textContent(); const deductibleTextValue = (isItac || isNoComp) ? null : await this.deductibleText.textContent(); - const subtotalTextValue = await this.subtotalText.textContent(); - const finalAmountDueValue = await this.finalAmountDue.textContent(); + const subtotalTextValue = (claimDetails!.policyDeductible >= 0) ? await this.subtotalText.textContent() : null; + const finalAmountDueValue = (claimDetails!.policyDeductible >= 0) ? await this.finalAmountDue.textContent() : null; // Extract service package price - const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', '')); + // const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', '')); // General Validations expect.soft(serviceTextValue).toContain(`${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`); - expect.soft(apptDateValue).toContain(customerDetails!.apptDate); - expect.soft(emailTextValue).toContain(customerDetails!.email); + //expect.soft(apptDateValue).toContain(customerDetails!.apptDate); + //expect.soft(emailTextValue).toContain(customerDetails!.email); // Service package validations - await expect.soft(this.cartServicePackageText).toContainText(`${servicePackage}`) if ((servicePackage === ServicePackage.Premium && testData.isReplace === true) || servicePackage === ServicePackage.Standard) { - expect.soft(servicePackageValue).toContain('New wiper blades'); + expect.soft(servicePackageValue).toContain('Front advanced beam blades'); } if (servicePackage === ServicePackage.Premium) { - expect.soft(servicePackageValue).toContain('Rain Defense™ treatment'); + expect.soft(servicePackageValue).toContain('Safelite Rain Repellent Treatment'); } // Price validations - if (servicePackage === ServicePackage.GlassOnly) { + /*if (servicePackage === ServicePackage.GlassOnly) { expect.soft(servicePackageAmt).toEqual(0); } else { expect.soft(servicePackageAmt).toBeGreaterThan(0); - } + }*/ - if (isPolicyFound && (isUseVehicleOnPolicy ?? true)) { + if (isPolicyFound && (claimDetails!.policyDeductible === 0 && (servicePackage === ServicePackage.GlassOnly))) { + const zeroDeductibleText = await this.deductibleText.textContent(); + expect.soft(zeroDeductibleText).toEqual('$0.00'); + } else if (isPolicyFound && (finalAmountDueValue !== null && subtotalTextValue !== null)) { // Extract numbers - const amountDueAmt = Number.parseFloat(amountDueValue!.split('$')[1].replaceAll(',', '')); + //const amountDueAmt = Number.parseFloat(amountDueValue!.split('$')[1].replaceAll(',', '')); const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : 0; const subtotalAmt = Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', '')); subtotalTextValue?.replaceAll(',', '') const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')); if (!(isItac || isNoComp)) { - expect.soft(subtotalAmt).toEqual(claimDetails!.policyDeductible + servicePackageAmt); + //expect.soft(subtotalAmt).toEqual(claimDetails!.policyDeductible + servicePackageAmt); expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible); } else { expect.soft(subtotalAmt).toBeGreaterThan(0); expect.soft(deductibleAmt).toEqual(0); } - if (paymentDetails!.paymentType === PaymentType.PayAtService && (claimDetails!.policyDeductible > 0 || servicePackageAmt > 0)) { + if ((paymentDetails!.paymentType === PaymentType.PayAtService) && (claimDetails!.policyDeductible >= 0 && (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium))) { // Verify amount due > 0 - expect.soft(amountDueAmt).toBeGreaterThan(0); + //expect.soft(amountDueAmt).toBeGreaterThan(0); expect.soft(finalAmountDueAmt).toBeGreaterThan(0); } else { // Verify amount due 0 - expect.soft(amountDueAmt).toEqual(0); + //expect.soft(amountDueAmt).toEqual(0); expect.soft(finalAmountDueAmt).toEqual(0); } } else { - expect.soft(amountDueValue).toContain('Verifying coverage'); - expect.soft(subtotalTextValue).toEqual('Verifying coverage'); + // essential flows will have deductible and amount due as "Verifying coverage" + const verifyingCoverageText = this.VerifiyingCoverageDeductibleText; + + expect.soft(verifyingCoverageText).toContainText('Verifying coverage'); expect.soft(finalAmountDueValue).toEqual('Verifying coverage'); } } From 75e84452a83ab428bb5f2252ff46de9d28941589 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Mon, 2 Mar 2026 11:35:37 -0500 Subject: [PATCH 05/80] Clean up logic for Afterpay flow --- playwright-tests/pages/AfterpayPage.ts | 32 ++++---------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index 4691163f..aa345ef4 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -4,51 +4,27 @@ import { IPaymentDetails } from "@business-logic/types/CustomerDetails"; export class AfterpayPage extends BasePage { readonly page: Page; - readonly submitButton: Locator; - - // Login + readonly passwordTextBox: Locator; - - // Card details - readonly cardholderNameTextBox: Locator; - readonly cardNumberTextBox: Locator; - readonly expirationDateTextBox: Locator; - readonly cvvTextBox: Locator; - + readonly submitButton: Locator; readonly confirmButton: Locator; constructor(page: Page) { super(page); this.page = page; - this.passwordTextBox = page.getByRole('textbox', { name: 'Please enter your password' }); + this.passwordTextBox = page.getByTestId('login-password-input'); this.submitButton = page.getByRole('button', { name: 'Continue' }); - - this.cardholderNameTextBox = page.getByTestId('payment-method-cardHolderName-input'); - this.cardNumberTextBox = page.getByTestId('payment-method-cardNumber-input'); - this.expirationDateTextBox = page.getByTestId('payment-method-cardExpiry-input'); - this.cvvTextBox = page.getByTestId('payment-method-cardCvv-input'); - this.confirmButton = page.getByRole('button', { name: 'Confirm' }); } async login(password: string) { + await this.passwordTextBox.click(); await this.passwordTextBox.fill(password); await this.submitButton.click(); } - async populateCardDetails(paymentDetails: IPaymentDetails) { - await this.cardholderNameTextBox.fill('Roberts'); // TODO: Add cardholder name field - await this.cardNumberTextBox.fill(paymentDetails.cardNumber!); - await this.expirationDateTextBox.fill(`${paymentDetails.expirationMonth}/${paymentDetails.expirationYear}`); - await this.cvvTextBox.fill(paymentDetails.cvv!); - await this.submitButton.click(); - } - async executeAfterpayPayment(paymentDetails: IPaymentDetails) { await this.login(paymentDetails.password!); - - await this.populateCardDetails(paymentDetails); - await this.confirmButton.click(); } From 5908b7b807e6d2d139e8e0b4228456afc5d64c43 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Mon, 2 Mar 2026 14:35:53 -0500 Subject: [PATCH 06/80] Update locators for Paypal flow --- .../business-logic/data/ClientData.ts | 1 + playwright-tests/pages/PaypalPage.ts | 50 +++++++++++-------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/playwright-tests/business-logic/data/ClientData.ts b/playwright-tests/business-logic/data/ClientData.ts index adce9400..1f1c2da6 100644 --- a/playwright-tests/business-logic/data/ClientData.ts +++ b/playwright-tests/business-logic/data/ClientData.ts @@ -235,6 +235,7 @@ const defaultAfterpayDetails: IPaymentDetails = { const defaultPaypalDetails: IPaymentDetails = { paymentType: PaymentType.Paypal, + username:'Itqatest@safelite.com', password: 'Safelite1' } diff --git a/playwright-tests/pages/PaypalPage.ts b/playwright-tests/pages/PaypalPage.ts index 862a97ed..adb6ba5d 100644 --- a/playwright-tests/pages/PaypalPage.ts +++ b/playwright-tests/pages/PaypalPage.ts @@ -1,27 +1,33 @@ -import { expect, type Locator, type Page } from '@playwright/test'; -import { BasePage } from './BasePage'; -import { IPaymentDetails } from '@business-logic/types/CustomerDetails'; +import { expect, type Locator, type Page } from "@playwright/test"; +import { BasePage } from "./BasePage"; +import { IPaymentDetails } from "@business-logic/types/CustomerDetails"; export class PaypalPage extends BasePage { - readonly page: Page; - readonly loginWithPasswordButton: Locator; - readonly passwordTextBox: Locator; - readonly paypalLoginButton: Locator; - readonly completePurchaseButton: Locator; + readonly page: Page; + readonly usernameTextBox: Locator; + readonly nextButton: Locator; + readonly passwordTextBox: Locator; + readonly paypalLoginButton: Locator; + readonly payButton: Locator; - constructor(page: Page) { - super(page); - this.page = page; - this.loginWithPasswordButton = page.getByRole('link', { name: 'Log in with a password instead' }); - this.passwordTextBox = page.getByPlaceholder('Password'); - this.paypalLoginButton = page.getByRole('button', { name: 'Log In', exact: true }); - this.completePurchaseButton = page.getByTestId('submit-button-initial'); - } + constructor(page: Page) { + super(page); + this.page = page; + this.usernameTextBox = page.locator("#email"); + this.nextButton = page.getByRole("button", { name: "Next" }); + this.passwordTextBox = page.getByRole("textbox", { name: "Password" }); + this.paypalLoginButton = page.getByRole("button", { + name: "Log In", + exact: true, + }); + this.payButton = page.getByRole("button", { name: "Pay $" }); + } - async completePaypalPurchase(paymentDetails: IPaymentDetails){ - await this.loginWithPasswordButton.click(); - await this.passwordTextBox.fill(paymentDetails.password!); - await this.paypalLoginButton.click(); - await this.completePurchaseButton.click(); - } + async completePaypalPurchase(paymentDetails: IPaymentDetails) { + await this.usernameTextBox.fill(paymentDetails.username!); + await this.nextButton.click(); + await this.passwordTextBox.fill(paymentDetails.password!); + await this.paypalLoginButton.click(); + await this.payButton.click(); + } } From c899e367d1936f5d4ef175b552183eb1a12759b8 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Mon, 2 Mar 2026 14:49:47 -0500 Subject: [PATCH 07/80] Update locators and flow for each payment option --- playwright-tests/pages/PaymentMethodPage.ts | 72 +++++++++++++-------- playwright-tests/pages/PaymentPage.ts | 3 +- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index 57d560be..ace2f8ac 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -8,25 +8,36 @@ import { PaypalPage } from './PaypalPage'; export class PaymentMethodPage extends BasePage { readonly page: Page; - readonly payAtServiceButton: Locator; - readonly havePaymentReadyMsg: Locator; - readonly payNowButton: Locator; - readonly payInFourButton: Locator; - readonly paypalButton: Locator; - readonly paymentPage: PaymentPage; readonly paypalPage: PaypalPage; + readonly paymentPage: PaymentPage; + + readonly payNowButton: Locator; + readonly paypalButton: Locator; + readonly payInFourButton: Locator; + readonly payAtAppointmentButton: Locator; + + readonly textReminderYesButton: Locator; + readonly textReminderNoButton: Locator; + readonly continueToCheckoutButton: Locator; + readonly submitButton: Locator; + issPageValue = 'payment-method'; constructor(page: Page) { super(page); this.page = page; - this.payAtServiceButton = this.page.locator('[buttonlabel="Pay at my appointment"]'); //this.page.getByText('Pay at time of service'); - this.havePaymentReadyMsg = this.page.getByText('Please have payment ready during your appointment.'); // it will be displayed when there is no payment option - this.payNowButton = this.page.locator('[buttonlabel="Pay now"]'); - this.payInFourButton = this.page.locator('[buttonlabel="Pay in 4 installments"]'); - this.paypalButton = this.page.frameLocator('iframe[name="card-frame"]').locator('div[id="paypalParentDiv"]'); - this.paymentPage = new PaymentPage(page); this.paypalPage = new PaypalPage(page); + this.paymentPage = new PaymentPage(page); + + this.payNowButton = this.page.locator('[buttonlabel="Pay now"]'); // credit and paypal options behind this button + this.paypalButton = this.page.frameLocator('iframe[name="card-frame"]').locator('div[id="paypalParentDiv"]'); + this.payInFourButton = this.page.locator('[buttonlabel="Pay in 4 installments"]'); // Afterpay + this.payAtAppointmentButton = this.page.locator('[buttonlabel="Pay at my appointment"]'); + + this.textReminderYesButton = this.page.locator('div.button-content', { hasText: /^yes$/i }); + this.textReminderNoButton = this.page.locator('div.button-content', { hasText: 'No' }); + this.continueToCheckoutButton = this.page.getByRole('button', { name: 'Continue to checkout' }); + this.submitButton = this.page.getByRole('button', { name: 'Submit' }); } async executePayment(paymentDetails: IPaymentDetails) { @@ -34,16 +45,22 @@ export class PaymentMethodPage extends BasePage { switch (paymentDetails.paymentType) { case PaymentType.Credit: - await this.selectCreditCard(); - await this.paymentPage.populateCreditCardDetails(paymentDetails); + await this.payNowButton.click(); + await this.textReminderYesButton.click(); + await this.continueToCheckoutButton.click(); + await this.selectCreditCard(paymentDetails); break; case PaymentType.Paypal: + await this.payNowButton.click(); + await this.textReminderYesButton.click(); + await this.continueToCheckoutButton.click(); await this.selectPaypal(); await this.paypalPage.completePaypalPurchase(paymentDetails); break; case PaymentType.AfterPay: await this.payInFourButton.click(); - await this.nextPage(); + await this.textReminderYesButton.click(); + await this.continueToCheckoutButton.click(); // Capture popup const afterpayPopup = await browserContext.waitForEvent('page'); @@ -54,7 +71,9 @@ export class PaymentMethodPage extends BasePage { break; case PaymentType.PayAtService: - await this.selectPayAtService(); + await this.payAtAppointmentButton.click(); + await this.textReminderYesButton.click(); + await this.submitButton.click(); break; default: console.error('PaymentMethodPage >> Logic for this payment method unimplemented'); @@ -63,22 +82,19 @@ export class PaymentMethodPage extends BasePage { } async selectPaypal() { - await this.payNowButton.click(); - await this.nextPage(); await this.paypalButton.click(); } - async selectCreditCard() { - await this.payNowButton.click(); - await this.nextPage(); + async selectCreditCard(paymentDetails: IPaymentDetails) { + await this.paymentPage.populateCreditCardDetails(paymentDetails); + } + + async selectPayAtAppointment() { + await this.payAtAppointmentButton.click(); } - async selectPayAtService() { - await this.payAtServiceButton.click(); + async submitOrderWithoutPIA() { + await this.textReminderYesButton.click(); + await this.submitButton.click(); } - - // async validateAmountDue(customer){ - // await this.amountDueDropDown.click(); - // await expect(this.deductibleAmountTextField).toContainText(`${customer.deductibleAmount}`); - // } } \ No newline at end of file diff --git a/playwright-tests/pages/PaymentPage.ts b/playwright-tests/pages/PaymentPage.ts index 51942f0e..fff2edd3 100644 --- a/playwright-tests/pages/PaymentPage.ts +++ b/playwright-tests/pages/PaymentPage.ts @@ -26,8 +26,7 @@ export class PaymentPage extends BasePage { this.cityTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'City' }); this.stateDropDown = page.frameLocator('iframe[name="card-frame"]').getByRole('combobox', { name: 'State' }); this.billingZipTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'Billing ZIP code' });; - this.submitPaymentButton = page.frameLocator('iframe[name="card-frame"]').locator('#buttonContainer'); - // this.validateURL(this.url); + this.submitPaymentButton = page.getByRole('button', { name: 'Submit payment' }); } async populateCreditCardDetails(paymentDetails: IPaymentDetails){ From 6ffe7e229d2d03b3ed11f353aa0b7ee6e2d4d48f Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:20:56 -0400 Subject: [PATCH 08/80] Added locators and steps to fix Afterpay flow --- playwright-tests/pages/AfterpayPage.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index aa345ef4..cf71e250 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -9,12 +9,22 @@ export class AfterpayPage extends BasePage { readonly submitButton: Locator; readonly confirmButton: Locator; + readonly selectAfterpayWithInterestButton: Locator; + readonly selectAfterpayWithoutInterestButton: Locator; + readonly continueAfterpayButton: Locator; + constructor(page: Page) { super(page); this.page = page; this.passwordTextBox = page.getByTestId('login-password-input'); this.submitButton = page.getByRole('button', { name: 'Continue' }); this.confirmButton = page.getByRole('button', { name: 'Confirm' }); + + // Monthly with interest option + this.selectAfterpayWithInterestButton = page.getByRole('radio', { name: /month/ }); + // Biweekly without interest option + this.selectAfterpayWithoutInterestButton = page.getByTestId('payment-types-label').filter({ hasText: /every 2 weeks/ }); + this.continueAfterpayButton = page.getByRole('button', { name: 'Continue' }); } async login(password: string) { @@ -25,6 +35,18 @@ export class AfterpayPage extends BasePage { async executeAfterpayPayment(paymentDetails: IPaymentDetails) { await this.login(paymentDetails.password!); + + // For orders greater than $50, user will have 2 options to choose from: monthly with interest or biweekly without interest + await this.selectAfterpayWithoutInterestButton.waitFor({ state: 'visible' }); + + if (await this.selectAfterpayWithoutInterestButton.isVisible()) { + await this.selectAfterpayWithoutInterestButton.click(); + await this.continueAfterpayButton.click(); + } else if (await this.selectAfterpayWithInterestButton.isVisible()) { + await this.selectAfterpayWithInterestButton.click(); + await this.continueAfterpayButton.click(); + } + await this.confirmButton.click(); } From 23e9777c186d705465ad41d965e8ebc59de3dca2 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:21:50 -0400 Subject: [PATCH 09/80] Update locators and schedule page flow to fix tests --- playwright-tests/pages/SchedulePage.ts | 89 ++++++++++++++++++-------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 0bd340dc..11645b5d 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -3,56 +3,93 @@ import { BasePage } from './BasePage'; import { IAppointmentDetails } from '@business-logic/types/CustomerDetails'; import { formatDate, formatTime } from '@impl/utils/DateUtils'; import { ServiceLocation } from '@business-logic/types/Enums'; +import { getCustomerDetails, ITestData } from 'safelite-playwright-core'; export class SchedulePage extends BasePage { readonly page: Page; issPageValue = 'schedule-page'; - readonly firstAvailableDate: Locator; + + readonly inShopButton: Locator; + readonly mobileButton: Locator; + readonly moreLocationsButton: Locator; + readonly zipCodeTextBox: Locator; + readonly findButton: Locator; + readonly selectAShopOptions: Locator; + readonly firstAppointmentButton : Locator; + readonly firstAvailableAMDate: Locator; + readonly firstAvailablePMDate: Locator; readonly firstAvailableTime: Locator; + readonly firstAvailabeAMTime: Locator; readonly modalContinueButton: Locator; readonly dropOffButton: Locator; readonly dateText: Locator; readonly viewMoreDatesLink: Locator; + readonly continueButton: Locator; + constructor(page: Page) { super(page); this.page = page; - this.firstAvailableDate = this.page.locator('.selectable-day').locator('nth=0'); - this.firstAvailableTime = this.page.locator('label').filter({ hasText: /AM|PM/ }).locator('div').locator('nth=0'); + + this.inShopButton = this.page.locator('[buttonlabel="At a Safelite shop"]'); + this.mobileButton = this.page.locator('[buttonlabel="Have Safelite come to me"]'); + // For in shop + this.moreLocationsButton = this.page.getByRole('button', { name: 'More Locations' }); + // For mobile + this.zipCodeTextBox = this.page.locator('#serviceZipCode'); + this.findButton = this.page.getByRole('button', { name: 'Find' }); + // For in-shop and drop off + this.selectAShopOptions = this.page.locator('[class="shop-question"]'); + this.firstAppointmentButton = this.page.locator('#availabilityIndicator').first(); + this.firstAvailableAMDate = this.page.locator('morning-row-cell has-appointments selected-date').first(); + this.firstAvailablePMDate = this.page.locator('afternoon-row-cell has-appointments selected-date').first(); + this.firstAvailableTime = this.page.locator('.time-slot-button').first(); + this.firstAvailabeAMTime = this.page.locator('.time-slot-button').nth(0); this.modalContinueButton = this.page.locator('#modalbtn'); - this.dropOffButton = this.page.getByText('Drop off your vehicle', { exact: true }); + this.dropOffButton = this.page.locator('[class="dropoff-label"]'); this.dateText = this.page.locator('[class="modal-header mb-2 mt-2"]'); - this.viewMoreDatesLink = this.page.getByText(/View more dates/).first(); + this.viewMoreDatesLink = this.page.getByRole('button', { name: 'More Right arrow icon' }); + + this.continueButton = this.page.locator('#stacked').locator('button:has-text("Continue")'); } - async scheduleAppointment(appointmentDetails: IAppointmentDetails) { - const formattedDate = formatDate(appointmentDetails.appointmentDate!); - const formattedTime = formatTime(appointmentDetails.appointmentDate!); - const dateInput = this.page.locator(`div[id="${formattedDate}"]`); - const timeButton = this.page.locator(`div[aria-label="${formattedTime}"]`); - if (await dateInput.isVisible()) { - await dateInput.click(); + // Select in shop and schedule + async scheduleInShop(appointmentDetails?: IAppointmentDetails){ + + if (await this.inShopButton.isVisible() && appointmentDetails?.serviceLocation !== ServiceLocation.DropOff) { + await this.inShopButton.click(); + (await this.getTimeSlot(1)).click(); + } else if (appointmentDetails?.serviceLocation === ServiceLocation.DropOff) { + await this.inShopButton.isVisible(); + await this.inShopButton.click(); + await this.dropOffButton.isVisible() ? await this.dropOffButton.click() : null; } else { - await this.viewMoreDatesLink.click(); - await dateInput.click(); + await this.selectFirstAvailableTime(); } - await timeButton.click(); - await this.modalContinueButton.click(); + await this.continueButton.click(); } - async scheduleFirstAppointment(serviceLocation: ServiceLocation) { - if (await this.firstAvailableDate.isVisible()) { - await this.firstAvailableDate.click(); + // Select mobile button, enter zip code, click find button, select first available time and continue + async scheduleMobile(appointmentDetails: IAppointmentDetails){ + await this.mobileButton.click(); + await this.zipCodeTextBox.fill(appointmentDetails.serviceAddress!.postalCode!); + await this.findButton.click(); + await this.selectFirstAvailableTime(); + await this.continueButton.click(); + } + + // Selects the first available time slot but if its not visible, click "More" first to load more dates and times + async selectFirstAvailableTime(): Promise { + if (await this.firstAvailableTime.isVisible()) { + await this.firstAvailableTime.click(); } else { await this.viewMoreDatesLink.click(); - while(await this.firstAvailableDate.isHidden()){ - await this.viewMoreDatesLink.click(); - } - await this.firstAvailableDate.click(); + await this.firstAvailableTime.waitFor({ state: 'visible' }); + await this.firstAvailableTime.click(); } + } - serviceLocation === ServiceLocation.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click(); - await this.modalContinueButton.click(); - return (`${await this.dateText.allInnerTexts()}`); + async getTimeSlot(index: number): Promise { + return this.page.locator('.time-slot-button').nth(index); } } \ No newline at end of file From 72e4269fd8212fe87d1cbdb2f4108c9595ff6526 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:22:19 -0400 Subject: [PATCH 10/80] Update locator for welcome page --- playwright-tests/pages/WelcomePage.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/playwright-tests/pages/WelcomePage.ts b/playwright-tests/pages/WelcomePage.ts index e7c8c4ae..fe3e0e76 100644 --- a/playwright-tests/pages/WelcomePage.ts +++ b/playwright-tests/pages/WelcomePage.ts @@ -16,6 +16,7 @@ export class WelcomePage extends BasePage { readonly city: Locator; readonly state: Locator; readonly cookieCloseButton: Locator; + readonly GetStartedButton: Locator; url = process.env['BASE_URL']! + '/?issPage=welcome-page'; issPageValue = 'welcome-page'; @@ -24,13 +25,14 @@ export class WelcomePage extends BasePage { this.page = page; this.policyNumber = page.getByRole('textbox', { name: 'Policy number' }); this.policyZip = page.getByRole('textbox', { name: 'Policy ZIP' }); - this.damageDate = page.getByRole('textbox', { name: 'When did the damage occur?<' }); + this.damageDate = page.locator('#dateOfLossField'); this.damageCause = page.locator('#damageCauseQuestionField'); this.phoneNumber = page.getByRole('textbox', { name: 'Best number to reach you' }); this.emailAddress = page.getByRole('textbox', { name: 'Email address' }); this.city = page.getByRole('textbox', { name: 'In which city did the damage' }); this.state = page.locator('select[name="\\38 fdf9dc2e13e430eb57529499dceb3eb"]'); this.cookieCloseButton = page.getByRole('button', { name: 'Close' }); + this.GetStartedButton = page.getByRole('button', { name: 'Get Started' }); } async goto(clientTag: string) { @@ -66,18 +68,17 @@ export class WelcomePage extends BasePage { async populatePage(customerDetails: ICustomerDetails, claimDetails: IClaimDetails, isFillCityInfo: boolean) { await this.policyNumber.fill(claimDetails.policyNumber); - await this.policyZip.fill(customerDetails.address.postalCode); + await this.phoneNumber.fill(customerDetails.phoneNumber); await this.damageDate.click(); await this.damageDate.fill(claimDetails.damageDate); await this.damageCause.selectOption(claimDetails.damageCause); await this.damageCause.press('Tab'); - await this.phoneNumber.fill(customerDetails.phoneNumber); - // Phone number fixed 12/5. Can't start with 1 or 0 - await this.emailAddress.fill(customerDetails.email); + await this.policyZip.fill(customerDetails.address.postalCode); if (isFillCityInfo) { - await this.city.fill(customerDetails.address.city); await this.state.selectOption(customerDetails.address.state); + await this.city.fill(customerDetails.address.city); + } } } \ No newline at end of file From ad228f735c25eb315746946239d97a940c30df20 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:23:45 -0400 Subject: [PATCH 11/80] Fix flaky global continue button locator --- playwright-tests/pages/BasePage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 26c8dfeb..cdd6725d 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -17,7 +17,7 @@ export class BasePage { constructor(page: Page) { this.page = page; - this.continueButton = page.locator('[id="infoBox"]').getByRole('button'); + this.continueButton = page.locator('button', { hasText: /get\s*started|continue/i }); this.pageSpinner = page.getByRole('status'); this.buttonLoadSpin = page.getByRole('alert'); } From 31d2d7d5f5611003725f26aad066452543a95f68 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:32:35 -0400 Subject: [PATCH 12/80] Update validations for various deductible types --- .../pages/CoverageStatementPage.ts | 94 ++++++++++++++++--- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index 9832cf9c..3bce4330 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -1,30 +1,44 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; +import { IClaimDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails'; +import { defaultTestData, ITestData, VehicleDamage } from 'safelite-playwright-core'; export class CoverageStatementPage extends BasePage { readonly page: Page; - readonly scheduleOnlineButton: Locator; readonly cancelMyClaimButton: Locator; readonly cancelMyClaimConfirm: Locator; readonly deductibleAmount: Locator; - readonly verfiyingCoverageText: Locator; - readonly continueToScheduleButton: Locator; // For ITAC/NoComp + readonly noDeductibleRepairText: Locator; // For essential flow when there is no deductible and repair is covered + readonly youPayYourDeductibleText: Locator; + readonly verifiedCoverageHeader: Locator; // For essential flow when coverage is verified + readonly headerForITAC: Locator; // For ITAC when coverage is found + readonly headerForNoComp: Locator; // For NoComp when coverage is found + readonly unverifiedCoverageHeader: Locator; // For essential flows when verifying deductible + readonly continueSchedulingButton: Locator; // For ITAC/NoComp + readonly continueButton: Locator; // For essential flows issPageValue = 'coverage-statement'; constructor(page: Page) { super(page); this.page = page; - this.scheduleOnlineButton = this.page.getByText('Continue to schedule online'); + this.cancelMyClaimButton = this.page.getByText('Cancel my claim'); - this.cancelMyClaimConfirm = this.page.getByText('No, I want to cancel'); + this.cancelMyClaimConfirm = this.page.getByRole('link', { name: "No, I want to cancel" }); // seen for ITAC/NoComp flows - this.deductibleAmount = this.page.getByText('$'); - this.verfiyingCoverageText = this.page.getByRole('heading', { name: 'We’re verifying your coverage' }); - this.continueToScheduleButton = page.locator('div[class*="button-content"]', {hasText:'Continue to schedule online'}); + this.deductibleAmount = this.page.locator('.cost-underline'); + this.noDeductibleRepairText = this.page.getByText('According to your policy, there is no deductible for a repair.'); // no deductible for repair text + this.youPayYourDeductibleText = this.page.getByText('You pay your deductible:'); // no deductible for replacement text + this.verifiedCoverageHeader = this.page.getByRole('heading', { name: 'We have verified your coverage' }); + this.unverifiedCoverageHeader = this.page.getByRole('heading', { level: 5, name: "We’re verifying your coverage" }); + this.headerForITAC = this.page.getByRole('heading', { name: 'Good news! Safelite\'s price is lower than your deductible' }); + this.headerForNoComp = this.page.getByRole('heading', { name: 'Looks like your policy doesn\'t include comprehensive coverage.\nSafelite can still get you fixed up and safely back on the road.' }); + + this.continueSchedulingButton = page.getByRole('button', { name: 'Continue scheduling' }); // continue button for ITAC/NoComp flow + this.continueButton = page.getByRole('button', { name: 'Continue' }); // continue button for essential flow } - async scheduleOnline(){ - await this.scheduleOnlineButton.click(); + async scheduleOnline(){ + await this.continueSchedulingButton.click(); } async cancelMyClaim(){ @@ -32,11 +46,61 @@ export class CoverageStatementPage extends BasePage { await this.cancelMyClaimConfirm.click(); } - async validateDeductibleAmount(customer){ - await expect(this.deductibleAmount). toContainText(`$${customer.deductibleAmount}`, {timeout: 60000}); + async handleRepairWithNoDeductibleFlow() { + + if (VehicleDamage.WindshieldOneChip || VehicleDamage.WindshieldTwoChips || VehicleDamage.WindshieldThreeChips) { + await expect(this.verifiedCoverageHeader).toBeVisible(); + await expect(this.noDeductibleRepairText).toBeVisible(); + } } - async validateUnverifiedText(){ - await expect(this.verfiyingCoverageText).toBeEnabled(); + async handleReplacementOrRepairWithNoDeductibleFlow(claimDetails: IClaimDetails, VehicleDamage){ + const noDeductibleFlow = claimDetails.policyDeductible === 0; + + if (noDeductibleFlow && VehicleDamage === VehicleDamage.WindshieldOneChip || VehicleDamage.WindshieldTwoChips || VehicleDamage.WindshieldThreeChips) { + + // await expect(this.noDeductibleRepairText).toBeVisible(); + await this.continueButton.click(); + + } else { + await expect(this.youPayYourDeductibleText).toBeVisible(); + await expect(this.deductibleAmount).toContainText(`$0`, {timeout: 60000}); + await this.continueButton.click(); + } + } + + async handleReplacementWithDeductibleFlow(claimDetails: IClaimDetails){ + const replaceWithDeductibleFlow = claimDetails.policyDeductible! > 0 && claimDetails.policyDeductible !== 9999; + + if (replaceWithDeductibleFlow) { + await expect(this.youPayYourDeductibleText).toBeVisible(); + await expect(this.deductibleAmount).toContainText(`$${claimDetails.policyDeductible?.toLocaleString()}`, {timeout: 60000}); + + await this.continueButton.click(); + } + } + + + async handleUnverifiedFlow(claimDetails: IClaimDetails){ + const unVerifiedCustomer = claimDetails.policyDeductible === -1; // if deductible is -1, we treat it as unverified flow + + if (unVerifiedCustomer) { + + await expect(this.unverifiedCoverageHeader).toBeVisible(); + + await this.continueButton.click(); + } + } + + async handleITACFlow(){ + await expect(this.headerForITAC).toBeVisible(); // verify ITAC header is visible + + await this.continueSchedulingButton.click(); // happy path to scheduling for ITAC + } + + async handleNoCompFlow(){ + await expect(this.headerForNoComp).toBeVisible(); // verify NoComp header is visible + + await this.continueSchedulingButton.click(); // happy path to scheduling for NoComp + } } -} \ No newline at end of file From fb6c3ee7fb8bf3bb3bc25db632a06c14e911b98d Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:34:42 -0400 Subject: [PATCH 13/80] Update locators and flow for contact details page --- playwright-tests/pages/ContactDetailsPage.ts | 55 +++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/playwright-tests/pages/ContactDetailsPage.ts b/playwright-tests/pages/ContactDetailsPage.ts index a942a12e..2f030149 100644 --- a/playwright-tests/pages/ContactDetailsPage.ts +++ b/playwright-tests/pages/ContactDetailsPage.ts @@ -6,35 +6,52 @@ export class ContactDetailsPage extends BasePage { readonly page: Page; issPageValue = 'contact-details'; - // Contact details form - // TODO: Check if we can consolidate - readonly firstNameTextBox: Locator; - readonly lastNameTextBox: Locator; - readonly emailAddressTextBox: Locator; - readonly phoneNumberTextBox: Locator; + readonly appointmentInformationAlert: Locator; + readonly changeZipCodeAlert: Locator; + + readonly streetAddressTextBox: Locator; + readonly apartmentNumberTextBox: Locator; + readonly cityTextBox: Locator; + readonly stateTextBox: Locator; + readonly zipCodeTextBox: Locator; + readonly coveredLocationYesButton: Locator; + readonly coveredLocationNoButton: Locator readonly notesTextBox: Locator; constructor(page: Page) { super(page); this.page = page; + // Alerts + this.appointmentInformationAlert = this.page.locator('[class*="widget-name-AppointmentInformationAlertWidget"]'); + this.changeZipCodeAlert = this.page.locator('[class*="widget-name-ChangeZipCodeAlertWidget"]'); + // Contact details form - this.firstNameTextBox = this.page.getByRole('textbox', { name: 'First name' }); - this.lastNameTextBox = this.page.getByRole('textbox', { name: 'Last name' }); - this.emailAddressTextBox = this.page.getByRole('textbox', { name: 'Email address' }); - this.phoneNumberTextBox = this.page.getByRole('textbox', { name: 'Phone number' }); - this.notesTextBox = this.page.getByRole('textbox', { name: 'Notes' }); + this.streetAddressTextBox = this.page.getByRole('textbox', { name: 'Street address' }); + this.apartmentNumberTextBox = this.page.getByRole('textbox', { name: 'Apartment number or letter (Optional)' }); + this.cityTextBox = this.page.getByRole('textbox', { name: 'City' }); + this.stateTextBox = this.page.getByRole('textbox', { name: 'state' }); + this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'ZIP' }); + this.coveredLocationYesButton = this.page.locator('#vehicleProtectedQuestion-true'); + this.coveredLocationNoButton = this.page.locator('#vehicleProtectedQuestion-false'); + this.notesTextBox = this.page.getByRole('textbox', { name: 'technicianNotes' }); } - - async getContactDetails() { - const customerDetails: Partial = {}; - customerDetails.firstName = await this.firstNameTextBox.inputValue(); - customerDetails.lastName = await this.lastNameTextBox.inputValue(); - customerDetails.email = await this.emailAddressTextBox.inputValue(); - customerDetails.phoneNumber = await this.phoneNumberTextBox.inputValue(); + async validateAlertsAreVisible() { + await expect(this.appointmentInformationAlert).toBeVisible(); + await this.appointmentInformationAlert.click(); // + await expect(this.appointmentInformationAlert).toContainText('Please have your vehicle keys or key card available, as your technician needs access inside the vehicle and may need to move your vehicle to complete service.'); - return customerDetails; + await expect(this.changeZipCodeAlert).toBeVisible(); + await this.changeZipCodeAlert.click(); + await expect(this.changeZipCodeAlert).toContainText('If your service ZIP code has changed, please go back and update it to ensure we have the correct information for your appointment.'); + } + + // TO-DO add method for when checkbox for same as policy address displays + + async fillContactDetails(customerDetails: ICustomerDetails) { + await this.streetAddressTextBox.fill(customerDetails.address.street!); + await this.cityTextBox.fill(customerDetails.address.city!); } async fillNotes(notes?: string) { From dc58951630e8e34ee0617af0ec980f5e45656a41 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:36:48 -0400 Subject: [PATCH 14/80] Update locators for yes or no buttons --- playwright-tests/pages/EndorsementsPage.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/playwright-tests/pages/EndorsementsPage.ts b/playwright-tests/pages/EndorsementsPage.ts index 8e154119..47f2db64 100644 --- a/playwright-tests/pages/EndorsementsPage.ts +++ b/playwright-tests/pages/EndorsementsPage.ts @@ -12,11 +12,11 @@ export class EndorsementsPage extends BasePage { constructor(page: Page) { super(page); this.page = page; - this.educatorYesButton = this.page.locator('label[for="schoolProperty-Yes"]'); - this.educatorNoButton = this.page.locator('label[for="schoolProperty-No"]'); + this.educatorYesButton = this.page.locator('label:has(div.button-content:has(span:has-text("Yes")))'); + this.educatorNoButton = this.page.locator('label:has(div.button-content:has(span:has-text("No")))'); } - async verifyEndorsements(endorsements: IEndorsementDetails[]) { +async verifyEndorsements(endorsements: IEndorsementDetails[]) { for (const endorsement of endorsements) { switch(endorsement.endorsementType) { case EndorsementType.Educator: From 7505742467af08efbe202f5f60544772deefba1d Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:38:47 -0400 Subject: [PATCH 15/80] Add import for Afterpay --- playwright-tests/pages/PaymentPage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/playwright-tests/pages/PaymentPage.ts b/playwright-tests/pages/PaymentPage.ts index fff2edd3..4ba47ee6 100644 --- a/playwright-tests/pages/PaymentPage.ts +++ b/playwright-tests/pages/PaymentPage.ts @@ -1,5 +1,6 @@ import { type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; +import { AfterpayPage } from './AfterpayPage'; import { IPaymentDetails } from '@business-logic/types/CustomerDetails'; export class PaymentPage extends BasePage { From 80663bea23916b894a4d12ad08a3bbb112588287 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:39:38 -0400 Subject: [PATCH 16/80] Update locators for vehicle damage buttons --- playwright-tests/pages/VehicleDamagePage.ts | 44 +++++++++++---------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/playwright-tests/pages/VehicleDamagePage.ts b/playwright-tests/pages/VehicleDamagePage.ts index 004ca3c1..96eb6711 100644 --- a/playwright-tests/pages/VehicleDamagePage.ts +++ b/playwright-tests/pages/VehicleDamagePage.ts @@ -4,13 +4,13 @@ import { VehicleDamage } from '@business-logic/types/Enums'; export class VehicleDamagePage extends BasePage { readonly page: Page; - readonly windshieldChkBox: Locator; - readonly crackButton: Locator; - readonly chipButton: Locator; + readonly windshieldButton: Locator; + readonly sideDoorButton: Locator; + readonly replaceButton: Locator; + readonly repairButton: Locator; readonly singleWindshieldButton: Locator; readonly splitWindshieldDriverSideButton: Locator; readonly splitWindshieldPassengerSideButton: Locator; - readonly sideDoorButton: Locator; readonly driverSideButton: Locator; readonly passengerSideButton: Locator; readonly driverQuarterPanelChkBox: Locator; @@ -30,9 +30,11 @@ export class VehicleDamagePage extends BasePage { constructor(page: Page) { super(page); this.page = page; - this.windshieldChkBox = this.page.locator('[buttonlabel="Windshield"]'); - this.crackButton = this.page.locator('[buttonlabel="Crack"]'); - this.chipButton = this.page.locator('[buttonlabel="Chip(s)"]'); + this.windshieldButton = this.page.locator('[buttonlabel="Windshield"]'); + this.sideDoorButton = this.page.locator('[buttonlabel="Side door"]'); + this.rearWindowChkBox = this.page.locator('[buttonlabel="Rear window"]'); + this.replaceButton = this.page.locator('div.list-card-content:has-text("Replace")'); + this.repairButton = this.page.locator('div.list-card-content:has-text("Repair")'); this.singleWindshieldButton = this.page.locator('[buttonlabel="Single windshield"]'); this.splitWindshieldDriverSideButton = this.page.locator('[buttonlabel="Split windshield, driver side"]'); this.splitWindshieldPassengerSideButton = this.page.locator('[buttonlabel="Split windshield, passenger side"]'); @@ -55,17 +57,17 @@ export class VehicleDamagePage extends BasePage { async checkSeparateApptsWarning() { // Select conflict - await this.windshieldChkBox.check(); - await this.chipButton.check(); + await this.windshieldButton.check(); + await this.repairButton.check(); await this.rearWindowChkBox.check(); // Expect warning await expect.soft(this.separateApptsWarning).toBeAttached(); // Undo changes - await this.crackButton.check(); - await this.windshieldChkBox.uncheck(); - await expect.soft(this.crackButton).not.toBeVisible(); + await this.replaceButton.check(); + await this.windshieldButton.uncheck(); + await expect.soft(this.replaceButton).not.toBeVisible(); await this.rearWindowChkBox.uncheck(); } @@ -73,33 +75,33 @@ export class VehicleDamagePage extends BasePage { for (const damage of vehicleDamage) { switch(damage) { case VehicleDamage.WindshieldOneChip: - await this.windshieldChkBox.check(); + await this.windshieldButton.check(); await this.selectChips('1'); break; case VehicleDamage.WindshieldTwoChips: - await this.windshieldChkBox.check(); + await this.windshieldButton.check(); await this.selectChips('2'); break; case VehicleDamage.WindshieldThreeChips: - await this.windshieldChkBox.check(); + await this.windshieldButton.check(); await this.selectChips('3'); break; case VehicleDamage.WindshieldCrack: - await this.windshieldChkBox.check(); + await this.windshieldButton.check(); await this.selectCrack(); break; case VehicleDamage.WindshieldCrackSingleWindshield: - await this.windshieldChkBox.check(); + await this.windshieldButton.check(); await this.selectCrack(); await this.singleWindshieldButton.check(); break; case VehicleDamage.WindshieldCrackDriverSide: - await this.windshieldChkBox.check(); + await this.windshieldButton.check(); await this.selectCrack(); await this.splitWindshieldDriverSideButton.check(); break; case VehicleDamage.WindshieldCrackPassengerSide: - await this.windshieldChkBox.check(); + await this.windshieldButton.check(); await this.selectCrack(); await this.splitWindshieldPassengerSideButton.check(); break; @@ -156,12 +158,12 @@ export class VehicleDamagePage extends BasePage { } async selectCrack(){ - await this.crackButton.check(); + await this.replaceButton.click(); } async selectChips(numChips: string){ const numChipsButton = this.page.getByText(numChips, {exact: true}); - await this.chipButton.check(); + await this.repairButton.check(); await numChipsButton.check(); } From 92f15d5019a563a9b1fee54b696ec5fef0895c74 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:41:06 -0400 Subject: [PATCH 17/80] Add functions to validate provider preference page flow --- .../pages/ProviderPreferencePage.ts | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/playwright-tests/pages/ProviderPreferencePage.ts b/playwright-tests/pages/ProviderPreferencePage.ts index 7b6a53bc..1199a8bb 100644 --- a/playwright-tests/pages/ProviderPreferencePage.ts +++ b/playwright-tests/pages/ProviderPreferencePage.ts @@ -3,34 +3,47 @@ import { BasePage } from './BasePage'; export class ProviderPreferencePage extends BasePage { readonly page: Page; - readonly scheduleWithSafelite: Locator; - readonly scheduleWithOther: Locator; - readonly acknowledgeAdasButton: Locator; + readonly scheduleNowButton: Locator; + readonly findAnotherShopButton: Locator; + + readonly acknowledgeAdasCheckbox: Locator; readonly gotItButton: Locator; + readonly learnMoreLink: Locator; + readonly yesButton: Locator; + readonly noButton: Locator; readonly acknowledgeCheckbox: Locator; - readonly stateLawModalHeading: Locator; + readonly stateLawModalText: Locator; + readonly stateLawModalOkayButton: Locator; issPageValue = 'provider-preference'; constructor(page: Page) { super(page); this.page = page; - // this.scheduleWithSafelite = this.page.locator('div').filter({ hasText: /Schedule online with |Safelite AutoGlass/ }).first(); - this.scheduleWithSafelite = this.page.getByText(/Schedule online with|Safelite AutoGlass/).first(); - this.scheduleWithOther = this.page.getByText(/Find another shop|Choose my own shop/).first(); - this.acknowledgeAdasButton = this.page.getByLabel('I acknowledge that my vehicle'); + + this.scheduleNowButton = this.page.getByRole('button', { name: 'Schedule now' }); + this.findAnotherShopButton = this.page.getByRole('link', { name: 'Find another shop' }); + + //Recal modal + this.learnMoreLink = this.page.locator('#moreDetails'); + this.yesButton = this.page.getByRole('button', { name: 'Yes' }); // schedule with Safelite + this.noButton = this.page.getByRole('button', { name: 'No' }); // find another shop (TPA?) + this.acknowledgeAdasCheckbox = this.page.getByRole('checkbox', { name: 'tpaAcknowledgement' }); // shows up when user clicks no on recal modal + + this.gotItButton = this.page.getByRole('button', { name: 'Got it' }); this.acknowledgeCheckbox = this.page.locator('#tpaAcknowledgement'); - this.stateLawModalHeading = this.page.getByRole('heading').filter({ hasText: /.* State Law/}); + this.stateLawModalText = this.page.getByText(/law prohibits us from requiring you.*You have the right to select the motor vehicle repair shop of your choice\./is); + this.stateLawModalOkayButton = this.page.getByRole('button', { name: 'Okay' }) } async selectProvider(isSafelite = true) { if (isSafelite) { - await this.scheduleWithSafelite.click(); + await this.scheduleNowButton.click(); await this.nextPage(); } else { - await this.scheduleWithOther.waitFor({ state: 'visible' }); - await this.scheduleWithOther.click(); + await this.findAnotherShopButton.waitFor({ state: 'visible' }); + await this.findAnotherShopButton.click(); await this.continueButton.click(); //if(await this.acknowledgeAdasButton.isEnabled({timeout: 2500})){ // await this.acknowledgeAdasButton.click(); @@ -39,12 +52,20 @@ export class ProviderPreferencePage extends BasePage { //} } } - async acknowledgeRecalNotificaiton() { - await this.acknowledgeAdasButton.click(); - await this.gotItButton.click(); + async scheduleWithSafeliteADAS() { + await this.learnMoreLink.click(); + await this.yesButton.click(); + await this.continueButton.click(); + } + + async scheduleTPAWithAdas() { + await this.learnMoreLink.click(); + await this.noButton.click(); + await this.acknowledgeAdasCheckbox.click(); + await this.continueButton.click(); } async validateStateLawModalIsVisible() { - await expect.soft(this.stateLawModalHeading).toBeVisible(); + await expect.soft(this.stateLawModalText).toBeVisible(); } } \ No newline at end of file From 806f27bbd1166b4d71a8eddab5180b9b33459eb8 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:41:58 -0400 Subject: [PATCH 18/80] Update flow to cover essential and advanced clients --- .../pages/PolicyHolderDetailsPage.ts | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/playwright-tests/pages/PolicyHolderDetailsPage.ts b/playwright-tests/pages/PolicyHolderDetailsPage.ts index 47f11031..d37ba9cb 100644 --- a/playwright-tests/pages/PolicyHolderDetailsPage.ts +++ b/playwright-tests/pages/PolicyHolderDetailsPage.ts @@ -5,16 +5,59 @@ import { AddressForm } from './forms/AddressForm'; export class PolicyHolderDetailsPage extends BasePage { readonly page: Page; - readonly addressForm: AddressForm; + readonly firstNameTextBox: Locator; + readonly lastNameTextBox: Locator; + readonly addressForm: AddressForm; // double check if this is used for advanced flows ? + readonly addressInputBox: Locator; + readonly emailAddressTextBox: Locator; + readonly issPageValue = 'policy-holder-details'; constructor(page: Page) { super(page); this.page = page; + this.firstNameTextBox = page.locator('#firstNameField'); + this.lastNameTextBox = page.locator('#lastNameField'); this.addressForm = new AddressForm(page); + this.emailAddressTextBox = page.locator('#emailField'); + this.addressInputBox = page.locator('input[name="autocomplete"]'); // for essential flows } async fillCustomerDetails(customerDetails: ICustomerDetails){ - await this.addressForm.populateAddress(customerDetails); + // Check if firstName field is empty, then fill it + const firstNameValue = await this.firstNameTextBox.inputValue(); + if (!firstNameValue || firstNameValue.trim() === '') { + if (customerDetails.firstName) { + await this.firstNameTextBox.fill(customerDetails.firstName); + } + } + + // Check if lastName field is empty, then fill it + const lastNameValue = await this.lastNameTextBox.inputValue(); + if (!lastNameValue || lastNameValue.trim() === '') { + if (customerDetails.lastName) { + await this.lastNameTextBox.fill(customerDetails.lastName); + } + } + + // Fill email address for both essential and advanced flows + await this.emailAddressTextBox.fill(customerDetails.email); + + const addressValue = await this.addressInputBox.inputValue(); + + // For essential flows, when address fields are not pre-filled + if (!addressValue || addressValue.trim() === '') { + await this.addressInputBox.click(); + await this.addressInputBox.fill(customerDetails.address.street); + + // Wait for suggestions to load (Google Places has a slight delay) + await this.page.locator('.pac-item').first().waitFor({ state: 'visible', timeout: 5000 }); + + // Click the first result + await this.page.locator('.pac-item').first().click(); + + // Optional: wait for the input to be populated by Google Places + await this.addressInputBox.waitFor({ state: 'attached' }); + } } } \ No newline at end of file From ce2f640a5fe39c0484cdc90213f9d2e9269049d4 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 13:42:34 -0400 Subject: [PATCH 19/80] Update locators for policy vehicles page --- playwright-tests/pages/PolicyVehiclesPage.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/playwright-tests/pages/PolicyVehiclesPage.ts b/playwright-tests/pages/PolicyVehiclesPage.ts index 23ba54c7..8573709d 100644 --- a/playwright-tests/pages/PolicyVehiclesPage.ts +++ b/playwright-tests/pages/PolicyVehiclesPage.ts @@ -5,10 +5,14 @@ import { IVehicleDetails } from '@business-logic/types/CustomerDetails'; export class PolicyVehiclesPage extends BasePage { readonly page: Page; issPageValue = 'policy-vehicles'; + readonly addAnotherVehicleButton: Locator; + readonly selectVehicleOnPolicyButton: Locator; constructor(page: Page) { super(page); this.page = page; + this.addAnotherVehicleButton = this.page.getByText('Add another vehicle'); + this.selectVehicleOnPolicyButton = this.page.locator(`input[type="radio"][name="policyVehiclesQuestionOption"]`).first(); // this.validateURL(this.url); } @@ -21,8 +25,9 @@ export class PolicyVehiclesPage extends BasePage { const vehicleRegExp = new RegExp(`${vehicleDetails.year} .+ ${vehicleDetails.model}`, 'i'); await this.page.locator('label').filter({hasText: vehicleRegExp}).locator('div').click(); } - async selectVehicleNotListed(){ - await this.page.getByText('Vehicle not listed').click(); + + async addAnotherVehicle(){ + await this.page.getByText('Add another vehicle').click(); } async assertNonServiceableAlertBehavior(){ From 7481aff268fb0201428c35dbebda97b32ac3eb3f Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:16:53 -0400 Subject: [PATCH 20/80] Add OEM validation --- playwright-tests/pages/CoverageStatementPage.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index 3bce4330..0a8b2553 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -14,6 +14,7 @@ export class CoverageStatementPage extends BasePage { readonly headerForITAC: Locator; // For ITAC when coverage is found readonly headerForNoComp: Locator; // For NoComp when coverage is found readonly unverifiedCoverageHeader: Locator; // For essential flows when verifying deductible + readonly oemBullet: Locator; // For OEM bullet when OEM endorsement is present readonly continueSchedulingButton: Locator; // For ITAC/NoComp readonly continueButton: Locator; // For essential flows issPageValue = 'coverage-statement'; @@ -32,6 +33,7 @@ export class CoverageStatementPage extends BasePage { this.unverifiedCoverageHeader = this.page.getByRole('heading', { level: 5, name: "We’re verifying your coverage" }); this.headerForITAC = this.page.getByRole('heading', { name: 'Good news! Safelite\'s price is lower than your deductible' }); this.headerForNoComp = this.page.getByRole('heading', { name: 'Looks like your policy doesn\'t include comprehensive coverage.\nSafelite can still get you fixed up and safely back on the road.' }); + this.oemBullet = this.page.getByText('OEM (Original Equipment Manufacturer) glass replacement is included under your policy and will be installed in your vehicle.'); this.continueSchedulingButton = page.getByRole('button', { name: 'Continue scheduling' }); // continue button for ITAC/NoComp flow this.continueButton = page.getByRole('button', { name: 'Continue' }); // continue button for essential flow @@ -98,9 +100,14 @@ export class CoverageStatementPage extends BasePage { await this.continueSchedulingButton.click(); // happy path to scheduling for ITAC } - async handleNoCompFlow(){ + async handleNoCompFlow(){ await expect(this.headerForNoComp).toBeVisible(); // verify NoComp header is visible await this.continueSchedulingButton.click(); // happy path to scheduling for NoComp } + + async validateOEMBullet(){ + await expect(this.oemBullet).toBeVisible(); + } + } From 8cca6f5afc681d1f608b68e1c8a108bdb8e661c4 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:46:32 -0400 Subject: [PATCH 21/80] Add await so that PW can click on first time slot --- playwright-tests/pages/SchedulePage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 11645b5d..5c816ef9 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -58,7 +58,7 @@ export class SchedulePage extends BasePage { if (await this.inShopButton.isVisible() && appointmentDetails?.serviceLocation !== ServiceLocation.DropOff) { await this.inShopButton.click(); - (await this.getTimeSlot(1)).click(); + await (await this.getTimeSlot(1)).click(); } else if (appointmentDetails?.serviceLocation === ServiceLocation.DropOff) { await this.inShopButton.isVisible(); await this.inShopButton.click(); From b1bea6a0e871ee6a69e286ea1e5dfba2350c0b2d Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Thu, 12 Mar 2026 10:13:22 -0400 Subject: [PATCH 22/80] Add logic for Afterpay flow when order amount is low --- playwright-tests/pages/AfterpayPage.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index cf71e250..b6b9c0b9 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -1,6 +1,7 @@ import { Locator, Page } from "@playwright/test"; import { BasePage } from "./BasePage"; -import { IPaymentDetails } from "@business-logic/types/CustomerDetails"; +import { IClaimDetails, IPaymentDetails } from "@business-logic/types/CustomerDetails"; +import { ServicePackage } from "@business-logic/types/Enums"; export class AfterpayPage extends BasePage { readonly page: Page; @@ -33,9 +34,12 @@ export class AfterpayPage extends BasePage { await this.submitButton.click(); } - async executeAfterpayPayment(paymentDetails: IPaymentDetails) { + async executeAfterpayPayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) { await this.login(paymentDetails.password!); + if (claimDetails.policyDeductible === 50 && servicePackage === ServicePackage.GlassOnly) { + await this.confirmButton.click(); + } else { // For orders greater than $50, user will have 2 options to choose from: monthly with interest or biweekly without interest await this.selectAfterpayWithoutInterestButton.waitFor({ state: 'visible' }); @@ -46,9 +50,7 @@ export class AfterpayPage extends BasePage { await this.selectAfterpayWithInterestButton.click(); await this.continueAfterpayButton.click(); } - await this.confirmButton.click(); + } } - - } \ No newline at end of file From db55d5cf7d438dbf69287aaf7da4bf0b6bff59aa Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 18 Mar 2026 10:37:28 -0400 Subject: [PATCH 23/80] Update continue button locator to be more specific --- playwright-tests/pages/BasePage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index cdd6725d..6e0d2ece 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -17,7 +17,8 @@ export class BasePage { constructor(page: Page) { this.page = page; - this.continueButton = page.locator('button', { hasText: /get\s*started|continue/i }); + this.continueButton = page.locator('button[data-test-id="site-footer-main-button"]') + .and(page.locator('button', { hasText: /get\s*started|continue/i })); this.pageSpinner = page.getByRole('status'); this.buttonLoadSpin = page.getByRole('alert'); } From 02e0afe4f605f22f93dc86c4c403b5622bc0db2f Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 18 Mar 2026 10:40:06 -0400 Subject: [PATCH 24/80] Remove this mock response because its returning a 500 response --- playwright-tests/tests/mockResponses/mockResponsesConfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/tests/mockResponses/mockResponsesConfig.json b/playwright-tests/tests/mockResponses/mockResponsesConfig.json index f121da14..a5061acc 100644 --- a/playwright-tests/tests/mockResponses/mockResponsesConfig.json +++ b/playwright-tests/tests/mockResponses/mockResponsesConfig.json @@ -17,7 +17,6 @@ "coverage/api/v1/coverage/register-claim": "0002a_Advanced_Replace_Deductible_Client/coverage/api/v1/coverage/register-claim.json", "price/api/v1/price/order-items-with-itac-pricing": "0002a_Advanced_Replace_Deductible_Client/price/api/v1/price/order-items-with-itac-pricing.json", "location/api/v1/location/providers/75023/Replace/100/550036/true/CR00056707/DW01571GTNNOEM": "0002a_Advanced_Replace_Deductible_Client/location/api/v1/location/providers/75023/Replace/100/550036/true/CR00056707/DW01571GTNNOEM.json", - "schedule/api/v1/schedule/shop-time-slots": "0002a_Advanced_Replace_Deductible_Client/schedule/api/v1/schedule/shop-time-slots.json", "location/api/v1/location/alert-reasons/01813": "0002a_Advanced_Replace_Deductible_Client/location/api/v1/location/alert-reasons/01813.json", "parts/api/v1/parts/rain-repel": "0002a_Advanced_Replace_Deductible_Client/parts/api/v1/parts/rain-repel.json", "price/api/v1/price/combined-quote": "0002a_Advanced_Replace_Deductible_Client/price/api/v1/price/combined-quote.json" From 303fc247359dbbb4a52a9b1af2d1198c37f80f86 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 18 Mar 2026 10:42:38 -0400 Subject: [PATCH 25/80] Fix logic for scheduling appointments --- playwright-tests/pages/SchedulePage.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 5c816ef9..e48a3a8a 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -56,13 +56,20 @@ export class SchedulePage extends BasePage { // Select in shop and schedule async scheduleInShop(appointmentDetails?: IAppointmentDetails){ - if (await this.inShopButton.isVisible() && appointmentDetails?.serviceLocation !== ServiceLocation.DropOff) { + if (await this.inShopButton.isVisible()) { + await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); await this.inShopButton.click(); - await (await this.getTimeSlot(1)).click(); + await (await this.getFirstNonDropOffTimeSlot()).click(); + } else if (await this.inShopButton.isHidden() && appointmentDetails?.serviceLocation === ServiceLocation.InShop) { + await (await this.getFirstNonDropOffTimeSlot()).click(); } else if (appointmentDetails?.serviceLocation === ServiceLocation.DropOff) { - await this.inShopButton.isVisible(); + if (await this.inShopButton.isVisible()){ + await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); await this.inShopButton.click(); - await this.dropOffButton.isVisible() ? await this.dropOffButton.click() : null; + } else { + await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 }); + await this.dropOffButton.isVisible() ? await this.dropOffButton.click() : await (await this.getFirstNonDropOffTimeSlot()).click(); // drop off is available in the morning for same day so this is here to avoid flaky tests + } } else { await this.selectFirstAvailableTime(); } @@ -89,7 +96,9 @@ export class SchedulePage extends BasePage { } } - async getTimeSlot(index: number): Promise { - return this.page.locator('.time-slot-button').nth(index); + async getFirstNonDropOffTimeSlot(): Promise { + const timeSlots = this.page.locator('.time-slot-button'); + const nonDropOff = timeSlots.filter({ hasNotText: /Drop & Go/i }); + return nonDropOff.first(); } } \ No newline at end of file From df9f24aaa3871c46e24a019d0fff8198509837f1 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 18 Mar 2026 10:44:36 -0400 Subject: [PATCH 26/80] Update service package logic --- playwright-tests/pages/ServicePackagesPage.ts | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 9e8dd866..2b9d9e04 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -6,21 +6,36 @@ export class ServicePackagesPage extends BasePage { readonly page: Page; readonly standardPackageButton: Locator; readonly premiumPackageButton: Locator; - readonly glassOnlyButton: Locator; + readonly glassOnlyPackageButton: Locator; + readonly wiperModal: Locator; + readonly wiperModalCloseButton: Locator; + readonly continueButton: Locator; issPageValue = 'service-packages'; constructor(page: Page) { super(page); this.page = page; - this.standardPackageButton = this.page.locator('li').filter({ hasText: 'Standard' }); - this.premiumPackageButton = this.page.locator('li').filter({ hasText: 'Premium' }); - this.glassOnlyButton = this.page.locator('li').filter({ hasText: 'Glass service' }); + this.standardPackageButton = this.page.locator('label[for="ServicePackageQuestion-TierTwo"]'); // standard package + this.premiumPackageButton = this.page.locator('label[for="ServicePackageQuestion-TierThree"]'); // premium package + this.glassOnlyPackageButton = this.page.locator('label[for="ServicePackageQuestion-TierOne"]'); // glass only package + this.wiperModal = this.page.locator('#FrontWiperModal #modalbtn'); + this.wiperModalCloseButton = this.page.locator('#FrontWiperModal').getByLabel('Close') + this.continueButton = this.page.getByRole('button', { name: 'Continue' }); // this.validateURL(this.url); } async selectServicePackage(servicePackage: ServicePackage){ - await this.page.getByText(servicePackage).click(); - return (`${await this.page.locator('li').filter({ hasText: 'Premium' }).locator('[class="pricing-info"]').allInnerTexts()}`); - //return (`${await this.page.getByText(servicePackage).locator('[class="pricing-info"]').allInnerTexts()}`); + if (servicePackage === ServicePackage.Standard) { + await this.standardPackageButton.check(); + } else if (servicePackage === ServicePackage.Premium) { + await this.premiumPackageButton.check(); + if (await this.wiperModal.isVisible()) { + await this.wiperModal.waitFor({ state: 'visible', timeout: 5000 }); + await this.wiperModalCloseButton.click(); + } + } else if (servicePackage === ServicePackage.GlassOnly) { + await this.glassOnlyPackageButton.check(); + } + await this.continueButton.click(); } } \ No newline at end of file From 9e6fdf470708958940711d7bcde2a51757ced548 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 19 Mar 2026 10:51:04 -0400 Subject: [PATCH 27/80] Update enums used for validations --- .../business-logic/types/Enums.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/playwright-tests/business-logic/types/Enums.ts b/playwright-tests/business-logic/types/Enums.ts index 13557a92..a1ce16de 100644 --- a/playwright-tests/business-logic/types/Enums.ts +++ b/playwright-tests/business-logic/types/Enums.ts @@ -8,7 +8,7 @@ export enum DamageType { Theft = 'Attempted theft or theft', Hail = 'Hailstorm', HurricaneStorm = 'Hurricane/storm', - Collision = 'Collision', + OtherWeather = 'Other weather', Object = 'Object hit glass', Other = 'Other/unknown' } @@ -90,7 +90,11 @@ export enum PartQuestionType { RearWindowColor = 'Rear-Stationary', LeatherSeats = 'question-0-1', DriverSideColor = 'Driver-SideDoor', - LaneKeepAssist = 'question-0-1' + LaneKeepAssist = 'question-0-1', + GeneralQuestion1 = "question-0-1", + GeneralQuestion2 = "question-0-2", + GeneralQuestion3 = "question-0-3", + GeneralQuestion4 = "question-0-4" } export enum PaymentType{ @@ -170,4 +174,27 @@ export enum Authentication { RSATokenEncParams = 'RSATokenEncParams', RSATokenEncParamsOneTimeUse = 'RSATokenEncParamsOneTimeUse', Unknown = 'Unknown', +} + +// TODO: Add validations for the percentages on each page like url validations +export enum ProgressBarPercentage { + PolicyVehiclesPage = '20%', + PolicyHolderDetailsPage = '30%', + VehicleSelectionPage = '25%', + PolicyEndorsementsPage = '20%', + VehicleDamagePage = '35%', + VehicleLookupPage = '45%', + PartQuestionsPage = '40%', + MoldingQuestionsPage = '45%', + VehiclePartsPage = '45%', + CapabilityQuestionsPage = '45%', + CoverageStatementPage = '55%', + ProviderPreferencePage = '60%', + SchedulePage = '70%', + ContactDetailsPage = '80%', + ServicePackagesPage = '85%', + PaymentMethodPage = '90%', + OrderConfirmationPage = '100%', + TpaSearchPage = '80%', + TpaSubmitPage = '95%', } \ No newline at end of file From 623c40735770b1213dbea5f2e0f2ec41137c4105 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 19 Mar 2026 10:52:00 -0400 Subject: [PATCH 28/80] Remove bailout flag that is no longer valid --- playwright-tests/business-logic/types/IBailoutFlags.ts | 1 - playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts | 1 - .../tests/advanced/0026a_NoDeductibleAdasBailout5.ts | 3 +-- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/playwright-tests/business-logic/types/IBailoutFlags.ts b/playwright-tests/business-logic/types/IBailoutFlags.ts index 790d4209..2a5ec3c7 100644 --- a/playwright-tests/business-logic/types/IBailoutFlags.ts +++ b/playwright-tests/business-logic/types/IBailoutFlags.ts @@ -1,6 +1,5 @@ export default interface IBailoutFlags { isVehicleSelectBailout: boolean, - isDoNotSeeMyShopBailout: boolean, isTpaNotEnabledBailout: boolean, isRequestCallbackBailout: boolean, isHeavyTruckVehicleBailout: boolean, diff --git a/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts b/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts index 40bdd106..9f9e2f13 100644 --- a/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts +++ b/playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts @@ -16,7 +16,6 @@ const essentialDoNotSeeShopData: Partial = { partQuestions: undefined, isSafelite: false, bailoutFlags: { - isDoNotSeeMyShopBailout: true }, servicePackage: faker.helpers.enumValue(ServicePackage), customerDetails: { diff --git a/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts b/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts index ebfe3c79..c70be807 100644 --- a/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts +++ b/playwright-tests/tests/advanced/0026a_NoDeductibleAdasBailout5.ts @@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0026a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0026a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0026a', customerDetails, policyNumber); const advancedScenario0026aData: Partial = { @@ -35,7 +35,6 @@ const advancedScenario0026aData: Partial = { isRecalNotification: true, endorsements: undefined, bailoutFlags: { - isDoNotSeeMyShopBailout: true }, vehiclePartQuestions: [ ], From 4525ce95cebeb1bfc5a4e5e11e103037a849f2b4 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 19 Mar 2026 10:52:44 -0400 Subject: [PATCH 29/80] Add TPA submit button for global continue locator --- playwright-tests/pages/BasePage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/pages/BasePage.ts b/playwright-tests/pages/BasePage.ts index 6e0d2ece..1c8ba1da 100644 --- a/playwright-tests/pages/BasePage.ts +++ b/playwright-tests/pages/BasePage.ts @@ -18,7 +18,7 @@ export class BasePage { constructor(page: Page) { this.page = page; this.continueButton = page.locator('button[data-test-id="site-footer-main-button"]') - .and(page.locator('button', { hasText: /get\s*started|continue/i })); + .and(page.locator('button', { hasText: /get\s*started|continue|submit/i })); this.pageSpinner = page.getByRole('status'); this.buttonLoadSpin = page.getByRole('alert'); } From bd11b53413ac068fd99bd4b9f522953d3fa00882 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 19 Mar 2026 10:53:31 -0400 Subject: [PATCH 30/80] Clean up locators and validation --- .../pages/ProviderPreferencePage.ts | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/playwright-tests/pages/ProviderPreferencePage.ts b/playwright-tests/pages/ProviderPreferencePage.ts index 1199a8bb..58525955 100644 --- a/playwright-tests/pages/ProviderPreferencePage.ts +++ b/playwright-tests/pages/ProviderPreferencePage.ts @@ -8,10 +8,12 @@ export class ProviderPreferencePage extends BasePage { readonly acknowledgeAdasCheckbox: Locator; readonly gotItButton: Locator; + readonly tpaRecalModal: Locator; readonly learnMoreLink: Locator; readonly yesButton: Locator; readonly noButton: Locator; readonly acknowledgeCheckbox: Locator; + readonly tpaRecalModalContinueButton: Locator; readonly stateLawModalText: Locator; readonly stateLawModalOkayButton: Locator; issPageValue = 'provider-preference'; @@ -24,14 +26,14 @@ export class ProviderPreferencePage extends BasePage { this.findAnotherShopButton = this.page.getByRole('link', { name: 'Find another shop' }); //Recal modal + this.tpaRecalModal = this.page.locator('#TPARecalModal'); this.learnMoreLink = this.page.locator('#moreDetails'); - this.yesButton = this.page.getByRole('button', { name: 'Yes' }); // schedule with Safelite - this.noButton = this.page.getByRole('button', { name: 'No' }); // find another shop (TPA?) - this.acknowledgeAdasCheckbox = this.page.getByRole('checkbox', { name: 'tpaAcknowledgement' }); // shows up when user clicks no on recal modal + this.yesButton = this.page.getByText('Yes', { exact: true }); // schedule with Safelite + this.noButton = this.page.getByText('No', { exact: true }); // schedule with TPA + this.acknowledgeAdasCheckbox = this.page.getByRole('checkbox', { name: /I acknowledge/i }) + this.tpaRecalModalContinueButton = this.page.locator('#modalbtn', { hasText: 'Continue' }); - - this.gotItButton = this.page.getByRole('button', { name: 'Got it' }); - this.acknowledgeCheckbox = this.page.locator('#tpaAcknowledgement'); + // State law modal this.stateLawModalText = this.page.getByText(/law prohibits us from requiring you.*You have the right to select the motor vehicle repair shop of your choice\./is); this.stateLawModalOkayButton = this.page.getByRole('button', { name: 'Okay' }) } @@ -44,25 +46,22 @@ export class ProviderPreferencePage extends BasePage { else { await this.findAnotherShopButton.waitFor({ state: 'visible' }); await this.findAnotherShopButton.click(); - await this.continueButton.click(); - //if(await this.acknowledgeAdasButton.isEnabled({timeout: 2500})){ - // await this.acknowledgeAdasButton.click(); - // await this.gotItButton.click(); - // await this.page.waitForTimeout(1000); - //} + await this.tpaRecalModal.waitFor({ state: 'visible' }); + await this.scheduleTPAWithAdas(); } } + async scheduleWithSafeliteADAS() { await this.learnMoreLink.click(); await this.yesButton.click(); - await this.continueButton.click(); + await this.tpaRecalModalContinueButton.click(); } async scheduleTPAWithAdas() { await this.learnMoreLink.click(); await this.noButton.click(); await this.acknowledgeAdasCheckbox.click(); - await this.continueButton.click(); + await this.tpaRecalModalContinueButton.click(); } async validateStateLawModalIsVisible() { From b2f1d2d23a421907e848218e0c4b8fd441dd15e5 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 19 Mar 2026 10:53:55 -0400 Subject: [PATCH 31/80] Fix license plate validation --- playwright-tests/pages/VehicleLookupLicensePage.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/playwright-tests/pages/VehicleLookupLicensePage.ts b/playwright-tests/pages/VehicleLookupLicensePage.ts index 433ed849..926be302 100644 --- a/playwright-tests/pages/VehicleLookupLicensePage.ts +++ b/playwright-tests/pages/VehicleLookupLicensePage.ts @@ -16,7 +16,7 @@ export class VehicleLookupLicensePage extends BasePage { this.licensePlateNumTextBox = page.getByRole('textbox', { name: 'License plate number' }); this.licensePlateStateDrpDwn = page.getByRole('combobox', { name: 'License plate state' }); this.plateNoMatchError = page.getByText('Your license plate didn’t return a VIN match.Please re-enter the information'); - this.plateMismatchAlert = page.getByRole('alert').locator('div'); + this.plateMismatchAlert = page.getByText('We found a windshield, but the VIN associated with the license plate you provided is for a'); } async enterPlateDetails(vehicleDetails: IVehicleDetails, isVehicleLookupValidations = false) { @@ -26,6 +26,7 @@ export class VehicleLookupLicensePage extends BasePage { await this.continueButton.click({ timeout: 1000 }); await expect(this.plateNoMatchError).toBeVisible(); await this.licensePlateNumTextBox.fill(vehicleDetails.licensePlateNumber[1]); + await this.licensePlateStateDrpDwn.selectOption(vehicleDetails.licensePlateState![1]); await this.continueButton.click({ timeout: 1000 }); await expect(this.plateMismatchAlert).toBeVisible(); await this.licensePlateNumTextBox.fill(vehicleDetails.licensePlateNumber[2]); From e25466cd0790827a7a3e7262f206bac13e3bd573 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:08:44 -0400 Subject: [PATCH 32/80] Remove unused bailout code --- playwright-tests/business-logic/types/Enums.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/business-logic/types/Enums.ts b/playwright-tests/business-logic/types/Enums.ts index a1ce16de..25b4f9a8 100644 --- a/playwright-tests/business-logic/types/Enums.ts +++ b/playwright-tests/business-logic/types/Enums.ts @@ -141,7 +141,6 @@ export enum BailoutCode { VehicleNotFound, VehicleLookupError, CoverageStatementInvalidState, - DoNotSeeMyShop, PricingResponseError, TPANotEnabled, RequestCallback, From e537e41c9817aac9eb1577d2ac60e7d485588020 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Fri, 20 Mar 2026 10:32:40 -0400 Subject: [PATCH 33/80] Add and fix validation for TPA flow --- playwright-tests/pages/TpaConfirmationPage.ts | 20 +++++++++++--- playwright-tests/pages/TpaSearchPage.ts | 13 +++------- playwright-tests/pages/TpaSubmitPage.ts | 26 ++++++++++++++++--- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/playwright-tests/pages/TpaConfirmationPage.ts b/playwright-tests/pages/TpaConfirmationPage.ts index fb0460e2..eda89d42 100644 --- a/playwright-tests/pages/TpaConfirmationPage.ts +++ b/playwright-tests/pages/TpaConfirmationPage.ts @@ -4,16 +4,30 @@ import { BasePage } from './BasePage'; export class TpaConfirmationPage extends BasePage { readonly page: Page; readonly successMessage: Locator; + readonly shopConfirmationMessage: Locator; readonly issPageValue = 'tpa-confirmation'; constructor(page: Page) { super(page); this.page = page; - this.successMessage = page.getByText('Success'); + this.successMessage = page.getByText('Thank you for submitting your claim; there is one final step.'); + this.shopConfirmationMessage = page.locator('div.subheader').first() + } + + async getTpaShopSelectedName() { + const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')')); + const tpaShopSelectedName = sessionStorage.serviceLocation.provider.companyName; + return tpaShopSelectedName; } async validateSuccessMessage() { - await expect(this.successMessage).toBeVisible(); - } + const shopConfirmationMessageText = await this.shopConfirmationMessage.textContent(); + const tpaShopSelectedNameText = await this.getTpaShopSelectedName(); + await this.successMessage.waitFor({ state: 'visible' }); + await expect(this.successMessage).toBeVisible(); + expect.soft(shopConfirmationMessageText?.toLowerCase()).toContain( + ('Your information has been sent to' + ' ' + tpaShopSelectedNameText).toLowerCase() + ); + } } \ No newline at end of file diff --git a/playwright-tests/pages/TpaSearchPage.ts b/playwright-tests/pages/TpaSearchPage.ts index 8f4b8639..15d5e851 100644 --- a/playwright-tests/pages/TpaSearchPage.ts +++ b/playwright-tests/pages/TpaSearchPage.ts @@ -4,22 +4,15 @@ import { BasePage } from './BasePage'; export class TpaSearchPage extends BasePage { readonly page: Page; readonly firstLocationButton: Locator; - readonly doNotSeeMyShopButton: Locator; + //readonly doNotSeeMyShopButton: Locator; issPageValue = 'tpa-search'; constructor(page: Page) { super(page); this.page = page; - // this.firstLocationButton = page.locator("fieldset[aria-labelledby='chooseShop']/span").first(); - this.firstLocationButton = page.locator("#buttonLabelSpan").first(); - - this.doNotSeeMyShopButton = page.getByRole('link', { name: 'I don\'t see my shop' }); + this.firstLocationButton = page.locator('div.button-content').first(); } - - async selectDoNotSeeMyShop() { - await this.doNotSeeMyShopButton.click() - } - + async selectFirstLocation() { await this.firstLocationButton.click(); } diff --git a/playwright-tests/pages/TpaSubmitPage.ts b/playwright-tests/pages/TpaSubmitPage.ts index e563af7a..1726a75e 100644 --- a/playwright-tests/pages/TpaSubmitPage.ts +++ b/playwright-tests/pages/TpaSubmitPage.ts @@ -1,18 +1,38 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; +import { IClaimDetails } from '@business-logic/types/CustomerDetails'; export class TpaSubmitPage extends BasePage { readonly page: Page; readonly deductible: Locator; + readonly recalAlert: Locator; + readonly learnMoreLink: Locator; + readonly recalModal: Locator; + readonly closeRecalModalButton: Locator; issPageValue = 'tpa-submit'; constructor(page: Page) { super(page); this.page = page; - this.deductible = page.getByText('Deductible $'); + this.recalAlert = page.getByRole('alert').filter({ hasText: 'Some vehicle safety features may not work properly.'}); + this.learnMoreLink = page.getByRole('link', { name: 'Learn more' }); + this.recalModal = page.getByRole('dialog').filter({ hasText: 'IMPORTANT SERVICE REQUIRED' }).first(); + this.closeRecalModalButton = page.locator('.modalbtn', { hasText: 'Close' }); + this.deductible = page.locator('span#deductible-value, span.deductible-value'); } - async validateDeductible(expDeductible) { - await expect(this.deductible).toContainText(expDeductible); + /* TODO: Fix flakiness of recal alert + /*async validateRecalAlert() { + await this.recalAlert.waitFor({ state: 'visible', timeout: 5000 }); + await this.learnMoreLink.click(); + await this.recalModal.waitFor({ state: 'visible', timeout: 5000 }); + await this.recalModal.getByRole('button', { name: 'Close' }).click(); + + }*/ + + async validateDeductible(claimDetails: IClaimDetails) { + await expect(this.deductible).toContainText(claimDetails.policyDeductible.toLocaleString()); } + + // TODO: Validate change preferred shop and contact details flows } \ No newline at end of file From 11927752544fe7388f704763cf90816edeb0b388 Mon Sep 17 00:00:00 2001 From: JennyNou <167806377+JennyNou@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:13:47 -0400 Subject: [PATCH 34/80] Fix logic for Afterpay payment flow --- playwright-tests/pages/AfterpayPage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index b6b9c0b9..2896741a 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -37,10 +37,10 @@ export class AfterpayPage extends BasePage { async executeAfterpayPayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) { await this.login(paymentDetails.password!); - if (claimDetails.policyDeductible === 50 && servicePackage === ServicePackage.GlassOnly) { + if (await this.confirmButton.isVisible()) { await this.confirmButton.click(); } else { - // For orders greater than $50, user will have 2 options to choose from: monthly with interest or biweekly without interest + // For some orders, user will have 2 options to choose from: monthly with interest or biweekly without interest await this.selectAfterpayWithoutInterestButton.waitFor({ state: 'visible' }); if (await this.selectAfterpayWithoutInterestButton.isVisible()) { From 2dfdbb6d1d6f4bd0aaf83a2ee17a173deabcafa5 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Sun, 22 Mar 2026 22:48:41 -0400 Subject: [PATCH 35/80] Fix flakiness for Afterpay sandbox flow --- playwright-tests/pages/AfterpayPage.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index 2896741a..2eb7e690 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -37,9 +37,11 @@ export class AfterpayPage extends BasePage { async executeAfterpayPayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) { await this.login(paymentDetails.password!); - if (await this.confirmButton.isVisible()) { - await this.confirmButton.click(); - } else { + try { + await this.confirmButton.waitFor({ state: 'visible', timeout: 5000 }); + await this.confirmButton.isVisible() + await this.confirmButton.click(); + } catch { // For some orders, user will have 2 options to choose from: monthly with interest or biweekly without interest await this.selectAfterpayWithoutInterestButton.waitFor({ state: 'visible' }); From 5682987c066ee951c925959ad8b910ce4568f6f8 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Sun, 22 Mar 2026 22:49:25 -0400 Subject: [PATCH 36/80] Wip for order confirmation validations --- .../pages/OrderConfirmationPage.ts | 119 ++++++++++++------ 1 file changed, 83 insertions(+), 36 deletions(-) diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 1c4a5962..9a776587 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -14,14 +14,15 @@ export class OrderConfirmationPage extends BasePage { readonly serviceText: Locator; readonly workOrderNumberText: Locator; readonly emailText: Locator; - - readonly apptDateText: Locator; - //readonly amountDueText: Locator; + readonly appointmentTypeText: Locator; + readonly apptDetailsText: Locator; readonly deductibleText: Locator; readonly subtotalText: Locator; + readonly totalAmountDueText: Locator; + readonly amountPaidText: Locator; readonly finalAmountDue: Locator; readonly cartServicePackageText: Locator; - readonly VerifiyingCoverageDeductibleText: Locator; + readonly unverifiedCoverageDeductibleText: Locator; issPageValue = 'order-confirmation'; @@ -35,14 +36,17 @@ export class OrderConfirmationPage extends BasePage { this.emailText = this.page.locator('[class="subheader-text"]', { hasText: 'Your appointment is on Safelite\'s schedule and your confirmation email is on the way.' }); this.serviceText = this.page.locator('[class="service-description confirmation-section"]'); this.workOrderNumberText = this.page.locator('[class="work-order-description"]'); + this.appointmentTypeText = this.page.locator('[class="appointment-title"]'); + this.apptDetailsText = this.page.locator('[class="appointment-details confirmation-section"]'); - this.apptDateText = this.page.locator('[class="appointment-details confirmation-section"]'); - + // Order details section this.deductibleText = this.page.locator('span#deductible-value, span.deductible-value'); - this.subtotalText = this.page.locator("#subtotal-value"); + this.subtotalText = this.page.locator('#subtotal-value'); + this.totalAmountDueText = this.page.locator('#total-value'); + this.amountPaidText = this.page.locator('#amount-paid-value'); this.finalAmountDue = this.page.locator('#bottom-amount-due-value'); this.cartServicePackageText = this.page.locator('.cart-item-list'); - this.VerifiyingCoverageDeductibleText = this.page.locator('span.price cart-item', { hasText: 'Verifying coverage' }); + this.unverifiedCoverageDeductibleText = this.page.locator('span#deductible-value', { hasText: 'Verifying coverage' }); } async validateOrderConfirmationPage(testData: Partial) { @@ -51,36 +55,58 @@ export class OrderConfirmationPage extends BasePage { isNoComp, isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy } = testData; await expect.soft(this.successHeader).toBeVisible(); - - await this.serviceText.waitFor({ state: "visible" }); await this.logOrderNumber(); // Grab text const serviceTextValue = await this.serviceText.textContent(); - const apptDateValue = await this.apptDateText.textContent(); - const emailTextValue = await this.emailText.textContent(); - const servicePackageValue = (claimDetails!.policyDeductible >= 0) ? await this.cartServicePackageText.textContent(): null; - //const amountDueValue = await this.amountDueText.textContent(); - const deductibleTextValue = (isItac || isNoComp) ? null : await this.deductibleText.textContent(); - const subtotalTextValue = (claimDetails!.policyDeductible >= 0) ? await this.subtotalText.textContent() : null; - const finalAmountDueValue = (claimDetails!.policyDeductible >= 0) ? await this.finalAmountDue.textContent() : null; + const apptDetailsValue = await this.apptDetailsText.textContent(); + const appointmentTypeValue = await this.appointmentTypeText.textContent(); - // Extract service package price - // const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', '')); + // Validate header for appointment details matches inshop or mobile + await this.validateAppointmentTypeHeader(); + // Derived conditions + const isItacOrNoComp = !!(isItac || isNoComp); + const isUnverified = !isPolicyFound; + const hasPremiumOrStandard = + claimDetails!.policyDeductible >= 0 && + (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard); + + const payAtService = + claimDetails!.policyDeductible >= 0 && + paymentDetails?.paymentType === PaymentType.PayAtService; + + const paidWithPIA = + claimDetails!.policyDeductible >= 0 && + paymentDetails?.paymentType !== PaymentType.PayAtService; + + // Helper: grab textContent only when the element is expected on-screen + const textIf = async (condition: boolean, locator: Locator) => + condition ? await locator.textContent() : null; + + // Grab text content for elements that are expected on-screen + const servicePackageValue = await textIf(hasPremiumOrStandard, this.cartServicePackageText); + const deductibleTextValue = await textIf(!isItacOrNoComp, this.deductibleText); + const subtotalTextValue = await textIf(hasPremiumOrStandard || isItacOrNoComp, this.subtotalText); + const totalAmountDueValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService), this.totalAmountDueText); + const amountPaidTextValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService), this.totalAmountDueText); + const finalAmountDueValue = await textIf(hasPremiumOrStandard || isItacOrNoComp || isUnverified, this.finalAmountDue); + + // General Validations expect.soft(serviceTextValue).toContain(`${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`); //expect.soft(apptDateValue).toContain(customerDetails!.apptDate); //expect.soft(emailTextValue).toContain(customerDetails!.email); // Service package validations - if ((servicePackage === ServicePackage.Premium && testData.isReplace === true) || servicePackage === ServicePackage.Standard) { + /* if ((servicePackage === ServicePackage.Premium && testData.isReplace === true) || (servicePackage === ServicePackage.Standard && testData.isReplace === true)) { expect.soft(servicePackageValue).toContain('Front advanced beam blades'); - } - if (servicePackage === ServicePackage.Premium) { + } else if (servicePackage === ServicePackage.Premium) { expect.soft(servicePackageValue).toContain('Safelite Rain Repellent Treatment'); - } + } else { + expect.soft(Number.parseFloat(deductibleTextValue!.split('$')[1].replaceAll(',', ''))).toEqual(claimDetails!.policyDeductible); + } commenting out needs reworked for ITAC/no comp flows*/ // Price validations /*if (servicePackage === ServicePackage.GlassOnly) { @@ -92,34 +118,40 @@ export class OrderConfirmationPage extends BasePage { if (isPolicyFound && (claimDetails!.policyDeductible === 0 && (servicePackage === ServicePackage.GlassOnly))) { const zeroDeductibleText = await this.deductibleText.textContent(); expect.soft(zeroDeductibleText).toEqual('$0.00'); - } else if (isPolicyFound && (finalAmountDueValue !== null && subtotalTextValue !== null)) { - // Extract numbers - //const amountDueAmt = Number.parseFloat(amountDueValue!.split('$')[1].replaceAll(',', '')); + } else if (isPolicyFound && claimDetails!.policyDeductible > 0 && !(isItac || isNoComp) && servicePackage === ServicePackage.GlassOnly){ + expect.soft(deductibleTextValue).not.toBeNull(); const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : 0; - const subtotalAmt = Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', '')); - subtotalTextValue?.replaceAll(',', '') - const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')); + expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible); + + } else if ((isPolicyFound || (isItac || isNoComp && paymentDetails!.paymentType === PaymentType.PayAtService) && (finalAmountDueValue !== null && subtotalTextValue !== null))) { + // Extract numbers + const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : null; + + const subtotalAmt = subtotalTextValue ? Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', '')) : null; + //subtotalTextValue?.replaceAll(',', '') + + //const totalAmountDueAmt = Number.parseFloat(totalAmountDueValue!.split('$')[1].replaceAll(',', '')); + const finalAmountDueAmt = finalAmountDueValue ? Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')) : null; if (!(isItac || isNoComp)) { - //expect.soft(subtotalAmt).toEqual(claimDetails!.policyDeductible + servicePackageAmt); expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible); } else { expect.soft(subtotalAmt).toBeGreaterThan(0); - expect.soft(deductibleAmt).toEqual(0); + //expect.soft(deductibleAmt).toEqual(0); } if ((paymentDetails!.paymentType === PaymentType.PayAtService) && (claimDetails!.policyDeductible >= 0 && (servicePackage === ServicePackage.Standard || servicePackage === ServicePackage.Premium))) { - // Verify amount due > 0 - //expect.soft(amountDueAmt).toBeGreaterThan(0); expect.soft(finalAmountDueAmt).toBeGreaterThan(0); } else { - // Verify amount due 0 - //expect.soft(amountDueAmt).toEqual(0); + const totalAmountDueAmt = Number.parseFloat(totalAmountDueValue!.split('$')[1].replaceAll(',', '')); + const amountPaidValue = Number.parseFloat(amountPaidTextValue!.split('$')[1].replaceAll(',', '')); + // Verify amount due 0 usually when PIA is selected + expect.soft(amountPaidValue).toEqual(totalAmountDueAmt); expect.soft(finalAmountDueAmt).toEqual(0); } } else { // essential flows will have deductible and amount due as "Verifying coverage" - const verifyingCoverageText = this.VerifiyingCoverageDeductibleText; + const verifyingCoverageText = this.unverifiedCoverageDeductibleText; expect.soft(verifyingCoverageText).toContainText('Verifying coverage'); expect.soft(finalAmountDueValue).toEqual('Verifying coverage'); @@ -133,6 +165,21 @@ export class OrderConfirmationPage extends BasePage { console.log(`SessionStorage Work Order Number:${workOrderNumber}`); }); } + + async validateAppointmentTypeHeader() { + const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')')); + const appointmentType = sessionStorage.serviceLocation.appointmentType; + const appointmentTypeValue = await this.appointmentTypeText.textContent(); + const apptDetailsValue = await this.apptDetailsText.textContent(); + + if (appointmentTypeValue?.includes('going to a Safelite shop') && apptDetailsValue?.includes('Drop off before 9:30 AM')) { + expect.soft(appointmentType).toEqual('Dropoff'); + } else if (appointmentTypeValue?.includes('going to a Safelite shop')) { + expect.soft(appointmentType).toEqual('Inshop'); + } else { + expect.soft(appointmentType).toEqual('Mobile'); + } + } async validateURL() { let pass = false; From bc5331bd65afef29d51a0d57015d97ad8e8c0780 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Sun, 22 Mar 2026 22:49:59 -0400 Subject: [PATCH 37/80] Fix for Afterpay flakiness --- playwright-tests/pages/PaymentMethodPage.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playwright-tests/pages/PaymentMethodPage.ts b/playwright-tests/pages/PaymentMethodPage.ts index ace2f8ac..c715e02f 100644 --- a/playwright-tests/pages/PaymentMethodPage.ts +++ b/playwright-tests/pages/PaymentMethodPage.ts @@ -1,7 +1,7 @@ import { expect, type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; -import { IPaymentDetails } from '@business-logic/types/CustomerDetails'; -import { PaymentType } from '@business-logic/types/Enums'; +import { IClaimDetails, IPaymentDetails } from '@business-logic/types/CustomerDetails'; +import { PaymentType, ServicePackage } from '@business-logic/types/Enums'; import { PaymentPage } from './PaymentPage'; import { AfterpayPage } from './AfterpayPage'; import { PaypalPage } from './PaypalPage'; @@ -40,7 +40,7 @@ export class PaymentMethodPage extends BasePage { this.submitButton = this.page.getByRole('button', { name: 'Submit' }); } - async executePayment(paymentDetails: IPaymentDetails) { + async executePayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) { const browserContext = this.page.context(); switch (paymentDetails.paymentType) { @@ -67,7 +67,7 @@ export class PaymentMethodPage extends BasePage { const afterpayPage = new AfterpayPage(afterpayPopup); // Execute payment - await afterpayPage.executeAfterpayPayment(paymentDetails); + await afterpayPage.executeAfterpayPayment(paymentDetails, claimDetails, servicePackage); break; case PaymentType.PayAtService: From 1941bd8eb07e6b7c6bcd45abfcc9ed88befbbecb Mon Sep 17 00:00:00 2001 From: JennyNou Date: Sun, 22 Mar 2026 22:51:35 -0400 Subject: [PATCH 38/80] Fix validation for inshop and dropoff --- playwright-tests/pages/SchedulePage.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index e48a3a8a..60dd87d1 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -56,16 +56,19 @@ export class SchedulePage extends BasePage { // Select in shop and schedule async scheduleInShop(appointmentDetails?: IAppointmentDetails){ - if (await this.inShopButton.isVisible()) { + if (appointmentDetails?.serviceLocation === ServiceLocation.InShop) { await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); + await this.inShopButton.isVisible() await this.inShopButton.click(); await (await this.getFirstNonDropOffTimeSlot()).click(); - } else if (await this.inShopButton.isHidden() && appointmentDetails?.serviceLocation === ServiceLocation.InShop) { + } else if (await this.inShopButton.isHidden()) { await (await this.getFirstNonDropOffTimeSlot()).click(); } else if (appointmentDetails?.serviceLocation === ServiceLocation.DropOff) { if (await this.inShopButton.isVisible()){ await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); await this.inShopButton.click(); + await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 }); + await this.dropOffButton.click() } else { await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 }); await this.dropOffButton.isVisible() ? await this.dropOffButton.click() : await (await this.getFirstNonDropOffTimeSlot()).click(); // drop off is available in the morning for same day so this is here to avoid flaky tests From 5dc79f8e388884469d6f9ecd93c440d65dd7426f Mon Sep 17 00:00:00 2001 From: JennyNou Date: Sun, 22 Mar 2026 22:52:43 -0400 Subject: [PATCH 39/80] Fix for handling premium package flows --- playwright-tests/pages/ServicePackagesPage.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 2b9d9e04..83c0b133 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -29,9 +29,10 @@ export class ServicePackagesPage extends BasePage { await this.standardPackageButton.check(); } else if (servicePackage === ServicePackage.Premium) { await this.premiumPackageButton.check(); - if (await this.wiperModal.isVisible()) { - await this.wiperModal.waitFor({ state: 'visible', timeout: 5000 }); - await this.wiperModalCloseButton.click(); + try {await this.wiperModal.waitFor({ state: 'visible', timeout: 5000 }); + await this.wiperModalCloseButton.click(); + } catch { + await this.continueButton.click(); } } else if (servicePackage === ServicePackage.GlassOnly) { await this.glassOnlyPackageButton.check(); From 65c88ee04f9630e7ac3e91d32eea9c5784e10dc3 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Sun, 22 Mar 2026 22:53:25 -0400 Subject: [PATCH 40/80] Update locator --- playwright-tests/pages/PolicyVehiclesPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/pages/PolicyVehiclesPage.ts b/playwright-tests/pages/PolicyVehiclesPage.ts index 8573709d..2b647906 100644 --- a/playwright-tests/pages/PolicyVehiclesPage.ts +++ b/playwright-tests/pages/PolicyVehiclesPage.ts @@ -27,7 +27,7 @@ export class PolicyVehiclesPage extends BasePage { } async addAnotherVehicle(){ - await this.page.getByText('Add another vehicle').click(); + await this.page.getByRole('button', {name: 'Add another vehicle'}).click(); } async assertNonServiceableAlertBehavior(){ From 580969a39914f7feafa7dd2849e3100a4c4ee96b Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 10:00:46 -0400 Subject: [PATCH 41/80] Update template tag --- azure-pipelines-automated-testing.yml | 2 +- playwright-tests/playwright.config.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines-automated-testing.yml b/azure-pipelines-automated-testing.yml index b83a370d..d232fac9 100644 --- a/azure-pipelines-automated-testing.yml +++ b/azure-pipelines-automated-testing.yml @@ -14,7 +14,7 @@ resources: type: github name: Safelite/AzureDevOps endpoint: Safelite - ref: refs/tags/t5.7.51 + ref: refs/tags/t5.7.53 variables: # - group: Digital-Infrastructure diff --git a/playwright-tests/playwright.config.ts b/playwright-tests/playwright.config.ts index 070a1c7b..94842ffb 100644 --- a/playwright-tests/playwright.config.ts +++ b/playwright-tests/playwright.config.ts @@ -88,6 +88,7 @@ export default defineConfig({ // baseURL: 'http://127.0.0.1:3000', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + baseURL: process.env.BASE_URL || 'https://selfservice.test.glassclaim.com', trace: 'on-first-retry', headless: process.env.CI ? true : false, screenshot: "only-on-failure", From c8971ef97d6df87e7df74c9d94934a307a148191 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 10:58:14 -0400 Subject: [PATCH 42/80] Add flag for unlisted vehicle scenarios --- playwright-tests/business-logic/types/ITestData.ts | 1 + playwright-tests/pages/CoverageStatementPage.ts | 10 +++++++--- .../tests/advanced/0023a_VehicleByVINPartsQns.ts | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/playwright-tests/business-logic/types/ITestData.ts b/playwright-tests/business-logic/types/ITestData.ts index 01df8990..05bcbabd 100644 --- a/playwright-tests/business-logic/types/ITestData.ts +++ b/playwright-tests/business-logic/types/ITestData.ts @@ -7,6 +7,7 @@ export interface ITestData { clientTag: string, isDuplicateClaim: boolean, isPolicyFound: boolean, // Effective difference between advanced and essential + isUnverifiedPolicyAfterVehicleLookup: boolean, // Changing license plate can cause flow to change to unverified from verified isUseVehicleOnPolicy: boolean, // Should we use the vehicle on the policy? isVehicleLookupValidations: boolean, // Should we validate vehicle lookup? isAddressLookupValidations: boolean, // Should we validate address lookup errors? diff --git a/playwright-tests/pages/CoverageStatementPage.ts b/playwright-tests/pages/CoverageStatementPage.ts index 0a8b2553..b5f2a5f0 100644 --- a/playwright-tests/pages/CoverageStatementPage.ts +++ b/playwright-tests/pages/CoverageStatementPage.ts @@ -23,8 +23,8 @@ export class CoverageStatementPage extends BasePage { super(page); this.page = page; - this.cancelMyClaimButton = this.page.getByText('Cancel my claim'); - this.cancelMyClaimConfirm = this.page.getByRole('link', { name: "No, I want to cancel" }); // seen for ITAC/NoComp flows + this.cancelMyClaimButton = this.page.getByRole('link', { name: "No, I want to cancel" }); // seen for ITAC/NoComp flows + this.cancelMyClaimConfirm = this.page.locator('div.text-center', { hasText: "No, I want to cancel" }); // on modal for cancel claim this.deductibleAmount = this.page.locator('.cost-underline'); this.noDeductibleRepairText = this.page.getByText('According to your policy, there is no deductible for a repair.'); // no deductible for repair text @@ -87,13 +87,17 @@ export class CoverageStatementPage extends BasePage { const unVerifiedCustomer = claimDetails.policyDeductible === -1; // if deductible is -1, we treat it as unverified flow if (unVerifiedCustomer) { - await expect(this.unverifiedCoverageHeader).toBeVisible(); await this.continueButton.click(); } } + async handleUnverifiedPolicyAfterVehicleLookupFlow(){ + await expect(this.unverifiedCoverageHeader).toBeVisible(); + await this.continueButton.click(); + } + async handleITACFlow(){ await expect(this.headerForITAC).toBeVisible(); // verify ITAC header is visible diff --git a/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts b/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts index f6d650bb..5a33473c 100644 --- a/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts +++ b/playwright-tests/tests/advanced/0023a_VehicleByVINPartsQns.ts @@ -23,13 +23,14 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0023a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0023a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0023a', customerDetails, policyNumber); const advancedScenario0023Data: Partial = { clientTag: '', isDuplicateClaim: false, isPolicyFound: true, + isUnverifiedPolicyAfterVehicleLookup: true, isNoComp: false, hasStateLawPopup: true, endorsements: undefined, From 68d914dd0eb14e35fdac3de4a30d6f586aad7b19 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 10:59:01 -0400 Subject: [PATCH 43/80] Update locator for lookup buttons --- playwright-tests/pages/AddressLookupPage.ts | 2 +- playwright-tests/pages/VehicleLookupPage.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright-tests/pages/AddressLookupPage.ts b/playwright-tests/pages/AddressLookupPage.ts index 70f4f514..f64a17bc 100644 --- a/playwright-tests/pages/AddressLookupPage.ts +++ b/playwright-tests/pages/AddressLookupPage.ts @@ -15,7 +15,7 @@ export class AddressLookupPage extends BasePage { super(page); this.page = page; this.addressForm = new AddressForm(page); - this.addressNoMatchTextBox = page.getByText('Your address didn’t return a VIN matchPlease re-enter the information below or'); + this.addressNoMatchTextBox = page.getByText('Your address didn’t return a VIN match'); this.stateRestrictionsTextBox = page.getByText('State restrictionsLooks like'); } diff --git a/playwright-tests/pages/VehicleLookupPage.ts b/playwright-tests/pages/VehicleLookupPage.ts index 1dab391e..8e8433d3 100644 --- a/playwright-tests/pages/VehicleLookupPage.ts +++ b/playwright-tests/pages/VehicleLookupPage.ts @@ -19,7 +19,7 @@ export class VehicleLookupPage extends BasePage { constructor(page: Page) { super(page); this.page = page; - this.vinLookupButton = page.getByLabel('Provide my VIN manually', { exact: true }); + this.vinLookupButton = page.getByLabel('Provide my VIN', { exact: true }); this.addressLookupButton = page.getByLabel('Provide my home address', { exact: true }); this.licenseLookupButton = page.getByLabel('Provide my license plate #', { exact: true }); this.vinLookupPage = new VinLookupPage(page); From e44ca49c8ceba24ffaf9299b376b0a1d6d24ff01 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 10:59:26 -0400 Subject: [PATCH 44/80] Clean up validations for confirmation page --- .../pages/OrderConfirmationPage.ts | 71 +++++++++---------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 9a776587..40f5a29f 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -13,7 +13,7 @@ export class OrderConfirmationPage extends BasePage { readonly serviceText: Locator; readonly workOrderNumberText: Locator; - readonly emailText: Locator; + readonly confirmationText: Locator; readonly appointmentTypeText: Locator; readonly apptDetailsText: Locator; readonly deductibleText: Locator; @@ -21,7 +21,7 @@ export class OrderConfirmationPage extends BasePage { readonly totalAmountDueText: Locator; readonly amountPaidText: Locator; readonly finalAmountDue: Locator; - readonly cartServicePackageText: Locator; + readonly cartLineItemsText: Locator; readonly unverifiedCoverageDeductibleText: Locator; issPageValue = 'order-confirmation'; @@ -33,7 +33,7 @@ export class OrderConfirmationPage extends BasePage { this.successCheckmark = this.page.locator('img[src*="-checkmark.svg"]'); this.successHeader = this.page.locator('[class="header-text"]'); - this.emailText = this.page.locator('[class="subheader-text"]', { hasText: 'Your appointment is on Safelite\'s schedule and your confirmation email is on the way.' }); + this.confirmationText = this.page.locator('[class="subheader-text"]', { hasText: 'Your appointment is on Safelite\'s schedule and your confirmation email is on the way.' }); this.serviceText = this.page.locator('[class="service-description confirmation-section"]'); this.workOrderNumberText = this.page.locator('[class="work-order-description"]'); this.appointmentTypeText = this.page.locator('[class="appointment-title"]'); @@ -45,7 +45,7 @@ export class OrderConfirmationPage extends BasePage { this.totalAmountDueText = this.page.locator('#total-value'); this.amountPaidText = this.page.locator('#amount-paid-value'); this.finalAmountDue = this.page.locator('#bottom-amount-due-value'); - this.cartServicePackageText = this.page.locator('.cart-item-list'); + this.cartLineItemsText = this.page.locator('.cart-item-list'); this.unverifiedCoverageDeductibleText = this.page.locator('span#deductible-value', { hasText: 'Verifying coverage' }); } @@ -54,21 +54,17 @@ export class OrderConfirmationPage extends BasePage { const { vehicleDetails, customerDetails, servicePackage, isItac, isNoComp, isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy } = testData; - await expect.soft(this.successHeader).toBeVisible(); - await this.serviceText.waitFor({ state: "visible" }); - await this.logOrderNumber(); // Grab text const serviceTextValue = await this.serviceText.textContent(); const apptDetailsValue = await this.apptDetailsText.textContent(); const appointmentTypeValue = await this.appointmentTypeText.textContent(); - // Validate header for appointment details matches inshop or mobile - await this.validateAppointmentTypeHeader(); // Derived conditions const isItacOrNoComp = !!(isItac || isNoComp); const isUnverified = !isPolicyFound; + const isUnverifiedPolicyAfterVehicleLookup = testData.isUnverifiedPolicyAfterVehicleLookup === true; const hasPremiumOrStandard = claimDetails!.policyDeductible >= 0 && (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard); @@ -86,34 +82,36 @@ export class OrderConfirmationPage extends BasePage { condition ? await locator.textContent() : null; // Grab text content for elements that are expected on-screen - const servicePackageValue = await textIf(hasPremiumOrStandard, this.cartServicePackageText); + const lineItemsValue = await textIf(hasPremiumOrStandard, this.cartLineItemsText); const deductibleTextValue = await textIf(!isItacOrNoComp, this.deductibleText); - const subtotalTextValue = await textIf(hasPremiumOrStandard || isItacOrNoComp, this.subtotalText); - const totalAmountDueValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService), this.totalAmountDueText); - const amountPaidTextValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService), this.totalAmountDueText); - const finalAmountDueValue = await textIf(hasPremiumOrStandard || isItacOrNoComp || isUnverified, this.finalAmountDue); + const subtotalTextValue = await textIf((hasPremiumOrStandard || isItacOrNoComp || !isUnverified) && !isUnverifiedPolicyAfterVehicleLookup, this.subtotalText); + const totalAmountDueValue = await textIf(((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) || !isUnverified) && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); + const amountPaidTextValue = await textIf(((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) || !isUnverified) && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); + const finalAmountDueValue = await textIf(hasPremiumOrStandard || isItacOrNoComp || isUnverified || isUnverifiedPolicyAfterVehicleLookup, this.finalAmountDue); + const confirmationTextValue = await this.confirmationText.textContent(); + + - // General Validations + expect.soft(this.serviceText).toBeVisible();; + expect.soft(this.successHeader).toBeVisible(); + expect.soft(confirmationTextValue).toContain('Your appointment is on Safelite\'s schedule and your confirmation email is on the way.'); expect.soft(serviceTextValue).toContain(`${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`); - //expect.soft(apptDateValue).toContain(customerDetails!.apptDate); - //expect.soft(emailTextValue).toContain(customerDetails!.email); - // Service package validations - /* if ((servicePackage === ServicePackage.Premium && testData.isReplace === true) || (servicePackage === ServicePackage.Standard && testData.isReplace === true)) { - expect.soft(servicePackageValue).toContain('Front advanced beam blades'); - } else if (servicePackage === ServicePackage.Premium) { - expect.soft(servicePackageValue).toContain('Safelite Rain Repellent Treatment'); - } else { - expect.soft(Number.parseFloat(deductibleTextValue!.split('$')[1].replaceAll(',', ''))).toEqual(claimDetails!.policyDeductible); - } commenting out needs reworked for ITAC/no comp flows*/ + // Validate order number and appointment type header + await this.validateOrderNumber(); + await this.validateAppointmentTypeHeader(); + + // Validate line items + if (servicePackage === ServicePackage.Premium) { + expect.soft(lineItemsValue).toContain('Front advanced beam blades'); + expect.soft(lineItemsValue).toContain('Safelite Rain Repellent Treatment'); + } else { + if (servicePackage === ServicePackage.Standard) { + expect.soft(lineItemsValue).toContain('Front advanced beam blades'); + } + } - // Price validations - /*if (servicePackage === ServicePackage.GlassOnly) { - expect.soft(servicePackageAmt).toEqual(0); - } else { - expect.soft(servicePackageAmt).toBeGreaterThan(0); - }*/ if (isPolicyFound && (claimDetails!.policyDeductible === 0 && (servicePackage === ServicePackage.GlassOnly))) { const zeroDeductibleText = await this.deductibleText.textContent(); @@ -123,12 +121,12 @@ export class OrderConfirmationPage extends BasePage { const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : 0; expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible); - } else if ((isPolicyFound || (isItac || isNoComp && paymentDetails!.paymentType === PaymentType.PayAtService) && (finalAmountDueValue !== null && subtotalTextValue !== null))) { + } else if ((isPolicyFound || isItac || (isNoComp && paymentDetails!.paymentType === PaymentType.PayAtService)) && !isUnverifiedPolicyAfterVehicleLookup && finalAmountDueValue !== null && subtotalTextValue !== null) { // Extract numbers const deductibleAmt = deductibleTextValue ? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')) : null; const subtotalAmt = subtotalTextValue ? Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', '')) : null; - //subtotalTextValue?.replaceAll(',', '') + //const totalAmountDueAmt = Number.parseFloat(totalAmountDueValue!.split('$')[1].replaceAll(',', '')); const finalAmountDueAmt = finalAmountDueValue ? Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', '')) : null; @@ -158,12 +156,11 @@ export class OrderConfirmationPage extends BasePage { } } - async logOrderNumber() { + async validateOrderNumber() { const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')')); const workOrderNumber = sessionStorage.workOrderNumber; - await test.step(`SessionStorage Work Order Number:${workOrderNumber}`, async () => { - console.log(`SessionStorage Work Order Number:${workOrderNumber}`); - }); + const workOrderNumberValue = await this.workOrderNumberText.textContent(); + expect.soft(workOrderNumberValue).toEqual(workOrderNumber); } async validateAppointmentTypeHeader() { From d232267ba0bd78282d4f68c137f43bde7612c73c Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 11:00:11 -0400 Subject: [PATCH 45/80] Fix for flakiness in validation flow --- playwright-tests/pages/AfterpayPage.ts | 3 +-- playwright-tests/pages/SchedulePage.ts | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index 2eb7e690..98c66131 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -37,8 +37,7 @@ export class AfterpayPage extends BasePage { async executeAfterpayPayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) { await this.login(paymentDetails.password!); - try { - await this.confirmButton.waitFor({ state: 'visible', timeout: 5000 }); + try {await this.confirmButton.waitFor({ state: 'visible', timeout: 5000 }); await this.confirmButton.isVisible() await this.confirmButton.click(); } catch { diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 60dd87d1..d6544208 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -57,12 +57,14 @@ export class SchedulePage extends BasePage { async scheduleInShop(appointmentDetails?: IAppointmentDetails){ if (appointmentDetails?.serviceLocation === ServiceLocation.InShop) { - await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); + try {await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); await this.inShopButton.isVisible() await this.inShopButton.click(); await (await this.getFirstNonDropOffTimeSlot()).click(); - } else if (await this.inShopButton.isHidden()) { + } catch { + await this.inShopButton.isHidden() await (await this.getFirstNonDropOffTimeSlot()).click(); + } } else if (appointmentDetails?.serviceLocation === ServiceLocation.DropOff) { if (await this.inShopButton.isVisible()){ await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); From 39cc5fe73743b8e35e2f08d4740198d75e7a6f72 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 11:01:45 -0400 Subject: [PATCH 46/80] Delete service location page --- playwright-tests/pages/ServiceLocationPage.ts | 136 ------------------ 1 file changed, 136 deletions(-) delete mode 100644 playwright-tests/pages/ServiceLocationPage.ts diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts deleted file mode 100644 index d84f87df..00000000 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { expect, type Locator, type Page } from '@playwright/test'; -import { BasePage } from './BasePage'; -import { IAppointmentDetails } from '@business-logic/types/CustomerDetails'; -import { ServiceLocation } from '@business-logic/types/Enums'; -import { AddressForm } from './forms/AddressForm'; -import { faker } from '@faker-js/faker'; - -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; - - issPageValue = 'service-location'; - - constructor(page: Page) { - super(page); - this.page = page; - - this.addressForm = new AddressForm(page); - - // Initial selection - this.inShopButton = this.page.locator('[buttonlabel="In-shop"]'); - this.mobileButton = this.page.locator('[buttonlabel="Mobile"]') - this.dropOffButton = this.page.locator('[buttonlabel="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('div').filter({ hasText: /Appts/}).first(); - this.firstAppointmentButton = this.page.locator('#availabilityIndicator').first(); - this.changeZipButton = this.page.locator('[id="serviceZipLinkPromptId"]'); - this.updateZipTextBox = this.page.getByRole('textbox', { name: 'Update your service ZIP code'}); - this.saveZipButton = this.page.getByRole('button', { name: 'Save ZIP code' }); - - - // 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: 'Save Address' }); - } - - async selectLocation(appointmentDetails: IAppointmentDetails){ - if (appointmentDetails.alternateServiceZip) { - await this.changeZipButton.click(); - await this.fillAndValidate(this.updateZipTextBox, appointmentDetails.alternateServiceZip); - await this.saveZipButton.click(); - } - - switch(appointmentDetails.serviceLocation) { - case ServiceLocation.Mobile: - await this.scheduleMobile(appointmentDetails); - break; - case ServiceLocation.InShop: - await this.scheduleInShop(appointmentDetails); - break; - case ServiceLocation.DropOff: - await this.scheduleDropOff(appointmentDetails); - break; - } - } - - - async scheduleInShop(appointmentDetails?: IAppointmentDetails) { - await this.inShopButton.click(); - if (appointmentDetails && appointmentDetails.shopAddress) { - await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check(); - } else { - await this.firstAppointmentButton.click(); - } - } - - async scheduleMobile(appointmentDetails: IAppointmentDetails){ - if (appointmentDetails.serviceAddress) { - await this.mobileButton.click(); - await this.enterServiceAddressButton.click(); - await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! }); - 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(); - } - } - - async validateRecalWarning(){ - await expect(this.RecalWarningMessage1).toBeVisible(); - await expect(this.RecalWarningMessage2).toBeVisible(); - - } -} \ No newline at end of file From caf186d728cec0667809c4bd6e4ae7df1d3d562d Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 11:17:48 -0400 Subject: [PATCH 47/80] Add service location page back in --- playwright-tests/pages/ServiceLocationPage.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 playwright-tests/pages/ServiceLocationPage.ts diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts new file mode 100644 index 00000000..12f9de32 --- /dev/null +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -0,0 +1,42 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { BasePage } from './BasePage'; +import { IAppointmentDetails } from '@business-logic/types/CustomerDetails'; +import { ServiceLocation } from '@business-logic/types/Enums'; +import { AddressForm } from './forms/AddressForm'; +import { faker } from '@faker-js/faker'; + +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; + + constructor(page: Page) { + super(page); + this.page = page; + + this.addressForm = new AddressForm(page); + + // Initial selection + this.inShopButton = this.page.locator('[buttonlabel="In-shop"]'); + this.mobileButton = this.page.locator('[buttonlabel="Mobile"]') + this.dropOffButton = this.page.locator('[buttonlabel="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"]'); + + } + + async validateRecalWarning(){ + await expect(this.RecalWarningMessage1).toBeVisible(); + await expect(this.RecalWarningMessage2).toBeVisible(); + + } +} \ No newline at end of file From 54c45fddb5e62311ca5c51d21243b6bec1287f62 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 11:21:39 -0400 Subject: [PATCH 48/80] Add logic to select part question responses --- playwright-tests/pages/VehiclePartsPage.ts | 26 +++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/playwright-tests/pages/VehiclePartsPage.ts b/playwright-tests/pages/VehiclePartsPage.ts index 9671b7f9..1d778f82 100644 --- a/playwright-tests/pages/VehiclePartsPage.ts +++ b/playwright-tests/pages/VehiclePartsPage.ts @@ -1,5 +1,6 @@ -import { Page } from "@playwright/test"; +import { expect, type Locator, type Page } from "@playwright/test"; import { PartQuestionsPage } from "./PartQuestionsPage"; +import { IPartQuestion } from '@business-logic/types/CustomerDetails'; export default class VehiclePartQuestionsPage extends PartQuestionsPage{ issPageValue = 'vehicle-parts'; @@ -7,4 +8,27 @@ export default class VehiclePartQuestionsPage extends PartQuestionsPage{ constructor(page: Page) { super(page); } +/* this fails for some reason/some cases so may need to rework maybe only validate for part questions page and not on vehicle parts +async validatePartQuestions(partQuestions: IPartQuestion[]) { + for (const pq of partQuestions) { + const partQuestionOptions = this.page.locator(`fieldset[aria-labelledby="${pq.partQuestionType}"]`); + if (pq.isOnPage) { + await expect(partQuestionOptions).toBeAttached(); + } else { + await expect(partQuestionOptions).not.toBeAttached(); + } + } + } +*/ + async selectPartQuestionResponses(partQuestions: IPartQuestion[]) { + for (const pq of partQuestions) { + const parentobject=this.page.locator(`fieldset[aria-labelledby="${pq.partQuestionType}"]`); + const partQuestionOptionButton = parentobject.locator(`[buttonlabel="${pq.optionToSelect}"]`); + await partQuestionOptionButton.nth(0).click(); + if (pq.secondaryQuestionOptionToSelect != null) { + const secondaryQuestionButton = parentobject.getByText(`${pq.secondaryQuestionOptionToSelect}`); + await secondaryQuestionButton.nth(1).click(); + } + } + } } \ No newline at end of file From 8490a6a290012ce67bf263ff15a4e2d20697e8f9 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 11:23:40 -0400 Subject: [PATCH 49/80] Update flows to fix playwright tests --- playwright-tests/tests/0000__M.test.ts | 259 ++++++++++++++++--------- 1 file changed, 165 insertions(+), 94 deletions(-) diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 9026a21a..0e3fa2a5 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -5,7 +5,7 @@ import { RuleEngine, ValidationOptions } from "@business-logic/types/RuleEngine" import { ICustomerDetails } from "@business-logic/types/CustomerDetails"; import essentialHpTestCases from "./0016_EssentialRepairInShop"; import essentialReplaceTestCases from "./0013_EssentialReplace"; -import { BailoutCode, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums"; +import { BailoutCode, ServiceLocation, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums"; import essentialTpaNotEnabledTestCases from "./0003_EssentialTpaNotEnabledBailout"; import essentialDoNotSeeShopTestCases from "./0011_EssentialDoNotSeeShopBailout"; import essentialVehicleNotFoundTestCases from "./0010_EssentialVehicleNotFoundBailout"; @@ -61,6 +61,7 @@ import advancedScenario0029aTestCases from "./advanced/0029a_HeavyVehicleBailout import advancedScenario0030aTestCases from "./advanced/0030a_PartsServiceBailout"; import advancedScenario0031aTestCases from "./advanced/0031a_ReplaceServiceableBigTruck"; import advancedScenario0032aTestCases from "./advanced/0032a_DeclineNonServiceableBigTruck"; +import { getDefaultTestData } from "safelite-playwright-core"; @@ -382,8 +383,8 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Destructure data for easy access const { customerDetails, claimDetails, vehicleDetails, vehicleDamage, appointmentDetails, isSafelite, endorsements, - partQuestions, paymentDetails, isNoComp, isItac, isRecalNotification, - isRecalWarning, servicePackage, hasStateLawPopup, otherVehiclesOnPolicy, + partQuestions, paymentDetails, isNoComp, isItac, isRecalNotification, isUnverifiedPolicyAfterVehicleLookup, + isRecalWarning, servicePackage, hasOemEndorsement, hasStateLawPopup, otherVehiclesOnPolicy, isSeparateApptsWarning, vehiclePartQuestions, editVehicleDetails, isAddressLookupValidations, hasMilitaryWarning, capabilityQuestions, isUseVehicleOnPolicy, isVehicleLookupValidations, isMoldingQuestion, isNonServiceable, isNonServiceableVin } = testCase.testData; @@ -401,7 +402,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { vehiclePartQuestionsPage, capabilityQuestionsPage, moldingQuestionsPage, addressLookupPage } = testCase.pages; // Destructure bailout flags - const { isVehicleSelectBailout, isDoNotSeeMyShopBailout, isTpaNotEnabledBailout, + const { isVehicleSelectBailout, isTpaNotEnabledBailout, isPartsServiceErrorBailout, isVehicleLookupBailout, isPriceServiceErrorBailout, isRequestCallbackBailout } = testCase.testData.bailoutFlags || {}; @@ -420,7 +421,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { await test.step('WelcomePage >> Populate Customer Details', async () => { await welcomePage.populatePage(customerDetails!, claimDetails!, await welcomePage.hasCityInfo()); - await welcomePage.nextPage(); + await welcomePage.GetStartedButton.click(); }); if (testCase.testData.isDuplicateClaim) { @@ -445,26 +446,28 @@ async function runWorkflow(page: Page, testCase: TestCase) { } } if (!(isUseVehicleOnPolicy ?? true)) { - await policyVehiclesPage.selectVehicleNotListed(); + await policyVehiclesPage.addAnotherVehicle(); await policyVehiclesPage.nextPage(); await vehicleSelectionPage.selectVehicle(vehicleDetails!); await policyVehiclesPage.nextPage(); + + await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => { + await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue); + await policyHolderDetailsPage.fillCustomerDetails(customerDetails!); + await policyHolderDetailsPage.nextPage(); + }); } else { // Select vehicle await policyVehiclesPage.selectVehicle(vehicleDetails!); + if (isNonServiceable) { await policyVehiclesPage.assertNonServiceableAlertBehavior(); return; } await policyVehiclesPage.nextPage(); - } - }); + await policyVehiclesPage.logReferralNumber(); - if (isNonServiceable) { - return; - } - - if (hasEndorsements) { + if (hasEndorsements) { await test.step('EndorsementsPage >> Select Endorsements', async () => { await endorsementsPage.validateURL(endorsementsPage.issPageValue); await endorsementsPage.verifyEndorsements(endorsements); @@ -472,20 +475,23 @@ async function runWorkflow(page: Page, testCase: TestCase) { await endorsementsPage.nextPage(); }); } - - } else { - await policyVehiclesPage.logReferralNumber(); - await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => { - await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue); - await policyHolderDetailsPage.fillCustomerDetails(customerDetails!); - await policyHolderDetailsPage.nextPage(); + await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => { + await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue); + await policyHolderDetailsPage.fillCustomerDetails(customerDetails!); + await policyHolderDetailsPage.nextPage(); }); + } + }); + + if (isNonServiceable) { + return; + } if (isVehicleLookupBailout) { forceAPIError(page, '/vehicle/api/v1/vehicle/lookup') } - await test.step('VehicleDetailsPage >> Select Vehicle', async () => { + /*await test.step('VehicleDetailsPage >> Select Vehicle', async () => { await vehicleSelectionPage.validateURL(vehicleSelectionPage.issPageValue); await vehicleSelectionPage.selectVehicle(vehicleDetails!); if (isNonServiceable) { @@ -493,7 +499,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { return; } await vehicleSelectionPage.nextPage(); - }); + });*/ if (isNonServiceable) { return; @@ -508,20 +514,26 @@ async function runWorkflow(page: Page, testCase: TestCase) { } } - if (editVehicleDetails) { - isPolicyFound = false; // Flow proceeds as unverified + // Essential flow when policy is not found + if (isPolicyFound === false) { testCase.testData.isPolicyFound = false; - await test.step('VehicleDamagePage >> Click Edit Vehicle', async () => { + /* await test.step('VehicleDamagePage >> Click Edit Vehicle', async () => { await vehicleDamagePage.validateURL(vehicleDamagePage.issPageValue); await vehicleDamagePage.editVehicleButton.click(); }); + */ // commented out for now because is there an edit vehicle flow? - await test.step('VehicleDetailsPage >> Select edited vehicle', async () => { - await test.step('VehicleDetailsPage >> Select Vehicle', async () => { + await test.step('VehicleSelectionPage >> Select vehicle for essential flow', async () => { await vehicleSelectionPage.validateURL(vehicleSelectionPage.issPageValue); - await vehicleSelectionPage.selectVehicle(editVehicleDetails); + await vehicleSelectionPage.selectVehicle(vehicleDetails!); await vehicleSelectionPage.nextPage(); - }); + }); + + await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => { + await policyHolderDetailsPage.validateURL(policyHolderDetailsPage.issPageValue); + await policyHolderDetailsPage.fillCustomerDetails(customerDetails!); + + await policyHolderDetailsPage.nextPage(); }); } @@ -584,10 +596,14 @@ async function runWorkflow(page: Page, testCase: TestCase) { await test.step('Address Lookup validations >> Vehicle Lookup by address: ' + customerDetails!.address.street, async () => { if (Array.isArray(vehicleDetails?.address)) { await addressLookupPage.validateURL(addressLookupPage.issPageValue); - await addressLookupPage.validateAddressLookupAlerts(vehicleDetails?.address); + await addressLookupPage.validateAddressLookupAlerts(vehicleDetails?.address!); await addressLookupPage.nextPage(); } }); + + await test.step('Address Vehicles >> Next page', async () => { + await addressLookupPage.nextPage(); + }); } else { await test.step('VehicleLookupAddressPage >> Lookup by address: ' + customerDetails!.address.street, async () => { await vehicleLookupAddressPage.validateURL(vehicleLookupAddressPage.issPageValue); @@ -627,6 +643,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { break; } } + if (isMoldingQuestion) { await test.step('Molding Questions Page >> Select Yes', async () => { await moldingQuestionsPage.validateURL(moldingQuestionsPage.issPageValue); @@ -637,6 +654,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { await moldingQuestionsPage.nextPage(); }); } + if (capabilityQuestions && capabilityQuestions.length > 0) { await capabilityQuestionsPage.validateURL(capabilityQuestionsPage.issPageValue); await capabilityQuestionsPage.validatePartQuestions(capabilityQuestions); @@ -645,6 +663,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { } if (partQuestions && partQuestions.length > 0) { + let partQuestionsPage = testCase.pages.partQuestionsPage; await partQuestionsPage.validateURL(partQuestionsPage.issPageValue); await partQuestionsPage.validatePartQuestions(partQuestions); await partQuestionsPage.selectPartQuestionResponses(partQuestions); @@ -653,12 +672,13 @@ async function runWorkflow(page: Page, testCase: TestCase) { if (vehiclePartQuestions && vehiclePartQuestions.length > 0) { await vehiclePartQuestionsPage.validateURL(vehiclePartQuestionsPage.issPageValue); - await vehiclePartQuestionsPage.validatePartQuestions(vehiclePartQuestions); + //await vehiclePartQuestionsPage.validatePartQuestions(vehiclePartQuestions); await vehiclePartQuestionsPage.selectPartQuestionResponses(vehiclePartQuestions); await vehiclePartQuestionsPage.nextPage(); } + if (isRequestCallbackBailout) { - await test.step('CoverageStatementPage >> Cancel My claim', async () => { + await test.step('CoverageStatementPage >> Cancel my claim', async () => { await coverageStatementPage.validateURL(coverageStatementPage.issPageValue); await coverageStatementPage.cancelMyClaim(); }); @@ -669,44 +689,95 @@ async function runWorkflow(page: Page, testCase: TestCase) { }); return; } + await test.step('CoverageStatementPage >> Next page', async () => { await coverageStatementPage.validateURL(coverageStatementPage.issPageValue); - // Confirm no coverage - if (isPolicyFound && (isItac || isNoComp)) { - await coverageStatementPage.continueToScheduleButton.click(); - } - await coverageStatementPage.nextPage(); + if (hasOemEndorsement) { + await coverageStatementPage.validateOEMBullet(); + } + // For essential/unverified flows + if (!isPolicyFound) { + await coverageStatementPage.handleUnverifiedFlow(claimDetails!); + } + + // For No Comp flows + if (isPolicyFound && isNoComp === true) { + await coverageStatementPage.handleNoCompFlow(); + } + + // For ITAC flows + if (isPolicyFound && isItac === true) { + await coverageStatementPage.handleITACFlow(); + } + + // For replacements with deductible + if (isPolicyFound && claimDetails?.policyDeductible! > 0 && claimDetails?.policyDeductible !== 9999 && testCase.testData.isUnverifiedPolicyAfterVehicleLookup !== true) { + await coverageStatementPage.handleReplacementWithDeductibleFlow(claimDetails!); + } + + // For replacements or repairs with 0 deductible + if (isPolicyFound && claimDetails?.policyDeductible === 0) { + await coverageStatementPage.handleReplacementOrRepairWithNoDeductibleFlow(claimDetails!, VehicleDamage!); + } + + // For unverified policy after vehicle lookup + if (isPolicyFound && testCase.testData.isUnverifiedPolicyAfterVehicleLookup === true) { + await coverageStatementPage.handleUnverifiedPolicyAfterVehicleLookupFlow(); + } + }); if (hasStateLawPopup) { await test.step('ProviderPreferencePage >> Dismiss state law popup', async () => { await providerPreferencePage.validateURL(providerPreferencePage.issPageValue); - await providerPreferencePage.validateStateLawModalIsVisible(); - await providerPreferencePage.gotItButton.click(); - }); + if (vehicleDamage!.includes(VehicleDamage.WindshieldCrack)) { + //await providerPreferencePage.validateStateLawModalIsVisible(); + await providerPreferencePage.stateLawModalOkayButton.click(); + } else { + //await providerPreferencePage.validateStateLawRepairModalIsVisible(); + await providerPreferencePage.stateLawModalOkayButton.click(); + } + }); } - if (!isPolicyFound || !(isItac || isNoComp)) { + if (!isPolicyFound || !(isItac || isNoComp) && !isUnverifiedPolicyAfterVehicleLookup) { await test.step('ProviderPreferencePage >> Select Provider ' + isSafelite ? "Safelite" : "Other shops(Non-Safelite)", async () => { await providerPreferencePage.validateURL(providerPreferencePage.issPageValue); await providerPreferencePage.selectProvider(isSafelite); + }); } - // validations for Recal warning mesage - if (isRecalWarning) { + if (isSafelite && isUnverifiedPolicyAfterVehicleLookup) { + await test.step('ProviderPreferencePage >> Select Provider ' + isSafelite ? "Safelite" : "Other shops(Non-Safelite)", async () => { + await providerPreferencePage.validateURL(providerPreferencePage.issPageValue); + await providerPreferencePage.selectProvider(isSafelite); + + }); + } + + // validations for Recal warning mesage on schedule page + // commented out for now will add back in to validate recal banner on schedule page + /*if (isRecalWarning) { await serviceLocationPage.validateURL(serviceLocationPage.issPageValue); await serviceLocationPage.validateRecalWarning(); - } + }*/ // if isRecalNotifidation flag true additional step to acknowledge Recal notification. if (isRecalNotification) { await providerPreferencePage.validateURL(providerPreferencePage.issPageValue); - await providerPreferencePage.acknowledgeRecalNotificaiton(); + //await providerPreferencePage.acknowledgeRecalNotificaiton(); + } + + if (!isSafelite && isUnverifiedPolicyAfterVehicleLookup) { + await test.step('ProviderPreferencePage >> Schedule TPA without Adas', async () => { + await providerPreferencePage.validateURL(providerPreferencePage.issPageValue); + await providerPreferencePage.scheduleTPAWithoutAdas(); + }); } // If No-Comp or ITAC, ProviderPreferencePage does not appear - if (!isPolicyFound || !(isItac || isNoComp)) { + if (!isPolicyFound || !(isItac || isNoComp) || isUnverifiedPolicyAfterVehicleLookup) { if (!isSafelite) { @@ -728,16 +799,6 @@ async function runWorkflow(page: Page, testCase: TestCase) { return; } - if (isDoNotSeeMyShopBailout) { - await test.step('TpaSearchPage >> Select "Do Not See My Shop"', async () => { - await tpaSearchPage.validateURL(tpaSearchPage.issPageValue); - await tpaSearchPage.selectDoNotSeeMyShop(); - }); - await bailoutPage.validateURL(bailoutPage.issPageValue); - await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.DoNotSeeMyShop); - return; - } - await test.step('TpaSearchPage >> TPA Search', async () => { await tpaSearchPage.validateURL(tpaSearchPage.issPageValue); await tpaSearchPage.selectFirstLocation(); @@ -745,9 +806,13 @@ async function runWorkflow(page: Page, testCase: TestCase) { }); await test.step('TpaSubmitPage >> TPA Submit', async () => { - // TODO: Validations await tpaSubmitPage.validateURL(tpaSubmitPage.issPageValue); + //await tpaSubmitPage.validateRecalAlert(); + if (!isUnverifiedPolicyAfterVehicleLookup) { + await tpaSubmitPage.validateDeductible(claimDetails!); + } else { await tpaSubmitPage.nextPage(); + } }); await test.step('TpaConfirmationPage >> TPA Confirmation', async () => { @@ -756,43 +821,42 @@ async function runWorkflow(page: Page, testCase: TestCase) { return; }); return; - } - } - - await test.step('ServiceLocationPage >> Select service location', async () => { - await serviceLocationPage.validateURL(serviceLocationPage.issPageValue); - await serviceLocationPage.selectLocation(appointmentDetails!); - if (hasMilitaryWarning) { - await expect.soft(serviceLocationPage.militaryWarningMessage).toBeVisible(); - } - await serviceLocationPage.nextPage(); - }); + }} await test.step('SchedulePage >> Select day and time', async () => { await schedulePage.validateURL(schedulePage.issPageValue); - customerDetails!.apptDate = await schedulePage.scheduleFirstAppointment(appointmentDetails!.serviceLocation); - }); - await test.step('ContactDetailsPage >> Validate contact details', async () => { - const expectedContactDetails: Partial = { - firstName: customerDetails!.firstName, - lastName: customerDetails!.lastName, - email: customerDetails!.email, - phoneNumber: customerDetails!.phoneNumber - }; - await contactDetailsPage.validateURL(contactDetailsPage.issPageValue); - const actualContactDetails = await contactDetailsPage.getContactDetails(); - expect.soft(actualContactDetails).toEqual(expectedContactDetails); - - await contactDetailsPage.fillNotes(customerDetails!.notes); - - if (isPriceServiceErrorBailout) { - forceAPIError(page, '/price/api/v1/price/combined-quote'); + if (appointmentDetails?.serviceLocation === ServiceLocation.Mobile) { + await schedulePage.scheduleMobile(appointmentDetails); } - await contactDetailsPage.nextPage(); + if (appointmentDetails?.serviceLocation === ServiceLocation.InShop || appointmentDetails?.serviceLocation === ServiceLocation.DropOff) { + await schedulePage.scheduleInShop(appointmentDetails); + } }); + + await test.step('ServicePackagesPage >> Choose service package', async () => { + await servicePackagesPage.validateURL(servicePackagesPage.issPageValue); + await servicePackagesPage.selectServicePackage(servicePackage!); + }); + + if (appointmentDetails?.serviceLocation === ServiceLocation.Mobile) { + await test.step('ContactDetailsPage >> Validate contact details', async () => { + await contactDetailsPage.validateURL(contactDetailsPage.issPageValue); + + + await contactDetailsPage.fillContactDetails(customerDetails!); + //await contactDetailsPage.validateAlertsAreVisible(); + //await contactDetailsPage.fillNotes(customerDetails!.notes!); + + await contactDetailsPage.nextPage(); + }); + /* if (isPriceServiceErrorBailout) { + forceAPIError(page, '/price/api/v1/price/combined-quote'); + }*/ + } + if (isPriceServiceErrorBailout) { await test.step('BailoutPage >> Price Service Error Bailout', async () => { await bailoutPage.validateURL(bailoutPage.issPageValue); @@ -801,22 +865,29 @@ async function runWorkflow(page: Page, testCase: TestCase) { return; } - await test.step('ServicePackagesPage >> Choose service package', async () => { - await servicePackagesPage.validateURL(servicePackagesPage.issPageValue); - customerDetails!.packagePrice = await servicePackagesPage.selectServicePackage(servicePackage!); - await servicePackagesPage.nextPage(); - }); - if (isPolicyFound && (isUseVehicleOnPolicy ?? true) && claimDetails!.policyDeductible > 0) { await test.step('PaymentMethodPage >> Execute Payment', async () => { await paymentMethodPage.validateURL(paymentMethodPage.issPageValue); - await paymentMethodPage.executePayment(paymentDetails!); + await paymentMethodPage.executePayment(paymentDetails!, claimDetails!, servicePackage!); await paymentMethodPage.nextPage(); + }); } else { await test.step('PaymentMethodPage >> Skip to Order Confirmation', async () => { await paymentMethodPage.validateURL(paymentMethodPage.issPageValue); - await paymentMethodPage.nextPage(); + + if (isPolicyFound && (isUseVehicleOnPolicy ?? true) && claimDetails!.policyDeductible === 0) { + await paymentMethodPage.submitOrderWithoutPIA(); + } + + if (!isPolicyFound) { + await paymentMethodPage.submitOrderWithoutPIA(); + } + + if (isPolicyFound && testCase.testData.isUnverifiedPolicyAfterVehicleLookup === true) { + await paymentMethodPage.submitOrderWithoutPIA(); + } + }); } From 8341db4a5835b688161c0a66732cdc3b9b8c020e Mon Sep 17 00:00:00 2001 From: JennyNou Date: Tue, 24 Mar 2026 13:12:02 -0400 Subject: [PATCH 50/80] Update address for failing test --- playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts b/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts index cc3da886..f3a4588a 100644 --- a/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts +++ b/playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts @@ -21,10 +21,10 @@ const essentialReplaceStaticAdasData: Partial = { phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/), notes: 'Automated Test', address: { - street: faker.location.streetAddress(), - city: 'Knoxville', - state: 'Tennessee', - postalCode: '37996', + street: "123 Test Road", + city: 'Columbus', + state: 'OHIO', + postalCode: '43085', country: 'United States' } }, From 2c2d709691501e19afd12c33b2bb289dc675bb2b Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 09:46:05 -0400 Subject: [PATCH 51/80] Add alternate mobile address method --- playwright-tests/business-logic/types/CustomerDetails.ts | 2 ++ playwright-tests/pages/ContactDetailsPage.ts | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/playwright-tests/business-logic/types/CustomerDetails.ts b/playwright-tests/business-logic/types/CustomerDetails.ts index 1152f7a4..9a4abd95 100644 --- a/playwright-tests/business-logic/types/CustomerDetails.ts +++ b/playwright-tests/business-logic/types/CustomerDetails.ts @@ -44,6 +44,8 @@ export interface IAppointmentDetails { serviceAddress?: IAddress, // Used for mobile isVehicleProtected?: boolean // Used for mobile alternateServiceZip?: string // Used for in-shop and drop-off if doing service in a different ZIP + alternateMobileCity?: string // Used for mobile if doing service in a different city then customers address + alternateMobileStreetAddress?: string // Used for mobile if doing service in a different street address then customers address } export interface IPartQuestion { diff --git a/playwright-tests/pages/ContactDetailsPage.ts b/playwright-tests/pages/ContactDetailsPage.ts index 2f030149..991cdff7 100644 --- a/playwright-tests/pages/ContactDetailsPage.ts +++ b/playwright-tests/pages/ContactDetailsPage.ts @@ -54,6 +54,11 @@ export class ContactDetailsPage extends BasePage { await this.cityTextBox.fill(customerDetails.address.city!); } + async fillAlternateMobileStreetAndCity(alternateMobileStreetAddress: string, alternateMobileCity: string) { + await this.streetAddressTextBox.fill(alternateMobileStreetAddress); + await this.cityTextBox.fill(alternateMobileCity); + } + async fillNotes(notes?: string) { if (notes) { await this.notesTextBox.fill(notes); From 8b9ac1ac70d149a70265524a6e0c42d2b4d2d208 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 09:47:27 -0400 Subject: [PATCH 52/80] Fix for bailout tests --- playwright-tests/business-logic/types/Enums.ts | 4 ++-- playwright-tests/pages/BailoutPage.ts | 10 +++++++--- playwright-tests/tests/0000__M.test.ts | 16 +++++++++++++--- .../tests/advanced/0029a_HeavyVehicleBailout.ts | 16 +++++++++++----- .../advanced/0031a_ReplaceServiceableBigTruck.ts | 12 ++---------- .../0032a_DeclineNonServiceableBigTruck.ts | 5 ++++- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/playwright-tests/business-logic/types/Enums.ts b/playwright-tests/business-logic/types/Enums.ts index 25b4f9a8..0ffb2257 100644 --- a/playwright-tests/business-logic/types/Enums.ts +++ b/playwright-tests/business-logic/types/Enums.ts @@ -143,8 +143,8 @@ export enum BailoutCode { CoverageStatementInvalidState, PricingResponseError, TPANotEnabled, - RequestCallback, - HeavyTruckVehicle, + RequestCallback = 17, // bailout code 17 is Request Callback + HeavyTruckVehicle = 9, // bailout code 9 is Heavy Truck Vehicle NoPartsAvailable, PartsServiceError, SafeliteNotTheProvider, diff --git a/playwright-tests/pages/BailoutPage.ts b/playwright-tests/pages/BailoutPage.ts index d387b15b..90d9caec 100644 --- a/playwright-tests/pages/BailoutPage.ts +++ b/playwright-tests/pages/BailoutPage.ts @@ -28,8 +28,8 @@ export class BailoutPage extends BasePage { await expect.soft(this.firstNameTextBox).toHaveValue(customerDetails.firstName); await expect.soft(this.lastNameTextBox).toHaveValue(customerDetails.lastName); await expect.soft(this.phoneNumberTextBox).toHaveValue(customerDetails.phoneNumber); - await expect.soft(this.emailAddressTextBox).toHaveValue(customerDetails.email); - await expect.soft(await this.getBailoutCode()).toEqual(bailoutCode); + //await expect.soft(this.emailAddressTextBox).toHaveValue(customerDetails.email); + expect.soft(await this.getBailoutCode()).toEqual(bailoutCode); } async validateBailoutDetailsNotNull(){ @@ -40,8 +40,12 @@ export class BailoutPage extends BasePage { expect(this.emailAddressTextBox.inputValue()).not.toBe(''); } + async validateHeavyTruckErrorMessage(){ + + } + async getBailoutCode() { - const mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')')); + const mainLocalStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'main\')')); const bailoutCode = mainLocalStorage.applicationUser.pageData['bailout-page'].bailoutCode as number; return bailoutCode; } diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 0e3fa2a5..3dbcfa68 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -404,7 +404,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Destructure bailout flags const { isVehicleSelectBailout, isTpaNotEnabledBailout, isPartsServiceErrorBailout, isVehicleLookupBailout, isPriceServiceErrorBailout, - isRequestCallbackBailout } = testCase.testData.bailoutFlags || {}; + isRequestCallbackBailout, isHeavyTruckVehicleBailout } = testCase.testData.bailoutFlags || {}; const repairTypes: VehicleDamage[] = [ VehicleDamage.WindshieldOneChip, @@ -460,12 +460,19 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Select vehicle await policyVehiclesPage.selectVehicle(vehicleDetails!); + await policyVehiclesPage.nextPage(); + await policyVehiclesPage.logReferralNumber(); + + if (isNonServiceable && isHeavyTruckVehicleBailout) { + await bailoutPage.validateURL(bailoutPage.issPageValue); + await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.HeavyTruckVehicle); + return; + } + if (isNonServiceable) { await policyVehiclesPage.assertNonServiceableAlertBehavior(); return; } - await policyVehiclesPage.nextPage(); - await policyVehiclesPage.logReferralNumber(); if (hasEndorsements) { await test.step('EndorsementsPage >> Select Endorsements', async () => { @@ -847,6 +854,9 @@ async function runWorkflow(page: Page, testCase: TestCase) { await contactDetailsPage.fillContactDetails(customerDetails!); + if (appointmentDetails?.alternateMobileCity) { + await contactDetailsPage.fillAlternateMobileStreetAndCity(appointmentDetails?.alternateMobileStreetAddress!, appointmentDetails?.alternateMobileCity!); + } //await contactDetailsPage.validateAlertsAreVisible(); //await contactDetailsPage.fillNotes(customerDetails!.notes!); diff --git a/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts b/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts index 6ecd2381..308ac97a 100644 --- a/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts +++ b/playwright-tests/tests/advanced/0029a_HeavyVehicleBailout.ts @@ -23,21 +23,27 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0029a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0029a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0029a', customerDetails, policyNumber); const advancedScenario0029Data: Partial = { clientTag: '', isDuplicateClaim: false, isPolicyFound: true, + isUnverifiedPolicyAfterVehicleLookup: true, + hasStateLawPopup: true, isNoComp: false, endorsements: undefined, + vehiclePartQuestions: [ + { + partQuestionType: PartQuestionType.WindshieldColor, + isOnPage: true, + optionToSelect: 'Heated glass, Encap, Asymmetrically Strengthen' + }, + ], isUseVehicleOnPolicy: false, servicePackage: faker.helpers.enumValue(ServicePackage), customerDetails: customerDetails, - bailoutFlags: { - isHeavyTruckVehicleBailout: true, - }, claimDetails: { policyNumber: policyNumber, policyDeductible: 500, @@ -50,7 +56,7 @@ const advancedScenario0029Data: Partial = { make: 'Freightliner', model: '114sd', vehicleLookupType: VehicleLookupType.Vin, - vin: '1FUJG3DV5HHJH2985', + vin: '1FVMG3DV5HHJA1388', }, vehicleDamage: [ VehicleDamage.WindshieldCrack, diff --git a/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts b/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts index da53e68a..b4fb227e 100644 --- a/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts +++ b/playwright-tests/tests/advanced/0031a_ReplaceServiceableBigTruck.ts @@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0031a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0031a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0031a', customerDetails, policyNumber); const advancedScenario0031Data: Partial = { @@ -33,19 +33,11 @@ const advancedScenario0031Data: Partial = { isNoComp: false, hasStateLawPopup: false, endorsements: undefined, - partQuestions: [ - { - partQuestionType: PartQuestionType.LaneKeepAssist, - isOnPage: true, - optionToSelect: 'Yes', - } - ], vehiclePartQuestions: [ { partQuestionType: PartQuestionType.WindshieldColor, isOnPage: true, - optionToSelect: 'Green Tint', - secondaryQuestionOptionToSelect: 'one-piece, w/lane departure warning system', + optionToSelect: 'One-Piece, With Lane Departure Warning System', }, ], isSafelite: true, diff --git a/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts b/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts index da064309..b2433ced 100644 --- a/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts +++ b/playwright-tests/tests/advanced/0032a_DeclineNonServiceableBigTruck.ts @@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0032a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0032a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0032a', customerDetails, policyNumber); const advancedScenario0032Data: Partial = { @@ -33,6 +33,9 @@ const advancedScenario0032Data: Partial = { isNoComp: false, hasStateLawPopup: false, endorsements: undefined, + bailoutFlags: { + isHeavyTruckVehicleBailout: true, + }, vehiclePartQuestions: [ ], From 07a8e2e516b54fa7b9ef20176c51db57d2dc3223 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 09:48:38 -0400 Subject: [PATCH 53/80] Remove drop off assertion from logic to fix flaky test --- playwright-tests/pages/SchedulePage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index d6544208..1fe05bee 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -72,7 +72,6 @@ export class SchedulePage extends BasePage { await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 }); await this.dropOffButton.click() } else { - await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 }); await this.dropOffButton.isVisible() ? await this.dropOffButton.click() : await (await this.getFirstNonDropOffTimeSlot()).click(); // drop off is available in the morning for same day so this is here to avoid flaky tests } } else { From 81862af867e93cb6ee7455c167e31e88000124fc Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 09:49:03 -0400 Subject: [PATCH 54/80] Fix conditions for cart validation --- playwright-tests/pages/OrderConfirmationPage.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index 40f5a29f..b7e64336 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -66,12 +66,12 @@ export class OrderConfirmationPage extends BasePage { const isUnverified = !isPolicyFound; const isUnverifiedPolicyAfterVehicleLookup = testData.isUnverifiedPolicyAfterVehicleLookup === true; const hasPremiumOrStandard = - claimDetails!.policyDeductible >= 0 && + (claimDetails!.policyDeductible >= 0 || !isPolicyFound) && (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard); const payAtService = - claimDetails!.policyDeductible >= 0 && - paymentDetails?.paymentType === PaymentType.PayAtService; + (claimDetails!.policyDeductible >= 0 && + paymentDetails?.paymentType === PaymentType.PayAtService) || !paymentDetails?.paymentType; const paidWithPIA = claimDetails!.policyDeductible >= 0 && @@ -84,9 +84,9 @@ export class OrderConfirmationPage extends BasePage { // Grab text content for elements that are expected on-screen const lineItemsValue = await textIf(hasPremiumOrStandard, this.cartLineItemsText); const deductibleTextValue = await textIf(!isItacOrNoComp, this.deductibleText); - const subtotalTextValue = await textIf((hasPremiumOrStandard || isItacOrNoComp || !isUnverified) && !isUnverifiedPolicyAfterVehicleLookup, this.subtotalText); - const totalAmountDueValue = await textIf(((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) || !isUnverified) && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); - const amountPaidTextValue = await textIf(((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) || !isUnverified) && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); + const subtotalTextValue = await textIf((hasPremiumOrStandard || isItacOrNoComp) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.subtotalText); + const totalAmountDueValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); + const amountPaidTextValue = await textIf((hasPremiumOrStandard && !payAtService) || (isItacOrNoComp && !payAtService) && !isUnverified && !isUnverifiedPolicyAfterVehicleLookup, this.totalAmountDueText); const finalAmountDueValue = await textIf(hasPremiumOrStandard || isItacOrNoComp || isUnverified || isUnverifiedPolicyAfterVehicleLookup, this.finalAmountDue); const confirmationTextValue = await this.confirmationText.textContent(); From c430ee82ef0570056f5123be55407979ccb1497f Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 09:50:03 -0400 Subject: [PATCH 55/80] Add method for non recal TPA flow --- playwright-tests/pages/ProviderPreferencePage.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/playwright-tests/pages/ProviderPreferencePage.ts b/playwright-tests/pages/ProviderPreferencePage.ts index 58525955..97afe80f 100644 --- a/playwright-tests/pages/ProviderPreferencePage.ts +++ b/playwright-tests/pages/ProviderPreferencePage.ts @@ -15,6 +15,7 @@ export class ProviderPreferencePage extends BasePage { readonly acknowledgeCheckbox: Locator; readonly tpaRecalModalContinueButton: Locator; readonly stateLawModalText: Locator; + readonly stateLawRepairModalText: Locator; readonly stateLawModalOkayButton: Locator; issPageValue = 'provider-preference'; @@ -33,8 +34,9 @@ export class ProviderPreferencePage extends BasePage { this.acknowledgeAdasCheckbox = this.page.getByRole('checkbox', { name: /I acknowledge/i }) this.tpaRecalModalContinueButton = this.page.locator('#modalbtn', { hasText: 'Continue' }); - // State law modal + // State law modal - is there a difference between repair and replacement modals? need to check this.stateLawModalText = this.page.getByText(/law prohibits us from requiring you.*You have the right to select the motor vehicle repair shop of your choice\./is); + this.stateLawRepairModalText = this.page.getByText(/We are prohibited by law from requiring that repairs be done at a specific automotive repair dealer. You are entitled to select the auto body repair shop to repair damage covered by us\./is); this.stateLawModalOkayButton = this.page.getByRole('button', { name: 'Okay' }) } @@ -64,7 +66,15 @@ export class ProviderPreferencePage extends BasePage { await this.tpaRecalModalContinueButton.click(); } + async scheduleTPAWithoutAdas() { + await this.findAnotherShopButton.click(); + } + async validateStateLawModalIsVisible() { await expect.soft(this.stateLawModalText).toBeVisible(); } + + async validateStateLawRepairModalIsVisible() { + await expect.soft(this.stateLawRepairModalText).toBeVisible(); + } } \ No newline at end of file From 82602ea348087d24cadac513aa9f84bc809b839d Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 09:51:45 -0400 Subject: [PATCH 56/80] Add part question responses to test case data --- .../tests/advanced/0003a_MobileAfterpay.ts | 7 ++++--- .../tests/advanced/0015a_NoCompPartQuestions.ts | 8 ++++---- .../tests/advanced/0016a_NoCompAllGlass.ts | 17 ++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts b/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts index 6e494225..f205e795 100644 --- a/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts +++ b/playwright-tests/tests/advanced/0003a_MobileAfterpay.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0003a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0003a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; const policySoap = MockPolicyData.getPolicySoapByScenario('0003a', customerDetails, policyNumber); const advancedScenario0003Data: Partial = { @@ -33,6 +33,7 @@ const advancedScenario0003Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, + hasOemEndorsement: true, hasStateLawPopup: false, endorsements: undefined, vehiclePartQuestions: [ @@ -44,7 +45,7 @@ const advancedScenario0003Data: Partial = { { partQuestionType: PartQuestionType.PassengerRearColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Solar, Passenger side, Rear' }, ], isSafelite: true, @@ -53,7 +54,7 @@ const advancedScenario0003Data: Partial = { claimDetails: { policyNumber: policyNumber, policyDeductible: 50, - damageDate: '2016-01-02', + damageDate: '2026-01-02', damageCause: faker.helpers.enumValue(DamageType) }, policySoap: policySoap, diff --git a/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts b/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts index c4108bb4..063059f9 100644 --- a/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts +++ b/playwright-tests/tests/advanced/0015a_NoCompPartQuestions.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0015a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0015a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0015a', customerDetails, policyNumber); const advancedScenario0015Data: Partial = { @@ -46,16 +46,16 @@ const advancedScenario0015Data: Partial = { { partQuestionType: PartQuestionType.WindshieldColor, isOnPage: true, - optionToSelect: 'Green Tint, Blue Shade' + optionToSelect: 'Heated glass, Heated glass Wiper Park' }, { partQuestionType: PartQuestionType.DriverFrontColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Driver side, Front' } ], isSafelite: true, - servicePackage: faker.helpers.enumValue(ServicePackage), + servicePackage: ServicePackage.Premium, customerDetails: customerDetails, claimDetails: { policyNumber: policyNumber, diff --git a/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts b/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts index 2b735a66..4fd55d9c 100644 --- a/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts +++ b/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0016a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0016a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0016a', customerDetails, policyNumber); const advancedScenario0016Data: Partial = { @@ -39,38 +39,37 @@ const advancedScenario0016Data: Partial = { { partQuestionType: PartQuestionType.WindshieldColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Solar, Third Visor Frit, soundproofing, Infrared Interlayer' }, { partQuestionType: PartQuestionType.DriverFrontColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Solar, Driver side, Front' }, { partQuestionType: PartQuestionType.DriverRearColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Solar, Driver side, Rear' }, { partQuestionType: PartQuestionType.DriverVentColor, isOnPage: true, - optionToSelect: 'Green Tint', - secondaryQuestionOptionToSelect: 'solar, driver side, rear' + optionToSelect: 'Solar, Driver side, Rear' }, { partQuestionType: PartQuestionType.PassengerFrontColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Solar, Passenger side, Front' }, { partQuestionType: PartQuestionType.PassengerRearColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Solar, Passenger side, Rear' }, { partQuestionType: PartQuestionType.RearWindowColor, isOnPage: true, - optionToSelect: 'Green Tint' + optionToSelect: 'Heated glass, Solar' }, ], isSafelite: true, From e6627f86f3be6b0e6fbaab93b409bb329b59baeb Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 09:53:26 -0400 Subject: [PATCH 57/80] Update payment method to match test name --- playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts b/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts index dc64aa11..2721d06b 100644 --- a/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts +++ b/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts @@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0001a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0001a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0001a', customerDetails, policyNumber); const advancedScenario0001Data: Partial = { @@ -60,7 +60,7 @@ const advancedScenario0001Data: Partial = { shopAddress: undefined, appointmentDate: nextWeekday }, - paymentDetails: ClientData.getDefaultPaypalDetails()//ClientData.getDefaultCreditCardDetails() + paymentDetails: ClientData.getDefaultCreditCardDetails() } // TODO: Add validation for deductible/covered amount From 0dbc77ce255ec5759673ec658269f02a7e9f89ee Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 10:04:14 -0400 Subject: [PATCH 58/80] Fix address for test scenario --- playwright-tests/tests/0002_EssentialRepairInShopAcura.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts b/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts index f05482a4..a255853d 100644 --- a/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts +++ b/playwright-tests/tests/0002_EssentialRepairInShopAcura.ts @@ -23,9 +23,9 @@ const essentialRepairInShopAcuraData: Partial = { phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/), notes: 'Automated Test', address: { - street: faker.location.streetAddress(), + street: '200 W. Oak Street', city: 'Fort Collins', - state: 'Colorado', + state: 'COLORADO', postalCode: '80526', country: 'United States' } From 9247ec1faefde5e43fc0c77766057be5e63e2577 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 10:06:26 -0400 Subject: [PATCH 59/80] Update address and vehicle --- playwright-tests/tests/0004_EssentialTpaEnabled.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/playwright-tests/tests/0004_EssentialTpaEnabled.ts b/playwright-tests/tests/0004_EssentialTpaEnabled.ts index 4aed4e19..3d521235 100644 --- a/playwright-tests/tests/0004_EssentialTpaEnabled.ts +++ b/playwright-tests/tests/0004_EssentialTpaEnabled.ts @@ -28,10 +28,10 @@ const essentialTpaEnabledData: Partial = { notes: 'Automated Test', address: { // street: faker.location.streetAddress(), - street: '134 Woodlands Place', - city: 'Dublin', - state: 'Florida', - postalCode: '32040', + street: '1343 Cameron Ave', + city: 'Lewis Center', + state: 'OH', + postalCode: '43035', country: 'United States' } }, @@ -42,10 +42,10 @@ const essentialTpaEnabledData: Partial = { damageCause: DamageType.Other }, vehicleDetails: { - year: '2022', + year: '2015', make: 'Honda', - model: 'Civic', - style: '4 door hatchback' + model: 'Accord', + style: '4 door sedan' }, vehicleDamage: [ VehicleDamage.WindshieldThreeChips, From 08cfe1347c5e70b77203359da20e22ba25606787 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 10:08:20 -0400 Subject: [PATCH 60/80] Change uuid for better policy number randomization --- .../advanced/0002a_ReplaceOemEndorsement.ts | 2 +- .../tests/advanced/0004a_NoDeductibleAdas.ts | 5 ++--- .../advanced/0005a_CapabilityQuestions.ts | 2 +- .../0006a_RepairNoDeductibleMobile.ts | 2 +- .../advanced/0007a_RepairStateLanguage.ts | 2 +- .../tests/advanced/0008a_RepairTpa.ts | 6 ++--- .../advanced/0009a_NoDeductibleFlorida.ts | 4 ++-- .../tests/advanced/0010a_RearGlass.ts | 4 ++-- .../tests/advanced/0011a_ItacNoAdas.ts | 8 +++---- .../tests/advanced/0012a_ItacDropOff.ts | 4 ++-- .../tests/advanced/0013a_ItacMobile.ts | 22 ++++++++++--------- .../tests/advanced/0014a_NoCompAdas.ts | 6 ++--- .../tests/advanced/0017a_NoCompPremium.ts | 2 +- .../tests/advanced/0018a_NoCompGlassOnly.ts | 2 +- .../tests/advanced/0019a_NoCompEditVehicle.ts | 2 +- .../tests/advanced/0020a_NoCompChangeLoc.ts | 12 +++++----- .../tests/advanced/0021a_VehicleByPlate.ts | 7 +++--- .../tests/advanced/0022a_VehicleByVIN.ts | 3 ++- .../0024a_VehicleByVINUnverifiedBailout.ts | 10 ++++----- .../0025a_VehicleByVINUnverifiedBailout.ts | 2 +- .../tests/advanced/0028a_ItacCancelMyClaim.ts | 2 +- .../advanced/0030a_PartsServiceBailout.ts | 2 +- 22 files changed, 58 insertions(+), 53 deletions(-) diff --git a/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts b/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts index 2d6bf104..56d2f722 100644 --- a/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts +++ b/playwright-tests/tests/advanced/0002a_ReplaceOemEndorsement.ts @@ -29,7 +29,7 @@ if ((process.env.ENABLE_MOCK_TESTING ?? 'false') === 'true') { customerDetails.lastName = 'Cormier'; customerDetails.address.street = '69825 W Pine Street'; } else { - policyNumber = `~AutomatedScenario0002a${faker.string.uuid().substring(0, 6)}`; + policyNumber = `~AutomatedScenario0002a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; }; const policySoap = MockPolicyData.getPolicySoapByScenario('0002a', customerDetails, policyNumber); diff --git a/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts b/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts index b64aaac5..21015d9d 100644 --- a/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts +++ b/playwright-tests/tests/advanced/0004a_NoDeductibleAdas.ts @@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0004a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0004a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0004a', customerDetails, policyNumber); const advancedScenario0004aData: Partial = { @@ -33,8 +33,7 @@ const advancedScenario0004aData: Partial = { isNoComp: false, hasStateLawPopup: false, endorsements: undefined, - vehiclePartQuestions: [ - ], + partQuestions: [], isSafelite: true, servicePackage: faker.helpers.enumValue(ServicePackage), customerDetails: customerDetails, diff --git a/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts b/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts index 64a1d1c6..0d0bc31f 100644 --- a/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts +++ b/playwright-tests/tests/advanced/0005a_CapabilityQuestions.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0005a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0005a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0005a', customerDetails, policyNumber); const advancedScenario0005Data: Partial = { diff --git a/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts b/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts index 7491c063..279f141c 100644 --- a/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts +++ b/playwright-tests/tests/advanced/0006a_RepairNoDeductibleMobile.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0006a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0006a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0006a', customerDetails, policyNumber); const advancedScenario0006Data: Partial = { diff --git a/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts b/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts index 6abec815..cfec1614 100644 --- a/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts +++ b/playwright-tests/tests/advanced/0007a_RepairStateLanguage.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0007a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0007a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0007a', customerDetails, policyNumber); const advancedScenario0007Data: Partial = { diff --git a/playwright-tests/tests/advanced/0008a_RepairTpa.ts b/playwright-tests/tests/advanced/0008a_RepairTpa.ts index c5b23efa..2404f831 100644 --- a/playwright-tests/tests/advanced/0008a_RepairTpa.ts +++ b/playwright-tests/tests/advanced/0008a_RepairTpa.ts @@ -12,7 +12,7 @@ const nextWeekday = getNextWeekday(); const customerAddress: IAddress = { street: faker.location.streetAddress(), city: 'Hamden', - state: 'CT', + state: 'CONNECTICUT', postalCode: '06517', country: 'United States' } @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0008a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0008a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0008a', customerDetails, policyNumber); const advancedScenario0008Data: Partial = { @@ -33,7 +33,7 @@ const advancedScenario0008Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, - hasStateLawPopup: true, + hasStateLawPopup: false, endorsements: undefined, vehiclePartQuestions: [ // { diff --git a/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts b/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts index 103f769c..7b0dae5b 100644 --- a/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts +++ b/playwright-tests/tests/advanced/0009a_NoDeductibleFlorida.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0009a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0009a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0009a', customerDetails, policyNumber); const advancedScenario0009Data: Partial = { @@ -38,7 +38,7 @@ const advancedScenario0009Data: Partial = { { endorsementType: EndorsementType.Educator, isOnPolicy: true, - isClickYes: false + isClickYes: true } ], vehiclePartQuestions: [ diff --git a/playwright-tests/tests/advanced/0010a_RearGlass.ts b/playwright-tests/tests/advanced/0010a_RearGlass.ts index de2fc541..c793e224 100644 --- a/playwright-tests/tests/advanced/0010a_RearGlass.ts +++ b/playwright-tests/tests/advanced/0010a_RearGlass.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0010a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0010a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0010a', customerDetails, policyNumber); const advancedScenario0010Data: Partial = { @@ -92,7 +92,7 @@ for (const client of advancedClients) { const data = {...advancedScenario0010Data}; data.clientTag = client.clientTag; const tc = new TestCase({ - name: `0010a Advanced Repair No Deductible FL Rear Client: "${client.accountName}"`, + name: `0010a Advanced Replace No Deductible FL Rear Client: "${client.accountName}"`, tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Advanced'], testData: data }, undefined, '0010a'); diff --git a/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts b/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts index bf008707..8bffcbf0 100644 --- a/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts +++ b/playwright-tests/tests/advanced/0011a_ItacNoAdas.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0011a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0011a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0011a', customerDetails, policyNumber); const advancedScenario0011Data: Partial = { @@ -33,13 +33,13 @@ const advancedScenario0011Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, - isItac: true, + isItac: false, hasStateLawPopup: false, endorsements: [ { endorsementType: EndorsementType.Educator, isOnPolicy: true, - isClickYes: false + isClickYes: true } ], partQuestions: undefined, @@ -48,7 +48,7 @@ const advancedScenario0011Data: Partial = { customerDetails: customerDetails, claimDetails: { policyNumber: policyNumber, - policyDeductible: 500, + policyDeductible: 0, damageDate: '2023-03-01', damageCause: faker.helpers.enumValue(DamageType) }, diff --git a/playwright-tests/tests/advanced/0012a_ItacDropOff.ts b/playwright-tests/tests/advanced/0012a_ItacDropOff.ts index 3cfe4788..213ec490 100644 --- a/playwright-tests/tests/advanced/0012a_ItacDropOff.ts +++ b/playwright-tests/tests/advanced/0012a_ItacDropOff.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0012a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0012a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0012a', customerDetails, policyNumber); const advancedScenario0012Data: Partial = { @@ -48,7 +48,7 @@ const advancedScenario0012Data: Partial = { customerDetails: customerDetails, claimDetails: { policyNumber: policyNumber, - policyDeductible: 500, + policyDeductible: 9999, damageDate: '2023-09-01', damageCause: faker.helpers.enumValue(DamageType) }, diff --git a/playwright-tests/tests/advanced/0013a_ItacMobile.ts b/playwright-tests/tests/advanced/0013a_ItacMobile.ts index 3e0cf9cc..288f631e 100644 --- a/playwright-tests/tests/advanced/0013a_ItacMobile.ts +++ b/playwright-tests/tests/advanced/0013a_ItacMobile.ts @@ -1,6 +1,6 @@ import ClientData from "@business-logic/data/ClientData"; import TestCase from "@business-logic/types/TestCase"; -import { DamageType, EndorsementType, ServiceLocation, ServicePackage, VehicleDamage } from "@business-logic/types/Enums"; +import { DamageType, EndorsementType, PaymentType, ServiceLocation, ServicePackage, VehicleDamage } from "@business-logic/types/Enums"; import { ITestData } from "@business-logic/types/ITestData" import { faker } from "@faker-js/faker"; import { getNextWeekday } from "@impl/utils/DateUtils"; @@ -11,9 +11,9 @@ import { IAddress } from "@business-logic/types/IAddress"; const nextWeekday = getNextWeekday(); const customerAddress: IAddress = { street: faker.location.streetAddress(), - city: 'Birmingham', - state: 'AL', - postalCode: '35118', + city: 'Worthington', + state: 'OH', + postalCode: '43085', country: 'United States' } const customerDetails: ICustomerDetails = { @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0013a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0013a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0013a', customerDetails, policyNumber); const advancedScenario0013Data: Partial = { @@ -33,13 +33,13 @@ const advancedScenario0013Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, - isItac: true, + isItac: false, hasStateLawPopup: false, endorsements: [ { endorsementType: EndorsementType.Educator, isOnPolicy: true, - isClickYes: false + isClickYes: true } ], vehiclePartQuestions: [ @@ -49,7 +49,7 @@ const advancedScenario0013Data: Partial = { customerDetails: customerDetails, claimDetails: { policyNumber: policyNumber, - policyDeductible: 9999, + policyDeductible: 0, damageDate: '2023-08-01', damageCause: faker.helpers.enumValue(DamageType) }, @@ -68,7 +68,9 @@ const advancedScenario0013Data: Partial = { serviceAddress: customerAddress, appointmentDate: nextWeekday }, - paymentDetails: ClientData.getDefaultCreditCardDetails() + paymentDetails: { + paymentType: PaymentType.PayAtService + } } @@ -80,7 +82,7 @@ for (const client of advancedClients) { const data = { ...advancedScenario0013Data }; data.clientTag = client.clientTag; const tc = new TestCase({ - name: `0013a Advanced ITAC Mobile Client: "${client.accountName}"`, + name: `0013a Advanced $0 Deductible with Vaps Mobile Client: "${client.accountName}"`, tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Advanced'], testData: data }, undefined, '0013a'); diff --git a/playwright-tests/tests/advanced/0014a_NoCompAdas.ts b/playwright-tests/tests/advanced/0014a_NoCompAdas.ts index c8a4c414..919833c0 100644 --- a/playwright-tests/tests/advanced/0014a_NoCompAdas.ts +++ b/playwright-tests/tests/advanced/0014a_NoCompAdas.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0014a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0014a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0014a', customerDetails, policyNumber); const advancedScenario0014Data: Partial = { @@ -33,13 +33,13 @@ const advancedScenario0014Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: true, - isRecalWarning: true, + isRecalWarning: false, hasStateLawPopup: false, endorsements: [ { endorsementType: EndorsementType.Educator, isOnPolicy: true, - isClickYes: true + isClickYes: false } ], partQuestions: undefined, diff --git a/playwright-tests/tests/advanced/0017a_NoCompPremium.ts b/playwright-tests/tests/advanced/0017a_NoCompPremium.ts index 6852eb76..5c70f919 100644 --- a/playwright-tests/tests/advanced/0017a_NoCompPremium.ts +++ b/playwright-tests/tests/advanced/0017a_NoCompPremium.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0017a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0017a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0017a', customerDetails, policyNumber); const advancedScenario0017Data: Partial = { diff --git a/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts b/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts index 4208b0d9..94619a0b 100644 --- a/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts +++ b/playwright-tests/tests/advanced/0018a_NoCompGlassOnly.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0018a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0018a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0018a', customerDetails, policyNumber); const advancedScenario0018Data: Partial = { diff --git a/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts b/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts index 5be94a82..d9b4c818 100644 --- a/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts +++ b/playwright-tests/tests/advanced/0019a_NoCompEditVehicle.ts @@ -31,7 +31,7 @@ const vehicleDetails: IVehicleDetails = { style: '4 door sedan' }; -const policyNumber = `~AutomatedScenario0019a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0019a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0019a', customerDetails, policyNumber); const advancedScenario0019Data: Partial = { diff --git a/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts b/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts index 1f9f86fb..a565be44 100644 --- a/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts +++ b/playwright-tests/tests/advanced/0020a_NoCompChangeLoc.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0020a${faker.string.uuid().substring(0,6)}`; +const policyNumber = `~AutomatedScenario0020a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0020a', customerDetails, policyNumber); const advancedScenario0020Data: Partial = { @@ -60,12 +60,14 @@ const advancedScenario0020Data: Partial = { serviceLocation: ServiceLocation.Mobile, serviceAddress: { street: faker.location.streetAddress(), - city: 'Goose Creek', - state: 'SC', - postalCode: '29445', + city: 'Worthington', + state: 'OH', + postalCode: '43085', country: 'US' }, - alternateServiceZip: '29445', + alternateServiceZip: '43085', + alternateMobileCity: 'Worthington', + alternateMobileStreetAddress: '177 Franklin Ave', appointmentDate: nextWeekday }, paymentDetails: { diff --git a/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts b/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts index 810e74bf..f5acaccf 100644 --- a/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts +++ b/playwright-tests/tests/advanced/0021a_VehicleByPlate.ts @@ -23,13 +23,14 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0021a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0021a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0021a', customerDetails, policyNumber); const advancedScenario0021Data: Partial = { clientTag: '', isDuplicateClaim: false, isPolicyFound: true, + isUnverifiedPolicyAfterVehicleLookup: true, isNoComp: false, hasStateLawPopup: true, endorsements: undefined, @@ -54,8 +55,8 @@ const advancedScenario0021Data: Partial = { model: 'RX 300', style: undefined, vehicleLookupType: VehicleLookupType.LicensePlateNumber, - licensePlateNumber: ['NoPlate753', 'BQ40903','1111'], - licensePlateState: ['Illinois','Illinois','California'], + licensePlateNumber: ['NoPlate753', 'GAH2307','1111'], + licensePlateState: ['Illinois','Ohio','California'], }, vehicleDamage: [ VehicleDamage.WindshieldCrack, diff --git a/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts b/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts index 7866c3eb..dffddf16 100644 --- a/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts +++ b/playwright-tests/tests/advanced/0022a_VehicleByVIN.ts @@ -23,13 +23,14 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0022a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0022a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0022a', customerDetails, policyNumber); const advancedScenario0022Data: Partial = { clientTag: '', isDuplicateClaim: false, isPolicyFound: true, + isUnverifiedPolicyAfterVehicleLookup: true, isNoComp: false, hasStateLawPopup: true, endorsements: undefined, diff --git a/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts b/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts index 7df0a5a4..26f1daa8 100644 --- a/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts +++ b/playwright-tests/tests/advanced/0024a_VehicleByVINUnverifiedBailout.ts @@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0024a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0024a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0024a', customerDetails, policyNumber); const advancedScenario0024Data: Partial = { @@ -50,14 +50,14 @@ const advancedScenario0024Data: Partial = { policySoap: policySoap, vehicleDetails: { year: '2015', - make: 'Ford', - model: 'F Series F150', - style: '2 door super cab', + make: 'Audi', + model: 'A7', + style: '4 door hatchback', vehicleLookupType: VehicleLookupType.Address, address: [ { street: '9 Test Street', city: 'Columbus', state: 'OH', postalCode: '43220', lastName: 'Test' }, { street: '8621 GREENLEAF AVE', city: 'Whittier', state: 'California', postalCode: '90602', lastName: 'Johnson' }, - { street: '10212 JEWEL CT', city: 'Conroe', state: 'Texas', postalCode: '77385', lastName: 'Reed' } + { street: '6531 CANYON RANCH', city: 'Frisco', state: 'Texas', postalCode: '75036', lastName: 'TRAINOR' } ], }, vehicleDamage: [ diff --git a/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts b/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts index f3e6c7a8..4851c4ab 100644 --- a/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts +++ b/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts @@ -23,7 +23,7 @@ const customerDetails: ICustomerDetails = { } } -const policyNumber = `~AutomatedScenario0025a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0025a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0025a', customerDetails, policyNumber); const advancedScenario0025Data: Partial = { diff --git a/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts b/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts index 8479feb4..1f805cc3 100644 --- a/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts +++ b/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0028a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0028a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0028a', customerDetails, policyNumber); const advancedScenario0028aData: Partial = { diff --git a/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts b/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts index 07752f51..fa565b37 100644 --- a/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts +++ b/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts @@ -25,7 +25,7 @@ const customerDetails: ICustomerDetails = { address: customerAddress } -const policyNumber = `~AutomatedScenario0030a${faker.string.uuid().substring(0, 6)}`; +const policyNumber = `~AutomatedScenario0030a${crypto.randomUUID().replace(/-/g, '').slice(0, 16).toUpperCase()}`; // Ensure unique policy number for each test run since same key in request can cause 500 error const policySoap = MockPolicyData.getPolicySoapByScenario('0030a', customerDetails, policyNumber); const advancedScenario0030aData: Partial = { From 3bc1dcaae90c0dcec6d5bee3b29a0b88c83c2632 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:16:42 -0400 Subject: [PATCH 61/80] Update bailout code and progress bar percentages --- .../business-logic/types/Enums.ts | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/playwright-tests/business-logic/types/Enums.ts b/playwright-tests/business-logic/types/Enums.ts index 0ffb2257..9280ab00 100644 --- a/playwright-tests/business-logic/types/Enums.ts +++ b/playwright-tests/business-logic/types/Enums.ts @@ -137,18 +137,23 @@ export enum SideDoorDamage{ export enum BailoutCode { Unknown = 0, - SaveSessionError, - VehicleNotFound, - VehicleLookupError, - CoverageStatementInvalidState, - PricingResponseError, - TPANotEnabled, - RequestCallback = 17, // bailout code 17 is Request Callback - HeavyTruckVehicle = 9, // bailout code 9 is Heavy Truck Vehicle - NoPartsAvailable, - PartsServiceError, - SafeliteNotTheProvider, - VehicleYMMSLookupError + SaveSessionError = 1, + VehicleNotFound = 2, + VehicleVinLookupError = 3, + CoverageStatementInvalidState = 4, + DoNotSeeMyShop = 5, + PricingResponseError = 6, + TPANotEnabled = 7, + RequestCallback = 8, + HeavyTruckVehicle = 9, + NoPartsAvailable = 10, + PartsServiceError = 11, + SafeliteNotTheProvider = 12, + VehicleYMMSLookupError = 13, + ApplicationError = 14, + ApiError = 15, + RouterError = 16, + CoverageCancelledByUser = 17 } export enum SignatureAlgorithm { @@ -177,22 +182,28 @@ export enum Authentication { // TODO: Add validations for the percentages on each page like url validations export enum ProgressBarPercentage { + DuplicateCheckPage = '15%', PolicyVehiclesPage = '20%', - PolicyHolderDetailsPage = '30%', - VehicleSelectionPage = '25%', PolicyEndorsementsPage = '20%', + VehicleSelectionPage = '25%', + PolicyHolderDetailsPage = '30%', VehicleDamagePage = '35%', - VehicleLookupPage = '45%', PartQuestionsPage = '40%', MoldingQuestionsPage = '45%', VehiclePartsPage = '45%', CapabilityQuestionsPage = '45%', + VehicleLookupPage = '45%', + VinLookupPage = '50%', + LicensePlateLookupPage = '50%', + AddressLookupPage = '50%', + AddressVehiclesPage = '50%', CoverageStatementPage = '55%', ProviderPreferencePage = '60%', SchedulePage = '70%', - ContactDetailsPage = '80%', - ServicePackagesPage = '85%', - PaymentMethodPage = '90%', + ServicePackagesPage = '75%', + ContactDetailsPage = '85%', + PaymentMethodPage = '95%', + PaymentPage = '0%', OrderConfirmationPage = '100%', TpaSearchPage = '80%', TpaSubmitPage = '95%', From 70f694eba26cc5f84553113a6ee2ecc779293002 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:17:54 -0400 Subject: [PATCH 62/80] Update to correct application name --- playwright-tests/impl/api/CcisApiUtil.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright-tests/impl/api/CcisApiUtil.ts b/playwright-tests/impl/api/CcisApiUtil.ts index acc5de7d..e4f9c8a5 100644 --- a/playwright-tests/impl/api/CcisApiUtil.ts +++ b/playwright-tests/impl/api/CcisApiUtil.ts @@ -13,7 +13,7 @@ export default class CcisApiUtil { return axios.post(this.postCreateMockPolicyUrl, requestBody, { headers: { 'X-Mule-Origin-Verify': authToken, - 'x-Application-Name': 'CCIS Playwright' + 'x-Application-Name': 'ISS Playwright' } }); } @@ -23,7 +23,7 @@ export default class CcisApiUtil { return axios.delete(deleteUrl, { headers: { 'X-Mule-Origin-Verify': authToken, - 'x-Application-Name': 'CCIS Playwright' + 'x-Application-Name': 'ISS Playwright' } }); } From 2ff1fb3186c3a3123d068e0e4566056e38906b06 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:25:59 -0400 Subject: [PATCH 63/80] Remove empty method and comment --- playwright-tests/pages/BailoutPage.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/playwright-tests/pages/BailoutPage.ts b/playwright-tests/pages/BailoutPage.ts index 90d9caec..a75e98fe 100644 --- a/playwright-tests/pages/BailoutPage.ts +++ b/playwright-tests/pages/BailoutPage.ts @@ -28,7 +28,6 @@ export class BailoutPage extends BasePage { await expect.soft(this.firstNameTextBox).toHaveValue(customerDetails.firstName); await expect.soft(this.lastNameTextBox).toHaveValue(customerDetails.lastName); await expect.soft(this.phoneNumberTextBox).toHaveValue(customerDetails.phoneNumber); - //await expect.soft(this.emailAddressTextBox).toHaveValue(customerDetails.email); expect.soft(await this.getBailoutCode()).toEqual(bailoutCode); } @@ -40,10 +39,6 @@ export class BailoutPage extends BasePage { expect(this.emailAddressTextBox.inputValue()).not.toBe(''); } - async validateHeavyTruckErrorMessage(){ - - } - async getBailoutCode() { const mainLocalStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'main\')')); const bailoutCode = mainLocalStorage.applicationUser.pageData['bailout-page'].bailoutCode as number; From 811795a699de85797a0c9fe76f8ee2f4e3612df8 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:27:12 -0400 Subject: [PATCH 64/80] Remove unused import --- playwright-tests/pages/OrderConfirmationPage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/pages/OrderConfirmationPage.ts b/playwright-tests/pages/OrderConfirmationPage.ts index b7e64336..9c8f22fd 100644 --- a/playwright-tests/pages/OrderConfirmationPage.ts +++ b/playwright-tests/pages/OrderConfirmationPage.ts @@ -3,7 +3,6 @@ import { BasePage } from './BasePage'; import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails'; import { PaymentType, ServicePackage } from '@business-logic/types/Enums'; import { ITestData } from '@business-logic/types/ITestData'; -import { log } from 'console'; export class OrderConfirmationPage extends BasePage { readonly page: Page; From a31c7f7e63e1d870e30112b089ffd888467928c2 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:30:32 -0400 Subject: [PATCH 65/80] Remove unnecessary click --- playwright-tests/pages/AfterpayPage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index 98c66131..5053cf14 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -29,7 +29,6 @@ export class AfterpayPage extends BasePage { } async login(password: string) { - await this.passwordTextBox.click(); await this.passwordTextBox.fill(password); await this.submitButton.click(); } From c4a6990d81d2fe8a3500802b35c8bf689ee8f2ea Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:36:35 -0400 Subject: [PATCH 66/80] Remove unused address form --- playwright-tests/pages/PolicyHolderDetailsPage.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/playwright-tests/pages/PolicyHolderDetailsPage.ts b/playwright-tests/pages/PolicyHolderDetailsPage.ts index d37ba9cb..0acde323 100644 --- a/playwright-tests/pages/PolicyHolderDetailsPage.ts +++ b/playwright-tests/pages/PolicyHolderDetailsPage.ts @@ -1,13 +1,11 @@ import { type Locator, type Page } from '@playwright/test'; import { BasePage } from './BasePage'; import { ICustomerDetails } from '@business-logic/types/CustomerDetails'; -import { AddressForm } from './forms/AddressForm'; export class PolicyHolderDetailsPage extends BasePage { readonly page: Page; readonly firstNameTextBox: Locator; readonly lastNameTextBox: Locator; - readonly addressForm: AddressForm; // double check if this is used for advanced flows ? readonly addressInputBox: Locator; readonly emailAddressTextBox: Locator; @@ -18,7 +16,6 @@ export class PolicyHolderDetailsPage extends BasePage { this.page = page; this.firstNameTextBox = page.locator('#firstNameField'); this.lastNameTextBox = page.locator('#lastNameField'); - this.addressForm = new AddressForm(page); this.emailAddressTextBox = page.locator('#emailField'); this.addressInputBox = page.locator('input[name="autocomplete"]'); // for essential flows } From 3692bd13ee666a349c9b983c6ac1451c60b7ea80 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:37:33 -0400 Subject: [PATCH 67/80] Remove comment --- playwright-tests/pages/PolicyVehiclesPage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/pages/PolicyVehiclesPage.ts b/playwright-tests/pages/PolicyVehiclesPage.ts index 2b647906..882341b6 100644 --- a/playwright-tests/pages/PolicyVehiclesPage.ts +++ b/playwright-tests/pages/PolicyVehiclesPage.ts @@ -13,7 +13,6 @@ export class PolicyVehiclesPage extends BasePage { this.page = page; this.addAnotherVehicleButton = this.page.getByText('Add another vehicle'); this.selectVehicleOnPolicyButton = this.page.locator(`input[type="radio"][name="policyVehiclesQuestionOption"]`).first(); - // this.validateURL(this.url); } async validateVehicleIsOnPolicy(vehicleDetails: IVehicleDetails) { From c03f8a32ea2a1029a7a96395e57d1cae0545684f Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:41:05 -0400 Subject: [PATCH 68/80] Remove commented code --- playwright-tests/pages/TpaSearchPage.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/pages/TpaSearchPage.ts b/playwright-tests/pages/TpaSearchPage.ts index 15d5e851..0919f2d8 100644 --- a/playwright-tests/pages/TpaSearchPage.ts +++ b/playwright-tests/pages/TpaSearchPage.ts @@ -4,7 +4,6 @@ import { BasePage } from './BasePage'; export class TpaSearchPage extends BasePage { readonly page: Page; readonly firstLocationButton: Locator; - //readonly doNotSeeMyShopButton: Locator; issPageValue = 'tpa-search'; constructor(page: Page) { From 5dd960a796689ed715bcc1adb553a538a60f9f99 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:42:46 -0400 Subject: [PATCH 69/80] Remove commented out code --- playwright-tests/pages/TpaSubmitPage.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/playwright-tests/pages/TpaSubmitPage.ts b/playwright-tests/pages/TpaSubmitPage.ts index 1726a75e..0439adbe 100644 --- a/playwright-tests/pages/TpaSubmitPage.ts +++ b/playwright-tests/pages/TpaSubmitPage.ts @@ -21,15 +21,6 @@ export class TpaSubmitPage extends BasePage { this.deductible = page.locator('span#deductible-value, span.deductible-value'); } - /* TODO: Fix flakiness of recal alert - /*async validateRecalAlert() { - await this.recalAlert.waitFor({ state: 'visible', timeout: 5000 }); - await this.learnMoreLink.click(); - await this.recalModal.waitFor({ state: 'visible', timeout: 5000 }); - await this.recalModal.getByRole('button', { name: 'Close' }).click(); - - }*/ - async validateDeductible(claimDetails: IClaimDetails) { await expect(this.deductible).toContainText(claimDetails.policyDeductible.toLocaleString()); } From fe32b1aa375c34d00472d808c1194c3ce0fc8585 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 15:43:57 -0400 Subject: [PATCH 70/80] Remove commented code --- playwright-tests/pages/VehiclePartsPage.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/playwright-tests/pages/VehiclePartsPage.ts b/playwright-tests/pages/VehiclePartsPage.ts index 1d778f82..70d9a7ee 100644 --- a/playwright-tests/pages/VehiclePartsPage.ts +++ b/playwright-tests/pages/VehiclePartsPage.ts @@ -8,18 +8,7 @@ export default class VehiclePartQuestionsPage extends PartQuestionsPage{ constructor(page: Page) { super(page); } -/* this fails for some reason/some cases so may need to rework maybe only validate for part questions page and not on vehicle parts -async validatePartQuestions(partQuestions: IPartQuestion[]) { - for (const pq of partQuestions) { - const partQuestionOptions = this.page.locator(`fieldset[aria-labelledby="${pq.partQuestionType}"]`); - if (pq.isOnPage) { - await expect(partQuestionOptions).toBeAttached(); - } else { - await expect(partQuestionOptions).not.toBeAttached(); - } - } - } -*/ + async selectPartQuestionResponses(partQuestions: IPartQuestion[]) { for (const pq of partQuestions) { const parentobject=this.page.locator(`fieldset[aria-labelledby="${pq.partQuestionType}"]`); From b9dd5fe7ac3c76e85d6d7d03977ad3f242e2f8d0 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 16:12:28 -0400 Subject: [PATCH 71/80] Fix bailout test --- playwright-tests/business-logic/types/IBailoutFlags.ts | 3 ++- playwright-tests/tests/0000__M.test.ts | 8 ++++---- .../tests/advanced/0028a_ItacCancelMyClaim.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/playwright-tests/business-logic/types/IBailoutFlags.ts b/playwright-tests/business-logic/types/IBailoutFlags.ts index 2a5ec3c7..93346057 100644 --- a/playwright-tests/business-logic/types/IBailoutFlags.ts +++ b/playwright-tests/business-logic/types/IBailoutFlags.ts @@ -6,5 +6,6 @@ export default interface IBailoutFlags { isPartsServiceErrorBailout: boolean, isSafeliteNotTheProviderBailout: boolean, isVehicleLookupBailout: boolean, - isPriceServiceErrorBailout: boolean + isPriceServiceErrorBailout: boolean, + isCoverageCancelledByUser: boolean } \ No newline at end of file diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 3dbcfa68..e38994e8 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -404,7 +404,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Destructure bailout flags const { isVehicleSelectBailout, isTpaNotEnabledBailout, isPartsServiceErrorBailout, isVehicleLookupBailout, isPriceServiceErrorBailout, - isRequestCallbackBailout, isHeavyTruckVehicleBailout } = testCase.testData.bailoutFlags || {}; + isRequestCallbackBailout, isHeavyTruckVehicleBailout, isCoverageCancelledByUser } = testCase.testData.bailoutFlags || {}; const repairTypes: VehicleDamage[] = [ VehicleDamage.WindshieldOneChip, @@ -684,15 +684,15 @@ async function runWorkflow(page: Page, testCase: TestCase) { await vehiclePartQuestionsPage.nextPage(); } - if (isRequestCallbackBailout) { + if (isCoverageCancelledByUser) { await test.step('CoverageStatementPage >> Cancel my claim', async () => { await coverageStatementPage.validateURL(coverageStatementPage.issPageValue); await coverageStatementPage.cancelMyClaim(); }); - await test.step('BailoutPage >> Request Callback Bailout', async () => { + await test.step('BailoutPage >> User canceled claim on ITAC/No Comp coverage statement', async () => { await bailoutPage.validateURL(bailoutPage.issPageValue); - await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.RequestCallback); + await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.CoverageCancelledByUser); }); return; } diff --git a/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts b/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts index 1f805cc3..358dfbd5 100644 --- a/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts +++ b/playwright-tests/tests/advanced/0028a_ItacCancelMyClaim.ts @@ -36,7 +36,7 @@ const advancedScenario0028aData: Partial = { isItac: true, hasStateLawPopup: false, bailoutFlags: { - isRequestCallbackBailout: true, + isCoverageCancelledByUser: true, }, isVehicleSelectBailout: false, endorsements: [ From f8b17416560629f6b7c3ad1c3d20eb56e8e76a10 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Wed, 25 Mar 2026 16:12:53 -0400 Subject: [PATCH 72/80] Change scenario to test in shop --- playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts b/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts index 4fd55d9c..e336e3ea 100644 --- a/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts +++ b/playwright-tests/tests/advanced/0016a_NoCompAllGlass.ts @@ -99,8 +99,8 @@ const advancedScenario0016Data: Partial = { // VehicleDamage.PassengerVentGlass // TODO: Verify if this is intentionally not in UI ], appointmentDetails: { - serviceLocation: ServiceLocation.Mobile, - serviceAddress: customerAddress, + serviceLocation: ServiceLocation.InShop, + serviceAddress: undefined, appointmentDate: nextWeekday }, paymentDetails: ClientData.getDefaultPaypalDetails() From adc0a013a8c326c4b5aaf2847c344349088ad4d8 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 09:53:15 -0400 Subject: [PATCH 73/80] Remove comments --- playwright-tests/tests/0000__M.test.ts | 40 ++------------------------ 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index e38994e8..073c0b7a 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -498,16 +498,6 @@ async function runWorkflow(page: Page, testCase: TestCase) { forceAPIError(page, '/vehicle/api/v1/vehicle/lookup') } - /*await test.step('VehicleDetailsPage >> Select Vehicle', async () => { - await vehicleSelectionPage.validateURL(vehicleSelectionPage.issPageValue); - await vehicleSelectionPage.selectVehicle(vehicleDetails!); - if (isNonServiceable) { - await vehicleSelectionPage.assertNonServiceableAlertBehavior(); - return; - } - await vehicleSelectionPage.nextPage(); - });*/ - if (isNonServiceable) { return; } @@ -524,11 +514,6 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Essential flow when policy is not found if (isPolicyFound === false) { testCase.testData.isPolicyFound = false; - /* await test.step('VehicleDamagePage >> Click Edit Vehicle', async () => { - await vehicleDamagePage.validateURL(vehicleDamagePage.issPageValue); - await vehicleDamagePage.editVehicleButton.click(); - }); - */ // commented out for now because is there an edit vehicle flow? await test.step('VehicleSelectionPage >> Select vehicle for essential flow', async () => { await vehicleSelectionPage.validateURL(vehicleSelectionPage.issPageValue); @@ -679,7 +664,6 @@ async function runWorkflow(page: Page, testCase: TestCase) { if (vehiclePartQuestions && vehiclePartQuestions.length > 0) { await vehiclePartQuestionsPage.validateURL(vehiclePartQuestionsPage.issPageValue); - //await vehiclePartQuestionsPage.validatePartQuestions(vehiclePartQuestions); await vehiclePartQuestionsPage.selectPartQuestionResponses(vehiclePartQuestions); await vehiclePartQuestionsPage.nextPage(); } @@ -739,10 +723,8 @@ async function runWorkflow(page: Page, testCase: TestCase) { await test.step('ProviderPreferencePage >> Dismiss state law popup', async () => { await providerPreferencePage.validateURL(providerPreferencePage.issPageValue); if (vehicleDamage!.includes(VehicleDamage.WindshieldCrack)) { - //await providerPreferencePage.validateStateLawModalIsVisible(); await providerPreferencePage.stateLawModalOkayButton.click(); } else { - //await providerPreferencePage.validateStateLawRepairModalIsVisible(); await providerPreferencePage.stateLawModalOkayButton.click(); } }); @@ -763,19 +745,6 @@ async function runWorkflow(page: Page, testCase: TestCase) { }); } - - // validations for Recal warning mesage on schedule page - // commented out for now will add back in to validate recal banner on schedule page - /*if (isRecalWarning) { - await serviceLocationPage.validateURL(serviceLocationPage.issPageValue); - await serviceLocationPage.validateRecalWarning(); - }*/ - - // if isRecalNotifidation flag true additional step to acknowledge Recal notification. - if (isRecalNotification) { - await providerPreferencePage.validateURL(providerPreferencePage.issPageValue); - //await providerPreferencePage.acknowledgeRecalNotificaiton(); - } if (!isSafelite && isUnverifiedPolicyAfterVehicleLookup) { await test.step('ProviderPreferencePage >> Schedule TPA without Adas', async () => { @@ -814,7 +783,6 @@ async function runWorkflow(page: Page, testCase: TestCase) { await test.step('TpaSubmitPage >> TPA Submit', async () => { await tpaSubmitPage.validateURL(tpaSubmitPage.issPageValue); - //await tpaSubmitPage.validateRecalAlert(); if (!isUnverifiedPolicyAfterVehicleLookup) { await tpaSubmitPage.validateDeductible(claimDetails!); } else { @@ -857,14 +825,10 @@ async function runWorkflow(page: Page, testCase: TestCase) { if (appointmentDetails?.alternateMobileCity) { await contactDetailsPage.fillAlternateMobileStreetAndCity(appointmentDetails?.alternateMobileStreetAddress!, appointmentDetails?.alternateMobileCity!); } - //await contactDetailsPage.validateAlertsAreVisible(); - //await contactDetailsPage.fillNotes(customerDetails!.notes!); - + await contactDetailsPage.nextPage(); }); - /* if (isPriceServiceErrorBailout) { - forceAPIError(page, '/price/api/v1/price/combined-quote'); - }*/ + } if (isPriceServiceErrorBailout) { From 0d1e553248e26422ccc7da249112fa8cee3fa193 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 09:54:02 -0400 Subject: [PATCH 74/80] Remove state law modal flag for Oregon scenario --- playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts b/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts index 2721d06b..1351e326 100644 --- a/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts +++ b/playwright-tests/tests/advanced/0001a_ReplaceInShopCredit.ts @@ -31,7 +31,6 @@ const advancedScenario0001Data: Partial = { isDuplicateClaim: false, isPolicyFound: true, isNoComp: false, - hasStateLawPopup: true, endorsements: undefined, vehiclePartQuestions: [ From eb4d3415fbb2a6e1b03773c5244a6b67ce893bc8 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 09:54:30 -0400 Subject: [PATCH 75/80] Update test with street name --- .../tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts b/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts index 4851c4ab..435d5a51 100644 --- a/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts +++ b/playwright-tests/tests/advanced/0025a_VehicleByVINUnverifiedBailout.ts @@ -15,7 +15,7 @@ const customerDetails: ICustomerDetails = { phoneNumber: '614-531-0031', notes: 'Automated Test', address: { - street: faker.location.streetAddress(), + street: '5273 Berrywood Dr', city: 'Columbus', state: 'OH', postalCode: '43220', From 3fd9b952aec6db1d97cf5f477b5e33f6414cd304 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 09:57:20 -0400 Subject: [PATCH 76/80] Add assertion to fix flaky Afterpay step --- playwright-tests/pages/AfterpayPage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index 5053cf14..b4c619c7 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -37,7 +37,7 @@ export class AfterpayPage extends BasePage { await this.login(paymentDetails.password!); try {await this.confirmButton.waitFor({ state: 'visible', timeout: 5000 }); - await this.confirmButton.isVisible() + expect(this.confirmButton).toBeVisible(); await this.confirmButton.click(); } catch { // For some orders, user will have 2 options to choose from: monthly with interest or biweekly without interest From 8c2bf8e8a71edee0171d84f9b9cf765dae491c75 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 09:59:47 -0400 Subject: [PATCH 77/80] Reorganize import --- playwright-tests/tests/0000__M.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 073c0b7a..f31bee29 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -1,5 +1,6 @@ import TestCase from "@business-logic/types/TestCase"; import { expect, Page } from "@playwright/test"; +import { getDefaultTestData } from "safelite-playwright-core"; import { addSmokeTagToRandomTest, prepareTest, test, TestInfo } from "@business-logic/types/Test"; import { RuleEngine, ValidationOptions } from "@business-logic/types/RuleEngine"; import { ICustomerDetails } from "@business-logic/types/CustomerDetails"; @@ -61,7 +62,6 @@ import advancedScenario0029aTestCases from "./advanced/0029a_HeavyVehicleBailout import advancedScenario0030aTestCases from "./advanced/0030a_PartsServiceBailout"; import advancedScenario0031aTestCases from "./advanced/0031a_ReplaceServiceableBigTruck"; import advancedScenario0032aTestCases from "./advanced/0032a_DeclineNonServiceableBigTruck"; -import { getDefaultTestData } from "safelite-playwright-core"; @@ -825,7 +825,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { if (appointmentDetails?.alternateMobileCity) { await contactDetailsPage.fillAlternateMobileStreetAndCity(appointmentDetails?.alternateMobileStreetAddress!, appointmentDetails?.alternateMobileCity!); } - + await contactDetailsPage.nextPage(); }); From cb1d606044e2972d6438584200e25c1e32d3a5c7 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 14:35:01 -0400 Subject: [PATCH 78/80] Fix test to correct bailout scenario --- .../business-logic/types/IBailoutFlags.ts | 3 ++- playwright-tests/tests/0000__M.test.ts | 10 +++++----- ...rtsServiceBailout.ts => 0030a_APIErrorBailout.ts} | 12 ++++++------ 3 files changed, 13 insertions(+), 12 deletions(-) rename playwright-tests/tests/advanced/{0030a_PartsServiceBailout.ts => 0030a_APIErrorBailout.ts} (92%) diff --git a/playwright-tests/business-logic/types/IBailoutFlags.ts b/playwright-tests/business-logic/types/IBailoutFlags.ts index 93346057..9889e337 100644 --- a/playwright-tests/business-logic/types/IBailoutFlags.ts +++ b/playwright-tests/business-logic/types/IBailoutFlags.ts @@ -7,5 +7,6 @@ export default interface IBailoutFlags { isSafeliteNotTheProviderBailout: boolean, isVehicleLookupBailout: boolean, isPriceServiceErrorBailout: boolean, - isCoverageCancelledByUser: boolean + isCoverageCancelledByUser: boolean, + isAPIErrorBailout: boolean } \ No newline at end of file diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index f31bee29..537ecfe4 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -59,7 +59,7 @@ import advancedScenario0025TestCases from "./advanced/0025a_VehicleByVINUnverifi import advancedScenario0026aTestCases from "./advanced/0026a_NoDeductibleAdasBailout5"; import advancedScenario0028aTestCases from "./advanced/0028a_ItacCancelMyClaim"; import advancedScenario0029aTestCases from "./advanced/0029a_HeavyVehicleBailout"; -import advancedScenario0030aTestCases from "./advanced/0030a_PartsServiceBailout"; +import advancedScenario0030aTestCases from "./advanced/0030a_APIErrorBailout"; import advancedScenario0031aTestCases from "./advanced/0031a_ReplaceServiceableBigTruck"; import advancedScenario0032aTestCases from "./advanced/0032a_DeclineNonServiceableBigTruck"; @@ -403,7 +403,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { // Destructure bailout flags const { isVehicleSelectBailout, isTpaNotEnabledBailout, - isPartsServiceErrorBailout, isVehicleLookupBailout, isPriceServiceErrorBailout, + isAPIErrorBailout, isVehicleLookupBailout, isPriceServiceErrorBailout, isRequestCallbackBailout, isHeavyTruckVehicleBailout, isCoverageCancelledByUser } = testCase.testData.bailoutFlags || {}; const repairTypes: VehicleDamage[] = [ @@ -538,7 +538,7 @@ async function runWorkflow(page: Page, testCase: TestCase) { await vehicleDamagePage.nextPage(); }); - if (isPartsServiceErrorBailout) { + if (isAPIErrorBailout) { await test.step('VehicleLookupPage >> Select Lookup Type', async () => { await vehicleLookupPage.validateURL(vehicleLookupPage.issPageValue); await vehicleLookupPage.vehicleLookup(vehicleDetails!); @@ -549,9 +549,9 @@ async function runWorkflow(page: Page, testCase: TestCase) { forceAPIError(page, '/parts/api/v1/parts') await vinLookupPage.nextPage(); }); - await test.step('BailoutPage >> Parts Service Error Bailout', async () => { + await test.step('BailoutPage >> API error occurred bailout', async () => { await bailoutPage.validateURL(bailoutPage.issPageValue); - await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.PartsServiceError); + await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.ApiError); return; }); return; diff --git a/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts b/playwright-tests/tests/advanced/0030a_APIErrorBailout.ts similarity index 92% rename from playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts rename to playwright-tests/tests/advanced/0030a_APIErrorBailout.ts index fa565b37..e392adc2 100644 --- a/playwright-tests/tests/advanced/0030a_PartsServiceBailout.ts +++ b/playwright-tests/tests/advanced/0030a_APIErrorBailout.ts @@ -10,10 +10,10 @@ import { IAddress } from "@business-logic/types/IAddress"; const nextWeekday = getNextWeekday(); const customerAddress: IAddress = { - street: faker.location.streetAddress(), - city: 'Birmingham', - state: 'AL', - postalCode: '35118', + street: '474 1st St', + city: 'Lindsay', + state: 'CA', + postalCode: '93247', country: 'United States' } const customerDetails: ICustomerDetails = { @@ -36,7 +36,7 @@ const advancedScenario0030aData: Partial = { isItac: false, hasStateLawPopup: false, bailoutFlags: { - isPartsServiceErrorBailout: true, + isAPIErrorBailout: true, }, vehiclePartQuestions: [ @@ -79,7 +79,7 @@ for (const client of advancedClients) { const data = { ...advancedScenario0030aData }; data.clientTag = client.clientTag; const tc = new TestCase({ - name: `0030a Advanced ITAC Cancel My Claim: "${client.accountName}"`, + name: `0030a Advanced API Error Bailout: "${client.accountName}"`, tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Advanced', '@INSR-2057'], testData: data }, undefined, '0030a'); From ae93c84fe51badef9cbf3a4e19cd5e57b8cf3d39 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 22:35:49 -0400 Subject: [PATCH 79/80] Fix logic to cover Afterpay payment options --- playwright-tests/pages/AfterpayPage.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/playwright-tests/pages/AfterpayPage.ts b/playwright-tests/pages/AfterpayPage.ts index b4c619c7..2f13ed04 100644 --- a/playwright-tests/pages/AfterpayPage.ts +++ b/playwright-tests/pages/AfterpayPage.ts @@ -34,15 +34,19 @@ export class AfterpayPage extends BasePage { } async executeAfterpayPayment(paymentDetails: IPaymentDetails, claimDetails: IClaimDetails, servicePackage: ServicePackage) { + + const confirmButtonOrPaymentOptions = this.confirmButton.or(this.selectAfterpayWithoutInterestButton); + await this.login(paymentDetails.password!); - try {await this.confirmButton.waitFor({ state: 'visible', timeout: 5000 }); - expect(this.confirmButton).toBeVisible(); - await this.confirmButton.click(); - } catch { - // For some orders, user will have 2 options to choose from: monthly with interest or biweekly without interest - await this.selectAfterpayWithoutInterestButton.waitFor({ state: 'visible' }); + await this.page.locator('div[data-testid=\'loading-icon-svg\']').filter({ visible: true}).first().waitFor({ state: 'hidden' }); + await confirmButtonOrPaymentOptions.waitFor({ state: 'visible' }); + if (await this.confirmButton.isVisible()) { + await this.confirmButton.waitFor({ state: 'attached' }); + await this.confirmButton.click(); + } else { + // For some orders, user will have 2 options to choose from: monthly with interest or biweekly without interest if (await this.selectAfterpayWithoutInterestButton.isVisible()) { await this.selectAfterpayWithoutInterestButton.click(); await this.continueAfterpayButton.click(); @@ -51,6 +55,6 @@ export class AfterpayPage extends BasePage { await this.continueAfterpayButton.click(); } await this.confirmButton.click(); - } } +} } \ No newline at end of file From 63015e8c4a0c69cee9e59be26f54798052deded0 Mon Sep 17 00:00:00 2001 From: JennyNou Date: Thu, 26 Mar 2026 23:12:58 -0400 Subject: [PATCH 80/80] Fix logic to address when drop off is not available --- playwright-tests/pages/SchedulePage.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/playwright-tests/pages/SchedulePage.ts b/playwright-tests/pages/SchedulePage.ts index 1fe05bee..8c8f3f8c 100644 --- a/playwright-tests/pages/SchedulePage.ts +++ b/playwright-tests/pages/SchedulePage.ts @@ -69,10 +69,15 @@ export class SchedulePage extends BasePage { if (await this.inShopButton.isVisible()){ await this.inShopButton.waitFor({ state: 'visible', timeout: 5000 }); await this.inShopButton.click(); - await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 }); - await this.dropOffButton.click() + + if (await this.dropOffButton.isVisible()) { + await this.dropOffButton.waitFor({ state: 'visible', timeout: 5000 }); + await this.dropOffButton.click() } else { - await this.dropOffButton.isVisible() ? await this.dropOffButton.click() : await (await this.getFirstNonDropOffTimeSlot()).click(); // drop off is available in the morning for same day so this is here to avoid flaky tests + await (await this.getFirstNonDropOffTimeSlot()).click(); + } + } else { + await (await this.getFirstNonDropOffTimeSlot()).click(); } } else { await this.selectFirstAvailableTime();