From 0bcd483d6fc248747f42450f0d783d9a0003ff34 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 25 Mar 2025 09:53:57 -0400 Subject: [PATCH 01/33] CASH-188 | Initial changes for webchat Still lacking higher environment setup Still need to pass through the hide flag into the component itself --- .../salesforce-helper-dev.js | 62 +++++++++++++++++++ .../salesforce-helper-prod.js | 2 + .../salesforce-helper-qa.js | 2 + .../salesforce-helper-sys.js | 2 + .../salesforce-webchat/salesforce-webchat.vue | 60 ++++++++++++++++++ .../funnel-header/funnel-header.vue | 8 +++ src/layouts/confirmation/confirmation.vue | 2 +- 7 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-dev.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-prod.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-qa.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-sys.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-webchat.vue diff --git a/src/digital-components/salesforce-webchat/salesforce-helper-dev.js b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js new file mode 100644 index 000000000..4132c330b --- /dev/null +++ b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js @@ -0,0 +1,62 @@ +/* +The code below is generated from salesforce but modified in the following ways: + * Only the javascript inside the second + + diff --git a/src/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue index 22d9e77fa..ac2e511a6 100644 --- a/src/fmg-components/funnel-header/funnel-header.vue +++ b/src/fmg-components/funnel-header/funnel-header.vue @@ -2,6 +2,8 @@
+ Safelite logo
@@ -24,6 +26,7 @@ import alert from "@/ux-components/alert/alert"; import eventBus from "@/helpers/event-bus/event-bus"; import { globalEvents } from "@/constants/events"; import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal"; +import salesforceWebchat from "../../digital-components/salesforce-webchat/salesforce-webchat.vue"; // Constants const ALERT_DURATION = 3000; // millisecond time to display alert before dismissal @@ -37,6 +40,10 @@ export default { }, props: { cmsWidgetName: String, + hideSalesforceWebchat: { + type: Boolean, + default: false + } }, computed: { imageSrc() { @@ -60,6 +67,7 @@ export default { components: { alert, menuModal, + salesforceWebchat }, mounted() { // Check if alert event is on the bus diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index 8c4d6cf1a..24720e665 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -5,7 +5,7 @@
- +
From 0b742ab84827c9db6b1d1343fc91e7dc2b7735b3 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Tue, 8 Apr 2025 16:17:05 -0400 Subject: [PATCH 02/33] Updates scheduled pipeline branch Updates the scheduled pipeline to run against the release branch. This ensures that the automated tests are executed against the stable release branch. --- playwright-tests/azure-pipelines-automated-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/azure-pipelines-automated-testing.yml b/playwright-tests/azure-pipelines-automated-testing.yml index b4d7da831..ccb5634c4 100644 --- a/playwright-tests/azure-pipelines-automated-testing.yml +++ b/playwright-tests/azure-pipelines-automated-testing.yml @@ -4,7 +4,7 @@ schedules: displayName: Daily Test Automation Run for FMG-NextGen branches: include: - - main + - release/2025.04.24 pool: 'AmazonLinuxPool' From 84fa84fdb3930d41830dd1fdc8bacd6eade23209 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Tue, 8 Apr 2025 16:18:09 -0400 Subject: [PATCH 03/33] Update azure-pipelines-automated-testing.yml for Azure Pipelines From 85c247322d6a16fc368baa58cf3218576f1cbd03 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Wed, 9 Apr 2025 10:36:59 -0400 Subject: [PATCH 04/33] CASH-188 | Add other files, small tweaks --- .../salesforce-helper-dev.js | 31 +++++----- .../salesforce-helper-prod.js | 59 ++++++++++++++++++ .../salesforce-helper-qa.js | 62 ++++++++++++++++++- .../salesforce-helper-sys.js | 2 - .../salesforce-webchat/salesforce-webchat.vue | 17 ++--- .../funnel-header/funnel-header.vue | 3 +- src/layouts/confirmation/confirmation.vue | 2 +- 7 files changed, 147 insertions(+), 29 deletions(-) delete mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-sys.js diff --git a/src/digital-components/salesforce-webchat/salesforce-helper-dev.js b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js index 4132c330b..ce4a4acb9 100644 --- a/src/digital-components/salesforce-webchat/salesforce-helper-dev.js +++ b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js @@ -4,22 +4,21 @@ The code below is generated from salesforce but modified in the following ways: * CSS is not copied in * First diff --git a/src/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue index 23f7c0ed9..226941652 100644 --- a/src/fmg-components/funnel-header/funnel-header.vue +++ b/src/fmg-components/funnel-header/funnel-header.vue @@ -2,9 +2,7 @@
- + Safelite logo
@@ -43,8 +41,8 @@ export default { cmsWidgetName: String, hideSalesforceWebchatLaunchButton: { type: Boolean, - default: false - } + default: false, + }, }, computed: { imageSrc() { @@ -68,7 +66,7 @@ export default { components: { alert, menuModal, - salesforceWebchat + salesforceWebchat, }, mounted() { // Check if alert event is on the bus diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index f9cacd29d..a54c51b7a 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -5,7 +5,10 @@
- +
From 219762ec30af2e87d848c0b4fc9cccc34f33ffa2 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Wed, 9 Apr 2025 14:50:30 -0400 Subject: [PATCH 06/33] Adds Jira integration for test results Adds a Jira writeback reporter to automatically create bug and test case subtasks in Jira based on playwright test results. Upgrades axios and adds axios-retry and axios-mock-adapter for more robust API calls. --- package-lock.json | 135 ++++++- package.json | 4 +- .../business-logic/types/JiraApi.ts | 111 ++++++ playwright-tests/impl/API/JiraApiUtil.ts | 143 ++++++++ .../impl/reporter/JiraWritebackReporter.ts | 339 ++++++++++++++++++ 5 files changed, 726 insertions(+), 6 deletions(-) create mode 100644 playwright-tests/business-logic/types/JiraApi.ts create mode 100644 playwright-tests/impl/API/JiraApiUtil.ts create mode 100644 playwright-tests/impl/reporter/JiraWritebackReporter.ts diff --git a/package-lock.json b/package-lock.json index 00fc7673a..2b541b385 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "dependencies": { "@iframe-resizer/child": "^5.3.3", - "axios": "^0.23.0", "bootstrap": "^5.3.3", "core-js": "^3.38.1", "http-status-codes": "^2.3.0", @@ -38,6 +37,9 @@ "@vue/eslint-config-prettier": "^9.0.0", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^27.0.0", + "axios": "^1.8.4", + "axios-mock-adapter": "^2.1.0", + "axios-retry": "^4.5.0", "dotenv-safe": "^9.1.0", "eslint": "8.57", "eslint-plugin-prettier": "^5.2.1", @@ -5412,12 +5414,82 @@ } }, "node_modules/axios": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", - "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.4" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-mock-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", + "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, + "node_modules/axios-mock-adapter/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/axios-retry": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" } }, "node_modules/babel-jest": { @@ -8005,6 +8077,22 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "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", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -9227,6 +9315,7 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, "funding": [ { "type": "individual", @@ -9694,6 +9783,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -10509,6 +10614,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-retry-allowed": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", + "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -16247,6 +16365,13 @@ "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==", + "dev": true, + "license": "MIT" + }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", diff --git a/package.json b/package.json index c63d8b5e8..923fec124 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ }, "dependencies": { "@iframe-resizer/child": "^5.3.3", - "axios": "^0.23.0", "bootstrap": "^5.3.3", "core-js": "^3.38.1", "http-status-codes": "^2.3.0", @@ -42,6 +41,9 @@ "@vue/eslint-config-prettier": "^9.0.0", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^27.0.0", + "axios": "^1.8.4", + "axios-mock-adapter": "^2.1.0", + "axios-retry": "^4.5.0", "dotenv-safe": "^9.1.0", "eslint": "8.57", "eslint-plugin-prettier": "^5.2.1", diff --git a/playwright-tests/business-logic/types/JiraApi.ts b/playwright-tests/business-logic/types/JiraApi.ts new file mode 100644 index 000000000..d5b34b081 --- /dev/null +++ b/playwright-tests/business-logic/types/JiraApi.ts @@ -0,0 +1,111 @@ +export interface PutEditIssueRequestBody { + fields: Partial +} + +export interface PostTransitionIssueRequestBody { + transition: { id: string } +} + +export interface PostCreateIssueRequestBody extends JiraIssue {} + +export interface PostCreateIssueResponse { + id: string, + key: string +} + +export interface PostBulkCreateIssueRequestBody { + issueUpdates: PostCreateIssueRequestBody[] +} + +export interface BulkTransitionInput { + selectedIssueIdsOrKeys: string[], + transitionId: string +} + +export interface PostBulkTransitionIssuesRequestBody { + bulkTransitionInputs: BulkTransitionInput[], + sendBulkNotification: false +} + +export interface PostBulkCreateIssueResponse { + issues: PostCreateIssueResponse[] +} + +export interface PostAddCommentResponse { + body: JiraContent +} + +export interface JiraIssueFields { + summary: string, + description: JiraContent, + project?: JiraProject, + issuetype?: { id: string }, + parent?: JiraParent, + fixVersions?: JiraVersion[], + subtasks?: JiraSubTask[], + customfield_14857?: JiraContent // Test Steps field +} + +export interface JiraSubTask { + id: string, + key: string +} + +export interface JiraProject { + key: string +} + +export interface JiraParent { + key: string +} + +export interface JiraContent { + type?: string, + text?: string, + version?: number + content?: JiraContent[] +} + +export interface JiraVersion { + id: string +} + +export interface JiraTransition { + id: string, + name: string +} + +export interface JiraIssueType { + id: string, + self: string, + description: string, + iconUrl: string, + name: string, + untranslatedName: string, + subtask: boolean, +} + +export interface GetIssueTransitionsResponse { + transitions: JiraTransition[] +} + +export interface GetIssueTypesResponse { + issueTypes: JiraIssueType[] +} + +export interface PostBulkFetchIssuesRequestBody { + issueIdsOrKeys: string[] +} + +export interface PostBulkFetchIssuesResponse { + issues: GetIssueResponse[]; +} + +export interface JiraIssue { + key?: string, + id?: string, + transition?: { id: string }, + fields: JiraIssueFields +} + +export interface GetIssueResponse extends JiraIssue {} \ No newline at end of file diff --git a/playwright-tests/impl/API/JiraApiUtil.ts b/playwright-tests/impl/API/JiraApiUtil.ts new file mode 100644 index 000000000..fdb970d1e --- /dev/null +++ b/playwright-tests/impl/API/JiraApiUtil.ts @@ -0,0 +1,143 @@ +import { PostBulkFetchIssuesRequestBody, PostBulkFetchIssuesResponse, GetIssueResponse, GetIssueTransitionsResponse, GetIssueTypesResponse, PostAddCommentResponse, PostBulkCreateIssueRequestBody, PostBulkCreateIssueResponse, PostCreateIssueRequestBody, PostCreateIssueResponse, PostTransitionIssueRequestBody, PutEditIssueRequestBody, PostBulkTransitionIssuesRequestBody } from "@business-logic/types/JiraApi"; +import axios, { request } from "axios"; +import axiosRetry from "axios-retry"; + +const jiraUrl = process.env.JIRA_SERVER!; +const jiraUsername = process.env.JIRA_USERNAME!; +const jiraApiKey = process.env.JIRA_API_KEY!; +const encodedAuthKey = Buffer.from(`${jiraUsername}:${jiraApiKey}`).toString('base64'); + +export default class JiraApiUtil { + readonly baseUrl: string; + readonly issueUrl: string; + readonly bulkIssueCreateUrl: string; + readonly bulkIssueFetchUrl: string; + readonly bulkTransitionIssuesUrl: string; + readonly axiosClient: axios.AxiosInstance; + + constructor() { + this.baseUrl = jiraUrl; + this.issueUrl = `${this.baseUrl}/rest/api/3/issue`; + this.bulkIssueCreateUrl = `${this.issueUrl}/bulk`; + this.bulkIssueFetchUrl = `${this.issueUrl}/bulkfetch`; + this.bulkTransitionIssuesUrl = `${this.baseUrl}/rest/api/3/bulk/issues/transition` + this.axiosClient = axios.create(); + // interceptor to log error message from api + this.axiosClient.interceptors.response.use( + response => response, + error => { + console.error('Axios Error:', error?.response?.data || error.message); + return Promise.reject(error); + } + ); + // Set up retries if requests are made too quickly + axiosRetry(this.axiosClient, { + retries: 4, + retryDelay: (retryCount) => { return Math.pow(2, retryCount) * 1000; }, // Exponential backoff + retryCondition: (error) => { return error.response?.status === 429 } // If rate-limit error + }); + } + + getIssue(issueKey: string) { + const url = `${this.issueUrl}/${issueKey}`; + return this.axiosClient.get(url, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + Accept: 'application/json' + } + }); + } + + postBulkFetchIssues(requestBody: PostBulkFetchIssuesRequestBody) { + return this.axiosClient.post(this.bulkIssueFetchUrl, requestBody, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }); + } + + getIssueTransitions(issueKey: string) { + const url = `${this.issueUrl}/${issueKey}/transitions` + return this.axiosClient.get(url, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + Accept: 'application/json' + } + }); + } + + getIssueTypes(projectKey: string) { + const url = `${this.baseUrl}/rest/api/3/issue/createmeta/${projectKey}/issuetypes`; + return this.axiosClient.get(url, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + Accept: 'application/json' + } + }); + } + + postAddComment(issueKey: string, commentBody: PostAddCommentResponse) { + const url = `${this.issueUrl}/${issueKey}/comment`; + return this.axiosClient.post(url, commentBody, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }); + } + + postTransitionIssue(issueKey: string, requestBody: PostTransitionIssueRequestBody) { + const transitionUrl = `${this.issueUrl}/${issueKey}/transitions` + return this.axiosClient.post(transitionUrl, requestBody, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + }); + } + + putEditIssue(issueKey: string, requestBody: PutEditIssueRequestBody) { + const editIssueUrl = `${this.issueUrl}/${issueKey}` + return this.axiosClient.put(editIssueUrl, requestBody, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }); + } + + postBulkTransitionIssues(requestBody: PostBulkTransitionIssuesRequestBody) { + return this.axiosClient.post(this.bulkTransitionIssuesUrl, requestBody, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + }); + } + + postCreateIssue(requestBody: PostCreateIssueRequestBody) { + return this.axiosClient.post(this.issueUrl, requestBody, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + }); + } + + postBulkCreateIssue(requestBody: PostBulkCreateIssueRequestBody) { + return this.axiosClient.post(this.bulkIssueCreateUrl, requestBody, { + headers: { + Authorization: `Basic ${encodedAuthKey}`, + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + }); + } +} \ No newline at end of file diff --git a/playwright-tests/impl/reporter/JiraWritebackReporter.ts b/playwright-tests/impl/reporter/JiraWritebackReporter.ts new file mode 100644 index 000000000..c1db33244 --- /dev/null +++ b/playwright-tests/impl/reporter/JiraWritebackReporter.ts @@ -0,0 +1,339 @@ +import JiraApiUtil from "@impl/API/JiraApiUtil"; +import type { FullResult, Reporter, TestCase, TestResult } from "@playwright/test/reporter"; +import { formatDate } from "@impl/utils/DateUtils"; +import { GetIssueResponse, JiraIssue, JiraIssueFields } from "@business-logic/types/JiraApi"; + +const jiraCardNumber = process.env.JIRA_CARD_NUMBER!; +const jiraProjectKey = 'INSR'; +const passTransitionId = '111'; +const failTransitionId = '101'; + +export default class JiraWritebackReporter implements Reporter { + readonly jiraApiUtil: JiraApiUtil = new JiraApiUtil(); + readonly loadIssueCalls: (() => void)[] = []; // Calls to loadIssue must be deferred because they depend on information from the API. + readonly issuesToPass: string[] = []; // Issues to transition to "Pass". + readonly issuesToFail: string[] = []; // Issues to transition to "Fail". + readonly issuesToCreate: JiraIssue[] = []; // Issues to be batch created. + existingSubtasks: JiraIssue[] = []; // Array to hold existing subtasks of the dev card. + testCaseTypeId: string|undefined = undefined; // ID of the test case subtask type in Jira. Will be filled by API call. + bugTypeId:string|undefined = undefined; // ID of the bug subtask type. + parentCard: GetIssueResponse | undefined = undefined; // Variable to hold the parent card. Will be filled by API call. + + // Will need this if we want to move ortoni report upload into this reporter. + // readonly ortoniReport = new OrtoniReport(reportConfig); + + /** + * This function loads the IDs for the Jira Issue Types we use. + * + */ + async loadJiraIssueTypes() { + console.log(`JiraWritebackReporter >> Loading Jira Issue Types for project '${jiraProjectKey}'...`); + const issueTypesRes = await this.jiraApiUtil.getIssueTypes(jiraProjectKey); + const issueTypes = issueTypesRes.data; + this.testCaseTypeId = issueTypes.issueTypes.find(issueType => { + return issueType.subtask === true && issueType.name === 'Test Case Sub-task' + })?.id; + this.bugTypeId = issueTypes.issueTypes.find(issueType => { + return issueType.subtask === true && issueType.name === 'Bug Sub-task' + })?.id; + console.log('JiraWritebackReporter >> Loaded issue types.'); + } + + /** + * This function generates a test subtask based on the test results we pass in. + * @param test TestCase from onTestEnd() + * @param result TestResult from onTestEnd() + * @returns Jira Test Subtask based on test result. + */ + getCreateTestSubtask(test: TestCase, result: TestResult) { + if (result.status === 'skipped') { + return undefined; + } + const testStepTitles = result.steps.map(step => { + return `-\t${step.title}`; + }).join('\n'); + const allErrors = result.errors.map(value => { + return value.message + }).join('\n'); + + const description = `Most recent test status: ${result.status}.\nDuration: ${result.duration/1000} seconds.\nErrors:\n${allErrors}` + const testSubtaskBody: JiraIssueFields = { + summary: test.title, + description: { + content: [ + { + "content": [ + { + "text": description, + "type": "text" + } + ], + "type": "paragraph" + } + ], + "type": "doc", + "version": 1 + }, + project: { + key: jiraProjectKey + }, + issuetype: { + id: this.testCaseTypeId! + }, + parent: { + key: jiraCardNumber + }, + customfield_14857: { + type: 'doc', + version: 1, + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text: testStepTitles + } + ] + } + ] + } + }; + return testSubtaskBody; + } + + /** + * This function generates a bug subtask based on the test results we pass in. + * @param test TestCase from onTestEnd() + * @param result TestResult from onTestEnd() + * @returns Jira Bug Subtask based on test result. + */ + getCreateBug(test: TestCase, result: TestResult) { + if (result.status === 'passed' || result.status === 'skipped') { + return undefined; + } + const allErrors = result.errors.map(value => { + return value.message + }).join('\n'); + + const bug: JiraIssueFields = { + summary: `TEST FAILED ${formatDate(new Date())}: ${test.title}`, + description: { + content: [ + { + "content": [ + { + "text": allErrors, + "type": "text" + } + ], + "type": "paragraph" + } + ], + "type": "doc", + "version": 1 + }, + project: { + key: jiraProjectKey + }, + issuetype: { + id: this.bugTypeId! + }, + parent: { + key: jiraCardNumber + } + }; + return bug; + } + + /** + * This function gives us the correct transition for a test subtask based on this test result. + * @param result TestResult from onTestEnd() + * @returns Correct transition to pass to the Jira API. + */ + getSubtaskTransition(result: TestResult): { id: string } | undefined { + if (result.status === 'skipped') { + return undefined; + } + + if (result.status === 'passed') { + return { + id: passTransitionId + }; + } else { + return { + id: failTransitionId + }; + } + } + + /** + * Grabs parent card and existing subtasks. + */ + async loadReporterData() { + await this.loadJiraIssueTypes(); + + console.log('JiraWritebackReporter >> Searching for parent card...'); + const parentRes = await this.jiraApiUtil.getIssue(jiraCardNumber); + const parentCard = parentRes.data; + console.log(`JiraWritebackReporter >> Retrieved parent card: ${parentCard.key}: ${parentCard.fields.summary}`); + + console.log(`JiraWritebackReporter >> Searching for all subtasks of parent card '${jiraCardNumber}'...`); + const subtaskIds = parentCard.fields.subtasks?.map(subtask => { + return subtask.id; + }); + + if (subtaskIds && subtaskIds.length > 0) { + for (let i = 0; i < subtaskIds.length; i+=50) { + const batch = subtaskIds.slice(i, i+50); + const subTasksRes = await this.jiraApiUtil.postBulkFetchIssues({ issueIdsOrKeys: batch }); + this.existingSubtasks.push(...subTasksRes.data.issues); + } + console.log('JiraWritebackReporter >> Subtasks retrieved.'); + } else { + console.log('JiraWritebackReporter >> No subtasks were found.'); + } + + } + + /** + * Bulk creates test cases and bugs in batches of up to 50. Bulk transitions existing test case subtasks. + */ + async writeResults() { + for (let i = 0; i < this.issuesToCreate.length; i+=50) { + const batch = this.issuesToCreate.slice(i, i + 50); + console.log('JiraWritebackReporter >> Creating issue batch...'); + // fire request + await this.jiraApiUtil.postBulkCreateIssue({ issueUpdates: batch }); + console.log('JiraWritebackReporter >> Issue batch created.'); + } + + if (this.issuesToPass.length > 0) { + console.log('JiraWritebackReporter >> Transitioning passed test cases to "Pass"...') + await this.jiraApiUtil.postBulkTransitionIssues( { + bulkTransitionInputs: [{ + selectedIssueIdsOrKeys: this.issuesToPass, + transitionId: passTransitionId + }], + sendBulkNotification: false + }); + console.log('JiraWritebackReporter >> Transition success.'); + } + + if (this.issuesToFail.length > 0) { + console.log('JiraWritebackReporter >> Transitioning failed test cases to "Fail"...') + await this.jiraApiUtil.postBulkTransitionIssues( { + bulkTransitionInputs: [{ + selectedIssueIdsOrKeys: this.issuesToFail, + transitionId: failTransitionId + }], + sendBulkNotification: false + }); + console.log('JiraWritebackReporter >> Transition success.') + } + } + + /** + * Builds and loads appropriate response based on test result. This can be a test subtask/bug subtask or a call to transition a test subtask. + * @param test TestCase from onTestEnd() + * @param result TestResult from onTestEnd() + * @returns Void + */ + loadIssue(test: TestCase, result: TestResult) { + if (result.status === 'skipped') return; + if (result.status !== 'passed' && result.retry < test.retries) return; // Skip if test case failed and this isn't the last retry. + + let existingTestSubtask: JiraIssue | undefined = undefined; + let existingBug: JiraIssue | undefined = undefined; + const subtaskTransition = this.getSubtaskTransition(result)!; // Get either a Pass or Fail transition depending on test results. + + console.log('JiraWritebackReporter >> Checking for existing subtasks for this test...'); + for (const card of this.existingSubtasks) { + if (card.fields.issuetype?.id === this.testCaseTypeId) { + if (card.fields.summary === test.title) { + existingTestSubtask = card; + console.log('JiraWritebackReporter >> Found existing test subtask.'); + } + } else if (card.fields.issuetype?.id === this.bugTypeId) { + if (new RegExp(`^TEST FAILED [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]: ${test.title}$`).test(card.fields.summary)) { + existingBug = card; + console.log('JiraWritebackReporter >> Found existing bug.'); + // TODO: Handle multiple matches for bug/test case. Currently picks one in that case. + } + } + } + if (!(existingTestSubtask || existingBug)) { + console.log('JiraWritebackReporter >> No existing test subtask or bug was found.') + } + + const subTask = this.getCreateTestSubtask(test, result); // Will return undefined if test status is 'skipped' + const bug = this.getCreateBug(test, result); // Will return undefined if we don't need one + if (subTask) { + // Create or Edit Subtask + if (existingTestSubtask) { + if (result.status === 'passed') { + this.issuesToPass.push(existingTestSubtask.key!); + } else { + this.issuesToFail.push(existingTestSubtask.key!); + } + } else { + this.issuesToCreate.push({ + fields: subTask, + transition: subtaskTransition + }); + } + } + + if (bug) { + // Create or Edit Bug + if (existingBug) { + // Leave it alone. This reporter should not modify existing bugs. + } else { + this.issuesToCreate.push({ + fields: bug + }); + } + } + } + + /** + * This function loads calls to loadIssue() in an array to be executed when the necessary information from the Jira API is available. + * @param test + * @param result + */ + onTestEnd(test: TestCase, result: TestResult) { + // this.ortoniReport.onTestEnd(test, result); + this.loadIssueCalls.push(() => { this.loadIssue(test, result); }); + } + + /** + * Makes calls to load and execute API calls to Jira. + * @param result + * @returns Promise to write results to Jira. + */ + onEnd(result: FullResult): Promise<{ status?: FullResult["status"]; } | undefined | void>|void { + // this.ortoniReport.onEnd(result); + return this.loadReporterData().then(async () => { + this.loadIssueCalls.map(fn => fn()); + await this.writeResults(); + console.log(`JiraWritebackReporter >> Posted necessary changes for '${jiraCardNumber}'`); + }); + } + + // These overloads will become necessary if we move the ortoni report upload into this reporter + // onBegin(config: FullConfig, suite: Suite): void { + // this.ortoniReport.onBegin(config, suite); + // } + + // onError(error: TestError): void { + // this.ortoniReport.onError(error); + // } + + // onExit(): Promise { + // return this.ortoniReport.onExit(); + // } + + // onStdOut(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void { + // this.ortoniReport.onStdOut(chunk, test, result); + // } +} \ No newline at end of file From 918853670880620a129025f00a3c9dab676ac29d Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Wed, 9 Apr 2025 15:09:45 -0400 Subject: [PATCH 07/33] Adding pr gate pipeline functionality to pipeline file --- azure-pipelines.yml | 168 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2cb18429c..8c5e61265 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -34,6 +34,12 @@ resources: variables: - group: Digital-Infrastructure - group: FixMyGlass-BuildBranches + - name: dockerImageName + value: 'playwright-tests' + - name: imageTag + value: '$(Build.BuildId)' + - name: totalShards + value: 2 stages: # PR's @@ -57,6 +63,168 @@ stages: npmLocation: $(Build.SourcesDirectory) testResultsFile: junit.xml summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml + - job: playwright_tests + continueOnError: true + strategy: + matrix: + shard1: + shardNumber: 1 + shard2: + shardNumber: 2 + + steps: + - task: Docker@2 + displayName: 'Build Docker Image' + inputs: + command: build + dockerfile: Dockerfile.playwright + repository: $(dockerImageName) + tags: $(imageTag) + arguments: '--no-cache --pull' + + - script: | + branch_name=$(System.PullRequest.SourceBranch) + echo "Retrieved branch name: '$branch_name'" + JIRA_CARD_NUMBER="${branch_name##*/}" + echo "Extracted JIRA Card number: '$JIRA_CARD_NUMBER'" + # Create container and run tests + container_id=$(docker create \ + --ipc=host \ + -e CCIS_API_AUTH=$(CCIS_API_AUTH) \ + -e BASE_URL=$(BASE_URL) \ + -e CCIS_API_URL=$(CCIS_API_URL) \ + -e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \ + -e SHARD=$(shardNumber) \ + -e CI=true \ + -e ENABLE_MOCK_TESTING=true \ + -e ENABLE_ACCESSIBILITY_TESTING=true \ + -e NODE_ENV=$(NODE_ENV) \ + $(dockerImageName):$(imageTag) \ + npx concurrently -k -n "server,playwright"\ + "sed -i \"s|^process\.env\.VUE_APP_CONSUMER_CF_DISTRO = .*|process\.env\.VUE_APP_CONSUMER_CF_DISTRO='https://digitalapi.test.safelite.io'|\" \"./vue.config.js\" && echo \"Updated config file to use TEST APIs\" && npm run serve -- --port=8080"\ + "npx wait-on http://localhost:8080 && npm run test:playwright -- --shard=$(shardNumber)/$(totalShards) --reporter=list,blob --grep \"@$JIRA_CARD_NUMBER\"") + + # Start container and stream logs + echo "Starting tests for shard $(shardNumber)..." + docker start -a $container_id + + # Create directory for test results + echo "Creating test results directory..." + mkdir -p $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber) + + # Copy test results from container + echo "Copying test results..." + docker cp $container_id:/app/blob-report/. $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)/ + + # Remove container + echo "Cleaning up container..." + docker rm $container_id + + # Check if tests failed + if [ $? -ne 0 ]; then + echo "Tests failed in shard $(shardNumber) or tests don't exist for this shard number!" + exit 0 # Suppress error. It will be visible in report. + fi + displayName: 'Run Playwright Tests - Shard $(shardNumber)' + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Reports - Shard $(shardNumber)' + condition: always() + inputs: + targetPath: '$(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)' + artifact: 'playwright-report-shard-$(shardNumber)' + publishLocation: 'pipeline' + + - script: | + docker rmi $(dockerImageName):$(imageTag) -f + displayName: 'Cleanup Docker Image' + condition: always() + + # Add job to merge test reports and update Jira + - job: download_and_merge_reports + dependsOn: playwright_tests + timeoutInMinutes: 10 + cancelTimeoutInMinutes: 10 + steps: + - task: DownloadPipelineArtifact@2 + inputs: + targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports' + - task: Docker@2 + displayName: 'Build Docker Image' + inputs: + command: build + dockerfile: Dockerfile.playwright + repository: $(dockerImageName) + tags: $(imageTag) + arguments: '--no-cache --pull' + - bash: | + branch_name=$(System.PullRequest.SourceBranch) + echo "Retrieved branch name: '$branch_name'" + JIRA_CARD_NUMBER="${branch_name##*/}" + echo "Extracted JIRA Card number: '$JIRA_CARD_NUMBER'" + # Create container for jira writeback + container_id=$(docker create \ + --ipc=host \ + -e JIRA_SERVER=$(JIRA_SERVER) \ + -e JIRA_USERNAME=$(JIRA_USERNAME) \ + -e JIRA_API_KEY=$(JIRA_API_KEY) \ + -e JIRA_CARD_NUMBER="$JIRA_CARD_NUMBER" \ + $(dockerImageName):$(imageTag) \ + bash -c "echo \"Moving Playwright reports out of subfolders...\" && + find ./playwright-reports/ -mindepth 2 -type f -exec mv {} ./playwright-reports/ \; && + echo \"Merging reports...\" && + export NODE_OPTIONS=--max_old_space_size=4096 + PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx playwright merge-reports --reporter=ortoni-report,junit,\"/app/playwright-tests/impl/reporter/JiraWritebackReporter.ts\" ./playwright-reports + echo \"Contents of ortoni-report:\" && ls ./ortoni-report && + echo 'Current dir: ' && pwd + echo 'Contents of current dir: ' && ls + echo 'Contents of /app/test-results' && ls /app/test-results + echo \"Writing report to Jira card '$JIRA_CARD_NUMBER'...\" && + /app/devops/scripts/jira_writeback.sh add_comment \"$JIRA_CARD_NUMBER\" /app/ortoni-report/ortoni-report.html \"AUTOMATED TEST RUN: $(date)\" ") + + # Start container and stream logs + echo "Starting merge" + docker start -a $container_id + + # Create directory for test results + echo "Creating test results directory..." + mkdir -p $(System.DefaultWorkingDirectory)/ortoni-report + mkdir -p $(System.DefaultWorkingDirectory)/test-results + + # Copy test results from container + echo "Copying test results..." + docker cp $container_id:/app/ortoni-report/. $(System.DefaultWorkingDirectory)/ortoni-report + docker cp $container_id:/app/test-results/junit_results.xml $(System.DefaultWorkingDirectory)/test-results + + # Remove container + echo "Cleaning up container..." + docker rm $container_id + env: + JIRA_API_KEY: $(JIRA_API_KEY) + displayName: merge_and_publish_results_to_jira + + - task: PublishTestResults@2 + displayName: 'Publish test results' + inputs: + searchFolder: 'test-results' + testResultsFormat: 'JUnit' + testResultsFiles: 'junit_results.xml' + mergeTestResults: true + failTaskOnFailedTests: false + testRunTitle: 'Playwright Tests' + condition: succeededOrFailed() + + - task: PublishPipelineArtifact@1 + displayName: 'Publish Merged Report' + condition: always() + inputs: + targetPath: '$(System.DefaultWorkingDirectory)/ortoni-report' + artifact: 'playwright-merged-report' + publishLocation: 'pipeline' + - script: | + docker rmi $(dockerImageName):$(imageTag) -f + displayName: 'Cleanup Docker Image' + condition: always() - ${{ else }}: # Dev Build/Deploy - stage: Dev From 4e82930ea358a7a4ba724224284c38b268e35df7 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 10 Apr 2025 09:24:37 -0400 Subject: [PATCH 08/33] Removed Cron Job scheduling from automated testing pipeline Now setting it up through Azure --- playwright-tests/azure-pipelines-automated-testing.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/playwright-tests/azure-pipelines-automated-testing.yml b/playwright-tests/azure-pipelines-automated-testing.yml index ccb5634c4..db2a902da 100644 --- a/playwright-tests/azure-pipelines-automated-testing.yml +++ b/playwright-tests/azure-pipelines-automated-testing.yml @@ -1,11 +1,3 @@ -schedules: -- cron: 0 9 * * MON-FRI - always: true - displayName: Daily Test Automation Run for FMG-NextGen - branches: - include: - - release/2025.04.24 - pool: 'AmazonLinuxPool' variables: From 49ea0fa13ec559c02d2cb4e71f1b68de7ce1aaaa Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 10 Apr 2025 09:27:14 -0400 Subject: [PATCH 09/33] Adding Environment to Jira comment --- playwright-tests/azure-pipelines-automated-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/azure-pipelines-automated-testing.yml b/playwright-tests/azure-pipelines-automated-testing.yml index db2a902da..50466bbd2 100644 --- a/playwright-tests/azure-pipelines-automated-testing.yml +++ b/playwright-tests/azure-pipelines-automated-testing.yml @@ -143,7 +143,7 @@ stages: fi echo \"Writing report to Jira card '$jira_card_number'...\" - /app/devops/scripts/jira_writeback.sh add_comment \"$jira_card_number\" /app/ortoni-report/\${REPORT_NAME} \"AUTOMATED TEST RUN: $(date) - \${CURRENT_DAY} (\${NUMERIC_DATE})\" ") + /app/devops/scripts/jira_writeback.sh add_comment \"$jira_card_number\" /app/ortoni-report/\${REPORT_NAME} \"AUTOMATED TEST RUN: $(date) - \${CURRENT_DAY} (\${NUMERIC_DATE}) - Environment: \${NODE_ENV}\" ") # Start container and stream logs echo "Starting merge" From d918d85ee48186fe371703f103b271c5273880bd Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 10 Apr 2025 09:48:01 -0400 Subject: [PATCH 10/33] Pulls NODE_ENV variable from env and uses this in the comment --- playwright-tests/azure-pipelines-automated-testing.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/playwright-tests/azure-pipelines-automated-testing.yml b/playwright-tests/azure-pipelines-automated-testing.yml index 50466bbd2..5634b4bd6 100644 --- a/playwright-tests/azure-pipelines-automated-testing.yml +++ b/playwright-tests/azure-pipelines-automated-testing.yml @@ -123,6 +123,7 @@ stages: -e REPORT_NAME="$report_name" \ -e CURRENT_DAY="$current_day" \ -e NUMERIC_DATE="$numeric_date" \ + -e NODE_ENV=$(NODE_ENV) \ $(dockerImageName):$(imageTag) \ bash -c "chmod +x devops/scripts/jira_writeback.sh echo \"Moving Playwright reports out of subfolders...\" From d44367274529557dbefaee9cae0af8e217475bb9 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 10 Apr 2025 11:43:49 -0400 Subject: [PATCH 11/33] Updates default postal code for customer details Updates the default postal code in the customer details data. This ensures that the default data reflects a more accurate or valid postal code for testing purposes. --- playwright-tests/business-logic/constants/DefaultTestData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/business-logic/constants/DefaultTestData.ts b/playwright-tests/business-logic/constants/DefaultTestData.ts index 6efb2c1db..197297b4b 100644 --- a/playwright-tests/business-logic/constants/DefaultTestData.ts +++ b/playwright-tests/business-logic/constants/DefaultTestData.ts @@ -43,7 +43,7 @@ export function getCustomerDetails(): ICustomerDetails { street: faker.location.streetAddress(), city: 'Columbus', state: 'Ohio', - postalCode: '43215', + postalCode: '21237', country: 'United States' } }; From d26885d0f0d344a861c52b9bbd82a264e0c28b0f Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 10 Apr 2025 13:37:41 -0400 Subject: [PATCH 12/33] Improves handling of repeated clicks modal Updates the selector for the repeated clicks modal close button to be more specific, and adds a short wait after closing the modal before proceeding. This improves reliability when the modal appears and interferes with subsequent actions. Removes redundant check for modal visibility in the service location page as it is handled on the service packages page. --- playwright-tests/pages/ServiceLocationPage.ts | 3 --- playwright-tests/pages/ServicePackagesPage.ts | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts index b8c8649b6..c9cb6c98f 100644 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -114,9 +114,6 @@ export class ServiceLocationPage extends BasePage { await this.mobileButton.click(); await this.enterServiceAddressButton.click(); await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! }); - if (await this.repeatedClicksModalCloseButton.isVisible()) { - await this.repeatedClicksModalCloseButton.click(); - } if (faker.datatype.boolean()) { await this.vehicleProtectedYesButton.check(); } else { diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 9a3b87ae3..8b1597598 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -41,7 +41,7 @@ export class ServicePackagesPage extends BasePage { this.closeButton = this.page. getByRole('dialog').locator('button').filter({ hasText: 'Close' }); this.promoCodeTextbox = this.page.getByLabel('Enter a promo code'); this.applyPromoButton = this.page.getByRole('button', { name: 'Apply promo code' }); - this.repeatedClicksModalCloseButton = this.page.getByRole('img').nth(1); + this.repeatedClicksModalCloseButton = this.page.locator('#app').getByRole('dialog').locator('button').filter({ hasText: 'Close' }); // this.validateURL(this.url); } @@ -76,6 +76,7 @@ export class ServicePackagesPage extends BasePage { if (await this.repeatedClicksModalCloseButton.isVisible()) { await this.repeatedClicksModalCloseButton.click(); } + await this.page.waitForTimeout(3000); await this.closeButton.click(); } else { From 2510f3c9debb9b2acecc99b30701cd0467ef047d Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 10 Apr 2025 13:51:18 -0400 Subject: [PATCH 13/33] Adds skip content site functionality. Allows skipping the content site (home page) based on an environment variable. This provides flexibility in test execution, enabling tests to bypass the home page when it's not relevant to the specific scenario being tested. The SKIP_CONTENT_SITE environment variable controls whether to skip the content site. --- playwright-tests/.env.dev | 4 ++++ playwright-tests/tests/0000__M.test.ts | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/playwright-tests/.env.dev b/playwright-tests/.env.dev index 47ec8336d..8c064ba62 100644 --- a/playwright-tests/.env.dev +++ b/playwright-tests/.env.dev @@ -4,9 +4,13 @@ # Environment type NODE_ENV="qa" +SKIP_CONTENT_SITE=false + # Base URLs by environment (uncomment the one you need) # qa BASE_URL="https://www-qa2.safelite.com/" +# qa with skipToInsurance Turned Off +# BASE_URL="https://fixmyglassqa.safelite.com/?cns=all&experiments=ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_VinAndEmailOptional=true,NextGen_IGQSkipToInsurance=NextGen_IGQSkipToInsurance_V1=NextGen_IGQSkipToInsurance_CONTROL=true" # sys # BASE_URL="https://www-test2.safelite.com/fmg/?fmgPage=vehicle" # dev diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 6bef5a2f3..656e078c2 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -192,8 +192,9 @@ async function runWorkflow(page: Page, testCase: TestCase) { //============================= TEST WORKFLOW STEPS ============================= - // Execute home page for qa and dev environments (skip for sys) - if (process.env.NODE_ENV !== 'sys') { + // Use Environment Variable to decide whether or not we want to skip content site aka home page + + if (process.env.SKIP_CONTENT_SITE == "false") { await test.step('HomePage >> Lets Get Started', async () => { let homePage = testCase.pages.homePage; console.log(`Customer for this test: ${customerDetails?.firstName} ${customerDetails?.lastName}`); From 7d20b0074ccd5409d0d79918d08cabf8b710e031 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 10 Apr 2025 14:03:06 -0400 Subject: [PATCH 14/33] Removes PR gate pipeline config Removes the Playwright tests and related jobs from the Azure Pipelines configuration file. These jobs were unintentionally added to the branch and are no longer needed. --- azure-pipelines.yml | 168 --------- .../business-logic/types/JiraApi.ts | 111 ------ playwright-tests/impl/API/JiraApiUtil.ts | 143 -------- .../impl/reporter/JiraWritebackReporter.ts | 339 ------------------ 4 files changed, 761 deletions(-) delete mode 100644 playwright-tests/business-logic/types/JiraApi.ts delete mode 100644 playwright-tests/impl/API/JiraApiUtil.ts delete mode 100644 playwright-tests/impl/reporter/JiraWritebackReporter.ts diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8c5e61265..2cb18429c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -34,12 +34,6 @@ resources: variables: - group: Digital-Infrastructure - group: FixMyGlass-BuildBranches - - name: dockerImageName - value: 'playwright-tests' - - name: imageTag - value: '$(Build.BuildId)' - - name: totalShards - value: 2 stages: # PR's @@ -63,168 +57,6 @@ stages: npmLocation: $(Build.SourcesDirectory) testResultsFile: junit.xml summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml - - job: playwright_tests - continueOnError: true - strategy: - matrix: - shard1: - shardNumber: 1 - shard2: - shardNumber: 2 - - steps: - - task: Docker@2 - displayName: 'Build Docker Image' - inputs: - command: build - dockerfile: Dockerfile.playwright - repository: $(dockerImageName) - tags: $(imageTag) - arguments: '--no-cache --pull' - - - script: | - branch_name=$(System.PullRequest.SourceBranch) - echo "Retrieved branch name: '$branch_name'" - JIRA_CARD_NUMBER="${branch_name##*/}" - echo "Extracted JIRA Card number: '$JIRA_CARD_NUMBER'" - # Create container and run tests - container_id=$(docker create \ - --ipc=host \ - -e CCIS_API_AUTH=$(CCIS_API_AUTH) \ - -e BASE_URL=$(BASE_URL) \ - -e CCIS_API_URL=$(CCIS_API_URL) \ - -e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \ - -e SHARD=$(shardNumber) \ - -e CI=true \ - -e ENABLE_MOCK_TESTING=true \ - -e ENABLE_ACCESSIBILITY_TESTING=true \ - -e NODE_ENV=$(NODE_ENV) \ - $(dockerImageName):$(imageTag) \ - npx concurrently -k -n "server,playwright"\ - "sed -i \"s|^process\.env\.VUE_APP_CONSUMER_CF_DISTRO = .*|process\.env\.VUE_APP_CONSUMER_CF_DISTRO='https://digitalapi.test.safelite.io'|\" \"./vue.config.js\" && echo \"Updated config file to use TEST APIs\" && npm run serve -- --port=8080"\ - "npx wait-on http://localhost:8080 && npm run test:playwright -- --shard=$(shardNumber)/$(totalShards) --reporter=list,blob --grep \"@$JIRA_CARD_NUMBER\"") - - # Start container and stream logs - echo "Starting tests for shard $(shardNumber)..." - docker start -a $container_id - - # Create directory for test results - echo "Creating test results directory..." - mkdir -p $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber) - - # Copy test results from container - echo "Copying test results..." - docker cp $container_id:/app/blob-report/. $(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)/ - - # Remove container - echo "Cleaning up container..." - docker rm $container_id - - # Check if tests failed - if [ $? -ne 0 ]; then - echo "Tests failed in shard $(shardNumber) or tests don't exist for this shard number!" - exit 0 # Suppress error. It will be visible in report. - fi - displayName: 'Run Playwright Tests - Shard $(shardNumber)' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish Test Reports - Shard $(shardNumber)' - condition: always() - inputs: - targetPath: '$(System.DefaultWorkingDirectory)/blob-reports/shard-$(shardNumber)' - artifact: 'playwright-report-shard-$(shardNumber)' - publishLocation: 'pipeline' - - - script: | - docker rmi $(dockerImageName):$(imageTag) -f - displayName: 'Cleanup Docker Image' - condition: always() - - # Add job to merge test reports and update Jira - - job: download_and_merge_reports - dependsOn: playwright_tests - timeoutInMinutes: 10 - cancelTimeoutInMinutes: 10 - steps: - - task: DownloadPipelineArtifact@2 - inputs: - targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports' - - task: Docker@2 - displayName: 'Build Docker Image' - inputs: - command: build - dockerfile: Dockerfile.playwright - repository: $(dockerImageName) - tags: $(imageTag) - arguments: '--no-cache --pull' - - bash: | - branch_name=$(System.PullRequest.SourceBranch) - echo "Retrieved branch name: '$branch_name'" - JIRA_CARD_NUMBER="${branch_name##*/}" - echo "Extracted JIRA Card number: '$JIRA_CARD_NUMBER'" - # Create container for jira writeback - container_id=$(docker create \ - --ipc=host \ - -e JIRA_SERVER=$(JIRA_SERVER) \ - -e JIRA_USERNAME=$(JIRA_USERNAME) \ - -e JIRA_API_KEY=$(JIRA_API_KEY) \ - -e JIRA_CARD_NUMBER="$JIRA_CARD_NUMBER" \ - $(dockerImageName):$(imageTag) \ - bash -c "echo \"Moving Playwright reports out of subfolders...\" && - find ./playwright-reports/ -mindepth 2 -type f -exec mv {} ./playwright-reports/ \; && - echo \"Merging reports...\" && - export NODE_OPTIONS=--max_old_space_size=4096 - PLAYWRIGHT_JUNIT_OUTPUT_DIR='/app/test-results' PLAYWRIGHT_JUNIT_OUTPUT_NAME='junit_results.xml' npx playwright merge-reports --reporter=ortoni-report,junit,\"/app/playwright-tests/impl/reporter/JiraWritebackReporter.ts\" ./playwright-reports - echo \"Contents of ortoni-report:\" && ls ./ortoni-report && - echo 'Current dir: ' && pwd - echo 'Contents of current dir: ' && ls - echo 'Contents of /app/test-results' && ls /app/test-results - echo \"Writing report to Jira card '$JIRA_CARD_NUMBER'...\" && - /app/devops/scripts/jira_writeback.sh add_comment \"$JIRA_CARD_NUMBER\" /app/ortoni-report/ortoni-report.html \"AUTOMATED TEST RUN: $(date)\" ") - - # Start container and stream logs - echo "Starting merge" - docker start -a $container_id - - # Create directory for test results - echo "Creating test results directory..." - mkdir -p $(System.DefaultWorkingDirectory)/ortoni-report - mkdir -p $(System.DefaultWorkingDirectory)/test-results - - # Copy test results from container - echo "Copying test results..." - docker cp $container_id:/app/ortoni-report/. $(System.DefaultWorkingDirectory)/ortoni-report - docker cp $container_id:/app/test-results/junit_results.xml $(System.DefaultWorkingDirectory)/test-results - - # Remove container - echo "Cleaning up container..." - docker rm $container_id - env: - JIRA_API_KEY: $(JIRA_API_KEY) - displayName: merge_and_publish_results_to_jira - - - task: PublishTestResults@2 - displayName: 'Publish test results' - inputs: - searchFolder: 'test-results' - testResultsFormat: 'JUnit' - testResultsFiles: 'junit_results.xml' - mergeTestResults: true - failTaskOnFailedTests: false - testRunTitle: 'Playwright Tests' - condition: succeededOrFailed() - - - task: PublishPipelineArtifact@1 - displayName: 'Publish Merged Report' - condition: always() - inputs: - targetPath: '$(System.DefaultWorkingDirectory)/ortoni-report' - artifact: 'playwright-merged-report' - publishLocation: 'pipeline' - - script: | - docker rmi $(dockerImageName):$(imageTag) -f - displayName: 'Cleanup Docker Image' - condition: always() - ${{ else }}: # Dev Build/Deploy - stage: Dev diff --git a/playwright-tests/business-logic/types/JiraApi.ts b/playwright-tests/business-logic/types/JiraApi.ts deleted file mode 100644 index d5b34b081..000000000 --- a/playwright-tests/business-logic/types/JiraApi.ts +++ /dev/null @@ -1,111 +0,0 @@ -export interface PutEditIssueRequestBody { - fields: Partial -} - -export interface PostTransitionIssueRequestBody { - transition: { id: string } -} - -export interface PostCreateIssueRequestBody extends JiraIssue {} - -export interface PostCreateIssueResponse { - id: string, - key: string -} - -export interface PostBulkCreateIssueRequestBody { - issueUpdates: PostCreateIssueRequestBody[] -} - -export interface BulkTransitionInput { - selectedIssueIdsOrKeys: string[], - transitionId: string -} - -export interface PostBulkTransitionIssuesRequestBody { - bulkTransitionInputs: BulkTransitionInput[], - sendBulkNotification: false -} - -export interface PostBulkCreateIssueResponse { - issues: PostCreateIssueResponse[] -} - -export interface PostAddCommentResponse { - body: JiraContent -} - -export interface JiraIssueFields { - summary: string, - description: JiraContent, - project?: JiraProject, - issuetype?: { id: string }, - parent?: JiraParent, - fixVersions?: JiraVersion[], - subtasks?: JiraSubTask[], - customfield_14857?: JiraContent // Test Steps field -} - -export interface JiraSubTask { - id: string, - key: string -} - -export interface JiraProject { - key: string -} - -export interface JiraParent { - key: string -} - -export interface JiraContent { - type?: string, - text?: string, - version?: number - content?: JiraContent[] -} - -export interface JiraVersion { - id: string -} - -export interface JiraTransition { - id: string, - name: string -} - -export interface JiraIssueType { - id: string, - self: string, - description: string, - iconUrl: string, - name: string, - untranslatedName: string, - subtask: boolean, -} - -export interface GetIssueTransitionsResponse { - transitions: JiraTransition[] -} - -export interface GetIssueTypesResponse { - issueTypes: JiraIssueType[] -} - -export interface PostBulkFetchIssuesRequestBody { - issueIdsOrKeys: string[] -} - -export interface PostBulkFetchIssuesResponse { - issues: GetIssueResponse[]; -} - -export interface JiraIssue { - key?: string, - id?: string, - transition?: { id: string }, - fields: JiraIssueFields -} - -export interface GetIssueResponse extends JiraIssue {} \ No newline at end of file diff --git a/playwright-tests/impl/API/JiraApiUtil.ts b/playwright-tests/impl/API/JiraApiUtil.ts deleted file mode 100644 index fdb970d1e..000000000 --- a/playwright-tests/impl/API/JiraApiUtil.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { PostBulkFetchIssuesRequestBody, PostBulkFetchIssuesResponse, GetIssueResponse, GetIssueTransitionsResponse, GetIssueTypesResponse, PostAddCommentResponse, PostBulkCreateIssueRequestBody, PostBulkCreateIssueResponse, PostCreateIssueRequestBody, PostCreateIssueResponse, PostTransitionIssueRequestBody, PutEditIssueRequestBody, PostBulkTransitionIssuesRequestBody } from "@business-logic/types/JiraApi"; -import axios, { request } from "axios"; -import axiosRetry from "axios-retry"; - -const jiraUrl = process.env.JIRA_SERVER!; -const jiraUsername = process.env.JIRA_USERNAME!; -const jiraApiKey = process.env.JIRA_API_KEY!; -const encodedAuthKey = Buffer.from(`${jiraUsername}:${jiraApiKey}`).toString('base64'); - -export default class JiraApiUtil { - readonly baseUrl: string; - readonly issueUrl: string; - readonly bulkIssueCreateUrl: string; - readonly bulkIssueFetchUrl: string; - readonly bulkTransitionIssuesUrl: string; - readonly axiosClient: axios.AxiosInstance; - - constructor() { - this.baseUrl = jiraUrl; - this.issueUrl = `${this.baseUrl}/rest/api/3/issue`; - this.bulkIssueCreateUrl = `${this.issueUrl}/bulk`; - this.bulkIssueFetchUrl = `${this.issueUrl}/bulkfetch`; - this.bulkTransitionIssuesUrl = `${this.baseUrl}/rest/api/3/bulk/issues/transition` - this.axiosClient = axios.create(); - // interceptor to log error message from api - this.axiosClient.interceptors.response.use( - response => response, - error => { - console.error('Axios Error:', error?.response?.data || error.message); - return Promise.reject(error); - } - ); - // Set up retries if requests are made too quickly - axiosRetry(this.axiosClient, { - retries: 4, - retryDelay: (retryCount) => { return Math.pow(2, retryCount) * 1000; }, // Exponential backoff - retryCondition: (error) => { return error.response?.status === 429 } // If rate-limit error - }); - } - - getIssue(issueKey: string) { - const url = `${this.issueUrl}/${issueKey}`; - return this.axiosClient.get(url, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - Accept: 'application/json' - } - }); - } - - postBulkFetchIssues(requestBody: PostBulkFetchIssuesRequestBody) { - return this.axiosClient.post(this.bulkIssueFetchUrl, requestBody, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }); - } - - getIssueTransitions(issueKey: string) { - const url = `${this.issueUrl}/${issueKey}/transitions` - return this.axiosClient.get(url, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - Accept: 'application/json' - } - }); - } - - getIssueTypes(projectKey: string) { - const url = `${this.baseUrl}/rest/api/3/issue/createmeta/${projectKey}/issuetypes`; - return this.axiosClient.get(url, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - Accept: 'application/json' - } - }); - } - - postAddComment(issueKey: string, commentBody: PostAddCommentResponse) { - const url = `${this.issueUrl}/${issueKey}/comment`; - return this.axiosClient.post(url, commentBody, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }); - } - - postTransitionIssue(issueKey: string, requestBody: PostTransitionIssueRequestBody) { - const transitionUrl = `${this.issueUrl}/${issueKey}/transitions` - return this.axiosClient.post(transitionUrl, requestBody, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - }); - } - - putEditIssue(issueKey: string, requestBody: PutEditIssueRequestBody) { - const editIssueUrl = `${this.issueUrl}/${issueKey}` - return this.axiosClient.put(editIssueUrl, requestBody, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } - }); - } - - postBulkTransitionIssues(requestBody: PostBulkTransitionIssuesRequestBody) { - return this.axiosClient.post(this.bulkTransitionIssuesUrl, requestBody, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - }); - } - - postCreateIssue(requestBody: PostCreateIssueRequestBody) { - return this.axiosClient.post(this.issueUrl, requestBody, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - }); - } - - postBulkCreateIssue(requestBody: PostBulkCreateIssueRequestBody) { - return this.axiosClient.post(this.bulkIssueCreateUrl, requestBody, { - headers: { - Authorization: `Basic ${encodedAuthKey}`, - 'Accept': 'application/json', - 'Content-Type': 'application/json' - }, - }); - } -} \ No newline at end of file diff --git a/playwright-tests/impl/reporter/JiraWritebackReporter.ts b/playwright-tests/impl/reporter/JiraWritebackReporter.ts deleted file mode 100644 index c1db33244..000000000 --- a/playwright-tests/impl/reporter/JiraWritebackReporter.ts +++ /dev/null @@ -1,339 +0,0 @@ -import JiraApiUtil from "@impl/API/JiraApiUtil"; -import type { FullResult, Reporter, TestCase, TestResult } from "@playwright/test/reporter"; -import { formatDate } from "@impl/utils/DateUtils"; -import { GetIssueResponse, JiraIssue, JiraIssueFields } from "@business-logic/types/JiraApi"; - -const jiraCardNumber = process.env.JIRA_CARD_NUMBER!; -const jiraProjectKey = 'INSR'; -const passTransitionId = '111'; -const failTransitionId = '101'; - -export default class JiraWritebackReporter implements Reporter { - readonly jiraApiUtil: JiraApiUtil = new JiraApiUtil(); - readonly loadIssueCalls: (() => void)[] = []; // Calls to loadIssue must be deferred because they depend on information from the API. - readonly issuesToPass: string[] = []; // Issues to transition to "Pass". - readonly issuesToFail: string[] = []; // Issues to transition to "Fail". - readonly issuesToCreate: JiraIssue[] = []; // Issues to be batch created. - existingSubtasks: JiraIssue[] = []; // Array to hold existing subtasks of the dev card. - testCaseTypeId: string|undefined = undefined; // ID of the test case subtask type in Jira. Will be filled by API call. - bugTypeId:string|undefined = undefined; // ID of the bug subtask type. - parentCard: GetIssueResponse | undefined = undefined; // Variable to hold the parent card. Will be filled by API call. - - // Will need this if we want to move ortoni report upload into this reporter. - // readonly ortoniReport = new OrtoniReport(reportConfig); - - /** - * This function loads the IDs for the Jira Issue Types we use. - * - */ - async loadJiraIssueTypes() { - console.log(`JiraWritebackReporter >> Loading Jira Issue Types for project '${jiraProjectKey}'...`); - const issueTypesRes = await this.jiraApiUtil.getIssueTypes(jiraProjectKey); - const issueTypes = issueTypesRes.data; - this.testCaseTypeId = issueTypes.issueTypes.find(issueType => { - return issueType.subtask === true && issueType.name === 'Test Case Sub-task' - })?.id; - this.bugTypeId = issueTypes.issueTypes.find(issueType => { - return issueType.subtask === true && issueType.name === 'Bug Sub-task' - })?.id; - console.log('JiraWritebackReporter >> Loaded issue types.'); - } - - /** - * This function generates a test subtask based on the test results we pass in. - * @param test TestCase from onTestEnd() - * @param result TestResult from onTestEnd() - * @returns Jira Test Subtask based on test result. - */ - getCreateTestSubtask(test: TestCase, result: TestResult) { - if (result.status === 'skipped') { - return undefined; - } - const testStepTitles = result.steps.map(step => { - return `-\t${step.title}`; - }).join('\n'); - const allErrors = result.errors.map(value => { - return value.message - }).join('\n'); - - const description = `Most recent test status: ${result.status}.\nDuration: ${result.duration/1000} seconds.\nErrors:\n${allErrors}` - const testSubtaskBody: JiraIssueFields = { - summary: test.title, - description: { - content: [ - { - "content": [ - { - "text": description, - "type": "text" - } - ], - "type": "paragraph" - } - ], - "type": "doc", - "version": 1 - }, - project: { - key: jiraProjectKey - }, - issuetype: { - id: this.testCaseTypeId! - }, - parent: { - key: jiraCardNumber - }, - customfield_14857: { - type: 'doc', - version: 1, - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: testStepTitles - } - ] - } - ] - } - }; - return testSubtaskBody; - } - - /** - * This function generates a bug subtask based on the test results we pass in. - * @param test TestCase from onTestEnd() - * @param result TestResult from onTestEnd() - * @returns Jira Bug Subtask based on test result. - */ - getCreateBug(test: TestCase, result: TestResult) { - if (result.status === 'passed' || result.status === 'skipped') { - return undefined; - } - const allErrors = result.errors.map(value => { - return value.message - }).join('\n'); - - const bug: JiraIssueFields = { - summary: `TEST FAILED ${formatDate(new Date())}: ${test.title}`, - description: { - content: [ - { - "content": [ - { - "text": allErrors, - "type": "text" - } - ], - "type": "paragraph" - } - ], - "type": "doc", - "version": 1 - }, - project: { - key: jiraProjectKey - }, - issuetype: { - id: this.bugTypeId! - }, - parent: { - key: jiraCardNumber - } - }; - return bug; - } - - /** - * This function gives us the correct transition for a test subtask based on this test result. - * @param result TestResult from onTestEnd() - * @returns Correct transition to pass to the Jira API. - */ - getSubtaskTransition(result: TestResult): { id: string } | undefined { - if (result.status === 'skipped') { - return undefined; - } - - if (result.status === 'passed') { - return { - id: passTransitionId - }; - } else { - return { - id: failTransitionId - }; - } - } - - /** - * Grabs parent card and existing subtasks. - */ - async loadReporterData() { - await this.loadJiraIssueTypes(); - - console.log('JiraWritebackReporter >> Searching for parent card...'); - const parentRes = await this.jiraApiUtil.getIssue(jiraCardNumber); - const parentCard = parentRes.data; - console.log(`JiraWritebackReporter >> Retrieved parent card: ${parentCard.key}: ${parentCard.fields.summary}`); - - console.log(`JiraWritebackReporter >> Searching for all subtasks of parent card '${jiraCardNumber}'...`); - const subtaskIds = parentCard.fields.subtasks?.map(subtask => { - return subtask.id; - }); - - if (subtaskIds && subtaskIds.length > 0) { - for (let i = 0; i < subtaskIds.length; i+=50) { - const batch = subtaskIds.slice(i, i+50); - const subTasksRes = await this.jiraApiUtil.postBulkFetchIssues({ issueIdsOrKeys: batch }); - this.existingSubtasks.push(...subTasksRes.data.issues); - } - console.log('JiraWritebackReporter >> Subtasks retrieved.'); - } else { - console.log('JiraWritebackReporter >> No subtasks were found.'); - } - - } - - /** - * Bulk creates test cases and bugs in batches of up to 50. Bulk transitions existing test case subtasks. - */ - async writeResults() { - for (let i = 0; i < this.issuesToCreate.length; i+=50) { - const batch = this.issuesToCreate.slice(i, i + 50); - console.log('JiraWritebackReporter >> Creating issue batch...'); - // fire request - await this.jiraApiUtil.postBulkCreateIssue({ issueUpdates: batch }); - console.log('JiraWritebackReporter >> Issue batch created.'); - } - - if (this.issuesToPass.length > 0) { - console.log('JiraWritebackReporter >> Transitioning passed test cases to "Pass"...') - await this.jiraApiUtil.postBulkTransitionIssues( { - bulkTransitionInputs: [{ - selectedIssueIdsOrKeys: this.issuesToPass, - transitionId: passTransitionId - }], - sendBulkNotification: false - }); - console.log('JiraWritebackReporter >> Transition success.'); - } - - if (this.issuesToFail.length > 0) { - console.log('JiraWritebackReporter >> Transitioning failed test cases to "Fail"...') - await this.jiraApiUtil.postBulkTransitionIssues( { - bulkTransitionInputs: [{ - selectedIssueIdsOrKeys: this.issuesToFail, - transitionId: failTransitionId - }], - sendBulkNotification: false - }); - console.log('JiraWritebackReporter >> Transition success.') - } - } - - /** - * Builds and loads appropriate response based on test result. This can be a test subtask/bug subtask or a call to transition a test subtask. - * @param test TestCase from onTestEnd() - * @param result TestResult from onTestEnd() - * @returns Void - */ - loadIssue(test: TestCase, result: TestResult) { - if (result.status === 'skipped') return; - if (result.status !== 'passed' && result.retry < test.retries) return; // Skip if test case failed and this isn't the last retry. - - let existingTestSubtask: JiraIssue | undefined = undefined; - let existingBug: JiraIssue | undefined = undefined; - const subtaskTransition = this.getSubtaskTransition(result)!; // Get either a Pass or Fail transition depending on test results. - - console.log('JiraWritebackReporter >> Checking for existing subtasks for this test...'); - for (const card of this.existingSubtasks) { - if (card.fields.issuetype?.id === this.testCaseTypeId) { - if (card.fields.summary === test.title) { - existingTestSubtask = card; - console.log('JiraWritebackReporter >> Found existing test subtask.'); - } - } else if (card.fields.issuetype?.id === this.bugTypeId) { - if (new RegExp(`^TEST FAILED [0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]: ${test.title}$`).test(card.fields.summary)) { - existingBug = card; - console.log('JiraWritebackReporter >> Found existing bug.'); - // TODO: Handle multiple matches for bug/test case. Currently picks one in that case. - } - } - } - if (!(existingTestSubtask || existingBug)) { - console.log('JiraWritebackReporter >> No existing test subtask or bug was found.') - } - - const subTask = this.getCreateTestSubtask(test, result); // Will return undefined if test status is 'skipped' - const bug = this.getCreateBug(test, result); // Will return undefined if we don't need one - if (subTask) { - // Create or Edit Subtask - if (existingTestSubtask) { - if (result.status === 'passed') { - this.issuesToPass.push(existingTestSubtask.key!); - } else { - this.issuesToFail.push(existingTestSubtask.key!); - } - } else { - this.issuesToCreate.push({ - fields: subTask, - transition: subtaskTransition - }); - } - } - - if (bug) { - // Create or Edit Bug - if (existingBug) { - // Leave it alone. This reporter should not modify existing bugs. - } else { - this.issuesToCreate.push({ - fields: bug - }); - } - } - } - - /** - * This function loads calls to loadIssue() in an array to be executed when the necessary information from the Jira API is available. - * @param test - * @param result - */ - onTestEnd(test: TestCase, result: TestResult) { - // this.ortoniReport.onTestEnd(test, result); - this.loadIssueCalls.push(() => { this.loadIssue(test, result); }); - } - - /** - * Makes calls to load and execute API calls to Jira. - * @param result - * @returns Promise to write results to Jira. - */ - onEnd(result: FullResult): Promise<{ status?: FullResult["status"]; } | undefined | void>|void { - // this.ortoniReport.onEnd(result); - return this.loadReporterData().then(async () => { - this.loadIssueCalls.map(fn => fn()); - await this.writeResults(); - console.log(`JiraWritebackReporter >> Posted necessary changes for '${jiraCardNumber}'`); - }); - } - - // These overloads will become necessary if we move the ortoni report upload into this reporter - // onBegin(config: FullConfig, suite: Suite): void { - // this.ortoniReport.onBegin(config, suite); - // } - - // onError(error: TestError): void { - // this.ortoniReport.onError(error); - // } - - // onExit(): Promise { - // return this.ortoniReport.onExit(); - // } - - // onStdOut(chunk: string | Buffer, test: void | TestCase, result: void | TestResult): void { - // this.ortoniReport.onStdOut(chunk, test, result); - // } -} \ No newline at end of file From 27b6e968e2435adab9638be3f9e4f09ccd7c4000 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Fri, 11 Apr 2025 10:13:03 -0400 Subject: [PATCH 15/33] adding quotes when defining base url because of -e problem --- playwright-tests/azure-pipelines-automated-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/azure-pipelines-automated-testing.yml b/playwright-tests/azure-pipelines-automated-testing.yml index 5634b4bd6..7ae582f8e 100644 --- a/playwright-tests/azure-pipelines-automated-testing.yml +++ b/playwright-tests/azure-pipelines-automated-testing.yml @@ -42,7 +42,7 @@ stages: container_id=$(docker create \ --ipc=host \ -e CCIS_API_AUTH=$(CCIS_API_AUTH) \ - -e BASE_URL=$(BASE_URL) \ + -e BASE_URL="$(BASE_URL)" \ -e CCIS_API_URL=$(CCIS_API_URL) \ -e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \ -e SHARD=$(shardNumber) \ From 60f053f8519e198b864393b0d47afffed25dab5e Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Wed, 16 Apr 2025 10:41:55 -0400 Subject: [PATCH 16/33] CASH-203: UI Updates for Afterpay breakout with service package discounts --- .../service-package-radio-for-afterpay.vue | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/experiment-components/service-package-radio-for-afterpay.vue b/src/experiment-components/service-package-radio-for-afterpay.vue index b936e21bf..6fc51b5b4 100644 --- a/src/experiment-components/service-package-radio-for-afterpay.vue +++ b/src/experiment-components/service-package-radio-for-afterpay.vue @@ -5,6 +5,7 @@ :class="[ this.buttonLabelSubCopy ? 'has-subheader' : '', this.additionalButtonData.isInsuranceSelected ? 'is-insurance' : '', + this.hasPackageDiscount() ? 'has-package-discount' : '', ]" for="testradio">
@@ -55,10 +56,7 @@
@@ -144,6 +142,10 @@ export default { truncatedSinglePayment() { return "$" + Math.trunc(this.buttonAuxillaryCopy.replace("$", "")); }, + hasPackageDiscount() { + return this.additionalButtonData.servicePackageDiscount && + this.additionalButtonData.Text; + }, }, }; @@ -212,11 +214,6 @@ export default { } &.has-subheader { - min-height: 150px; - - &.is-insurance { - min-height: 72px; - } & > .package-specs { & > div:first-of-type { @@ -225,6 +222,10 @@ export default { } } + &.has-package-discount { + min-height: 150px; + } + &:before { content: ""; position: relative; From 58c0b91b2b5c2d461cefba687ab5713853027ff1 Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Wed, 16 Apr 2025 10:51:59 -0400 Subject: [PATCH 17/33] CASH-203: Prettier --- .../service-package-radio-for-afterpay.vue | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/experiment-components/service-package-radio-for-afterpay.vue b/src/experiment-components/service-package-radio-for-afterpay.vue index 6fc51b5b4..f38621462 100644 --- a/src/experiment-components/service-package-radio-for-afterpay.vue +++ b/src/experiment-components/service-package-radio-for-afterpay.vue @@ -55,9 +55,7 @@ v-html="this.buttonFooterCopy">
-
+
@@ -143,8 +141,9 @@ export default { return "$" + Math.trunc(this.buttonAuxillaryCopy.replace("$", "")); }, hasPackageDiscount() { - return this.additionalButtonData.servicePackageDiscount && - this.additionalButtonData.Text; + return ( + this.additionalButtonData.servicePackageDiscount && this.additionalButtonData.Text + ); }, }, }; @@ -214,7 +213,6 @@ export default { } &.has-subheader { - & > .package-specs { & > div:first-of-type { display: flex; From a08bc842a755ef29d8323a0b13e2e18755792368 Mon Sep 17 00:00:00 2001 From: hiteshkumar87 Date: Wed, 16 Apr 2025 21:05:32 +0530 Subject: [PATCH 18/33] Revert "CASH-433" This reverts commit 1649ea14077449503533cc4f99fa29c16402418a. --- .../appointment-type-question.vue | 19 +++---------------- .../service-location/service-location.vue | 13 ------------- 2 files changed, 3 insertions(+), 29 deletions(-) diff --git a/src/layouts/service-location/appointment-type-question/appointment-type-question.vue b/src/layouts/service-location/appointment-type-question/appointment-type-question.vue index 4c1c0d031..64bf159ff 100644 --- a/src/layouts/service-location/appointment-type-question/appointment-type-question.vue +++ b/src/layouts/service-location/appointment-type-question/appointment-type-question.vue @@ -32,7 +32,6 @@ export default { cmsWidgetName: String, isServiceableMobile: Boolean, isServiceableInshop: Boolean, - isServiceableDropoff: Boolean, mobileFeeApplies: Boolean, }, computed: { @@ -46,7 +45,7 @@ export default { const shouldShowMobile = this.isServiceableMobile; const shouldShowInshop = this.isServiceableInshop; const shouldShowDropoff = - this.isServiceableDropoff && !this.$store.getters.damage.isRepair; + this.isServiceableInshop && !this.$store.getters.damage.isRepair; var answers = this.answersFromCms ? this.answersFromCms.filter((answer) => { @@ -78,11 +77,6 @@ export default { isMobileOnly() { return this.isServiceableMobile && !this.isServiceableInshop; }, - isInshopOnly() { - return ( - this.isServiceableInshop && !this.isServiceableMobile && !this.isServiceableDropoff - ); - }, }, watch: { answersToDisplay: { @@ -92,7 +86,7 @@ export default { newValue.length == 1 && newValue.findIndex((answer) => answer.Name == "Mobile") != -1 ) { - this.selectedValue = "Mobile"; + this.selectedValues = "Mobile"; } }, immediate: true, @@ -100,14 +94,7 @@ export default { isMobileOnly: { handler(newValue) { if (newValue) { - this.selectedValue = "Mobile"; - } - }, - }, - isInshopOnly: { - handler(newValue) { - if (newValue) { - this.selectedValue = "Inshop"; + this.selectedValues = "Mobile"; } }, }, diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 7b3163c87..a85a5bd17 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -76,7 +76,6 @@ v-show="isAppointmentTypeDisplayed" :isServiceableMobile="isServiceableMobile" :isServiceableInshop="isServiceableInshop" - :isServiceableDropoff="isServiceableDropoff" :isDisplayed="isAppointmentTypeDisplayed" :mobileFeeApplies="mobileFeeApplies" ref="appointmentTypeQuestion" @@ -197,8 +196,6 @@ export default { isVehicleProtected: this.getIsVehicleProtectedFromStore(), isGlassServiceableInshop: null, isRecalibrationServiceableInshop: null, - isGlassServiceableDropoff: null, - isRecalibrationServiceableDropoff: null, isGlassServiceableMobile: null, isRecalibrationServiceableMobile: null, selectedAppointmentType: this.getSelectedAppointmentType(), @@ -357,13 +354,6 @@ export default { return this.isGlassServiceableInshop; } }, - isServiceableDropoff() { - if (this.isRecalibrationServiceableDropoff !== null) { - return this.isGlassServiceableDropoff && this.isRecalibrationServiceableDropoff; - } else { - return this.isGlassServiceableDropoff; - } - }, isShopQuestionDisplayed() { return ( this.selectedAppointmentType === "Inshop" || @@ -569,9 +559,6 @@ export default { this.isGlassServiceableInshop = serviceabilityDetails.isGlassServiceableInshop; this.isRecalibrationServiceableInshop = serviceabilityDetails.isRecalibrationServiceableInshop; - this.isGlassServiceableDropoff = serviceabilityDetails.isGlassServiceableDropoff; - this.isRecalibrationServiceableDropoff = - serviceabilityDetails.isRecalibrationServiceableDropoff; this.isGlassServiceableMobile = serviceabilityDetails.isGlassServiceableMobile; this.isRecalibrationServiceableMobile = serviceabilityDetails.isRecalibrationServiceableMobile; From 089632b7f1de38f334bf7807b957cf96756c4f2c Mon Sep 17 00:00:00 2001 From: Minojhini Valaiyapathi Date: Wed, 16 Apr 2025 12:04:47 -0400 Subject: [PATCH 19/33] CASH-353- Reverting back the 353 changes --- .../address-lookup/address-lookup.spec.js | 45 --------------- src/layouts/address-lookup/address-lookup.vue | 38 +------------ .../license-plate-lookup.spec.js | 49 ---------------- .../license-plate-lookup.vue | 37 ------------ src/layouts/service-zip/service-zip.spec.js | 38 ------------- src/layouts/service-zip/service-zip.vue | 37 +----------- src/layouts/vin-lookup/vin-lookup.spec.js | 33 ----------- src/layouts/vin-lookup/vin-lookup.vue | 56 +------------------ 8 files changed, 5 insertions(+), 328 deletions(-) diff --git a/src/layouts/address-lookup/address-lookup.spec.js b/src/layouts/address-lookup/address-lookup.spec.js index 19a46abb8..677c14069 100644 --- a/src/layouts/address-lookup/address-lookup.spec.js +++ b/src/layouts/address-lookup/address-lookup.spec.js @@ -182,39 +182,6 @@ describe("address-lookup.vue", () => { // Assert expect(wrapper.findComponent({ ref: "alertVinNotFound" }).isVisible()).toBe(true); }); - test("should show the AlertNoService when displayNoServiceAlert is true", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: true, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - - // Set displayNoServiceAlert to true - await wrapper.setData({ displayNoServiceAlert: true }); - - // Check if the AlertNoService component is rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(true); - }); - test("should hide the AlertNoService when displayNoServiceAlert is false", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: false, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - // Ensure displayNoServiceAlert is false - await wrapper.setData({ displayNoServiceAlert: false }); - - // Check if the AlertNoService component is not rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(false); - }); }); describe("navigation", () => { @@ -774,18 +741,6 @@ function setupMocks({ wrapper.vm.setCmsContent = jest.fn(); wrapper.vm.$refs.navbar.updateButtonText = jest.fn(); wrapper.vm.$refs.navbar.removeLoader = jest.fn(); - const closestShops = { - data: { - providers: [ - { id: 1, name: "Shop 1" }, - { id: 2, name: "Shop 2" }, - ], - }, - }; - - wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => { - return new Promise((resolve) => resolve(closestShops)); - }); return { wrapper }; } diff --git a/src/layouts/address-lookup/address-lookup.vue b/src/layouts/address-lookup/address-lookup.vue index c33ef68e5..8c4e21e9a 100644 --- a/src/layouts/address-lookup/address-lookup.vue +++ b/src/layouts/address-lookup/address-lookup.vue @@ -81,20 +81,13 @@
- + :isForwardActionDisabled="!meta.valid" />
@@ -171,8 +164,6 @@ export default { emailOrSms: this.getEmailOrSmsFromStore(), }, serviceZipCode: this.getServiceZipFromStore(), - carId: this.getCarIdfromStore(), - isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(), displayNonServiceableZipAlert: false, displayVinNotFoundAlert: false, displayMatchedDifferentVehicleAlert: false, @@ -184,7 +175,6 @@ export default { displayInvalidZipAlert: false, showServiceZipField: this.getServiceZipFromStore(), isZipServiceable: false, - displayNoServiceAlert: false, }; }, methods: { @@ -229,19 +219,6 @@ export default { getServiceZipFromStore() { return this.$store.getters.order.serviceLocation.zipCode; }, - getCarIdfromStore() { - return store.getters.vehicle.carId; - }, - getIsVehicleHeavyTruckFromStore() { - return store.getters.isHeavyTruckVehicle; - }, - async findClosestApplicableShops() { - return await this.dispatchStoreActionWithLogging( - storeActions.GET_CLOSEST_APPLICABLE_SHOPS, - { zip: this.serviceZipCode, carId: this.carId }, - "address-lookup" - ); - }, async forwardButtonAction() { this.resetWarningsAndErrors(); @@ -301,17 +278,6 @@ export default { return this.$refs.navbar.removeLoader(); } this.displayInvalidZipAlert = false; - if (this.serviceZipCode != null && this.isHeavyTruck) { - const closestShops = await this.findClosestApplicableShops( - this.serviceZipCode, - this.carId - ); - this.displayNoServiceAlert = !closestShops?.data?.providers?.length; - if (this.displayNoServiceAlert) { - return this.$refs.navbar.removeLoader(); - } - } - const carsFound = resultMap.vinLookupResponse.vinVehicles; // Handle cases for different amounts of VINS found for the address. @@ -463,7 +429,6 @@ export default { this.displayNonServiceableZipAlert = false; this.displayMatchedDifferentVehicleAlert = false; this.displayVinLookupByHomeAddressNotAllowedAlert = false; - this.displayNoServiceAlert = false; }, }, mounted() { @@ -515,7 +480,6 @@ export default { handler(newValue) { // If they modify the service zip code, then hide the error message. this.displayNonServiceableZipAlert = false; - this.displayNoServiceAlert = false; }, }, showServiceZipField: { diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.spec.js b/src/layouts/license-plate-lookup/license-plate-lookup.spec.js index 2ffe61193..14f0408d1 100644 --- a/src/layouts/license-plate-lookup/license-plate-lookup.spec.js +++ b/src/layouts/license-plate-lookup/license-plate-lookup.spec.js @@ -611,42 +611,6 @@ describe("license-plate-lookup.vue", () => { expect(arePagePrerequisitesValid).toBe(false); }); }); - - describe("alerts", () => { - test("should show the AlertNoService when displayNoServiceAlert is true", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: true, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - - // Set displayNoServiceAlert to true - await wrapper.setData({ displayNoServiceAlert: true }); - - // Check if the AlertNoService component is rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(true); - }); - test("should hide the AlertNoService when displayNoServiceAlert is false", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: false, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - // Ensure displayNoServiceAlert is false - await wrapper.setData({ displayNoServiceAlert: false }); - - // Check if the AlertNoService component is not rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(false); - }); - }); }); function setupMocks({ @@ -752,18 +716,5 @@ function setupMocks({ wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => ""); wrapper.vm.$refs.navbar.updateButtonText = jest.fn(); wrapper.vm.$refs.navbar.removeLoader = jest.fn(); - const closestShops = { - data: { - providers: [ - { id: 1, name: "Shop 1" }, - { id: 2, name: "Shop 2" }, - ], - }, - }; - - wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => { - return new Promise((resolve) => resolve(closestShops)); - }); - return { wrapper, apiPromise }; } diff --git a/src/layouts/license-plate-lookup/license-plate-lookup.vue b/src/layouts/license-plate-lookup/license-plate-lookup.vue index a359d3a9d..c581af4b7 100644 --- a/src/layouts/license-plate-lookup/license-plate-lookup.vue +++ b/src/layouts/license-plate-lookup/license-plate-lookup.vue @@ -77,14 +77,6 @@ alertClass="alert-warning" v-bind:isDismissible="false" /> - - { expect(wrapper.vm.displayInvalidZipAlert).toBe(false); expect(wrapper.vm.displayNonServiceableZipAlert).toBe(false); }); - test("should show the AlertNoService when displayNoServiceAlert is true", async () => { - // Arrange - // no changes to store - applyMockStoreDataToGetters(); - - const wrapper = setupMocks({}); - wrapper.vm.serviceZipCode = "12345"; - - // Act - wrapper.vm.displayNoServiceAlert = true; - - // Check if the AlertNoService component is rendered - expect(wrapper.vm.displayNoServiceAlert).toBe(true); - }); - test("should hide the AlertNoService when displayNoServiceAlert is false", async () => { - // Arrange - // no changes to store - applyMockStoreDataToGetters(); - - const wrapper = setupMocks({}); - wrapper.vm.serviceZipCode = "12345"; - //ACT - wrapper.vm.displayNoServiceAlert = false; - - // Check if the AlertNoService component is rendered - expect(wrapper.vm.displayNoServiceAlert).toBe(false); - }); }); }); @@ -538,16 +511,5 @@ function setupMocks({ customMountOptions, customZipQuery, customZipDataResponse ]; const wrapper = shallowMount(serviceZip, mountOptions); - wrapper.vm.findClosestApplicableShops = jest.fn().mockImplementation(() => { - return Promise.resolve({ - data: { - providers: [ - { id: 1, name: "Shop 1" }, - { id: 2, name: "Shop 2" }, - ], - }, - }); - }); - return wrapper; } diff --git a/src/layouts/service-zip/service-zip.vue b/src/layouts/service-zip/service-zip.vue index 7d53d105d..9528d50bc 100644 --- a/src/layouts/service-zip/service-zip.vue +++ b/src/layouts/service-zip/service-zip.vue @@ -47,18 +47,10 @@ v-if="displayNonServiceableZipAlert" alertClass="alert-danger" /> - - @@ -118,11 +110,8 @@ export default { return { serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode, emailOrSms: this.getEmailOrSmsFromStore(), - carId: this.getCarIdfromStore(), - isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(), displayInvalidZipAlert: false, displayNonServiceableZipAlert: false, - displayNoServiceAlert: false, }; }, @@ -195,19 +184,6 @@ export default { arePagePrerequisitesValid() { return store.getters.damage.isRepair || store.getters.damage.glassToReplace?.length > 0; }, - getCarIdfromStore() { - return store.getters.vehicle.carId; - }, - getIsVehicleHeavyTruckFromStore() { - return store.getters.isHeavyTruckVehicle; - }, - async findClosestApplicableShops() { - return await this.dispatchStoreActionWithLogging( - storeActions.GET_CLOSEST_APPLICABLE_SHOPS, - { zip: this.serviceZipCode, carId: this.carId }, - "service-zip" - ); - }, async backButtonAction() { const skipVin = await skipVinLookup(); // route to move backwards @@ -276,16 +252,6 @@ export default { return this.$refs.navbar.removeLoader(); } this.displayNonServiceableZipAlert = false; - if (this.isHeavyTruck) { - const closestShops = await this.findClosestApplicableShops( - this.serviceZipCode, - this.carId - ); - this.displayNoServiceAlert = !closestShops?.data?.providers?.length; - if (this.displayNoServiceAlert) { - return this.$refs.navbar.removeLoader(); - } - } const payment = this.$store.getters.payment; const policy = this.$store.getters.policy; @@ -343,7 +309,6 @@ export default { watch: { serviceZipCode() { this.displayNonServiceableZipAlert = false; - this.displayNoServiceAlert = false; }, }, components: { diff --git a/src/layouts/vin-lookup/vin-lookup.spec.js b/src/layouts/vin-lookup/vin-lookup.spec.js index 458f9fc48..6eb1aa515 100644 --- a/src/layouts/vin-lookup/vin-lookup.spec.js +++ b/src/layouts/vin-lookup/vin-lookup.spec.js @@ -258,39 +258,6 @@ describe("vin-lookup.vue", () => { // Assert expect(wrapper.findAllComponents({ name: "alert" }).length).toBe(1); }); - - test("should show the AlertNoService when displayNoServiceAlert is true", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: true, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - // Set displayNoServiceAlert to true - await wrapper.setData({ displayNoServiceAlert: true }); - - // Check if the AlertNoService component is rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(true); - }); - test("should hide the AlertNoService when displayNoServiceAlert is false", async () => { - const { wrapper } = setupMocks({ - isZipServiceable: true, - displayNoServiceAlert: false, - lookupVinbyAddressResponse: { - isStatePermissible: true, - vinVehicles: [], // Return no vehicles - }, - }); - // Ensure displayNoServiceAlert is false - await wrapper.setData({ displayNoServiceAlert: false }); - - // Check if the AlertNoService component is not rendered - const alertNoService = wrapper.findComponent({ ref: "AlertNoService" }); - expect(alertNoService.exists()).toBe(false); - }); }); describe("getVinFromImage", () => { diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 788425861..4c6897717 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -71,8 +71,7 @@ vinPopulatedOnPageLoad && isInsuranceVerified && !displayInvalidZipAlert && - !displayNonServiceableZipAlert && - !displayNoServiceAlert + !displayNonServiceableZipAlert " alertClass="alert-success" /> @@ -108,23 +107,14 @@ vinPopulatedOnPageLoad && !isInsuranceVerified && !displayInvalidZipAlert && - !displayNonServiceableZipAlert && - !displayNoServiceAlert + !displayNonServiceableZipAlert " alertClass="alert-success" /> - - @@ -207,8 +197,6 @@ export default { vin: this.getVinFromStore(), serviceZipCode: this.getZipFromStore() ?? this.$route.query.zipcode, emailOrSms: this.getEmailOrSmsFromStore(), - carId: this.getCarIdfromStore(), - isHeavyTruck: this.getIsVehicleHeavyTruckFromStore(), isCarIdDifferent: false, customAlertData: {}, previouslyEnteredCarId: "", @@ -219,7 +207,6 @@ export default { displayVinNotFoundAlert: false, displayMatchedDifferentVehicleAlert: false, displayVinScanFailedAlert: false, - displayNoServiceAlert: false, }; }, methods: { @@ -235,12 +222,6 @@ export default { getZipFromStore() { return this.$store.getters.order.serviceLocation.zipCode; }, - getCarIdfromStore() { - return store.getters.vehicle.carId; - }, - getIsVehicleHeavyTruckFromStore() { - return store.getters.isHeavyTruckVehicle; - }, attachCustomEvents() { this.prependActionToMethod(this, this.forwardButtonAction, () => { this.pushEventToGA( @@ -264,13 +245,6 @@ export default { ); } }, - async findClosestApplicableShops() { - return await this.dispatchStoreActionWithLogging( - storeActions.GET_CLOSEST_APPLICABLE_SHOPS, - { zip: this.serviceZipCode, carId: this.carId }, - "vin-lookup" - ); - }, async forwardButtonAction() { this.resetAlerts(); @@ -303,18 +277,6 @@ export default { return this.$refs.navbar.removeLoader(); } this.displayInvalidZipAlert = false; - if (this.isHeavyTruck) { - // If a VIN has already been found. Validate the Service Zip (in case of changes) - var closestShops = await this.findClosestApplicableShops( - this.serviceZipCode, - this.carId - ); - this.displayNoServiceAlert = !closestShops?.data?.providers?.length; - if (this.displayNoServiceAlert) { - return this.$refs.navbar.removeLoader(); - } - } - // If either lookup fails, remove the loader and stop processing the page. if (!resultMap.vehicleLookupResponse || !resultMap.zipCodeData.isServiceable) { // If the vehicle result is undefined, the vin entered was invalid. @@ -431,16 +393,6 @@ export default { ); } - closestShops = await this.findClosestApplicableShops( - this.serviceZipCode, - this.carId - ); - this.displayNoServiceAlert = !closestShops?.data?.providers?.length; - if (this.displayNoServiceAlert) { - return this.$refs.navbar.removeLoader(); - } - - // if no value due to field being optional, blank both phone and email address if (!this.emailOrSms) { await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false); await this.dispatchStoreAction(storeActions.SAVE_EMAIL, "", false); @@ -522,7 +474,6 @@ export default { this.displayInvalidZipAlert = false; this.displayVinNotFoundAlert = false; this.displayVinScanFailedAlert = false; - this.displayNoServiceAlert = false; }, }, mounted() { @@ -599,7 +550,6 @@ export default { }, serviceZipCode() { this.displayNonServiceableZipAlert = false; - this.displayNoServiceAlert = false; }, }, components: { From e4b74f846ea30790d89b93487caf209bf2e56871 Mon Sep 17 00:00:00 2001 From: hiteshkumar87 Date: Wed, 16 Apr 2025 22:11:30 +0530 Subject: [PATCH 20/33] revert cash-433 unit test case --- .../appointment-type-question.spec.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js b/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js index 8ff7c9b1c..c6af1377f 100644 --- a/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js +++ b/src/layouts/service-location/appointment-type-question/appointment-type-question.spec.js @@ -100,7 +100,7 @@ describe("appointment-type-question.vue", () => { ]); }); - it("Should display only the In-Shop answer when only in-shop service is available", async () => { + it("Should display only the In-Shop and Drop-Off answers when only in-shop service is available", async () => { // Arrange/Act const { wrapper } = setupMocks({ mixins: [mockMixin], @@ -108,7 +108,6 @@ describe("appointment-type-question.vue", () => { cmsWidgetName: cmsWidgetName, isServiceableInshop: true, isServiceableMobile: false, - isServiceableDropoff: false, }, mountOptions: { attachTo: document.body, @@ -124,6 +123,13 @@ describe("appointment-type-question.vue", () => { SubWidgetName: "", Text: "In-shop", }, + { + AnswerImageUrl: "", + Name: "Dropoff", + SubText: "", + SubWidgetName: "", + Text: "Drop-off", + }, ]); }); From 05e625cc01501a50dd965562bdf64f388e3739e9 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Mon, 21 Apr 2025 08:57:32 -0400 Subject: [PATCH 21/33] Drop jest % --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 905e907dc..e534b3e3f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,7 +35,7 @@ module.exports = { testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], coverageThreshold: { global: { - statements: 74, + statements: 72, }, }, // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit From c26b214084a8e7234c1de9970b6d38e29512cc04 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Mon, 21 Apr 2025 11:51:23 -0400 Subject: [PATCH 22/33] added skip_Content_site variable --- playwright-tests/azure-pipelines-automated-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright-tests/azure-pipelines-automated-testing.yml b/playwright-tests/azure-pipelines-automated-testing.yml index 7ae582f8e..ac332ef44 100644 --- a/playwright-tests/azure-pipelines-automated-testing.yml +++ b/playwright-tests/azure-pipelines-automated-testing.yml @@ -45,7 +45,7 @@ stages: -e BASE_URL="$(BASE_URL)" \ -e CCIS_API_URL=$(CCIS_API_URL) \ -e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \ - -e SHARD=$(shardNumber) \ + -e SKIP_CONTENT_SITE=$(SKIP_CONTENT_SITE) \ -e CI=true \ -e NODE_ENV=$(NODE_ENV) \ $(dockerImageName):$(imageTag) \ From 0965ed1207e86d750472db32f94f33b7b8264273 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Wed, 23 Apr 2025 10:30:10 -0400 Subject: [PATCH 23/33] CASH-188 | Update unit tests --- .../salesforce-helper.spec.js | 38 ++++++++++++++++ .../salesforce-webchat.spec.js | 45 +++++++++++++++++++ .../salesforce-webchat/salesforce-webchat.vue | 10 +++-- 3 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper.spec.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-webchat.spec.js diff --git a/src/digital-components/salesforce-webchat/salesforce-helper.spec.js b/src/digital-components/salesforce-webchat/salesforce-helper.spec.js new file mode 100644 index 000000000..ae7712c49 --- /dev/null +++ b/src/digital-components/salesforce-webchat/salesforce-helper.spec.js @@ -0,0 +1,38 @@ +import * as salesforceWebchatHelperdev from './salesforce-helper-dev'; +import * as salesforceWebchatHelperqa from './salesforce-helper-qa'; +import * as salesforceWebchatHelperprod from './salesforce-helper-prod'; + +const mockEmbeddedSvc = { + settings: {}, + init: jest.fn(), + testFlag: true, +} + +describe("Salesforce Webchat Helper Tests", () => { + describe("Files and exposed methods exist", () => { + it("exposes initialization methods", () => { + expect(typeof salesforceWebchatHelperdev.initializeSalesforceWebchatForDev).toBe("function"); + expect(typeof salesforceWebchatHelperqa.initializeSalesforceWebchatForQa).toBe("function"); + expect(typeof salesforceWebchatHelperprod.initializeSalesforceWebchatForProd).toBe("function"); + + }) + }), + describe("Custom code added correctly", ()=> { + it("defines a local variable 'embedded_svc' in the initESW method", ()=> { + mockEmbeddedSvc.testFlag = "dev"; + window.embedded_svc = mockEmbeddedSvc; + salesforceWebchatHelperdev.initializeSalesforceWebchatForDev(); + expect(embedded_svc.testFlag).toBe("dev"); + + mockEmbeddedSvc.testFlag = "qa"; + window.embedded_svc = mockEmbeddedSvc; + salesforceWebchatHelperqa.initializeSalesforceWebchatForQa(); + expect(embedded_svc.testFlag).toBe("qa"); + + mockEmbeddedSvc.testFlag = "prod"; + window.embedded_svc = mockEmbeddedSvc; + salesforceWebchatHelperprod.initializeSalesforceWebchatForProd(); + expect(embedded_svc.testFlag).toBe("prod"); + }) + }) +}) \ No newline at end of file diff --git a/src/digital-components/salesforce-webchat/salesforce-webchat.spec.js b/src/digital-components/salesforce-webchat/salesforce-webchat.spec.js new file mode 100644 index 000000000..0b368b6f0 --- /dev/null +++ b/src/digital-components/salesforce-webchat/salesforce-webchat.spec.js @@ -0,0 +1,45 @@ +import { shallowMount } from "@vue/test-utils"; +import salesforceWebchat from "./salesforce-webchat.vue"; +import * as devHelper from "./salesforce-helper-dev"; +import * as qaHelper from "./salesforce-helper-qa"; +import * as prodHelper from "./salesforce-helper-prod"; + +describe("salesforceWebchat.vue", () => { + it("renders correctly", () => { + const wrapper = shallowMount(salesforceWebchat); + expect(wrapper.exists()).toBe(true); + }); + + it("has the correct default data", () => { + const wrapper = shallowMount(salesforceWebchat); + expect(wrapper.vm.$data).toEqual({}); + }); + + it("has the correct default props", () => { + const wrapper = shallowMount(salesforceWebchat); + expect(wrapper.props().hideSalesforceWebchatLaunchButton).toBe(false); + }); + + it("appends script to document body on mount", () => { + }); + + it("calls the correct initialization function based on environment", () => { + process.env.VUE_APP_CURRENT_ENVIRONMENT = "Prod"; + let wrapper = shallowMount(salesforceWebchat); + mockProd = jest.spyOn(prodHelper, "initializeSalesforceWebchatForProd"); + wrapper.vm.initializeSalesforceWebchat(); + expect(mockProd).toHaveBeenCalled(); + + process.env.VUE_APP_CURRENT_ENVIRONMENT = "QA"; + wrapper = shallowMount(salesforceWebchat); + mockQa = jest.spyOn(qaHelper, "initializeSalesforceWebchatForQa"); + wrapper.vm.initializeSalesforceWebchat(); + expect(mockQa).toHaveBeenCalled(); + + process.env.VUE_APP_CURRENT_ENVIRONMENT = "Dev"; + wrapper = shallowMount(salesforceWebchat); + mockDev = jest.spyOn(devHelper, "initializeSalesforceWebchatForDev"); + wrapper.vm.initializeSalesforceWebchat(); + expect(mockDev).toHaveBeenCalled(); + }); +}); diff --git a/src/digital-components/salesforce-webchat/salesforce-webchat.vue b/src/digital-components/salesforce-webchat/salesforce-webchat.vue index 58da503fa..75f689d10 100644 --- a/src/digital-components/salesforce-webchat/salesforce-webchat.vue +++ b/src/digital-components/salesforce-webchat/salesforce-webchat.vue @@ -23,7 +23,11 @@ export default { // Load in script from salesforce CDN const script = document.createElement("script"); script.src = "https://service.force.com/embeddedservice/5.0/esw.min.js"; - script.onload = () => { + script.onload = this.initializeSalesforceWebchat; + document.body.appendChild(script); + }, + methods: { + initializeSalesforceWebchat() { switch (process.env.VUE_APP_CURRENT_ENVIRONMENT) { case "Prod": initializeSalesforceWebchatForProd(); @@ -37,10 +41,8 @@ export default { initializeSalesforceWebchatForDev(); break; } - }; - document.body.appendChild(script); + }, }, - methods: {}, computed: {}, components: {}, }; From 4a178f50092c5ab5cf8d657ce19148f4a2ed0ab7 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Wed, 23 Apr 2025 10:31:06 -0400 Subject: [PATCH 24/33] CASH-188 | Unit tests --- .../salesforce-webchat/salesforce-webchat.spec.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/digital-components/salesforce-webchat/salesforce-webchat.spec.js b/src/digital-components/salesforce-webchat/salesforce-webchat.spec.js index 0b368b6f0..178e431cc 100644 --- a/src/digital-components/salesforce-webchat/salesforce-webchat.spec.js +++ b/src/digital-components/salesforce-webchat/salesforce-webchat.spec.js @@ -20,9 +20,6 @@ describe("salesforceWebchat.vue", () => { expect(wrapper.props().hideSalesforceWebchatLaunchButton).toBe(false); }); - it("appends script to document body on mount", () => { - }); - it("calls the correct initialization function based on environment", () => { process.env.VUE_APP_CURRENT_ENVIRONMENT = "Prod"; let wrapper = shallowMount(salesforceWebchat); From 1a4c90fc3f1277fc7bbdfb8663a1e02f06d56a90 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Wed, 23 Apr 2025 11:02:25 -0400 Subject: [PATCH 25/33] CASH-188 | Formatting --- .../salesforce-helper.spec.js | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/digital-components/salesforce-webchat/salesforce-helper.spec.js b/src/digital-components/salesforce-webchat/salesforce-helper.spec.js index ae7712c49..aac2869f1 100644 --- a/src/digital-components/salesforce-webchat/salesforce-helper.spec.js +++ b/src/digital-components/salesforce-webchat/salesforce-helper.spec.js @@ -1,38 +1,43 @@ -import * as salesforceWebchatHelperdev from './salesforce-helper-dev'; -import * as salesforceWebchatHelperqa from './salesforce-helper-qa'; -import * as salesforceWebchatHelperprod from './salesforce-helper-prod'; +import * as salesforceWebchatHelperdev from "./salesforce-helper-dev"; +import * as salesforceWebchatHelperqa from "./salesforce-helper-qa"; +import * as salesforceWebchatHelperprod from "./salesforce-helper-prod"; const mockEmbeddedSvc = { settings: {}, init: jest.fn(), testFlag: true, -} +}; describe("Salesforce Webchat Helper Tests", () => { describe("Files and exposed methods exist", () => { it("exposes initialization methods", () => { - expect(typeof salesforceWebchatHelperdev.initializeSalesforceWebchatForDev).toBe("function"); - expect(typeof salesforceWebchatHelperqa.initializeSalesforceWebchatForQa).toBe("function"); - expect(typeof salesforceWebchatHelperprod.initializeSalesforceWebchatForProd).toBe("function"); - - }) + expect(typeof salesforceWebchatHelperdev.initializeSalesforceWebchatForDev).toBe( + "function" + ); + expect(typeof salesforceWebchatHelperqa.initializeSalesforceWebchatForQa).toBe( + "function" + ); + expect(typeof salesforceWebchatHelperprod.initializeSalesforceWebchatForProd).toBe( + "function" + ); + }); }), - describe("Custom code added correctly", ()=> { - it("defines a local variable 'embedded_svc' in the initESW method", ()=> { - mockEmbeddedSvc.testFlag = "dev"; - window.embedded_svc = mockEmbeddedSvc; - salesforceWebchatHelperdev.initializeSalesforceWebchatForDev(); - expect(embedded_svc.testFlag).toBe("dev"); + describe("Custom code added correctly", () => { + it("defines a local variable 'embedded_svc' in the initESW method", () => { + mockEmbeddedSvc.testFlag = "dev"; + window.embedded_svc = mockEmbeddedSvc; + salesforceWebchatHelperdev.initializeSalesforceWebchatForDev(); + expect(embedded_svc.testFlag).toBe("dev"); - mockEmbeddedSvc.testFlag = "qa"; - window.embedded_svc = mockEmbeddedSvc; - salesforceWebchatHelperqa.initializeSalesforceWebchatForQa(); - expect(embedded_svc.testFlag).toBe("qa"); + mockEmbeddedSvc.testFlag = "qa"; + window.embedded_svc = mockEmbeddedSvc; + salesforceWebchatHelperqa.initializeSalesforceWebchatForQa(); + expect(embedded_svc.testFlag).toBe("qa"); - mockEmbeddedSvc.testFlag = "prod"; - window.embedded_svc = mockEmbeddedSvc; - salesforceWebchatHelperprod.initializeSalesforceWebchatForProd(); - expect(embedded_svc.testFlag).toBe("prod"); - }) - }) -}) \ No newline at end of file + mockEmbeddedSvc.testFlag = "prod"; + window.embedded_svc = mockEmbeddedSvc; + salesforceWebchatHelperprod.initializeSalesforceWebchatForProd(); + expect(embedded_svc.testFlag).toBe("prod"); + }); + }); +}); From be213ef7d6614d2d3cdcae116a37e396c79ea513 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Wed, 23 Apr 2025 11:11:44 -0400 Subject: [PATCH 26/33] Revert "Merge pull request #2421 from Safelite/feature/CASH-188" This reverts commit 6fe56a56f26a27ba5332e44fe5c241b840272581, reversing changes made to 9c2a35f896fd496e1169a39da095f9dae2c4f200. --- jest.config.js | 2 +- .../salesforce-helper-dev.js | 66 ------------------ .../salesforce-helper-prod.js | 63 ----------------- .../salesforce-helper-qa.js | 67 ------------------- .../salesforce-webchat/salesforce-webchat.vue | 57 ---------------- .../funnel-header/funnel-header.vue | 7 -- src/layouts/confirmation/confirmation.vue | 5 +- 7 files changed, 2 insertions(+), 265 deletions(-) delete mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-dev.js delete mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-prod.js delete mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-qa.js delete mode 100644 src/digital-components/salesforce-webchat/salesforce-webchat.vue diff --git a/jest.config.js b/jest.config.js index e534b3e3f..905e907dc 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,7 +35,7 @@ module.exports = { testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], coverageThreshold: { global: { - statements: 72, + statements: 74, }, }, // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit diff --git a/src/digital-components/salesforce-webchat/salesforce-helper-dev.js b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js deleted file mode 100644 index fb29bc9eb..000000000 --- a/src/digital-components/salesforce-webchat/salesforce-helper-dev.js +++ /dev/null @@ -1,66 +0,0 @@ -/* -The code below is generated from salesforce but modified in the following ways: - * Only the javascript inside the second - - diff --git a/src/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue index 4862b5205..c6cbb8727 100644 --- a/src/fmg-components/funnel-header/funnel-header.vue +++ b/src/fmg-components/funnel-header/funnel-header.vue @@ -2,7 +2,6 @@
- Safelite logo @@ -27,7 +26,6 @@ import alert from "@/ux-components/alert/alert"; import eventBus from "@/helpers/event-bus/event-bus"; import { globalEvents } from "@/constants/events"; import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal"; -import salesforceWebchat from "../../digital-components/salesforce-webchat/salesforce-webchat.vue"; // Constants const ALERT_DURATION = 3000; // millisecond time to display alert before dismissal @@ -41,10 +39,6 @@ export default { }, props: { cmsWidgetName: String, - hideSalesforceWebchatLaunchButton: { - type: Boolean, - default: false, - }, }, computed: { imageSrc() { @@ -68,7 +62,6 @@ export default { components: { alert, menuModal, - salesforceWebchat, }, mounted() { // Check if alert event is on the bus diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index 40736b2c2..3b3ead4c1 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -5,10 +5,7 @@
- +
From abb78523c97f8b6c3e62d06d9095b3552a203cf7 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Wed, 23 Apr 2025 14:45:20 -0400 Subject: [PATCH 27/33] Fixes service location page selector issue Updates the selectors for the first appointment button and the repeated clicks modal close button on the service location page. Also, adjusts the logic to ensure the shop selection is visible and clickable, particularly when dealing with dynamic content loading or potential rendering issues. Additionally, shop address is saved in appointment details This resolves issues with element selection and interaction on the service location page, improving the reliability of the tests. --- playwright-tests/pages/ServiceLocationPage.ts | 22 ++++++++++++++----- playwright-tests/pages/ServicePackagesPage.ts | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts index c9cb6c98f..609f2d1db 100644 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -55,7 +55,7 @@ export class ServiceLocationPage extends BasePage { // 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('fieldset:has(legend#chooseShop)').locator('label').first(); this.changeZipButton = this.page.locator('a:has(span.sr-only:has-text("edit zip code"))'); this.updateZipTextBox = this.page.locator('#serviceZipCode'); this.saveZipButton = this.page.getByText('Save ZIP code', { exact: true }); @@ -71,7 +71,7 @@ export class ServiceLocationPage extends BasePage { this.vehicleProtectedYesButton = this.page.locator('label').filter({ hasText: 'Yes' }).locator('div'); this.vehicleProtectedNoButton = this.page.locator('label').filter({ hasText: 'No' }).locator('div'); this.saveAddressButton = this.page.getByRole('button', { name: 'Continue' }) - this.repeatedClicksModalCloseButton = this.page.getByRole('img').nth(1); + this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); } async selectLocation(appointmentDetails: IAppointmentDetails){ @@ -103,9 +103,14 @@ export class ServiceLocationPage extends BasePage { await this.saveZipButton.click(); } await this.inShopButton.click(); - await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check(); + await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).scrollIntoViewIfNeeded().then(() => this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).click()); } else { - await this.firstAppointmentButton.click(); + await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => { + await this.firstAppointmentButton.click(); + if (appointmentDetails) { + appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText(); + } + }); } } @@ -130,8 +135,13 @@ export class ServiceLocationPage extends BasePage { if (appointmentDetails && appointmentDetails.shopAddress) { await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check(); } else { - // await this.firstAppointmentButton.click(); - await this.clickWithRetry(this.firstAppointmentButton, this.page); + /// await this.clickWithRetry(this.firstAppointmentButton, this.page); + await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => { + await this.firstAppointmentButton.click(); + if (appointmentDetails) { + appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText(); + } + }); } } diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 8b1597598..65bba63b8 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -41,7 +41,7 @@ export class ServicePackagesPage extends BasePage { this.closeButton = this.page. getByRole('dialog').locator('button').filter({ hasText: 'Close' }); this.promoCodeTextbox = this.page.getByLabel('Enter a promo code'); this.applyPromoButton = this.page.getByRole('button', { name: 'Apply promo code' }); - this.repeatedClicksModalCloseButton = this.page.locator('#app').getByRole('dialog').locator('button').filter({ hasText: 'Close' }); + this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); // this.validateURL(this.url); } From 3c84eefa05a999f75aadedc31fd964f02432aa04 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Wed, 23 Apr 2025 15:26:01 -0400 Subject: [PATCH 28/33] Adds sqlite dependencies Adds sqlite and sqlite3 as dependencies. These dependencies are required for implementing local database functionality. --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 923fec124..24c5e32e9 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,8 @@ "prettier": "^3.3.3", "sass": "^1.77.8", "sass-loader": "^8.0.2", + "sqlite": "^5.1.1", + "sqlite3": "^5.1.7", "typescript": "^4.9.5" }, "eslintConfig": { From eb97825cdfa9a2850595444c3a68ffdbf2bc1bc3 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 24 Apr 2025 10:00:50 -0400 Subject: [PATCH 29/33] fixed axios imports --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 24c5e32e9..fb65152d5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ }, "dependencies": { "@iframe-resizer/child": "^5.3.3", + "axios": "^0.23.0", "bootstrap": "^5.3.3", "core-js": "^3.38.1", "http-status-codes": "^2.3.0", @@ -41,9 +42,6 @@ "@vue/eslint-config-prettier": "^9.0.0", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^27.0.0", - "axios": "^1.8.4", - "axios-mock-adapter": "^2.1.0", - "axios-retry": "^4.5.0", "dotenv-safe": "^9.1.0", "eslint": "8.57", "eslint-plugin-prettier": "^5.2.1", From cc380d29bbe881977a77a253b1aa476b7cd5d4f4 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Thu, 24 Apr 2025 10:37:18 -0400 Subject: [PATCH 30/33] Reapply "Merge pull request #2421 from Safelite/feature/CASH-188" This reverts commit be213ef7d6614d2d3cdcae116a37e396c79ea513. --- jest.config.js | 2 +- .../salesforce-helper-dev.js | 66 ++++++++++++++++++ .../salesforce-helper-prod.js | 63 +++++++++++++++++ .../salesforce-helper-qa.js | 67 +++++++++++++++++++ .../salesforce-webchat/salesforce-webchat.vue | 57 ++++++++++++++++ .../funnel-header/funnel-header.vue | 7 ++ src/layouts/confirmation/confirmation.vue | 5 +- 7 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-dev.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-prod.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-helper-qa.js create mode 100644 src/digital-components/salesforce-webchat/salesforce-webchat.vue diff --git a/jest.config.js b/jest.config.js index 905e907dc..e534b3e3f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -35,7 +35,7 @@ module.exports = { testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], coverageThreshold: { global: { - statements: 74, + statements: 72, }, }, // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit diff --git a/src/digital-components/salesforce-webchat/salesforce-helper-dev.js b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js new file mode 100644 index 000000000..fb29bc9eb --- /dev/null +++ b/src/digital-components/salesforce-webchat/salesforce-helper-dev.js @@ -0,0 +1,66 @@ +/* +The code below is generated from salesforce but modified in the following ways: + * Only the javascript inside the second + + diff --git a/src/fmg-components/funnel-header/funnel-header.vue b/src/fmg-components/funnel-header/funnel-header.vue index 092224551..bf4f17ec1 100644 --- a/src/fmg-components/funnel-header/funnel-header.vue +++ b/src/fmg-components/funnel-header/funnel-header.vue @@ -2,6 +2,7 @@
+ Safelite logo @@ -27,6 +28,7 @@ import alert from "@/ux-components/alert/alert"; import eventBus from "@/helpers/event-bus/event-bus"; import { globalEvents } from "@/constants/events"; import menuModal from "@/fmg-components/funnel-header/menu-modal/menu-modal"; +import salesforceWebchat from "../../digital-components/salesforce-webchat/salesforce-webchat.vue"; import progressBar from "@/fmg-components/funnel-header/progress-bar/progress-bar"; // Constants @@ -41,6 +43,10 @@ export default { }, props: { cmsWidgetName: String, + hideSalesforceWebchatLaunchButton: { + type: Boolean, + default: false, + }, }, computed: { imageSrc() { @@ -64,6 +70,7 @@ export default { components: { alert, menuModal, + salesforceWebchat, progressBar, }, mounted() { diff --git a/src/layouts/confirmation/confirmation.vue b/src/layouts/confirmation/confirmation.vue index 3b3ead4c1..40736b2c2 100644 --- a/src/layouts/confirmation/confirmation.vue +++ b/src/layouts/confirmation/confirmation.vue @@ -5,7 +5,10 @@
- +
From daf89bdb32c95e2b507a6a5d491e558e83bdd788 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Thu, 24 Apr 2025 12:41:00 -0400 Subject: [PATCH 31/33] CASH-353 | Fix missing 'const' error --- src/layouts/vin-lookup/vin-lookup.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/layouts/vin-lookup/vin-lookup.vue b/src/layouts/vin-lookup/vin-lookup.vue index 1b57ee068..14a87bf45 100644 --- a/src/layouts/vin-lookup/vin-lookup.vue +++ b/src/layouts/vin-lookup/vin-lookup.vue @@ -341,7 +341,7 @@ export default { false ); if (this.isHeavyTruck) { - closestShops = await this.findClosestApplicableShops( + const closestShops = await this.findClosestApplicableShops( this.serviceZipCode, this.carId ); From c7ca767a25dc9e560b2dc5c772345cba354e4316 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Thu, 24 Apr 2025 13:32:48 -0400 Subject: [PATCH 32/33] CASH-188 | Debug info added. --- .../salesforce-webchat/salesforce-helper-qa.js | 2 ++ .../salesforce-webchat/salesforce-webchat.vue | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/digital-components/salesforce-webchat/salesforce-helper-qa.js b/src/digital-components/salesforce-webchat/salesforce-helper-qa.js index ef08d42de..46ae49359 100644 --- a/src/digital-components/salesforce-webchat/salesforce-helper-qa.js +++ b/src/digital-components/salesforce-webchat/salesforce-helper-qa.js @@ -52,6 +52,7 @@ export function initializeSalesforceWebchatForQa() { }; if (!window.embedded_svc) { + console.log("no embedded svc found"); var s = document.createElement("script"); s.setAttribute( "src", @@ -62,6 +63,7 @@ export function initializeSalesforceWebchatForQa() { }; document.body.appendChild(s); } else { + console.log("embedded svc found"); initESW("https://service.force.com"); } } diff --git a/src/digital-components/salesforce-webchat/salesforce-webchat.vue b/src/digital-components/salesforce-webchat/salesforce-webchat.vue index 75f689d10..95c1348eb 100644 --- a/src/digital-components/salesforce-webchat/salesforce-webchat.vue +++ b/src/digital-components/salesforce-webchat/salesforce-webchat.vue @@ -28,16 +28,23 @@ export default { }, methods: { initializeSalesforceWebchat() { + console.log( + "process.env.VUE_APP_CURRENT_ENVIRONMENT", + process.env.VUE_APP_CURRENT_ENVIRONMENT + ); switch (process.env.VUE_APP_CURRENT_ENVIRONMENT) { case "Prod": + console.log("Prod"); initializeSalesforceWebchatForProd(); break; case "QA": case "SysTest": + console.log("QA"); initializeSalesforceWebchatForQa(); break; case "Dev": case "Localhost": + console.log("Dev"); initializeSalesforceWebchatForDev(); break; } From bf6e80d05db377b162089a0ac69eb60a17745536 Mon Sep 17 00:00:00 2001 From: maguire-arman Date: Thu, 24 Apr 2025 14:12:14 -0400 Subject: [PATCH 33/33] Revert "Merge pull request #2413 from Safelite/feature/CASH-488" This reverts commit e79314e8fe66fba819dcec3c0975368caf263b6d, reversing changes made to 7356268f2fd77175529b216f55ff215d6326f48b. --- package-lock.json | 135 +----------------- package.json | 2 - playwright-tests/.env.dev | 4 - .../azure-pipelines-automated-testing.yml | 15 +- .../constants/DefaultTestData.ts | 2 +- playwright-tests/pages/ServiceLocationPage.ts | 25 ++-- playwright-tests/pages/ServicePackagesPage.ts | 3 +- playwright-tests/tests/0000__M.test.ts | 5 +- 8 files changed, 29 insertions(+), 162 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b541b385..00fc7673a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@iframe-resizer/child": "^5.3.3", + "axios": "^0.23.0", "bootstrap": "^5.3.3", "core-js": "^3.38.1", "http-status-codes": "^2.3.0", @@ -37,9 +38,6 @@ "@vue/eslint-config-prettier": "^9.0.0", "@vue/test-utils": "^2.4.6", "@vue/vue3-jest": "^27.0.0", - "axios": "^1.8.4", - "axios-mock-adapter": "^2.1.0", - "axios-retry": "^4.5.0", "dotenv-safe": "^9.1.0", "eslint": "8.57", "eslint-plugin-prettier": "^5.2.1", @@ -5414,82 +5412,12 @@ } }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", - "dev": true, + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.23.0.tgz", + "integrity": "sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios-mock-adapter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", - "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "is-buffer": "^2.0.5" - }, - "peerDependencies": { - "axios": ">= 0.17.0" - } - }, - "node_modules/axios-mock-adapter/node_modules/is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/axios-retry": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", - "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "is-retry-allowed": "^2.2.0" - }, - "peerDependencies": { - "axios": "0.x || 1.x" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "follow-redirects": "^1.14.4" } }, "node_modules/babel-jest": { @@ -8077,22 +8005,6 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "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", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -9315,7 +9227,6 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, "funding": [ { "type": "individual", @@ -9783,22 +9694,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "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" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", @@ -10614,19 +10509,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -16365,13 +16247,6 @@ "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==", - "dev": true, - "license": "MIT" - }, "node_modules/pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", diff --git a/package.json b/package.json index fb65152d5..c63d8b5e8 100644 --- a/package.json +++ b/package.json @@ -52,8 +52,6 @@ "prettier": "^3.3.3", "sass": "^1.77.8", "sass-loader": "^8.0.2", - "sqlite": "^5.1.1", - "sqlite3": "^5.1.7", "typescript": "^4.9.5" }, "eslintConfig": { diff --git a/playwright-tests/.env.dev b/playwright-tests/.env.dev index 8c064ba62..47ec8336d 100644 --- a/playwright-tests/.env.dev +++ b/playwright-tests/.env.dev @@ -4,13 +4,9 @@ # Environment type NODE_ENV="qa" -SKIP_CONTENT_SITE=false - # Base URLs by environment (uncomment the one you need) # qa BASE_URL="https://www-qa2.safelite.com/" -# qa with skipToInsurance Turned Off -# BASE_URL="https://fixmyglassqa.safelite.com/?cns=all&experiments=ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_VinAndEmailOptional=true,NextGen_IGQSkipToInsurance=NextGen_IGQSkipToInsurance_V1=NextGen_IGQSkipToInsurance_CONTROL=true" # sys # BASE_URL="https://www-test2.safelite.com/fmg/?fmgPage=vehicle" # dev diff --git a/playwright-tests/azure-pipelines-automated-testing.yml b/playwright-tests/azure-pipelines-automated-testing.yml index ac332ef44..b4d7da831 100644 --- a/playwright-tests/azure-pipelines-automated-testing.yml +++ b/playwright-tests/azure-pipelines-automated-testing.yml @@ -1,3 +1,11 @@ +schedules: +- cron: 0 9 * * MON-FRI + always: true + displayName: Daily Test Automation Run for FMG-NextGen + branches: + include: + - main + pool: 'AmazonLinuxPool' variables: @@ -42,10 +50,10 @@ stages: container_id=$(docker create \ --ipc=host \ -e CCIS_API_AUTH=$(CCIS_API_AUTH) \ - -e BASE_URL="$(BASE_URL)" \ + -e BASE_URL=$(BASE_URL) \ -e CCIS_API_URL=$(CCIS_API_URL) \ -e ADMIN_SERVICE_API_URL=$(ADMIN_SERVICE_API_URL) \ - -e SKIP_CONTENT_SITE=$(SKIP_CONTENT_SITE) \ + -e SHARD=$(shardNumber) \ -e CI=true \ -e NODE_ENV=$(NODE_ENV) \ $(dockerImageName):$(imageTag) \ @@ -123,7 +131,6 @@ stages: -e REPORT_NAME="$report_name" \ -e CURRENT_DAY="$current_day" \ -e NUMERIC_DATE="$numeric_date" \ - -e NODE_ENV=$(NODE_ENV) \ $(dockerImageName):$(imageTag) \ bash -c "chmod +x devops/scripts/jira_writeback.sh echo \"Moving Playwright reports out of subfolders...\" @@ -144,7 +151,7 @@ stages: fi echo \"Writing report to Jira card '$jira_card_number'...\" - /app/devops/scripts/jira_writeback.sh add_comment \"$jira_card_number\" /app/ortoni-report/\${REPORT_NAME} \"AUTOMATED TEST RUN: $(date) - \${CURRENT_DAY} (\${NUMERIC_DATE}) - Environment: \${NODE_ENV}\" ") + /app/devops/scripts/jira_writeback.sh add_comment \"$jira_card_number\" /app/ortoni-report/\${REPORT_NAME} \"AUTOMATED TEST RUN: $(date) - \${CURRENT_DAY} (\${NUMERIC_DATE})\" ") # Start container and stream logs echo "Starting merge" diff --git a/playwright-tests/business-logic/constants/DefaultTestData.ts b/playwright-tests/business-logic/constants/DefaultTestData.ts index 197297b4b..6efb2c1db 100644 --- a/playwright-tests/business-logic/constants/DefaultTestData.ts +++ b/playwright-tests/business-logic/constants/DefaultTestData.ts @@ -43,7 +43,7 @@ export function getCustomerDetails(): ICustomerDetails { street: faker.location.streetAddress(), city: 'Columbus', state: 'Ohio', - postalCode: '21237', + postalCode: '43215', country: 'United States' } }; diff --git a/playwright-tests/pages/ServiceLocationPage.ts b/playwright-tests/pages/ServiceLocationPage.ts index 609f2d1db..b8c8649b6 100644 --- a/playwright-tests/pages/ServiceLocationPage.ts +++ b/playwright-tests/pages/ServiceLocationPage.ts @@ -55,7 +55,7 @@ export class ServiceLocationPage extends BasePage { // For in-shop and drop off this.selectAShopOptions = this.page.locator('[class="shop-question"]'); - this.firstAppointmentButton = this.page.locator('fieldset:has(legend#chooseShop)').locator('label').first(); + this.firstAppointmentButton = this.page.locator('div').filter({ hasText: /Appts/}).first(); this.changeZipButton = this.page.locator('a:has(span.sr-only:has-text("edit zip code"))'); this.updateZipTextBox = this.page.locator('#serviceZipCode'); this.saveZipButton = this.page.getByText('Save ZIP code', { exact: true }); @@ -71,7 +71,7 @@ export class ServiceLocationPage extends BasePage { this.vehicleProtectedYesButton = this.page.locator('label').filter({ hasText: 'Yes' }).locator('div'); this.vehicleProtectedNoButton = this.page.locator('label').filter({ hasText: 'No' }).locator('div'); this.saveAddressButton = this.page.getByRole('button', { name: 'Continue' }) - this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); + this.repeatedClicksModalCloseButton = this.page.getByRole('img').nth(1); } async selectLocation(appointmentDetails: IAppointmentDetails){ @@ -103,14 +103,9 @@ export class ServiceLocationPage extends BasePage { await this.saveZipButton.click(); } await this.inShopButton.click(); - await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).scrollIntoViewIfNeeded().then(() => this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).click()); + await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check(); } else { - await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => { - await this.firstAppointmentButton.click(); - if (appointmentDetails) { - appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText(); - } - }); + await this.firstAppointmentButton.click(); } } @@ -119,6 +114,9 @@ export class ServiceLocationPage extends BasePage { await this.mobileButton.click(); await this.enterServiceAddressButton.click(); await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! }); + if (await this.repeatedClicksModalCloseButton.isVisible()) { + await this.repeatedClicksModalCloseButton.click(); + } if (faker.datatype.boolean()) { await this.vehicleProtectedYesButton.check(); } else { @@ -135,13 +133,8 @@ export class ServiceLocationPage extends BasePage { if (appointmentDetails && appointmentDetails.shopAddress) { await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check(); } else { - /// await this.clickWithRetry(this.firstAppointmentButton, this.page); - await this.firstAppointmentButton.scrollIntoViewIfNeeded().then(async () => { - await this.firstAppointmentButton.click(); - if (appointmentDetails) { - appointmentDetails.shopAddress = await this.firstAppointmentButton.locator('.row-two').innerText(); - } - }); + // await this.firstAppointmentButton.click(); + await this.clickWithRetry(this.firstAppointmentButton, this.page); } } diff --git a/playwright-tests/pages/ServicePackagesPage.ts b/playwright-tests/pages/ServicePackagesPage.ts index 65bba63b8..9a3b87ae3 100644 --- a/playwright-tests/pages/ServicePackagesPage.ts +++ b/playwright-tests/pages/ServicePackagesPage.ts @@ -41,7 +41,7 @@ export class ServicePackagesPage extends BasePage { this.closeButton = this.page. getByRole('dialog').locator('button').filter({ hasText: 'Close' }); this.promoCodeTextbox = this.page.getByLabel('Enter a promo code'); this.applyPromoButton = this.page.getByRole('button', { name: 'Apply promo code' }); - this.repeatedClicksModalCloseButton = this.page.locator('.QSISlider').locator('img[src*=\'close\']'); + this.repeatedClicksModalCloseButton = this.page.getByRole('img').nth(1); // this.validateURL(this.url); } @@ -76,7 +76,6 @@ export class ServicePackagesPage extends BasePage { if (await this.repeatedClicksModalCloseButton.isVisible()) { await this.repeatedClicksModalCloseButton.click(); } - await this.page.waitForTimeout(3000); await this.closeButton.click(); } else { diff --git a/playwright-tests/tests/0000__M.test.ts b/playwright-tests/tests/0000__M.test.ts index 656e078c2..6bef5a2f3 100644 --- a/playwright-tests/tests/0000__M.test.ts +++ b/playwright-tests/tests/0000__M.test.ts @@ -192,9 +192,8 @@ async function runWorkflow(page: Page, testCase: TestCase) { //============================= TEST WORKFLOW STEPS ============================= - // Use Environment Variable to decide whether or not we want to skip content site aka home page - - if (process.env.SKIP_CONTENT_SITE == "false") { + // Execute home page for qa and dev environments (skip for sys) + if (process.env.NODE_ENV !== 'sys') { await test.step('HomePage >> Lets Get Started', async () => { let homePage = testCase.pages.homePage; console.log(`Customer for this test: ${customerDetails?.firstName} ${customerDetails?.lastName}`);