Merge remote-tracking branch 'origin/develop' into feature/CASH-1529
This commit is contained in:
commit
7853750139
24 changed files with 254 additions and 231 deletions
74
package-lock.json
generated
74
package-lock.json
generated
|
|
@ -9,7 +9,7 @@
|
|||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@iframe-resizer/child": "^5.3.3",
|
||||
"axios": "^0.23.0",
|
||||
"axios": "^0.30.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"core-js": "^3.38.1",
|
||||
"http-status-codes": "^2.3.0",
|
||||
|
|
@ -5024,7 +5024,6 @@
|
|||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
|
|
@ -5082,12 +5081,30 @@
|
|||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz",
|
||||
"integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==",
|
||||
"version": "0.30.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.30.2.tgz",
|
||||
"integrity": "sha512-0pE4RQ4UQi1jKY6p7u6i1Tkzqmu+d+/tHS7Q7rKunWLB9WyilBTpHHpXzPNMDj5hTbK0B0PTLSz07yqMBiF6xg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
"follow-redirects": "^1.15.4",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios/node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
|
|
@ -5598,7 +5615,6 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
|
|
@ -5969,7 +5985,6 @@
|
|||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
|
|
@ -6009,9 +6024,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/compression": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz",
|
||||
"integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==",
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
|
||||
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
@ -6019,7 +6034,7 @@
|
|||
"compressible": "~2.0.18",
|
||||
"debug": "2.6.9",
|
||||
"negotiator": "~0.6.4",
|
||||
"on-headers": "~1.0.2",
|
||||
"on-headers": "~1.1.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
|
|
@ -6841,7 +6856,6 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
|
|
@ -7083,7 +7097,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
|
|
@ -7300,7 +7313,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -7310,7 +7322,6 @@
|
|||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -7327,7 +7338,6 @@
|
|||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
|
|
@ -7340,7 +7350,6 @@
|
|||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
|
|
@ -8544,15 +8553,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.3.tgz",
|
||||
"integrity": "sha512-q5YBMeWy6E2Un0nMGWMgI65MAKtaylxfNJGJxpGh45YDciZB4epbWpaAfImil6CPAPTYB4sh0URQNDRIZG5F2w==",
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz",
|
||||
"integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -8642,7 +8652,6 @@
|
|||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
|
|
@ -8672,7 +8681,6 @@
|
|||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
|
|
@ -8707,7 +8715,6 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
|
|
@ -8807,7 +8814,6 @@
|
|||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -8880,7 +8886,6 @@
|
|||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -8893,7 +8898,6 @@
|
|||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
|
|
@ -8916,7 +8920,6 @@
|
|||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
|
|
@ -11976,7 +11979,6 @@
|
|||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
|
@ -12090,7 +12092,6 @@
|
|||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
|
|
@ -12100,7 +12101,6 @@
|
|||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
|
|
@ -12643,9 +12643,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
|
||||
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
|
@ -13922,6 +13922,12 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pseudomap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@iframe-resizer/child": "^5.3.3",
|
||||
"axios": "^0.23.0",
|
||||
"axios": "^0.30.2",
|
||||
"bootstrap": "^5.3.3",
|
||||
"core-js": "^3.38.1",
|
||||
"http-status-codes": "^2.3.0",
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ import { PaymentMethod } from './localTypes/Enums'
|
|||
export interface ITestData extends base {
|
||||
// Put any project-specific data here. Anything useful to other Safelite projects should be submitted as a pull request to safelite-playwright-core.
|
||||
paymentMethod: PaymentMethod
|
||||
isOptedInForTextMessages: boolean
|
||||
}
|
||||
|
|
@ -5,23 +5,23 @@ export enum PaymentMethod {
|
|||
}
|
||||
|
||||
export enum ProgressBarPercentages {
|
||||
VehicleSelectionPage = '8%',
|
||||
VehicleDamagePage = '11%',
|
||||
EstimatePage = '18%',
|
||||
ServiceZipPage = '22%',
|
||||
VehicleLookupAddressPage = '22%',
|
||||
VehicleLookupLicensePage = '22%',
|
||||
VinLookupPage = '22%',
|
||||
PartQuestionsPage = '30%',
|
||||
VehicleSelectionPage = '5%',
|
||||
VehicleDamagePage = '9%',
|
||||
EstimatePage = '12%',
|
||||
ServiceZipPage = '20%',
|
||||
VehicleLookupAddressPage = '20%',
|
||||
VehicleLookupLicensePage = '20%',
|
||||
VinLookupPage = '15%',
|
||||
PartQuestionsPage = '15%',
|
||||
MoldingQuestionsPage = '30%',
|
||||
CapabilityQuestionsPage = '30%',
|
||||
VehiclePartsPage = '30%',
|
||||
ServicePackagePage = '62%',
|
||||
InsuranceCompanyPage = '52%',
|
||||
VehiclePartsPage = '20%',
|
||||
ServicePackagePage = '60%',
|
||||
InsuranceCompanyPage = '60%',
|
||||
ServiceLocationPage = '76%',
|
||||
SchedulePage = '76%',
|
||||
MobileDetailsPage = '76%',
|
||||
ContactDetailsPage = '84%',
|
||||
PaymentMethodPage = '97%',
|
||||
ContactDetailsPage = '85%',
|
||||
PaymentMethodPage = '98.5%',
|
||||
OrderConfirmationPage = '100%'
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ export class BasePage {
|
|||
this.pageSpinner = page.getByRole('status');
|
||||
this.buttonLoadSpin = page.getByRole('alert');
|
||||
this.hamburgerMenu = this.page.getByRole('button', { name: 'Hamburger Menu (modal window)' });
|
||||
this.progressBar = this.page.locator('.progress-bar-outer.progress-bar-inner');
|
||||
this.progressBar = this.page.locator('.progress-bar-outer .progress-bar-inner');
|
||||
}
|
||||
|
||||
async nextPage() {
|
||||
|
|
@ -133,22 +133,22 @@ export class BasePage {
|
|||
async validateProgressBar(progressPercentage: string, timeout: number = 60000) {
|
||||
// This section has been commented out until progress bar work is completed for parity.
|
||||
|
||||
// await waitUntil(async () => {
|
||||
// let loaderElements = await this.page.locator('button .loader, .buy-loader, timeout, .modal-loader').all();
|
||||
// return !(await Promise.any(loaderElements.map(el => el.isVisible())).catch(() => false));
|
||||
// });
|
||||
await waitUntil(async () => {
|
||||
let loaderElements = await this.page.locator('button .loader, .buy-loader, timeout, .modal-loader').all();
|
||||
return !(await Promise.any(loaderElements.map(el => el.isVisible())).catch(() => false));
|
||||
});
|
||||
|
||||
// // Take a screenshot of the page before validating the progress bar
|
||||
// await this.page.screenshot({ path: `test-results\\ortoni-data\\progress-bar-${Date.now()}.png`, fullPage: true });
|
||||
// // Wait until the progress bar element is attached and visible
|
||||
// Take a screenshot of the page before validating the progress bar
|
||||
await this.page.screenshot({ path: `test-results\\ortoni-data\\progress-bar-${Date.now()}.png`, fullPage: true });
|
||||
// Wait until the progress bar element is attached and visible
|
||||
|
||||
// const actualProgressPercentage = await this.progressBar.evaluate(
|
||||
// async (element) => {
|
||||
// await new Promise(resolve => setTimeout(resolve, 200));
|
||||
// return element.style.width || "Not Found";
|
||||
// }
|
||||
// );
|
||||
// Soft.expect(actualProgressPercentage).toBe(progressPercentage);
|
||||
// console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`);
|
||||
const actualProgressPercentage = await this.progressBar.evaluate(
|
||||
async (element) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 200));
|
||||
return element.style.width || "Not Found";
|
||||
}
|
||||
);
|
||||
Soft.expect(actualProgressPercentage).toBe(progressPercentage);
|
||||
console.log(`Progress Bar Percentage: Actual - ${actualProgressPercentage} vs Expected - ${progressPercentage}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ export class ContactDetailsPage extends BasePage {
|
|||
readonly lastNameTextBox: Locator;
|
||||
readonly emailAddressTextBox: Locator;
|
||||
readonly phoneNumberTextBox: Locator;
|
||||
readonly notesTextBox: Locator;
|
||||
readonly textOptInBox: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
|
|
@ -25,7 +25,7 @@ export class ContactDetailsPage extends BasePage {
|
|||
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.textOptInBox = this.page.getByRole('checkbox', { name: 'Text me updates about my appointment*' });
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -36,10 +36,8 @@ export class ContactDetailsPage extends BasePage {
|
|||
await this.phoneNumberTextBox.fill(customerDetails.phoneNumber);
|
||||
}
|
||||
|
||||
async fillNotes(notes?: string) {
|
||||
if (notes) {
|
||||
await this.notesTextBox.fill(notes);
|
||||
}
|
||||
async checkTextMeUpdates(customerDetails: ICustomerDetails) {
|
||||
await this.textOptInBox.check();
|
||||
}
|
||||
|
||||
@step("ContactDetailsPage >> Enter contact details: ")
|
||||
|
|
@ -48,6 +46,11 @@ export class ContactDetailsPage extends BasePage {
|
|||
|
||||
await this.validateProgressBar(ProgressBarPercentages.ContactDetailsPage);
|
||||
await this.enterContactDetails(customerDetails!);
|
||||
|
||||
if (testData.isOptedInForTextMessages) {
|
||||
await this.checkTextMeUpdates(customerDetails!);
|
||||
}
|
||||
|
||||
await this.nextPage();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { getTestObject } from 'framework/Typedefs';
|
||||
import { ServicePackage, PaymentType, ServiceLocation, Soft } from 'safelite-playwright-core';
|
||||
import { ServicePackage, PaymentType, ServiceLocation, Soft, waitUntil } from 'safelite-playwright-core';
|
||||
import { PaymentMethod, ProgressBarPercentages } from "framework/localTypes/Enums";
|
||||
import { ITestData } from 'framework/TestData';
|
||||
import { step } from 'framework/localTypes/Step';
|
||||
|
|
@ -22,6 +22,8 @@ export class OrderConfirmationPage extends BasePage {
|
|||
readonly winshieldWiper: Locator;
|
||||
readonly rainDefense: Locator;
|
||||
readonly appointmentSummarySection: Locator;
|
||||
readonly orderDetailsSection: Locator;
|
||||
readonly amountPaid: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
super(page);
|
||||
|
|
@ -38,14 +40,16 @@ export class OrderConfirmationPage extends BasePage {
|
|||
this.finalAmountDue = this.page.locator('div.amount-due');
|
||||
this.cartServicePackageText = this.cartServicePackageText = this.page.locator('.cart-panel');
|
||||
this.appointmentSummarySection = this.page.locator('.main .scheduleText, .main .add-to-calendar-text, .main .appointment-text, .main .duration-text-block');
|
||||
this.orderDetailsSection = this.page.locator('.cart .vin-toggle a');
|
||||
this.amountPaid = this.page.locator('.amount-paid span').nth(1);
|
||||
}
|
||||
|
||||
async validateOrderConfirmationPage(testData: Partial<ITestData>) {
|
||||
// Destructure data we use
|
||||
const { vehicleDetails, customerDetails, servicePackage, isCashInsuranceFlow,
|
||||
isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified } = testData;
|
||||
isPolicyFound, claimDetails, paymentDetails, isUseVehicleOnPolicy, paymentMethod, isPolicyUnverified } = testData;
|
||||
await this.serviceText.waitFor({ state: "visible" });
|
||||
|
||||
|
||||
Soft.expect((await this.getActualAppointmentSummary()).map(item => item.toLowerCase())).toEqual((await this.getExpectedAppointmentSummary(testData)).map(item => item.toLowerCase()));
|
||||
|
||||
// Grab text
|
||||
|
|
@ -85,17 +89,15 @@ export class OrderConfirmationPage extends BasePage {
|
|||
subtotalTextValue?.replaceAll(',', '')
|
||||
const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', ''));
|
||||
|
||||
|
||||
|
||||
Soft.expect(subtotalAmt).toBeGreaterThan(0);
|
||||
// Soft.expect(deductibleAmt).toEqual(0);
|
||||
|
||||
if ((paymentDetails!.paymentType === PaymentType.PayAtService || isCashInsuranceFlow) && (servicePackageAmt > 0)) {
|
||||
|
||||
if ((paymentDetails!.paymentType === PaymentType.PayAtService || isCashInsuranceFlow) && (servicePackageAmt > 0)) {
|
||||
// Verify amount due > 0
|
||||
Soft.expect(amountDueAmt).toBeGreaterThan(0);
|
||||
Soft.expect(finalAmountDueAmt).toBeGreaterThan(0);
|
||||
|
||||
if (isPolicyUnverified && PaymentType.PayWithInsurance){
|
||||
if (isPolicyUnverified && PaymentType.PayWithInsurance) {
|
||||
Soft.expect(finalAmountDueAmt).toContain('Verifying coverage')
|
||||
}
|
||||
} else {
|
||||
|
|
@ -103,8 +105,8 @@ export class OrderConfirmationPage extends BasePage {
|
|||
Soft.expect(amountDueAmt).toEqual(0);
|
||||
Soft.expect(finalAmountDueAmt).toEqual(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getFormattedAppointmentDate(appointmentDate: string) {
|
||||
|
||||
|
|
@ -116,14 +118,39 @@ export class OrderConfirmationPage extends BasePage {
|
|||
return updatedAppointmentDate;
|
||||
}
|
||||
|
||||
async toggleOrderDetailsSection() {
|
||||
await this.orderDetailsSection.click().then(
|
||||
async () => {
|
||||
await waitUntil(async () => {
|
||||
return (await this.page.locator('.vin-toggle.expanded').evaluate(el => window.getComputedStyle(el, ':after').transform)) === 'matrix(-1, 0, 0, -1, 0, 0)';
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async logOrderNumber() {
|
||||
const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedState\')'));
|
||||
return sessionStorage.order.workOrderNumber;
|
||||
}
|
||||
|
||||
async validateSettledAmountForPia() {
|
||||
const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedState\')'));
|
||||
const isPia = sessionStorage.order.payment.isPia;
|
||||
if (isPia) {
|
||||
const nextGenSettledAmount = Number.parseFloat(sessionStorage.order.payment.nextGenSettledAmount);
|
||||
Soft.expect(nextGenSettledAmount, `NextGen Settled Amount > 0. NextGenSettledAmount: ${nextGenSettledAmount}`).toBeGreaterThan(0);
|
||||
await this.toggleOrderDetailsSection();
|
||||
let amountPaid = await this.amountPaid.textContent();
|
||||
const parsedAmount = amountPaid === null
|
||||
? console.log("Amount paid is null. Check the locator.")
|
||||
: Number.parseFloat(amountPaid.replace(/[^0-9.]/g, ''));
|
||||
Soft.expect(parsedAmount).toEqual(nextGenSettledAmount);
|
||||
}
|
||||
}
|
||||
|
||||
async getActualAppointmentSummary(): Promise<string[]> {
|
||||
let appointmentSummary: string[] = [];
|
||||
for (let element of await this.appointmentSummarySection.all()){
|
||||
for (let element of await this.appointmentSummarySection.all()) {
|
||||
let text = (await element.textContent() || "").replaceAll(/\u00A0| /g, ' ');
|
||||
appointmentSummary.push(text.trim());
|
||||
}
|
||||
|
|
@ -146,12 +173,12 @@ export class OrderConfirmationPage extends BasePage {
|
|||
appointmentSummary.push("Add to calendar");
|
||||
appointmentSummary.push(
|
||||
appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
? appointmentDetails?.serviceAddress
|
||||
? ("We're coming to you at" + appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode + "to service your " + `${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`)
|
||||
: ""
|
||||
: appointmentDetails?.shopAddress
|
||||
? ("You're going to a Safelite shop at" + appointmentDetails.shopAddress + "to service your " + `${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`)
|
||||
: "");
|
||||
? appointmentDetails?.serviceAddress
|
||||
? ("We're coming to you at" + appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode + "to service your " + `${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`)
|
||||
: ""
|
||||
: appointmentDetails?.shopAddress
|
||||
? ("You're going to a Safelite shop at" + appointmentDetails.shopAddress + "to service your " + `${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`)
|
||||
: "");
|
||||
appointmentSummary.push("Duration: " + customerDetails!.apptDuration!);
|
||||
|
||||
return appointmentSummary;
|
||||
|
|
@ -159,10 +186,11 @@ export class OrderConfirmationPage extends BasePage {
|
|||
|
||||
@step("OrderConfirmationPage >> Validate order")
|
||||
async verifyOrderConfirmationPage(testData: Partial<ITestData>) {
|
||||
await this.page.waitForURL(new RegExp('(.+)confirmation'), {timeout: 60000});
|
||||
await this.page.waitForURL(new RegExp('(.+)confirmation'), { timeout: 60000 });
|
||||
await this.validateProgressBar(ProgressBarPercentages.OrderConfirmationPage);
|
||||
await this.validateOrderConfirmationPage(testData);
|
||||
const workOrderNumber = await this.logOrderNumber();
|
||||
await this.validateSettledAmountForPia();
|
||||
await test.step(`Session Storage Work Order Number: ${workOrderNumber}`, async () => {
|
||||
console.log(`Session Storage Work Order Number: ${workOrderNumber}`);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { expect, type Locator, type Page } from '@playwright/test';
|
||||
import { BasePage } from './BasePage';
|
||||
import { IPaymentDetails, Soft, waitUntil } from 'safelite-playwright-core';
|
||||
import { AppointmentTimeslot, ServiceLocation, PaymentType, ServicePackage, VehicleDamage } from 'safelite-playwright-core';
|
||||
import { AppointmentTimeslot, ServiceLocation, PaymentType, ServicePackage, VehicleDamage, } from 'safelite-playwright-core';
|
||||
import { PaymentMethod, ProgressBarPercentages } from 'framework/localTypes/Enums';
|
||||
import { PaymentPage } from './PaymentPage';
|
||||
import { AfterpayPage } from './AfterpayPage';
|
||||
|
|
@ -29,11 +29,9 @@ export class PaymentMethodPage extends BasePage {
|
|||
|
||||
// Payment detail page validation locators
|
||||
readonly reviewTable: Locator;
|
||||
readonly vehicleSection: Locator;
|
||||
readonly damageSection: Locator;
|
||||
readonly serviceDetailsSection: Locator;
|
||||
readonly serviceLocationSection: Locator;
|
||||
readonly appointmentDateSection: Locator;
|
||||
readonly serviceLocationDateandTimeSection: Locator;
|
||||
readonly vehicleDamageLocationsSection: Locator;
|
||||
readonly contactDetailsSection: Locator;
|
||||
readonly cartPanelDetails: Locator;
|
||||
readonly subtotalText: Locator;
|
||||
|
|
@ -65,11 +63,8 @@ export class PaymentMethodPage extends BasePage {
|
|||
this.reviewTable = this.page.locator('div.review-table');
|
||||
|
||||
// Section locators - find by heading text
|
||||
this.vehicleSection = this.page.locator('.review-table').locator('div', { hasText: 'Vehicle' }).first();
|
||||
this.damageSection = this.page.locator('.review-table').locator('div', { hasText: 'Damage' }).first();
|
||||
this.serviceDetailsSection = this.page.locator('.review-table').locator('div', { hasText: 'Glass service only' }).first();
|
||||
this.serviceLocationSection = this.page.locator('.review-table').locator('div', { hasText: "We're coming to you" }).first();
|
||||
this.appointmentDateSection = this.page.locator('.review-table').locator('div', { hasText: 'Appointment date + time' }).first();
|
||||
this.serviceLocationDateandTimeSection = this.page.locator('.review-table').locator('div .service-location');
|
||||
this.vehicleDamageLocationsSection= this.page.locator('.review-table').locator('div.py-3[damagelocationswidgetname="DamageLocationsWidget"]');
|
||||
this.contactDetailsSection = this.page.locator('.review-table').locator('div', { hasText: 'Contact details' }).first();
|
||||
|
||||
// Cart panel elements
|
||||
|
|
@ -99,15 +94,12 @@ export class PaymentMethodPage extends BasePage {
|
|||
);
|
||||
|
||||
let expectedAppointmentDetails = new Map<string, string[]>();
|
||||
expectedAppointmentDetails = await this.getExpectedVehicleDetails(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedVehicleDamage(testData, expectedAppointmentDetails);
|
||||
// expectedAppointmentDetails = await this.expectedServicePackageDetails(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedServiceLocation(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedAppointmentDate(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedCustomerDetails(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedServiceLocationDateAndTimeSection(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedVehicleDamageAndVehicle(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedContactDetails(testData, expectedAppointmentDetails);
|
||||
expectedAppointmentDetails = await this.getExpectedTextServiceUpdatesOptInOrOut(testData, expectedAppointmentDetails);
|
||||
|
||||
let actualAppointmentDetails = await this.getActualAppointmentDetails();
|
||||
|
||||
for (const key in expectedAppointmentDetails) {
|
||||
Soft.expect(actualAppointmentDetails[key]?.map(item => item.toLowerCase()))
|
||||
.toEqual(expectedAppointmentDetails[key].map(item => item.toLowerCase()));
|
||||
|
|
@ -288,64 +280,66 @@ l
|
|||
return expectedServicePackageDetails;
|
||||
}
|
||||
|
||||
async getExpectedVehicleDamage(testdata: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
async getExpectedVehicleDamageAndVehicle(testdata: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
|
||||
const {vehicleDamage} = testdata;
|
||||
const {vehicleDetails, vehicleDamage} = testdata;
|
||||
|
||||
let vehicleDamageText: string[] = [];
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldOneChip)) {
|
||||
vehicleDamageText.push("Windshield repair - 1 chip");
|
||||
let vehicleDamageText: string= '';
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldOneChip || item == VehicleDamage.WindshieldTwoChips || item == VehicleDamage.WindshieldThreeChips)) {
|
||||
vehicleDamageText = "Repair the windshield of your";
|
||||
}
|
||||
|
||||
else if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldTwoChips)) {
|
||||
vehicleDamageText.push("Windshield repair - 2 chips");
|
||||
}
|
||||
|
||||
else if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldThreeChips)) {
|
||||
vehicleDamageText.push("Windshield repair - 3 chips");
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) {
|
||||
vehicleDamageText.push("Windshield crack");
|
||||
vehicleDamageText = vehicleDamage?.length > 1 ? "Replace the Windshield" : "Replace the windshield";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverVentGlass)) {
|
||||
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nVent glass" : vehicleDamageText.push("Side door - Driver side", "Vent glass");
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverVentGlass) && !vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) {
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Vent Glass" : "Replace the Driver Vent Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverFrontDoor)) {
|
||||
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nFront door" : vehicleDamageText.push("Side door - Driver side", "Front door");
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Front Glass" : "Replace the Driver Front Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverRearDoor)) {
|
||||
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nBack door" : vehicleDamageText.push("Side door - Driver side", "Back door");
|
||||
}
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Back Glass" : "Replace the Driver Back Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverVentGlass) && vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.WindshieldCrack)) {
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Vent Glass" : "Replace the Driver Vent Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.DriverQuarterPanel)) {
|
||||
vehicleDamageText.includes("Side door - Driver side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nQuarter panel" : vehicleDamageText.push("Side door - Driver side", "Quarter panel");
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Driver Quarter Glass" : "Replace the Driver Quarter Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerVentGlass)) {
|
||||
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nVent glass" : vehicleDamageText.push("Side door - Passenger side", "Vent glass");
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Vent Glass" : "Replace the Passenger Vent Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerFrontDoor)) {
|
||||
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nFront door" : vehicleDamageText.push("Side door - Passenger side", "Front door");
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Front Glass" : "Replace the Passenger Front Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerRearDoor)) {
|
||||
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nBack door" : vehicleDamageText.push("Side door - Passenger side", "Back door");
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Back Glass" : "Replace the Passenger Back Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.PassengerQuarterPanel)) {
|
||||
vehicleDamageText.includes("Side door - Passenger side") ? vehicleDamageText[vehicleDamageText.length - 1 ] += "\nQuarter panel" : vehicleDamageText.push("Side door - Passenger side", "Quarter panel");
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Passenger Quarter Glass" : "Replace the Passenger Quarter Glass";
|
||||
}
|
||||
|
||||
if (vehicleDamage?.find((item: VehicleDamage) => item == VehicleDamage.RearWindow || item == VehicleDamage.RearSliding)) {
|
||||
vehicleDamageText.push("Rear window");
|
||||
vehicleDamageText.includes("Replace") ? vehicleDamageText += ", Back Glass" : "Replace the Back Glass";
|
||||
}
|
||||
|
||||
expectedServicePackageDetails["Damage"] = vehicleDamageText;
|
||||
// Replace last comma with " and" if there are multiple damages
|
||||
if (vehicleDamageText.includes(",") && vehicleDamageText !== "Replace the windshield")
|
||||
{
|
||||
vehicleDamageText = vehicleDamageText.replace(/,([^,]*)$/, " and$1") + " of your";
|
||||
}
|
||||
|
||||
expectedServicePackageDetails[vehicleDamageText] = [vehicleDetails?.year + " " + vehicleDetails?.make + " " + vehicleDetails?.model];
|
||||
return expectedServicePackageDetails;
|
||||
}
|
||||
|
||||
|
|
@ -399,55 +393,39 @@ l
|
|||
return expectedServicePackageDetails;
|
||||
}
|
||||
|
||||
async getExpectedServiceLocation(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { appointmentDetails } = testData;
|
||||
async getExpectedServiceLocationDateAndTimeSection(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { customerDetails, appointmentDetails } = testData;
|
||||
let serviceLocationTitle = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
? "We're coming to you"
|
||||
: "You're coming to us";
|
||||
let serviceLocation: string[] = [];
|
||||
|
||||
serviceLocation.push(
|
||||
appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
let serviceLocationText = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
? appointmentDetails?.serviceAddress
|
||||
? appointmentDetails.serviceAddress.street + ", " + appointmentDetails.serviceAddress.city + ", " + appointmentDetails.serviceAddress.state + " " + appointmentDetails.serviceAddress.postalCode
|
||||
: ""
|
||||
: appointmentDetails?.shopAddress
|
||||
? appointmentDetails.shopAddress
|
||||
: ""
|
||||
);
|
||||
: "";
|
||||
serviceLocationText += " on "
|
||||
serviceLocationText += customerDetails?.apptDate ? await this.getFormattedAppointmentDate(customerDetails.apptDate) + " " + customerDetails?.apptTime?.replace("-", "—") : "";
|
||||
|
||||
expectedServicePackageDetails[serviceLocationTitle] = serviceLocation;
|
||||
expectedServicePackageDetails[serviceLocationTitle] = [serviceLocationText];
|
||||
return expectedServicePackageDetails;
|
||||
}
|
||||
|
||||
async getExpectedAppointmentDate(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { customerDetails, appointmentDetails } = testData;
|
||||
let appointmentDateText: string[] = [];
|
||||
const isMobileAppointment = appointmentDetails?.serviceLocation == ServiceLocation.Mobile
|
||||
if (isMobileAppointment) {
|
||||
let localStorage= JSON.parse(await this.page.evaluate('localStorage.getItem(\'vuex\')'));
|
||||
let jobMinMinutes = localStorage.order.schedule.jobMinMinutes as number;
|
||||
let jobMaxMinutes = localStorage.order.schedule.jobMaxMinutes as number;
|
||||
async getExpectedVehicleDamageLocations(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { customerDetails } = testData;
|
||||
let VehicleDamageLocationsText: string[] = [];
|
||||
if (VehicleDamage.WindshieldCrack) {
|
||||
let VehicleDamageLocationsText: string[] = [];
|
||||
VehicleDamageLocationsText.push("Replace the windshield");
|
||||
};
|
||||
|
||||
if (jobMinMinutes < 60) {
|
||||
customerDetails!.apptDuration = `${jobMinMinutes} - ${jobMaxMinutes} minutes`;
|
||||
} else {
|
||||
const minHours = Math.floor(jobMinMinutes / 60);
|
||||
const maxHours = Math.floor(jobMaxMinutes / 60);
|
||||
customerDetails!.apptDuration = `${minHours} - ${maxHours} hours`;
|
||||
}
|
||||
}
|
||||
|
||||
appointmentDateText.push(
|
||||
customerDetails?.apptDate ? await this.getFormattedAppointmentDate(customerDetails.apptDate) + " " + customerDetails?.apptTime?.replace("-", "—") : "",
|
||||
customerDetails?.apptDuration ? "Estimated appointment length: " + customerDetails.apptDuration : ""
|
||||
);
|
||||
|
||||
expectedServicePackageDetails["Appointment Date + Time"] = appointmentDateText;
|
||||
expectedServicePackageDetails["VehicleDamageLocationsText"] = VehicleDamageLocationsText;
|
||||
return expectedServicePackageDetails;
|
||||
|
||||
}
|
||||
|
||||
async getExpectedCustomerDetails(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
async getExpectedContactDetails(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { customerDetails } = testData;
|
||||
let customerDetailsText: string[] = [];
|
||||
|
||||
|
|
@ -455,10 +433,21 @@ l
|
|||
// customerDetails?.firstName.toUpperCase() + " " + customerDetails?.lastName.toUpperCase(),
|
||||
customerDetails?.email ? customerDetails?.email.toUpperCase() : "",
|
||||
customerDetails?.phoneNumber ? customerDetails?.phoneNumber : "",
|
||||
"Opted out of text message updates"
|
||||
);
|
||||
|
||||
expectedServicePackageDetails["Contact details"] = customerDetailsText;
|
||||
expectedServicePackageDetails["Contact details"] = customerDetailsText
|
||||
return expectedServicePackageDetails;
|
||||
}
|
||||
|
||||
async getExpectedTextServiceUpdatesOptInOrOut(testData: Partial<ITestData>, expectedServicePackageDetails: Map<string, string[]>): Promise<any> {
|
||||
const { customerDetails } = testData;
|
||||
|
||||
let isOptedInForTextMessages = testData.isOptedInForTextMessages ?? false;
|
||||
|
||||
let expectedText = isOptedInForTextMessages ? customerDetails?.phoneNumber : "Not opted in";
|
||||
|
||||
|
||||
expectedServicePackageDetails["We’ll text service updates to"] = [expectedText];
|
||||
return expectedServicePackageDetails;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ export class SchedulePage extends BasePage {
|
|||
{
|
||||
formattedTimeSlot = selectedTimeSlot.includes("overnight") ? "drop off by 5:30 pm on the night of your scheduled appointment. Pick-up time dependent on shop schedule" : "drop off before 9:30 AM";
|
||||
}
|
||||
else if (selectedTimeSlot.includes("-") || selectedTimeSlot.includes("Earlybird"))
|
||||
else if (selectedTimeSlot.includes("-") || selectedTimeSlot.includes("Earlybird")) // - means Mobile time slot where service time is between 8:00 AM - 12:00 PM
|
||||
{
|
||||
formattedTimeSlot = selectedTimeSlot.includes("Earlybird") ? "arriving between 8:00 AM - 12:00 PM" : `arriving between ${selectedTimeSlot}`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { ITestCase } from '../framework/Typedefs'
|
|||
import { VehicleLookupType } from 'safelite-playwright-core';
|
||||
import { getDefaultTestData, setFakerSeedFromTestName } from 'safelite-playwright-core';
|
||||
import { PaymentMethod } from 'framework/localTypes/Enums';
|
||||
|
||||
// Set the seed before generating any data
|
||||
setFakerSeedFromTestName("CashReplaceGlassPromoInshop");
|
||||
|
||||
|
|
@ -47,6 +46,9 @@ const cashReplaceGlassPromoInshopData: Partial<ITestData> = {
|
|||
...getDefaultTestData().appointmentDetails!,
|
||||
shopAddress: "6826 Sawmill Rd, Columbus, OH 43235"
|
||||
},
|
||||
|
||||
// Flag for opting in for text message appt updates
|
||||
isOptedInForTextMessages: true,
|
||||
|
||||
// Override payment details
|
||||
paymentDetails: {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
]"
|
||||
:validationRules="validationRules"
|
||||
@change="handleChange"
|
||||
@blur="handleChange"
|
||||
@blur="handleBlur"
|
||||
:maxlength="maxLength ? maxLength : '999'"
|
||||
@focus="$emit('focus', $event.target.value)"
|
||||
@keydown="keyDownHandler"
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export default {
|
|||
url: cfDistroUrl + endpoint,
|
||||
data: payloadAndAnalyticsData,
|
||||
crossDomain: true,
|
||||
responseType: {},
|
||||
responseType: "json",
|
||||
headers: headers,
|
||||
}).then(
|
||||
(response) => {
|
||||
|
|
|
|||
|
|
@ -275,6 +275,7 @@ export default {
|
|||
}
|
||||
.sub-text strong {
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -800,7 +800,7 @@ export default {
|
|||
isAfterpayBreakoutDisplay() {
|
||||
return (
|
||||
this.showInsuranceCoverageAs !== coverageStatus.PENDING &&
|
||||
(!this.isRecalPriceRemove || !this.isRecalibrationOnOrder) &&
|
||||
!this.isRecalPriceRemove &&
|
||||
this.hasSettingEqualTo(experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY, "true")
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }">
|
||||
<loadingModal ref="loadingModal" />
|
||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" ref="funnelHeader" />
|
||||
<div class="container quote position-relative">
|
||||
<div
|
||||
class="container quote position-relative"
|
||||
:class="[showSaveProgressPopup ? 'initial-popup-open' : '']">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12">
|
||||
<funnelSubHeader class="mb-5" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||
|
|
@ -287,6 +289,7 @@ export default {
|
|||
const isEmailInStoreOnPageLoad = emailFromStore?.length > 0;
|
||||
const isPopupSkipped =
|
||||
store.getters.pageData(routeData.QUOTE.name)?.saveProgressPopupSkipped === true;
|
||||
// "popup" is the automatic opening centered popup on load
|
||||
const showSaveProgressPopup = !(isEmailInStoreOnPageLoad || isPopupSkipped);
|
||||
const showSaveProgressModal = isEmailInStoreOnPageLoad ? false : true;
|
||||
const shouldSkipToInsurance =
|
||||
|
|
@ -556,8 +559,8 @@ export default {
|
|||
servicePackage: null,
|
||||
holdParentAccountNumber: null,
|
||||
holdBillToAccountNumber: null,
|
||||
showSaveProgressPopup: null,
|
||||
showSaveProgressModal: null,
|
||||
showSaveProgressPopup: null, // "popup" is the automatic opening centered popup on load
|
||||
showSaveProgressModal: null, // "modal" is the user-triggered slide-in drawer
|
||||
isSaveProgressComplete: null,
|
||||
packageNumber: null,
|
||||
skipToInsurance: null,
|
||||
|
|
@ -875,6 +878,10 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
// Unique to quote page for 2.0/Heritage parity
|
||||
// prettier-ignore
|
||||
.prevent-modal-scroll.modal-open .container.quote.initial-popup-open .row:not(.save-progress-popup) {
|
||||
display: none;
|
||||
}
|
||||
.quote {
|
||||
padding-bottom: 2rem;
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ export default {
|
|||
},
|
||||
isAfterpayBreakoutDisplay() {
|
||||
return (
|
||||
(!this.shouldHideRecalibration || !this.isRecalibrationOnOrder) &&
|
||||
!this.shouldHideRecalibration &&
|
||||
experimentMixin.methods.hasSettingEqualTo(
|
||||
experimentSettings.DISPLAY_AFTERPAY_BREAKOUT_DISPLAY,
|
||||
"true"
|
||||
|
|
|
|||
|
|
@ -483,7 +483,7 @@ export default {
|
|||
serviceZipCodeCtu: zipCodeData.zipCodeCtu,
|
||||
mobileProviderNumber: shopProviderData.data.mobileProviderNumber,
|
||||
},
|
||||
"schedule",
|
||||
to.name,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
|
@ -1322,7 +1322,7 @@ export default {
|
|||
) {
|
||||
navigateToHeritageFunnel({
|
||||
shouldSaveSession: false,
|
||||
pageNameToLog: "service-location",
|
||||
pageNameToLog: this.pageName,
|
||||
navType: "back",
|
||||
});
|
||||
} else {
|
||||
|
|
@ -1676,7 +1676,7 @@ export default {
|
|||
handleZipCodeChange(newZipCode) {
|
||||
this.resetMobileLocation();
|
||||
|
||||
getShopProviderData(newZipCode.zipCode).then((result) => {
|
||||
getShopProviderData(newZipCode.zipCode, this.pageName).then((result) => {
|
||||
this.shopProviderData = result.data;
|
||||
this.resetSelectedProvider();
|
||||
this.state = newZipCode.state;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const zipCodeData = await getZipCodeData(serviceZipCode);
|
||||
const providerData = await getShopProviderData(serviceZipCode);
|
||||
const zipCodeData = await getZipCodeData(serviceZipCode, pageNameToLog);
|
||||
const providerData = await getShopProviderData(serviceZipCode, pageNameToLog);
|
||||
|
||||
// Get the Mobile Fee Part
|
||||
const mobileFeePart = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
|
|
@ -50,7 +50,7 @@ export async function getPricedRecycleFeePart(serviceZipCode, pageNameToLog) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const zipCodeData = await getZipCodeData(serviceZipCode);
|
||||
const zipCodeData = await getZipCodeData(serviceZipCode, pageNameToLog);
|
||||
|
||||
// Get supporting items
|
||||
const supportingItems = await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
|
|
@ -102,23 +102,23 @@ export async function getServiceabilityDetails(serviceZipCode, lineItems, pageNa
|
|||
);
|
||||
}
|
||||
|
||||
export async function getBillToAccountNumber(providerNumber) {
|
||||
export async function getBillToAccountNumber(providerNumber, pageNameToLog) {
|
||||
// Get the BillTo Account Number
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_BILL_TO_ACCOUNT_NUMBER,
|
||||
{ providerNumber: providerNumber },
|
||||
"service-location",
|
||||
pageNameToLog,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
export async function getShopProviderData(serviceZipCode) {
|
||||
export async function getShopProviderData(serviceZipCode, pageNameToLog) {
|
||||
return await baseMixin.methods.dispatchStoreActionWithLogging(
|
||||
storeActions.GET_PROVIDERS,
|
||||
{
|
||||
serviceZipCode: serviceZipCode,
|
||||
},
|
||||
"service-location"
|
||||
pageNameToLog
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +139,6 @@ export async function getClosestApplicableShops(serviceZipCode, carId, pageNameT
|
|||
return closestShops.data;
|
||||
}
|
||||
|
||||
export async function getZipCodeData(serviceZipCode) {
|
||||
return await baseMixin.methods.getZipCodeData(serviceZipCode, "service-location");
|
||||
export async function getZipCodeData(serviceZipCode, pageNameToLog) {
|
||||
return await baseMixin.methods.getZipCodeData(serviceZipCode, pageNameToLog);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,8 +193,7 @@ export default {
|
|||
return;
|
||||
}
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.addressQuestions.zipCode,
|
||||
"service-location"
|
||||
this.internalModel.addressQuestions.zipCode
|
||||
);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
|
|
@ -216,26 +215,21 @@ export default {
|
|||
|
||||
// retrieve mobile fee part
|
||||
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
const mobileFeePart = await getPricedMobileFeePart(serviceZipCode, this.pageName);
|
||||
|
||||
// retrieve recycle fee part
|
||||
const recycleFeePart = await getPricedRecycleFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
);
|
||||
const recycleFeePart = await getPricedRecycleFeePart(serviceZipCode, this.pageName);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
const billToAccountNumber = await getBillToAccountNumber(
|
||||
this.internalModel.zipCodeCtu
|
||||
this.internalModel.zipCodeCtu,
|
||||
this.pageName
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
|
|
|
|||
|
|
@ -226,17 +226,17 @@ export default {
|
|||
const cmsContentPromise = fetchCmsContentForPage(to.name);
|
||||
|
||||
const serviceZipCode = store.getters.order.serviceLocation.zipCode;
|
||||
const zipCodeDataPromise = getZipCodeData(serviceZipCode);
|
||||
const zipCodeDataPromise = getZipCodeData(serviceZipCode, to.name);
|
||||
|
||||
const serviceabilityDetailsPromise = getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
to.name
|
||||
);
|
||||
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, "service-location");
|
||||
const mobileFeePartPromise = getPricedMobileFeePart(serviceZipCode, to.name);
|
||||
|
||||
const shopProviderDataPromise = getShopProviderData(serviceZipCode); // shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
const shopProviderDataPromise = getShopProviderData(serviceZipCode, to.name); // shopQuestion.methods.loadInitialData(serviceZipCode);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
|
|
@ -846,7 +846,7 @@ export default {
|
|||
zipCode: {
|
||||
handler(newValue) {
|
||||
if (!this.navigatingForward) {
|
||||
getShopProviderData(this.zipCode).then(async (result) => {
|
||||
getShopProviderData(this.zipCode, this.pageName).then(async (result) => {
|
||||
this.shopProviderData = result.data;
|
||||
if (this.selectedAppointmentType === "Mobile") {
|
||||
this.selectedProvider = new Provider(
|
||||
|
|
|
|||
|
|
@ -162,7 +162,7 @@ export default {
|
|||
|
||||
const zipCodeData = await this.getZipCodeData(
|
||||
this.internalModel.zipCode,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
if (!zipCodeData.isValid) {
|
||||
|
|
@ -175,7 +175,7 @@ export default {
|
|||
const closestShops = await getClosestApplicableShops(
|
||||
this.internalModel.zipCode,
|
||||
this.carId,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
if (!closestShops || closestShops.inShopProviders?.length === 0) {
|
||||
|
|
@ -193,24 +193,25 @@ export default {
|
|||
const serviceZipCode = this.internalModel.zipCode;
|
||||
const mobileFeePart = await getPricedMobileFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
// retrieve recycle fee part
|
||||
const recycleFeePart = await getPricedRecycleFeePart(
|
||||
serviceZipCode,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
// retrieve serviceability details
|
||||
const serviceabilityDetails = await getServiceabilityDetails(
|
||||
serviceZipCode,
|
||||
null,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
|
||||
const billToAccountNumber = await getBillToAccountNumber(
|
||||
this.internalModel.zipCodeCtu
|
||||
this.internalModel.zipCodeCtu,
|
||||
this.pageName
|
||||
);
|
||||
|
||||
// update content related to service zip code
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ export default {
|
|||
}
|
||||
this.isLoading = true;
|
||||
|
||||
this.zipCodeData = await this.getZipCodeData(this.localZipCode, "service-location");
|
||||
this.zipCodeData = await this.getZipCodeData(this.localZipCode, this.pageName);
|
||||
// Add zipCode to zipCodeData as it does not come back from endpoint
|
||||
this.zipCodeData.zipCode = this.localZipCode;
|
||||
if (this.zipCodeData.isValid) {
|
||||
|
|
@ -289,7 +289,7 @@ export default {
|
|||
const closestShops = await getClosestApplicableShops(
|
||||
this.localZipCode,
|
||||
this.$store.getters.order.vehicle.carId,
|
||||
"service-location"
|
||||
this.pageName
|
||||
);
|
||||
if (!closestShops || closestShops.inShopProviders?.length === 0) {
|
||||
this.isLoading = false;
|
||||
|
|
|
|||
|
|
@ -318,10 +318,6 @@ export default {
|
|||
"vin-lookup"
|
||||
);
|
||||
|
||||
// Update button "Continue with..."
|
||||
this.$refs.navbar.updateButtonText(
|
||||
`Continue with ${resultMap.vehicleLookupResponse.year} ${resultMap.vehicleLookupResponse.make} ${resultMap.vehicleLookupResponse.model}`
|
||||
);
|
||||
return this.$refs.navbar.removeLoader();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,21 @@
|
|||
<template>
|
||||
<loadingModal v-if="useLoadingModal" notFullScreen ref="loadingModal" />
|
||||
<a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href">
|
||||
{{ text }}
|
||||
<slot name="after-text"></slot>
|
||||
</a>
|
||||
<!-- NOTE: each prettier-ignore is needed below...
|
||||
each anchor that follows needs to be one single line in markup or adds unwanted spaces within link (fixes CASH-1502) -->
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href">{{ text }}<slot name="after-text"></slot></a>
|
||||
<!-- prettier-ignore -->
|
||||
<a
|
||||
v-else-if="linkType === 'newWindowLink'"
|
||||
@click="handleClick"
|
||||
class="new-window-link"
|
||||
:href="href"
|
||||
target="_blank">
|
||||
{{ text }}
|
||||
<slot name="after-text"></slot>
|
||||
</a>
|
||||
<a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href">
|
||||
{{ text }}
|
||||
<slot name="after-text"></slot>
|
||||
</a>
|
||||
<a v-else-if="linkType === 'text'" @click="handleClick" :href="href">
|
||||
{{ text }}
|
||||
<slot name="after-text"></slot>
|
||||
</a>
|
||||
target="_blank">{{ text }}<slot name="after-text"></slot></a>
|
||||
<!-- prettier-ignore -->
|
||||
<a v-else-if="linkType === 'textSmall'" @click="handleClick" class="small" :href="href">{{ text }}<slot name="after-text"></slot></a>
|
||||
<!-- prettier-ignore -->
|
||||
<a v-else-if="linkType === 'text'" @click="handleClick" :href="href">{{ text }}<slot name="after-text"></slot></a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue