Added playwright tests into repo (#923)
* Initial import of playwright tests * Pipeline changes for automated tests * Modified pipeline for testing * Attempt #2 * Attempt #3 * Added missing paren * Removed debug stuff from pipeline * Changes from playwright repo * Changed pipeline for debugging * Fix for ServiceLocationPage playwright locators * Change pipeline to run with TEST APIs * Moved more of Siraj's changes to this repo * Changed where updating env occurs * Changed location of env update again * Escaped double quotes * Added visible report in Azure * Moved changes into main pipeline * Made it so dotenv only runs config in local
This commit is contained in:
parent
1cdcaa7bd6
commit
5d3ab0ef59
135 changed files with 11467 additions and 137 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -22,6 +22,13 @@ pnpm-debug.log*
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
artifacts/
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
coverage/*
|
coverage/*
|
||||||
junit.xml
|
junit.xml
|
||||||
|
|
|
||||||
21
Dockerfile.playwright
Normal file
21
Dockerfile.playwright
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
FROM node:16
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/playwright:v1.48.0-noble
|
||||||
|
|
||||||
|
# Set the working directory in the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Install Playwright browsers
|
||||||
|
RUN npx playwright install --with-deps
|
||||||
|
|
||||||
|
# Copy the rest of the application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Run Playwright tests
|
||||||
|
CMD ["npx", "playwright", "test"]
|
||||||
|
|
@ -31,12 +31,18 @@ resources:
|
||||||
variables:
|
variables:
|
||||||
- group: Digital-Infrastructure
|
- group: Digital-Infrastructure
|
||||||
- group: ISS-BuildBranches
|
- group: ISS-BuildBranches
|
||||||
|
- name: dockerImageName
|
||||||
|
value: 'playwright-tests'
|
||||||
|
- name: imageTag
|
||||||
|
value: '$(Build.BuildId)'
|
||||||
|
- name: totalShards
|
||||||
|
value: 4
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
# PR's
|
# PR's
|
||||||
- ${{ if eq(variables['Build.Reason'], 'PullRequest') }}:
|
- ${{ if eq(variables['Build.Reason'], 'PullRequest') }}:
|
||||||
- stage: TestPr
|
- stage: TestPr
|
||||||
displayName: Run Unit Tests For PullRequest
|
displayName: Run Tests For PullRequest
|
||||||
jobs:
|
jobs:
|
||||||
- template: templates/digital/vue-jest-run-unit-tests.yml@AzureDevOps
|
- template: templates/digital/vue-jest-run-unit-tests.yml@AzureDevOps
|
||||||
parameters:
|
parameters:
|
||||||
|
|
@ -44,6 +50,123 @@ stages:
|
||||||
npmLocation: $(Build.SourcesDirectory)
|
npmLocation: $(Build.SourcesDirectory)
|
||||||
testResultsFile: junit.xml
|
testResultsFile: junit.xml
|
||||||
summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml
|
summaryFileLocation: $(Build.SourcesDirectory)/coverage/cobertura-coverage.xml
|
||||||
|
- job: playwright_tests
|
||||||
|
continueOnError: true
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
shard1:
|
||||||
|
shardNumber: 1
|
||||||
|
shard2:
|
||||||
|
shardNumber: 2
|
||||||
|
shard3:
|
||||||
|
shardNumber: 3
|
||||||
|
shard4:
|
||||||
|
shardNumber: 4
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: Docker@2
|
||||||
|
displayName: 'Build Docker Image'
|
||||||
|
inputs:
|
||||||
|
command: build
|
||||||
|
dockerfile: Dockerfile.playwright
|
||||||
|
repository: $(dockerImageName)
|
||||||
|
tags: $(imageTag)
|
||||||
|
arguments: '--no-cache --pull'
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
# 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 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 \"@smoke | @Advanced\"")
|
||||||
|
|
||||||
|
# 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)!"
|
||||||
|
exit 1
|
||||||
|
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()
|
||||||
|
|
||||||
|
- job: download_and_merge_reports
|
||||||
|
container:
|
||||||
|
image: mcr.microsoft.com/playwright:v1.48.0-noble
|
||||||
|
dependsOn: playwright_tests
|
||||||
|
steps:
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
inputs:
|
||||||
|
targetPath: '$(System.DefaultWorkingDirectory)/playwright-reports'
|
||||||
|
- script: |
|
||||||
|
npm i ortoni-report &&
|
||||||
|
for dir in $(System.DefaultWorkingDirectory)/playwright-reports/*/; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
mv "$dir"* $(System.DefaultWorkingDirectory)/playwright-reports/
|
||||||
|
rmdir "$dir"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
npx playwright merge-reports --reporter=ortoni-report,junit $(System.DefaultWorkingDirectory)/playwright-reports
|
||||||
|
ls
|
||||||
|
displayName: merge_reports
|
||||||
|
env:
|
||||||
|
PLAYWRIGHT_JUNIT_OUTPUT_FILE: "test-results/results.xml"
|
||||||
|
|
||||||
|
# TODO: Enable later when working on PR gate
|
||||||
|
# - task: PublishTestResults@2
|
||||||
|
# displayName: 'Publish test results'
|
||||||
|
# inputs:
|
||||||
|
# searchFolder: 'test-results'
|
||||||
|
# testResultsFormat: 'JUnit'
|
||||||
|
# testResultsFiles: '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'
|
||||||
|
|
||||||
- ${{ else }}:
|
- ${{ else }}:
|
||||||
# Dev Build/Deploy
|
# Dev Build/Deploy
|
||||||
|
|
|
||||||
2317
package-lock.json
generated
2317
package-lock.json
generated
File diff suppressed because it is too large
Load diff
18
package.json
18
package.json
|
|
@ -12,7 +12,8 @@
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"test:unit": "vue-cli-service test:unit --coverage --ci --colors",
|
"test:unit": "vue-cli-service test:unit --coverage --ci --colors",
|
||||||
"test:unit:lite": "vue-cli-service test:unit --ci"
|
"test:unit:lite": "vue-cli-service test:unit --ci",
|
||||||
|
"test:playwright": "playwright test --config=playwright-tests/playwright.config.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
|
|
@ -29,11 +30,16 @@
|
||||||
"vue-router": "4.2.4"
|
"vue-router": "4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^9.0.3",
|
||||||
"@pinia/testing": "0.1.2",
|
"@pinia/testing": "0.1.2",
|
||||||
|
"@playwright/test": "^1.48.0",
|
||||||
"@rushstack/eslint-patch": "^1.3.2",
|
"@rushstack/eslint-patch": "^1.3.2",
|
||||||
|
"@saucelabs/playwright-reporter": "^1.5.0",
|
||||||
"@testing-library/jest-dom": "5.16.5",
|
"@testing-library/jest-dom": "5.16.5",
|
||||||
"@testing-library/user-event": "14.4.3",
|
"@testing-library/user-event": "14.4.3",
|
||||||
"@testing-library/vue": "6.6.1",
|
"@testing-library/vue": "6.6.1",
|
||||||
|
"@types/dotenv-safe": "^8.1.6",
|
||||||
|
"@types/node": "^22.7.5",
|
||||||
"@vitejs/plugin-vue": "4.2.3",
|
"@vitejs/plugin-vue": "4.2.3",
|
||||||
"@vitest/coverage-v8": "^0.34.1",
|
"@vitest/coverage-v8": "^0.34.1",
|
||||||
"@vue/cli-plugin-babel": "^5.0.8",
|
"@vue/cli-plugin-babel": "^5.0.8",
|
||||||
|
|
@ -42,8 +48,11 @@
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/cli-service": "~5.0.0",
|
||||||
"@vue/test-utils": "^2.4.1",
|
"@vue/test-utils": "^2.4.1",
|
||||||
"@vue/vue3-jest": "^27.0.0-alpha.1",
|
"@vue/vue3-jest": "^27.0.0-alpha.1",
|
||||||
|
"axios": "^1.7.8",
|
||||||
"axios-mock-adapter": "^1.21.5",
|
"axios-mock-adapter": "^1.21.5",
|
||||||
"babel-jest": "^27.0.6",
|
"babel-jest": "^27.0.6",
|
||||||
|
"concurrently": "^9.1.2",
|
||||||
|
"dotenv-safe": "^9.1.0",
|
||||||
"eslint": "^8.45.0",
|
"eslint": "^8.45.0",
|
||||||
"eslint-config-airbnb-base": "15.0.0",
|
"eslint-config-airbnb-base": "15.0.0",
|
||||||
"eslint-import-resolver-alias": "1.1.2",
|
"eslint-import-resolver-alias": "1.1.2",
|
||||||
|
|
@ -55,10 +64,15 @@
|
||||||
"jest-serializer-vue": "^3.1.0",
|
"jest-serializer-vue": "^3.1.0",
|
||||||
"jsdoc": "^4.0.2",
|
"jsdoc": "^4.0.2",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
|
"luxon": "^3.5.0",
|
||||||
|
"ortoni-report": "^2.0.8",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.77.8",
|
||||||
"sass-loader": "^12.0.0",
|
"sass-loader": "^12.0.0",
|
||||||
|
"saucectl": "^0.188.0",
|
||||||
|
"typescript-eslint": "^8.11.0",
|
||||||
"vite": "^4.5.9",
|
"vite": "^4.5.9",
|
||||||
"vitest": "^0.33.0",
|
"vitest": "^0.33.0",
|
||||||
"volar-service-vetur": "latest"
|
"volar-service-vetur": "latest",
|
||||||
|
"wait-on": "^8.0.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
playwright-tests/.dockerignore
Normal file
4
playwright-tests/.dockerignore
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
.git
|
||||||
|
*Dockerfile*
|
||||||
|
*docker-compose*
|
||||||
|
node_modules
|
||||||
14
playwright-tests/.env
Normal file
14
playwright-tests/.env
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# CLIENT_NAME="undefined" # TODO: evaluate necessity of this
|
||||||
|
# CLIENT_TAG="undefined"
|
||||||
|
# BASE_URL="https://selfservice.dev.glassclaim.com"
|
||||||
|
|
||||||
|
CCIS_API_AUTH= "" # Input manually
|
||||||
|
|
||||||
|
# DEV
|
||||||
|
BASE_URL="https://selfservice.test.glassclaim.com"
|
||||||
|
CCIS_API_URL="https://api.test.belronus.io" # Test API connects to our dev environment for some reason
|
||||||
|
ADMIN_SERVICE_API_URL="https://issadminapi.dev.sagaws.net/iss-admin/api/v1/"
|
||||||
|
|
||||||
|
# QA
|
||||||
|
# BASE_URL="https://selfservice.test.glassclaim.com"
|
||||||
|
# CCIS_API_URL="https://api.test.belronus.io"
|
||||||
14
playwright-tests/.env.dev
Normal file
14
playwright-tests/.env.dev
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# CLIENT_NAME="undefined" # TODO: evaluate necessity of this
|
||||||
|
# CLIENT_TAG="undefined"
|
||||||
|
# BASE_URL="https://selfservice.dev.glassclaim.com"
|
||||||
|
|
||||||
|
CCIS_API_AUTH="" # Input manually
|
||||||
|
|
||||||
|
# DEV
|
||||||
|
BASE_URL="https://selfservice.dev.glassclaim.com"
|
||||||
|
CCIS_API_URL="https://api.test.belronus.io" # Test API connects to our dev environment for some reason
|
||||||
|
ADMIN_SERVICE_API_URL="https://issadminapi.dev.sagaws.net/iss-admin/api/v1/"
|
||||||
|
|
||||||
|
# QA
|
||||||
|
# BASE_URL="https://selfservice.test.glassclaim.com"
|
||||||
|
# CCIS_API_URL="https://api.test.belronus.io"
|
||||||
10
playwright-tests/.env.example
Normal file
10
playwright-tests/.env.example
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
CCIS_API_AUTH=
|
||||||
|
|
||||||
|
# DEV
|
||||||
|
BASE_URL="https://selfservice.dev.glassclaim.com"
|
||||||
|
CCIS_API_URL="https://api.dev.belronus.io"
|
||||||
|
|
||||||
|
|
||||||
|
# QA
|
||||||
|
# BASE_URL="https://selfservice.test.glassclaim.com"
|
||||||
|
# CCIS_API_URL="https://api.test.belronus.io"
|
||||||
6
playwright-tests/.gitignore
vendored
Normal file
6
playwright-tests/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
artifacts/
|
||||||
74
playwright-tests/.sauce/config.yml
Normal file
74
playwright-tests/.sauce/config.yml
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
apiVersion: v1alpha
|
||||||
|
kind: playwright
|
||||||
|
showConsoleLog: true
|
||||||
|
sauce:
|
||||||
|
region: us-west-1
|
||||||
|
concurrency: 20
|
||||||
|
sauceignore: .sauceignore
|
||||||
|
playwright:
|
||||||
|
version: 1.48.2
|
||||||
|
configFile: playwright.config.ts
|
||||||
|
|
||||||
|
suites:
|
||||||
|
- name: 'ISS-NextGen-chromium'
|
||||||
|
numShards: 20
|
||||||
|
testMatch:
|
||||||
|
- tests/0000__M.test.ts
|
||||||
|
platformName: Windows 10
|
||||||
|
env:
|
||||||
|
DEBUG: "pw:worker"
|
||||||
|
SAUCE_USERNAME: $SAUCE_USERNAME
|
||||||
|
SAUCE_ACCESS_KEY: $SAUCE_ACCESS_KEY
|
||||||
|
params:
|
||||||
|
browserName: chrome
|
||||||
|
project: "chromium"
|
||||||
|
artifacts: "**test-results\\index.html"
|
||||||
|
artifacts:
|
||||||
|
cleanup: true
|
||||||
|
download:
|
||||||
|
match:
|
||||||
|
- '*'
|
||||||
|
when: always
|
||||||
|
directory: ./artifacts
|
||||||
|
|
||||||
|
# - name: 'Mobile Android Chrome Tests'
|
||||||
|
# shard: spec
|
||||||
|
# testMatch:
|
||||||
|
# - e2e/
|
||||||
|
# platformName: Windows 11
|
||||||
|
# params:
|
||||||
|
# browserName: chrome
|
||||||
|
# project: "Mobile Android Tests"
|
||||||
|
|
||||||
|
# - name: 'Desktop Safari'
|
||||||
|
# testMatch:
|
||||||
|
# - .ts
|
||||||
|
# platformName: Windows 10
|
||||||
|
# params:
|
||||||
|
# browserName: webkit
|
||||||
|
# project: "webkit"
|
||||||
|
|
||||||
|
|
||||||
|
# - name: 'Mobile iOS Tests'
|
||||||
|
# shard: spec
|
||||||
|
# testMatch:
|
||||||
|
# - e2e/
|
||||||
|
# platformName: macOS 13
|
||||||
|
# params:
|
||||||
|
# browserName: webkit
|
||||||
|
# project: "Mobile iOS Tests"
|
||||||
|
# env:
|
||||||
|
# DEBUG: "pw:worker"
|
||||||
|
|
||||||
|
docker:
|
||||||
|
file: Dockerfile
|
||||||
|
image: isscqa-playwright-image
|
||||||
|
|
||||||
|
rootDir: ./
|
||||||
|
reporters:
|
||||||
|
spotlight: # Prints an overview of failed or otherwise interesting jobs.
|
||||||
|
enabled: true
|
||||||
|
npm:
|
||||||
|
dependencies:
|
||||||
|
- "package.json"
|
||||||
|
|
||||||
16
playwright-tests/.sauceignore
Normal file
16
playwright-tests/.sauceignore
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# This file instructs saucectl to not package any files mentioned here.
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
.DS_Store
|
||||||
|
.hg/
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.gitignore
|
||||||
|
.hgignore
|
||||||
|
.gitlab-ci.yml
|
||||||
|
.npmrc
|
||||||
|
*.gif
|
||||||
|
screenshots
|
||||||
|
artifacts
|
||||||
|
# Remove this to have node_modules uploaded with code
|
||||||
|
# node_modules/
|
||||||
273
playwright-tests/business-logic/data/ClientData.ts
Normal file
273
playwright-tests/business-logic/data/ClientData.ts
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
import { IClient } from "@business-logic/types/Client";
|
||||||
|
import { IPaymentDetails } from "@business-logic/types/CustomerDetails";
|
||||||
|
import { PaymentType } from "@business-logic/types/Enums";
|
||||||
|
|
||||||
|
const essentialClients: IClient[] = [
|
||||||
|
{
|
||||||
|
clientTag: 'A8156D39-5943-4D3B-88C3-0D1A8810B51',
|
||||||
|
accountName: 'Acadia',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '4fc3010a-ef5f-434d-93aa-7fdf4878d667',
|
||||||
|
accountName: 'AIG Private Client',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '93FE1F55-19FA-4B7D-8F49-6E6C9F0E2B42',
|
||||||
|
accountName: 'Alfa Alliance',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '1FC7999E-E0AC-484C-9AC9-267D1A2E5493',
|
||||||
|
accountName: 'American Family Insurance',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '65FD47E0-F0DB-47FD-8CBB-E6F4F3BBF457',
|
||||||
|
accountName: 'Apparent Insurance',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '009E3AE9-8E4E-40B7-9C7B-4516F640F10',
|
||||||
|
accountName: 'Berkley One',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '0B3A3B02-5267-4302-A305-E742E1F1BFC5',
|
||||||
|
accountName: 'Branch Insurance',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '7DF15A1A-0FD3-42D3-A6B1-26FB36550BDC',
|
||||||
|
accountName: 'Brethren Mutual',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'E2D26369-1739-402C-9A85-F5F748C72647',
|
||||||
|
accountName: 'Certainly',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '394EF217-7882-4281-B4B8-FA11FDA7D500',
|
||||||
|
accountName: 'Continental Western',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'BF29B5A9-EFD8-4138-B301-CAD9C2495E74',
|
||||||
|
accountName: 'Donegal Mutual',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'FDEAB808-9922-4851-A692-7128ECF9F879',
|
||||||
|
accountName: 'Elephant Insurance',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '42AC5AFB-0A08-4845-935A-9FA3A1BBF2D0',
|
||||||
|
accountName: 'Encompass',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '1BED97B1-4593-4E09-AF1A-9DDD2CCAD0A4',
|
||||||
|
accountName: 'Farm Bureau',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '05CC1609-3631-4044-B45A-E78E13343B9A',
|
||||||
|
accountName: 'Federated Insurance',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'C05C2F61-26EB-4849-99DD-0172EDA4C70F',
|
||||||
|
accountName: 'Fremont Insurance',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'B6911ACB-4B6A-4549-B674-0FC79AA6E782',
|
||||||
|
accountName: 'Grange Insurance Association',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'BDA2D606-D34F-4ED3-83DD-7BF56AC9C2CE',
|
||||||
|
accountName: 'GuideOne Insurance',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '3CC58C8F-9207-44AF-B5F1-E1BA84CFADD1',
|
||||||
|
accountName: 'Hagerty',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '6D13AEF6-CE16-400F-9A29-A1AED5C47D48',
|
||||||
|
accountName: 'Liberty Mutual',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'F680CCE4-6D88-4845-9FA4-746CF54B4AC',
|
||||||
|
accountName: 'M Plate',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '988C2DCB-C95E-452D-9AE3-23B719FB3991',
|
||||||
|
accountName: 'Main Street America',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '1873b283-a0b5-4d10-b112-b6bab94cd9dd',
|
||||||
|
accountName: 'Maine Mutual Group',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'A639E2E5-6347-4E38-92B3-37DEF73BE51A',
|
||||||
|
accountName: 'Merchants Insurance Group',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '9B37A4B6-B4C2-4FCA-9E7C-FF8F0AB48067',
|
||||||
|
accountName: 'Midvale',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'F2617197-C39D-451C-A3D1-61C39BA0BD57',
|
||||||
|
accountName: 'M-Plate',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'f4730f4c-f83f-41de-a543-3ce761f66802',
|
||||||
|
accountName: 'Mutual Benefit',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '1DE5F9C7-34F0-4115-9582-4BFDF89F8504',
|
||||||
|
accountName: 'Nationwide Insurance',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'EA776DDA-0686-43F3-B222-70F1531B9610',
|
||||||
|
accountName: 'Nationwide Private Client',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'D3AA7E5D-2184-4761-99C3-69ED8515916E',
|
||||||
|
accountName: 'Northstar',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '8879E236-AFEA-4B46-A94D-06078B84B641',
|
||||||
|
accountName: 'OnStar Insurance',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '29EF1D8A-C3AF-4037-8FDB-AC7DBD50B1B5',
|
||||||
|
accountName: 'Pioneer State Mutual',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'BBC83F09-82D7-492D-A8E8-64594215E559',
|
||||||
|
accountName: 'Preferred Mutual',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'D27EBC04-A912-4974-A9D1-E3EBC2B2575B',
|
||||||
|
accountName: 'QBE',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'A5F7D473-29C4-4E2C-AD08-A3AB13FE0314',
|
||||||
|
accountName: 'Safeco Insurance',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '32978a45-dd2e-4fc6-b2ca-7178efb40373',
|
||||||
|
accountName: 'Secura',
|
||||||
|
clientFlags: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'C471BDE5-7005-47C2-A544-933D988BCB7F',
|
||||||
|
accountName: 'Travelers Insurance',
|
||||||
|
clientFlags: { isTpaEnabled: true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: 'd6ba651f-1611-4f32-a6f6-0267e1968eed',
|
||||||
|
accountName: 'Utica',
|
||||||
|
clientFlags: { isAuthenticationEnabled: true}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
clientTag: '1065734B-2292-4392-8CAF-205F07752BBE',
|
||||||
|
accountName: 'Wayne Insurance',
|
||||||
|
clientFlags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const advancedClients: IClient[] = [
|
||||||
|
{
|
||||||
|
clientTag: '1216EA5F-64D1-462A-A03F-34C0A652430E',
|
||||||
|
accountName: 'Liberty Mutual',
|
||||||
|
clientFlags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultCreditCardDetails: IPaymentDetails = {
|
||||||
|
paymentType: PaymentType.Credit,
|
||||||
|
cardNumber: '4111111111111111',
|
||||||
|
expirationMonth: '12 - December',
|
||||||
|
expirationYear: '2029',
|
||||||
|
cvv: '555',
|
||||||
|
billingAddress: {
|
||||||
|
street: '2088 Tuller St',
|
||||||
|
city: 'Columbus',
|
||||||
|
state: 'OH',
|
||||||
|
postalCode: '43028',
|
||||||
|
country: 'US'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultAfterpayDetails: IPaymentDetails = {
|
||||||
|
paymentType: PaymentType.AfterPay,
|
||||||
|
username: 'itqatest@safelite.com',
|
||||||
|
password: 'Safelite1',
|
||||||
|
cardNumber: '4111 1111 1111 1111',
|
||||||
|
expirationMonth: '12',
|
||||||
|
expirationYear: '34',
|
||||||
|
cvv: '000'
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPaypalDetails: IPaymentDetails = {
|
||||||
|
paymentType: PaymentType.Paypal,
|
||||||
|
password: 'Safelite1'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ClientData {
|
||||||
|
static getEssentialClients() {
|
||||||
|
return essentialClients.sort(() => 0.5 - Math.random());
|
||||||
|
}
|
||||||
|
|
||||||
|
static getEssentialClientsWithTpaEnabled() {
|
||||||
|
return essentialClients.sort(() => 0.5 - Math.random()).filter(value => {
|
||||||
|
return value.clientFlags.isTpaEnabled;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getEssentialClientsWithTpaDisabled() {
|
||||||
|
return essentialClients.sort(() => 0.5 - Math.random()).filter(value => {
|
||||||
|
return !value.clientFlags.isTpaEnabled;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAdvancedClients() {
|
||||||
|
return advancedClients;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaultCreditCardDetails() {
|
||||||
|
return defaultCreditCardDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaultAfterpayDetails() {
|
||||||
|
return defaultAfterpayDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaultPaypalDetails() {
|
||||||
|
return defaultPaypalDetails;
|
||||||
|
}
|
||||||
|
}
|
||||||
71
playwright-tests/business-logic/data/MockPolicyData.ts
Normal file
71
playwright-tests/business-logic/data/MockPolicyData.ts
Normal file
File diff suppressed because one or more lines are too long
70
playwright-tests/business-logic/rules/RuleEngineBuiltins.ts
Normal file
70
playwright-tests/business-logic/rules/RuleEngineBuiltins.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
//import { AircraftTypes, AssignmentTypes, EnhancementBundleOptions, EnhancementOptions, ModificationTypes, OpportunityTypes, ProductAttributes, ProgramTypes, TransactionSubTypes, TwentyFiveHrLeaseProducts } from "@business-logic/types/Enums";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { Rule } from "../types/RuleEngine";
|
||||||
|
import EnumUtils from "../../impl/utils/EnumUtils";
|
||||||
|
//import { validateEnhancementOptions, validateProductAttributes } from "./ApplicableModifications";
|
||||||
|
//import TransactionData from "@business-logic/types/TransactionData";
|
||||||
|
|
||||||
|
export enum BuiltInRules {
|
||||||
|
TransactionExists = 1000,
|
||||||
|
PricingExists = 1001,
|
||||||
|
TerminationIsMod = 1002,
|
||||||
|
TerminationHasReason = 1003,
|
||||||
|
AircraftRequirement = 1004,
|
||||||
|
ModificationTypeAssignment = 1005,
|
||||||
|
ModificationTypeTrade = 1006,
|
||||||
|
TwentyFiveHourLeaseSpecialty = 1007,
|
||||||
|
TransactionPremiumOrNonPremiumOnly = 1008,
|
||||||
|
OldTransactionPremiumOrNonPremiumOnly = 1009,
|
||||||
|
PremiumSelectionRequirement = 1010,
|
||||||
|
OldTransactionRequiresPremiumSelection = 1011,
|
||||||
|
TerminationAndRepurchaseAdjustmentAmount = 1012,
|
||||||
|
ProgramSpecificAttributes = 1013,
|
||||||
|
ProgramSpecificEnhancements = 1014,
|
||||||
|
InterimLeaseHasAircraftRate = 1015,
|
||||||
|
ShareBinderNoPremiumSelection = 1016,
|
||||||
|
TwentyFiveHourLeaseMustDefineTwentyFiveHourProduct = 1017,
|
||||||
|
PartialAssignmentHoursToBeAssigned = 1018,
|
||||||
|
MinicartLineItemsCannotBeEmpty = 1019,
|
||||||
|
WaiveInternationalFeesValue = 1020,
|
||||||
|
EarlyOutOptionTimeframeAndFeeStatus = 1021,
|
||||||
|
DelayedStartDateOffsetRequiresValue = 1020,
|
||||||
|
EnhancementsRequireAValue = 1021,
|
||||||
|
NonPremiumMMFIncentiveRequirements = 1022,
|
||||||
|
QSExecutiveRequirements = 1023
|
||||||
|
}
|
||||||
|
|
||||||
|
// Built-Ins shouldn't depend on other rules, custom rules however are supposed to depend on them
|
||||||
|
export const builtInRules: Rule<TestCase>[] =
|
||||||
|
[
|
||||||
|
//{
|
||||||
|
// id: BuiltInRules.TransactionExists,
|
||||||
|
// name: "Transaction Exists Rule",
|
||||||
|
// check: (testCase: TestCase) => {
|
||||||
|
// return testCase.transaction != null;
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// id: BuiltInRules.PricingExists,
|
||||||
|
// name: "Pricing must exist on Transaction Rule",
|
||||||
|
// check: (testCase: TestCase) => {
|
||||||
|
// return testCase.transaction != null && testCase.transaction.pricing != null;
|
||||||
|
// }
|
||||||
|
// }, {
|
||||||
|
// id: BuiltInRules.TerminationIsMod,
|
||||||
|
// name: "OpportunityType Termination requires TransactionSubType Mod",
|
||||||
|
// check: (testCase: TestCase) => {
|
||||||
|
|
||||||
|
// // Check old transactions
|
||||||
|
// for (var transaction of testCase.oldTransactions) {
|
||||||
|
// if (transaction.opportunityType == OpportunityTypes.Termination)
|
||||||
|
// return transaction.subType == TransactionSubTypes.Mod;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Check transaction
|
||||||
|
// if (testCase.transaction.opportunityType == OpportunityTypes.Termination)
|
||||||
|
// return testCase.transaction.subType == TransactionSubTypes.Mod;
|
||||||
|
// return true;
|
||||||
|
// },
|
||||||
|
// dependsOn: [BuiltInRules.TransactionExists]
|
||||||
|
// }, {
|
||||||
|
];
|
||||||
40
playwright-tests/business-logic/types/Authentication.ts
Normal file
40
playwright-tests/business-logic/types/Authentication.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { Authentication, CertificateType, SignatureAlgorithm, SubType } from "./Enums"
|
||||||
|
|
||||||
|
export interface IClientSignatureRequest {
|
||||||
|
clientTag: string,
|
||||||
|
token: string,
|
||||||
|
certificateFileName: string,
|
||||||
|
certificateKey: string,
|
||||||
|
certificateAlgorithm: SignatureAlgorithm,
|
||||||
|
certificateType: CertificateType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IClientSignatureResponse {
|
||||||
|
signature: string,
|
||||||
|
encrypted: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICertificateInfo {
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
type: CertificateType;
|
||||||
|
algorithm: SignatureAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IClientAuthenticationFlags {
|
||||||
|
claimRegistrationRequired: boolean;
|
||||||
|
tpaEnabled: boolean;
|
||||||
|
clientDisplayName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IClientAuthentication {
|
||||||
|
clientTag: string;
|
||||||
|
accountName: string;
|
||||||
|
accountNumber: string;
|
||||||
|
active: boolean;
|
||||||
|
authentication: Authentication;
|
||||||
|
certificateInfo: ICertificateInfo[];
|
||||||
|
clientFlags: IClientAuthenticationFlags;
|
||||||
|
parameters: string[];
|
||||||
|
subType: SubType;
|
||||||
|
}
|
||||||
12
playwright-tests/business-logic/types/CcisApi.ts
Normal file
12
playwright-tests/business-logic/types/CcisApi.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export interface IPostSaveFakeResponseRequestBody {
|
||||||
|
accountNumber: string,
|
||||||
|
responseType: string,
|
||||||
|
key: string,
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDeleteFakeResponseParams {
|
||||||
|
accountNumber: string,
|
||||||
|
key: string,
|
||||||
|
responseType: string
|
||||||
|
}
|
||||||
10
playwright-tests/business-logic/types/Client.ts
Normal file
10
playwright-tests/business-logic/types/Client.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface IClient {
|
||||||
|
clientTag: string,
|
||||||
|
accountName: string
|
||||||
|
clientFlags: Partial<IClientFlags>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IClientFlags {
|
||||||
|
isTpaEnabled: boolean;
|
||||||
|
isAuthenticationEnabled: boolean;
|
||||||
|
}
|
||||||
81
playwright-tests/business-logic/types/CustomerDetails.ts
Normal file
81
playwright-tests/business-logic/types/CustomerDetails.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { DamageLocation, DamageSubLocation, DamageType as DamageCause, WindshieldDamage, ServiceLocation, EndorsementType, VehicleLookupType, PartQuestionType, PaymentType } from "./Enums";
|
||||||
|
import { IAddress } from "./IAddress";
|
||||||
|
|
||||||
|
export interface ICustomerDetails {
|
||||||
|
firstName: string,
|
||||||
|
lastName: string,
|
||||||
|
email: string,
|
||||||
|
phoneNumber: string,
|
||||||
|
notes: string,
|
||||||
|
address: IAddress,
|
||||||
|
apptDate?: string,
|
||||||
|
packagePrice?: String
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IClaimDetails {
|
||||||
|
policyNumber: string,
|
||||||
|
policyDeductible: number,
|
||||||
|
damageDate: string,
|
||||||
|
damageCause: DamageCause
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEndorsementDetails {
|
||||||
|
endorsementType: EndorsementType,
|
||||||
|
isOnPolicy: boolean, // Should we expect this endorsement to appear?
|
||||||
|
isClickYes: boolean // Should we click Yes or No?
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVehicleDetails {
|
||||||
|
year: string,
|
||||||
|
make: string,
|
||||||
|
model: string,
|
||||||
|
style?: string,
|
||||||
|
vin?: string,
|
||||||
|
licensePlateNumber?: string,
|
||||||
|
licensePlateState?: string,
|
||||||
|
vehicleLookupType?: VehicleLookupType,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppointmentDetails {
|
||||||
|
serviceLocation: ServiceLocation,
|
||||||
|
appointmentDate?: Date,
|
||||||
|
shopAddress?: string, // Used for in-shop
|
||||||
|
serviceAddress?: IAddress, // Used for mobile
|
||||||
|
isVehicleProtected?: boolean // Used for mobile
|
||||||
|
alternateServiceZip?: string // Used for in-shop and drop-off if doing service in a different ZIP
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPartQuestion {
|
||||||
|
isOnPage: boolean,
|
||||||
|
optionToSelect: string,
|
||||||
|
partQuestionType: PartQuestionType,
|
||||||
|
secondaryQuestionOptionToSelect?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPaymentDetails {
|
||||||
|
paymentType: PaymentType,
|
||||||
|
username?: string,
|
||||||
|
password?: string,
|
||||||
|
cardNumber?: string,
|
||||||
|
expirationMonth?: string,
|
||||||
|
expirationYear?: string,
|
||||||
|
cvv?: string,
|
||||||
|
billingAddress?: IAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
// export interface IVehicleDamage {
|
||||||
|
// isRearWindowDamage?: boolean,
|
||||||
|
// windshieldDamage?: WindshieldDamage,
|
||||||
|
// windowDamage?: IWindowDamage
|
||||||
|
// }
|
||||||
|
|
||||||
|
// interface ISideDoorDamage {
|
||||||
|
// isFrontDoor: boolean,
|
||||||
|
// isBackDoor: boolean,
|
||||||
|
// isQuarterPanel: boolean
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export interface IWindowDamage {
|
||||||
|
// driverSideDamage?: ISideDoorDamage,
|
||||||
|
// passengerSideDamage?: ISideDoorDamage
|
||||||
|
// }
|
||||||
38
playwright-tests/business-logic/types/DigitalApi.ts
Normal file
38
playwright-tests/business-logic/types/DigitalApi.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
export interface IPartsOrQuestionsResponse {
|
||||||
|
partsOrQuestions: IPartOrQuestion[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPartOrQuestion {
|
||||||
|
glassPiece: IGlassPiece,
|
||||||
|
parts: IPart[],
|
||||||
|
partQuestions: IPartQuestion[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGlassPiece {
|
||||||
|
name: string,
|
||||||
|
location: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPart {
|
||||||
|
childPartQuestions: IPartQuestion[],
|
||||||
|
basePartNumber: string,
|
||||||
|
safelitePartNumber: string,
|
||||||
|
color: string,
|
||||||
|
requiresRecalibration: boolean,
|
||||||
|
recalibrationType: null, // TODO: Add types
|
||||||
|
canSafeliteRecalibrate: boolean,
|
||||||
|
requiresCapabilityQuestions: boolean,
|
||||||
|
childParts: IChildPart[],
|
||||||
|
partNumber: string,
|
||||||
|
description: string,
|
||||||
|
partType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPartQuestion {
|
||||||
|
// TODO: Define
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IChildPart {
|
||||||
|
partNumber: string,
|
||||||
|
safelitePartNumber: string
|
||||||
|
}
|
||||||
169
playwright-tests/business-logic/types/Enums.ts
Normal file
169
playwright-tests/business-logic/types/Enums.ts
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
export enum DamageLocation {}
|
||||||
|
|
||||||
|
export enum DamageSubLocation {}
|
||||||
|
|
||||||
|
export enum DamageType {
|
||||||
|
Rock = 'Rock from road',
|
||||||
|
Vandalism = 'Vandalism',
|
||||||
|
Theft = 'Attempted theft or theft',
|
||||||
|
Hail = 'Hailstorm',
|
||||||
|
HurricaneStorm = 'Hurricane/storm',
|
||||||
|
Collision = 'Collision',
|
||||||
|
Object = 'Object hit glass',
|
||||||
|
Other = 'Other/unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ServiceLocation {}
|
||||||
|
|
||||||
|
export enum ServicePackage {
|
||||||
|
GlassOnly = 'Glass service only',
|
||||||
|
Standard = 'Standard',
|
||||||
|
Premium = 'Premium'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EndorsementType {
|
||||||
|
Educator = '01',
|
||||||
|
EmployeeParking = '03'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ConsoleColor {
|
||||||
|
Default = "",
|
||||||
|
Red = "\x1b[31m",
|
||||||
|
Green = "\x1b[32m",
|
||||||
|
Yellow = "\x1b[33m",
|
||||||
|
Orange = "\x1b[202m",
|
||||||
|
Blue = "\x1b[34m",
|
||||||
|
Magenta = "\x1b[35m",
|
||||||
|
Cyan = "\x1b[36m",
|
||||||
|
Reset = "\x1b[0m"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ResultTypes {
|
||||||
|
None = 0,
|
||||||
|
Skipped = 1,
|
||||||
|
Success = 2,
|
||||||
|
Failure = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VehicleDamage {
|
||||||
|
WindshieldOneChip,
|
||||||
|
WindshieldTwoChips,
|
||||||
|
WindshieldThreeChips,
|
||||||
|
WindshieldCrack,
|
||||||
|
RearWindow,
|
||||||
|
DriverFrontDoor,
|
||||||
|
DriverRearDoor,
|
||||||
|
DriverVentGlass,
|
||||||
|
DriverQuarterPanel,
|
||||||
|
DriverSlidingDoor,
|
||||||
|
PassengerFrontDoor,
|
||||||
|
PassengerRearDoor,
|
||||||
|
PassengerVentGlass,
|
||||||
|
PassengerQuarterPanel
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VehicleLookupType {
|
||||||
|
Vin,
|
||||||
|
Address,
|
||||||
|
LicensePlateNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ServiceLocation {
|
||||||
|
Mobile,
|
||||||
|
InShop,
|
||||||
|
DropOff
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PartQuestionType {
|
||||||
|
WindshieldColor = 'Windshield-Single',
|
||||||
|
DriverFrontColor = 'Driver-Front',
|
||||||
|
DriverQuarterColor = 'Driver-Quarter',
|
||||||
|
DriverRearColor = 'Driver-Back',
|
||||||
|
DriverVentColor = 'Driver-Vent',
|
||||||
|
PassengerFrontColor = 'Passenger-Front',
|
||||||
|
PassengerQuarterColor = 'Passenger-Quarter',
|
||||||
|
PassengerRearColor = 'Passenger-Back',
|
||||||
|
PassengerVentColor = 'Passenger-Vent',
|
||||||
|
RearWindowColor = 'Rear-Stationary',
|
||||||
|
LeatherSeats = 'question-0-1',
|
||||||
|
DriverSideColor = 'Driver-SideDoor',
|
||||||
|
LaneKeepAssist = 'question-0-1'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PaymentType{
|
||||||
|
Credit = "Credit",
|
||||||
|
AfterPay = "AfterPay",
|
||||||
|
Paypal = "Paypal",
|
||||||
|
PayAtService = "Pay at Service"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enums from original ISSCQA Project
|
||||||
|
// TODO: re-evaluate
|
||||||
|
export enum WindshieldDamage{
|
||||||
|
Crack,
|
||||||
|
OneChip,
|
||||||
|
TwoChips,
|
||||||
|
ThreeChips
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum NumChips{
|
||||||
|
One = 1,
|
||||||
|
Two,
|
||||||
|
Three
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AppointmentType{
|
||||||
|
Inshop = "Inshop",
|
||||||
|
Mobile = "Mobile",
|
||||||
|
DropOff = "Drop-off"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ServiceProvider{
|
||||||
|
Safelite = "Safelite",
|
||||||
|
ThirdParty = "Third Party"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SideDoorDamage{
|
||||||
|
Passenger = "Passenger",
|
||||||
|
Driver = "Driver"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BailoutCode {
|
||||||
|
Unknown = 0,
|
||||||
|
SaveSessionError,
|
||||||
|
VehicleNotFound,
|
||||||
|
VehicleLookupError,
|
||||||
|
CoverageStatementInvalidState,
|
||||||
|
DoNotSeeMyShop,
|
||||||
|
PricingResponseError,
|
||||||
|
TPANotEnabled,
|
||||||
|
RequestCallback,
|
||||||
|
HeavyTruckVehicle,
|
||||||
|
NoPartsAvailable,
|
||||||
|
PartsServiceError,
|
||||||
|
SafeliteNotTheProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SignatureAlgorithm {
|
||||||
|
SHA1,
|
||||||
|
SHA256,
|
||||||
|
SHA512
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CertificateType {
|
||||||
|
RSA
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SubType {
|
||||||
|
Advanced = 'Advanced',
|
||||||
|
Essential = 'Essential',
|
||||||
|
Unknown = 'Unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Authentication {
|
||||||
|
None = 'None',
|
||||||
|
RSAToken = 'RSAToken',
|
||||||
|
RSATokenEncParams = 'RSATokenEncParams',
|
||||||
|
RSATokenEncParamsOneTimeUse = 'RSATokenEncParamsOneTimeUse',
|
||||||
|
Unknown = 'Unknown',
|
||||||
|
}
|
||||||
11
playwright-tests/business-logic/types/FrameworkConfig.ts
Normal file
11
playwright-tests/business-logic/types/FrameworkConfig.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
type FrameworkConfig = {
|
||||||
|
// company: NetJetsCompanies;
|
||||||
|
// companyString: string;
|
||||||
|
// currency: CurrencyTypes;
|
||||||
|
createResources: boolean;
|
||||||
|
destroyResources: boolean;
|
||||||
|
maxAllotmentHours: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FrameworkConfig;
|
||||||
7
playwright-tests/business-logic/types/IAddress.ts
Normal file
7
playwright-tests/business-logic/types/IAddress.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
export interface IAddress {
|
||||||
|
street: string,
|
||||||
|
city: string,
|
||||||
|
state: string,
|
||||||
|
postalCode: string,
|
||||||
|
country: string
|
||||||
|
}
|
||||||
11
playwright-tests/business-logic/types/IBailoutFlags.ts
Normal file
11
playwright-tests/business-logic/types/IBailoutFlags.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default interface IBailoutFlags {
|
||||||
|
isVehicleSelectBailout: boolean,
|
||||||
|
isDoNotSeeMyShopBailout: boolean,
|
||||||
|
isTpaNotEnabledBailout: boolean,
|
||||||
|
isRequestCallbackBailout: boolean,
|
||||||
|
isHeavyTruckVehicleBailout: boolean,
|
||||||
|
isPartsServiceErrorBailout: boolean,
|
||||||
|
isSafeliteNotTheProviderBailout: boolean,
|
||||||
|
isVehicleLookupBailout: boolean,
|
||||||
|
isPriceServiceErrorBailout: boolean
|
||||||
|
}
|
||||||
29
playwright-tests/business-logic/types/IDisposable.ts
Normal file
29
playwright-tests/business-logic/types/IDisposable.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import LoggingUtils from "@impl/utils/LoggingUtils";
|
||||||
|
import { ConsoleColor } from "@business-logic/types/Enums";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
|
||||||
|
export interface IDisposable {
|
||||||
|
disposeAll(): void;
|
||||||
|
setupAll(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class DisposableBase implements IDisposable {
|
||||||
|
protected abstract setup(): Promise<void>;
|
||||||
|
protected abstract dispose(): Promise<void>;
|
||||||
|
|
||||||
|
public async setupAll(): Promise<void> {
|
||||||
|
if (!TestCase.FrameworkConfig.destroyResources) {
|
||||||
|
LoggingUtils.log(TestCase.Constants.CREATION_HALTED, ConsoleColor.Yellow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async disposeAll(): Promise<void> {
|
||||||
|
if (!TestCase.FrameworkConfig.destroyResources || !TestCase.FrameworkConfig.createResources) {
|
||||||
|
LoggingUtils.log(TestCase.Constants.DISPOSE_HALTED, ConsoleColor.Yellow);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
playwright-tests/business-logic/types/ITestCase.ts
Normal file
16
playwright-tests/business-logic/types/ITestCase.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import ITestPages from "@business-logic/types/ITestPages";
|
||||||
|
import Validations from "./Validations";
|
||||||
|
import { ITestData } from "./ITestData";
|
||||||
|
|
||||||
|
export default interface ITestCase {
|
||||||
|
readonly testID?: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly tags: string[];
|
||||||
|
|
||||||
|
readonly validations?: Validations;
|
||||||
|
readonly tempData?: any[]
|
||||||
|
|
||||||
|
readonly testData: Partial<ITestData>;
|
||||||
|
|
||||||
|
pages?: ITestPages;
|
||||||
|
}
|
||||||
38
playwright-tests/business-logic/types/ITestData.ts
Normal file
38
playwright-tests/business-logic/types/ITestData.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { ServicePackage, VehicleDamage } from "./Enums"
|
||||||
|
import { IAppointmentDetails, IClaimDetails, ICustomerDetails, IEndorsementDetails, IPartQuestion, IPaymentDetails, IVehicleDetails } from "./CustomerDetails"
|
||||||
|
import IBailoutFlags from "./IBailoutFlags"
|
||||||
|
import { IPart } from "./DigitalApi"
|
||||||
|
|
||||||
|
export interface ITestData {
|
||||||
|
isMockTesting: boolean,
|
||||||
|
clientTag: string,
|
||||||
|
isDuplicateClaim: boolean,
|
||||||
|
isPolicyFound: boolean, // Effective difference between advanced and essential
|
||||||
|
isUseVehicleOnPolicy: boolean, // Should we use the vehicle on the policy?
|
||||||
|
isNoComp: boolean, // Is this a NoComp policy?
|
||||||
|
isItac: boolean, // Is this an ITAC scenario?
|
||||||
|
hasStateLawPopup: boolean, // Are we expecting a state law pop-up on ProviderSelectionPage?
|
||||||
|
hasOemEndorsement: boolean, // OEM Endorsement does not appear on endorsements page, so it has a separate flag.
|
||||||
|
hasMilitaryWarning: boolean, // Are we expecting military base warning on the Service Location page?
|
||||||
|
bailoutFlags: Partial<IBailoutFlags>,
|
||||||
|
endorsements: IEndorsementDetails[],
|
||||||
|
isReplace: boolean,
|
||||||
|
partQuestions: IPartQuestion[], // part-questions page has unrelated part questions like leather seats
|
||||||
|
vehiclePartQuestions: IPartQuestion[], // vehicle-parts page usually has glass question
|
||||||
|
capabilityQuestions: IPartQuestion[], // Capability questions page usually has questions about autonomous driving features
|
||||||
|
policySoap: string, // Include policy soap if you want to create a mock policy
|
||||||
|
isSafelite: boolean,
|
||||||
|
servicePackage: ServicePackage,
|
||||||
|
customerDetails: ICustomerDetails,
|
||||||
|
claimDetails: IClaimDetails,
|
||||||
|
vehicleDetails: IVehicleDetails,
|
||||||
|
editVehicleDetails: IVehicleDetails, // Vehicle details entered after clicking "Edit vehicle" on Vehicle Details page
|
||||||
|
otherVehiclesOnPolicy: IVehicleDetails[], // IF defined, we validate that the vehicles are present.
|
||||||
|
vehicleDamage: VehicleDamage[], // Array of vehicle damage
|
||||||
|
appointmentDetails: IAppointmentDetails,
|
||||||
|
paymentDetails: IPaymentDetails // Payment information
|
||||||
|
isRecalNotification: boolean,
|
||||||
|
isRecalWarning: boolean,
|
||||||
|
isSeparateApptsWarning: boolean, // IF true, check for the separate appts warning on VehicleDamagePage
|
||||||
|
isAuthenticationRequired: boolean
|
||||||
|
}
|
||||||
61
playwright-tests/business-logic/types/ITestPages.ts
Normal file
61
playwright-tests/business-logic/types/ITestPages.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { BailoutPage } from "../../pages/BailoutPage";
|
||||||
|
import CapabilityQuestionsPage from "../../pages/CapabilityQuestionsPage";
|
||||||
|
import { ContactConfirmationPage } from "../../pages/ContactConfirmationPage";
|
||||||
|
import { ContactDetailsPage } from "../../pages/ContactDetailsPage";
|
||||||
|
import { CoverageStatementPage } from "../../pages/CoverageStatementPage";
|
||||||
|
import { DuplicateCheckPage } from "../../pages/DuplicateCheckPage";
|
||||||
|
import { EndorsementsPage } from "../../pages/EndorsementsPage";
|
||||||
|
import { OrderConfirmationPage } from "../../pages/OrderConfirmationPage";
|
||||||
|
import { PartQuestionsPage } from "../../pages/PartQuestionsPage";
|
||||||
|
import { PaymentMethodPage } from "../../pages/PaymentMethodPage";
|
||||||
|
import { PaymentPage } from "../../pages/PaymentPage";
|
||||||
|
import { PaypalPage } from "../../pages/PaypalPage";
|
||||||
|
import { PolicyHolderDetailsPage } from "../../pages/PolicyHolderDetailsPage";
|
||||||
|
import { PolicyVehiclesPage } from "../../pages/PolicyVehiclesPage";
|
||||||
|
import { ProviderPreferencePage } from "../../pages/ProviderPreferencePage";
|
||||||
|
import { SchedulePage } from "../../pages/SchedulePage";
|
||||||
|
import { ServiceLocationPage } from "../../pages/ServiceLocationPage";
|
||||||
|
import { ServicePackagesPage } from "../../pages/ServicePackagesPage";
|
||||||
|
import { TpaConfirmationPage } from "../../pages/TpaConfirmationPage";
|
||||||
|
import { TpaSearchPage } from "../../pages/TpaSearchPage";
|
||||||
|
import { TpaSubmitPage } from "../../pages/TpaSubmitPage";
|
||||||
|
import { VehicleDamagePage } from "../../pages/VehicleDamagePage";
|
||||||
|
import { VehicleLookupAddressPage } from "../../pages/VehicleLookupAddressPage";
|
||||||
|
import { VehicleLookupLicensePage } from "../../pages/VehicleLookupLicensePage";
|
||||||
|
import { VehicleLookupPage } from "../../pages/VehicleLookupPage";
|
||||||
|
import VehiclePartQuestionsPage from "../../pages/VehiclePartsPage";
|
||||||
|
import { VehicleSelectionPage } from "../../pages/VehicleSelectionPage";
|
||||||
|
import { VinLookupPage } from "../../pages/VinLookupPage";
|
||||||
|
import { WelcomePage } from "../../pages/WelcomePage";
|
||||||
|
|
||||||
|
export default interface ITestPages {
|
||||||
|
bailoutPage: BailoutPage,
|
||||||
|
capabilityQuestionsPage: CapabilityQuestionsPage,
|
||||||
|
contactConfirmationPage: ContactConfirmationPage,
|
||||||
|
contactDetailsPage: ContactDetailsPage,
|
||||||
|
coverageStatementPage: CoverageStatementPage,
|
||||||
|
duplicateCheckPage: DuplicateCheckPage,
|
||||||
|
endorsementsPage: EndorsementsPage,
|
||||||
|
orderConfirmationPage: OrderConfirmationPage,
|
||||||
|
partQuestionsPage: PartQuestionsPage,
|
||||||
|
paymentMethodPage: PaymentMethodPage,
|
||||||
|
paymentPage: PaymentPage,
|
||||||
|
paypalPage: PaypalPage,
|
||||||
|
policyHolderDetailsPage: PolicyHolderDetailsPage,
|
||||||
|
policyVehiclesPage: PolicyVehiclesPage,
|
||||||
|
providerPreferencePage: ProviderPreferencePage,
|
||||||
|
schedulePage: SchedulePage,
|
||||||
|
serviceLocationPage: ServiceLocationPage,
|
||||||
|
servicePackagesPage: ServicePackagesPage,
|
||||||
|
tpaConfirmationPage: TpaConfirmationPage,
|
||||||
|
tpaSearchPage: TpaSearchPage,
|
||||||
|
tpaSubmitPage: TpaSubmitPage,
|
||||||
|
vehicleDamagePage: VehicleDamagePage,
|
||||||
|
vehicleLookupPage: VehicleLookupPage,
|
||||||
|
vehiclePartQuestionsPage: VehiclePartQuestionsPage,
|
||||||
|
vehicleSelectionPage: VehicleSelectionPage,
|
||||||
|
vinLookupPage: VinLookupPage,
|
||||||
|
vehicleLookupAddressPage: VehicleLookupAddressPage,
|
||||||
|
vehicleLookupLicensePage: VehicleLookupLicensePage,
|
||||||
|
welcomePage: WelcomePage
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default interface IValidationExpectation {
|
||||||
|
// readonly Minicart: boolean;
|
||||||
|
// readonly SummaryTotals: boolean;
|
||||||
|
// readonly SummarySection: boolean;
|
||||||
|
// readonly AgreementPricing: boolean;
|
||||||
|
// readonly AllotmentStatus: boolean;
|
||||||
|
// readonly ChevronStatus: boolean;
|
||||||
|
// readonly AgreementDates: boolean;
|
||||||
|
// readonly LineItems: boolean;
|
||||||
|
// readonly AccountingTotal: boolean;
|
||||||
|
}
|
||||||
13
playwright-tests/business-logic/types/IValidations.ts
Normal file
13
playwright-tests/business-logic/types/IValidations.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
|
||||||
|
export default interface IValidations {
|
||||||
|
// readonly minicartLineItems: boolean;
|
||||||
|
// readonly minicartTotals: boolean;
|
||||||
|
// readonly cartSummary: boolean;
|
||||||
|
// readonly agreementPricing: boolean;
|
||||||
|
// readonly dealDescription: boolean;
|
||||||
|
// readonly allotmentStatus: boolean;
|
||||||
|
// readonly chevronStatus: boolean;
|
||||||
|
// readonly agreementDates: boolean;
|
||||||
|
// readonly lineItems: boolean;
|
||||||
|
// readonly accountingTotal: boolean;
|
||||||
|
}
|
||||||
241
playwright-tests/business-logic/types/RuleEngine.ts
Normal file
241
playwright-tests/business-logic/types/RuleEngine.ts
Normal file
|
|
@ -0,0 +1,241 @@
|
||||||
|
import { BuiltInRules, builtInRules } from "@business-logic/rules/RuleEngineBuiltins";
|
||||||
|
import LoggingUtils from "@impl/utils/LoggingUtils";
|
||||||
|
import { ConsoleColor, ResultTypes } from "@business-logic/types/Enums";
|
||||||
|
|
||||||
|
export type Rule<T> = {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
check: (obj: T) => boolean;
|
||||||
|
dependsOn?: number[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ValidationOptions {
|
||||||
|
public skipBuiltIns: boolean;
|
||||||
|
public exclude: number[];
|
||||||
|
public throwOnError: boolean = true;
|
||||||
|
public errorsOnly: boolean = false;
|
||||||
|
|
||||||
|
constructor(options: {
|
||||||
|
throwOnError?: boolean;
|
||||||
|
errorsOnly?: boolean;
|
||||||
|
skipBuiltIns?: boolean;
|
||||||
|
exclude?: number[];
|
||||||
|
} = {}) {
|
||||||
|
this.throwOnError = options.throwOnError ?? true, this.errorsOnly = options.errorsOnly ?? true, this.skipBuiltIns = options.skipBuiltIns ?? false;
|
||||||
|
this.exclude = options.exclude ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RuleEngine<T> {
|
||||||
|
private rules: Rule<T>[] = [];
|
||||||
|
private nextCustomRuleId = 1;
|
||||||
|
|
||||||
|
static readonly BuiltInRuleIds: Record<BuiltInRules, number> = {} as Record<BuiltInRules, number>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.addBuiltInRules(builtInRules as { id: BuiltInRules; name: string; check: (obj: T) => boolean; dependsOn?: BuiltInRules[]; }[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addBuiltInRules(builtInRules: { id: BuiltInRules; name: string; check: (obj: T) => boolean; dependsOn?: BuiltInRules[]; }[]): void {
|
||||||
|
builtInRules.forEach(rule => this.addRuleInternal(rule.name, rule.check, rule.id, rule.dependsOn));
|
||||||
|
}
|
||||||
|
|
||||||
|
private addRuleInternal(name: string, check: (obj: T) => boolean, builtInId: BuiltInRules, dependsOn?: BuiltInRules[]): number {
|
||||||
|
const id = builtInId;
|
||||||
|
const dependsOnIds = dependsOn?.map(dep => dep as number);
|
||||||
|
this.rules.push({ id, name, check, dependsOn: dependsOnIds });
|
||||||
|
RuleEngine.BuiltInRuleIds[builtInId] = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
addRule(name: string, check: (obj: T) => boolean, options?: { dependsOn?: (number | BuiltInRules)[]; }): Rule<T> {
|
||||||
|
if (this.nextCustomRuleId >= 1000)
|
||||||
|
throw new Error("Maximum number of custom rules (999) has been reached.");
|
||||||
|
|
||||||
|
const id = this.nextCustomRuleId++;
|
||||||
|
const dependsOn = options?.dependsOn?.map(dep => typeof dep === "number" ? dep : RuleEngine.BuiltInRuleIds[dep]);
|
||||||
|
const rule: Rule<T> = { id, name, check, dependsOn };
|
||||||
|
this.rules.push(rule);
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
|
||||||
|
when(condition: (obj: T) => boolean): WhenClause<T> {
|
||||||
|
return new WhenClause(this, condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkRuleById(id: number | BuiltInRules, obj: T): boolean {
|
||||||
|
const ruleId = typeof id === "number" ? id : RuleEngine.BuiltInRuleIds[id];
|
||||||
|
const rule = this.rules.find(r => r.id === ruleId);
|
||||||
|
return rule ? rule.check(obj) : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(obj: T): ValidationResult[] {
|
||||||
|
const sortedRules = this.sortRules();
|
||||||
|
const results = new Map<number, boolean>();
|
||||||
|
const resultSummary: ValidationResult[] = [];
|
||||||
|
|
||||||
|
sortedRules.forEach(rule => {
|
||||||
|
const dependenciesValid = rule.dependsOn ? rule.dependsOn.every(dep => results.get(dep)) : true;
|
||||||
|
const result = dependenciesValid && rule.check(obj);
|
||||||
|
results.set(rule.id, result);
|
||||||
|
|
||||||
|
const resultType: ResultTypes = dependenciesValid ? result ? ResultTypes.Success : ResultTypes.Failure : ResultTypes.Skipped;
|
||||||
|
|
||||||
|
resultSummary.push(ValidationResult.FromRule(rule.name, rule.id, resultType, `Rule: "${rule.name}". Result: ${ResultTypes[resultType]}`));
|
||||||
|
});
|
||||||
|
return resultSummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortRules(): Rule<T>[] {
|
||||||
|
const sortedRules: Rule<T>[] = [];
|
||||||
|
const rulesMap = new Map(this.rules.map(rule => [rule.id, rule]));
|
||||||
|
|
||||||
|
const visit = (rule: Rule<T>, visited: Set<number>, stack: Set<number>) => {
|
||||||
|
if (stack.has(rule.id))
|
||||||
|
throw new Error(`Circular dependency detected in rule: ${rule.name}!`);
|
||||||
|
|
||||||
|
if (!visited.has(rule.id)) {
|
||||||
|
stack.add(rule.id);
|
||||||
|
if (rule.dependsOn) {
|
||||||
|
for (const dependency of rule.dependsOn) {
|
||||||
|
const dependencyRule = rulesMap.get(dependency);
|
||||||
|
if (dependencyRule)
|
||||||
|
visit(dependencyRule, visited, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stack.delete(rule.id);
|
||||||
|
visited.add(rule.id);
|
||||||
|
sortedRules.push(rule);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const visited = new Set<number>();
|
||||||
|
for (const rule of this.rules)
|
||||||
|
visit(rule, visited, new Set<number>());
|
||||||
|
|
||||||
|
return sortedRules;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRulesByName(name: string): Rule<T>[] {
|
||||||
|
return this.rules.filter(rule => rule.name === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRulesByID(id: number): Rule<T>[] {
|
||||||
|
return this.rules.filter(rule => rule.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PrintValidationResults(results: ValidationResult[], options: ValidationOptions = new ValidationOptions()) {
|
||||||
|
if (options.skipBuiltIns)
|
||||||
|
results = results.filter(x => x.ruleID < 1000);
|
||||||
|
|
||||||
|
if (options.exclude)
|
||||||
|
results = results.filter(x => !options.exclude!.includes(x.ruleID));
|
||||||
|
|
||||||
|
if (options.errorsOnly)
|
||||||
|
results = results.filter(x => x.result == ResultTypes.Failure);
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
LoggingUtils.log("Rule Validation:", ConsoleColor.Blue);
|
||||||
|
results.forEach(x => LoggingUtils.log(` ${LoggingUtils.icon(x.result!)} Rule: [${x.ruleID}] "${x.ruleName}". Result: ${ResultTypes[x.result!]}`, x.result == ResultTypes.Success ? ConsoleColor.Green : ConsoleColor.Red));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.throwOnError && results.filter(x => x.result != ResultTypes.Success).length > 0)
|
||||||
|
throw new Error("Rule Validation Errors");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WhenClause<T> {
|
||||||
|
constructor(private ruleEngine: RuleEngine<T>, private condition: (obj: T) => boolean) { }
|
||||||
|
|
||||||
|
then(consequent: (obj: T) => boolean): DescriptionClause<T> {
|
||||||
|
return new DescriptionClause(this.ruleEngine, (obj: T) => {
|
||||||
|
return !this.condition(obj) || consequent(obj);
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DescriptionClause<T> {
|
||||||
|
private dependencies: (number | BuiltInRules)[] = [];
|
||||||
|
|
||||||
|
constructor(private ruleEngine: RuleEngine<T>, private check: (obj: T) => boolean, dependencies: (number | BuiltInRules)[] = []) {
|
||||||
|
this.dependencies = dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
because(description: string): Rule<T> {
|
||||||
|
return this.ruleEngine.addRule(description, this.check, { dependsOn: this.dependencies });
|
||||||
|
}
|
||||||
|
|
||||||
|
dependsOn(...dependencies: (number | BuiltInRules | (number | BuiltInRules)[])[]): DescriptionClause<T> {
|
||||||
|
const flatDependencies = dependencies.flat();
|
||||||
|
const tmp = (obj: T) => {
|
||||||
|
const dependenciesMet = flatDependencies.every(depId => this.ruleEngine.checkRuleById(depId, obj));
|
||||||
|
return dependenciesMet && this.check(obj);
|
||||||
|
};
|
||||||
|
return new DescriptionClause(this.ruleEngine, tmp, [...this.dependencies, ...flatDependencies]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ValidationResult {
|
||||||
|
value: any;
|
||||||
|
ruleName: string;
|
||||||
|
ruleID: number;
|
||||||
|
error: string | null;
|
||||||
|
message: string;
|
||||||
|
result: ResultTypes | null;
|
||||||
|
|
||||||
|
public static FromRule(ruleName: string, ruleID: number, result: ResultTypes, message: string) {
|
||||||
|
const retval = new ValidationResult();
|
||||||
|
retval.ruleName = ruleName;
|
||||||
|
retval.ruleID = ruleID;
|
||||||
|
retval.result = result;
|
||||||
|
retval.message = message;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FromSuccess(value: any, message: string): ValidationResult {
|
||||||
|
const retval = new ValidationResult();
|
||||||
|
retval.value = value;
|
||||||
|
retval.message = message;
|
||||||
|
retval.result = ResultTypes.Success;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FromFailure(error: string): ValidationResult {
|
||||||
|
const retval = new ValidationResult();
|
||||||
|
retval.error = error;
|
||||||
|
retval.result = ResultTypes.Failure;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrintValidationResults(results: ValidationResult[], options: ValidationOptions) {
|
||||||
|
var errors = results.filter(x => x.result == ResultTypes.Failure);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
LoggingUtils.log("Type Validation Errors:", ConsoleColor.Red);
|
||||||
|
errors.forEach((message) => LoggingUtils.log(` ${LoggingUtils.icon(false)} ${message.error}`));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
var successes = results.filter(x => x.result != ResultTypes.Failure);
|
||||||
|
|
||||||
|
const suffix = "is valid.";
|
||||||
|
successes.sort((a, b) => {
|
||||||
|
const aa = a.message.endsWith(suffix);
|
||||||
|
const bb = b.message.endsWith(suffix);
|
||||||
|
|
||||||
|
return Number(bb) - Number(aa);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!options.errorsOnly && successes.length > 0) {
|
||||||
|
LoggingUtils.log("Type Validation Messages:", ConsoleColor.Green);
|
||||||
|
successes.forEach((message) => LoggingUtils.log(` ${LoggingUtils.icon(true)} ${message.message}`));
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.throwOnError && errors.length > 0)
|
||||||
|
throw new Error("Validation Errors");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HasError(results: ValidationResult[]): boolean {
|
||||||
|
return results.filter(x => x.result == ResultTypes.Failure).length > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
playwright-tests/business-logic/types/Test.ts
Normal file
44
playwright-tests/business-logic/types/Test.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { test as base } from "@playwright/test";
|
||||||
|
import type { Page, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, TestInfo as PlaywrightTestInfo } from "@playwright/test";
|
||||||
|
import ITestCase from "@business-logic/types/ITestCase";
|
||||||
|
import { RuleEngine, ValidationOptions } from "@business-logic/types/RuleEngine";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import Soft from "@business-logic/validations/Soft";
|
||||||
|
import FakerUtils from "@impl/utils/FakerUtils";
|
||||||
|
import LoggingUtils from "@impl/utils/LoggingUtils";
|
||||||
|
|
||||||
|
export type TestFunction = (args: PlaywrightTestArgs & PlaywrightTestOptions & PlaywrightWorkerArgs & PlaywrightWorkerOptions, testInfo: TestInfo) => void | Promise<void>;
|
||||||
|
export type TestRunnerFunction = (page: Page, testInfo: TestInfo, /*testCase: TestCase*/) => void | Promise<void>;
|
||||||
|
|
||||||
|
export interface TestInfo extends PlaywrightTestInfo {
|
||||||
|
testCase: TestCase;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const test = base.extend<{ testInfo: TestInfo; }>({
|
||||||
|
// Do not use the fixture because it is not extended, and will cause a circular reference error
|
||||||
|
// Use ({}, use, testInfo) not ({ testInfo }, use)
|
||||||
|
testInfo: async ({}, use, testInfo) => {
|
||||||
|
await use(testInfo as TestInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function addSmokeTagToRandomTest(testCases: ITestCase[]) {
|
||||||
|
const index = Math.floor(Math.random() * testCases.length);
|
||||||
|
testCases.at(index)?.tags.push("@smoke");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareTest(testData: ITestCase, testRunner: TestRunnerFunction, validationOptions: ValidationOptions, ruleEngine: RuleEngine<TestCase>): [string, object, TestFunction] {
|
||||||
|
const name = testData.name;
|
||||||
|
const attributes = { tag: TestCase.getTags(testData) };
|
||||||
|
const testFunction: TestFunction = ({ page }, testInfo) => {
|
||||||
|
// Do not instantiate TestCase outside of this function,
|
||||||
|
// otherwise it will be instantiated several times for each test case
|
||||||
|
Soft.initialize(testInfo, page);
|
||||||
|
testInfo.testCase = new TestCase(testData, validationOptions, FakerUtils.getRandomTestID());
|
||||||
|
const results = ruleEngine.validate(testInfo.testCase);
|
||||||
|
RuleEngine.PrintValidationResults(results, validationOptions);
|
||||||
|
console.log(LoggingUtils.logValidate(`TestID: ${testInfo.testCase.testID}`, true));
|
||||||
|
return testRunner(page, testInfo, /*testInfo.testCase*/);
|
||||||
|
};
|
||||||
|
return [name, attributes, testFunction];
|
||||||
|
}
|
||||||
227
playwright-tests/business-logic/types/TestCase.ts
Normal file
227
playwright-tests/business-logic/types/TestCase.ts
Normal file
|
|
@ -0,0 +1,227 @@
|
||||||
|
import PropertyUtils from "@impl/utils/PropertyUtils";
|
||||||
|
import { formatTag } from "@impl/utils/TaggingUtils";
|
||||||
|
import { Page } from "@playwright/test";
|
||||||
|
import { TestInfo } from "@business-logic/types/Test";
|
||||||
|
import { ConsoleColor } from "@business-logic/types/Enums";
|
||||||
|
import FrameworkConfig from "@business-logic/types/FrameworkConfig";
|
||||||
|
import { DisposableBase } from "@business-logic/types/IDisposable";
|
||||||
|
import ITestCase from "@business-logic/types/ITestCase";
|
||||||
|
import { ValidationOptions, ValidationResult } from "@business-logic/types/RuleEngine";
|
||||||
|
import Soft, { SoftError } from "@business-logic/validations/Soft";
|
||||||
|
import Validations from "./Validations";
|
||||||
|
import FakerUtils from "@impl/utils/FakerUtils";
|
||||||
|
import { ITestData } from "./ITestData";
|
||||||
|
import { BailoutPage } from "../../pages/BailoutPage";
|
||||||
|
import { ContactConfirmationPage } from "../../pages/ContactConfirmationPage";
|
||||||
|
import { CoverageStatementPage } from "../../pages/CoverageStatementPage";
|
||||||
|
import ITestPages from "./ITestPages";
|
||||||
|
import { DuplicateCheckPage } from "../../pages/DuplicateCheckPage";
|
||||||
|
import { OrderConfirmationPage } from "../../pages/OrderConfirmationPage";
|
||||||
|
import { PartQuestionsPage } from "../../pages/PartQuestionsPage";
|
||||||
|
import { PaymentMethodPage } from "../../pages/PaymentMethodPage";
|
||||||
|
import { PaymentPage } from "../../pages/PaymentPage";
|
||||||
|
import { PaypalPage } from "../../pages/PaypalPage";
|
||||||
|
import { PolicyHolderDetailsPage } from "../../pages/PolicyHolderDetailsPage";
|
||||||
|
import { PolicyVehiclesPage } from "../../pages/PolicyVehiclesPage";
|
||||||
|
import { ProviderPreferencePage } from "../../pages/ProviderPreferencePage";
|
||||||
|
import { SchedulePage } from "../../pages/SchedulePage";
|
||||||
|
import { ServiceLocationPage } from "../../pages/ServiceLocationPage";
|
||||||
|
import { ServicePackagesPage } from "../../pages/ServicePackagesPage";
|
||||||
|
import { TpaConfirmationPage } from "../../pages/TpaConfirmationPage";
|
||||||
|
import { TpaSearchPage } from "../../pages/TpaSearchPage";
|
||||||
|
import { TpaSubmitPage } from "../../pages/TpaSubmitPage";
|
||||||
|
import { VehicleDamagePage } from "../../pages/VehicleDamagePage";
|
||||||
|
import { VehicleLookupPage } from "../../pages/VehicleLookupPage";
|
||||||
|
import { VehicleSelectionPage } from "../../pages/VehicleSelectionPage";
|
||||||
|
import { VinLookupPage } from "../../pages/VinLookupPage";
|
||||||
|
import { WelcomePage } from "../../pages/WelcomePage";
|
||||||
|
import { ContactDetailsPage } from "../../pages/ContactDetailsPage";
|
||||||
|
import { EndorsementsPage } from "../../pages/EndorsementsPage";
|
||||||
|
import { VehicleLookupAddressPage } from "../../pages/VehicleLookupAddressPage";
|
||||||
|
import { VehicleLookupLicensePage } from "../../pages/VehicleLookupLicensePage";
|
||||||
|
import CcisApiUtil from "@impl/api/CcisApiUtil";
|
||||||
|
import MockPolicyData from "@business-logic/data/MockPolicyData";
|
||||||
|
import VehiclePartQuestionsPage from "../../pages/VehiclePartsPage";
|
||||||
|
import CapabilityQuestionsPage from "../../pages/CapabilityQuestionsPage";
|
||||||
|
|
||||||
|
export default class TestCase extends DisposableBase implements ITestCase {
|
||||||
|
public static FrameworkConfig: FrameworkConfig = {
|
||||||
|
// company: NetJetsCompanies.NJA,
|
||||||
|
// companyString: NetJetsCompanies[NetJetsCompanies.NJA],
|
||||||
|
// currency: CurrencyTypes.USD,
|
||||||
|
createResources: true, //process.env.FW_CREATE_RESOURCES! === "true",
|
||||||
|
destroyResources: true, // process.env.FW_DESTROY_RESOURCES! === "true",
|
||||||
|
maxAllotmentHours: Number(process.env.FW_MAX_ALLOTMENT_HOURS)
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly Constants = class {
|
||||||
|
static readonly DISPOSE_HALTED: string = "FrameworkConfig is set to NOT destroy resources. Teardown halted!";
|
||||||
|
static readonly CREATION_HALTED: string = "FrameworkConfig is set to NOT create resources. Preparation halted!";
|
||||||
|
};
|
||||||
|
|
||||||
|
public readonly testID?: string;
|
||||||
|
public readonly name: string;
|
||||||
|
public readonly tags: string[];
|
||||||
|
|
||||||
|
public readonly testData: Partial<ITestData>;
|
||||||
|
|
||||||
|
public readonly validations?: Validations;
|
||||||
|
public readonly tempData?: any[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
public location: Location;
|
||||||
|
public alternateLocation?: Location;
|
||||||
|
|
||||||
|
public pages: ITestPages;
|
||||||
|
|
||||||
|
public constructor(data: ITestCase, validationOptions: ValidationOptions = new ValidationOptions(), testID: string) {
|
||||||
|
super();
|
||||||
|
Object.assign(this, data);
|
||||||
|
this.testID = FakerUtils.getRandomTestID();
|
||||||
|
|
||||||
|
const validationResults: ValidationResult[] = [];
|
||||||
|
|
||||||
|
this.name = PropertyUtils.getValue<this>(data, TestCase.name, validationResults, { isRequired: true }, x => x.name);
|
||||||
|
this.tags = PropertyUtils.getValue<this>(data, TestCase.name, validationResults, { isRequired: true }, x => x.tags);
|
||||||
|
this.validations = PropertyUtils.getValue<this>(data, TestCase.name, validationResults, { isRequired: false }, x => x.validations);
|
||||||
|
|
||||||
|
// if (PropertyUtils.hasProperty<this>(data, TestCase.name, validationResults, { isRequired: false }, x => x.oldTransactions))
|
||||||
|
// this.oldTransactions = data.oldTransactions.map((item: any) => new TransactionData(item, validationResults, this.testID));
|
||||||
|
|
||||||
|
// if (PropertyUtils.hasProperty<this>(data, TestCase.name, validationResults, { isRequired: true }, x => x.transaction))
|
||||||
|
// this.transaction = new TransactionData(data.transaction, validationResults, this.testID);
|
||||||
|
|
||||||
|
ValidationResult.PrintValidationResults(validationResults, validationOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getTags(testCase: ITestCase): string[] {
|
||||||
|
let retval = [
|
||||||
|
...testCase.tags,
|
||||||
|
formatTag(testCase.name),
|
||||||
|
// formatTag(testCase.transaction.programType)
|
||||||
|
];
|
||||||
|
|
||||||
|
// if (testCase.transaction.aircraft)
|
||||||
|
// retval.push(formatTag(testCase.transaction.aircraft.type));
|
||||||
|
|
||||||
|
// testCase.oldTransactions.forEach((t) => {
|
||||||
|
// retval.push(formatTag(t.programType));
|
||||||
|
// if (t.aircraft)
|
||||||
|
// retval.push(formatTag(t.aircraft.type));
|
||||||
|
// });
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async afterEachMethod(page: Page, testInfo: TestInfo) {
|
||||||
|
const originalStatus = testInfo.status;
|
||||||
|
|
||||||
|
if (Soft.hasFailedAssertions())
|
||||||
|
testInfo.status = "failed";
|
||||||
|
|
||||||
|
for (let a of Soft.getFailedAssertions())
|
||||||
|
testInfo.errors.push(new SoftError(a));
|
||||||
|
|
||||||
|
// NOTE: This try catch is here because the tests when locally, frequently
|
||||||
|
// fail tests on screenshot, which we do not want, is it make it
|
||||||
|
// harder to parse any other real errors we do care about. T.S. 9.5.2024
|
||||||
|
try {
|
||||||
|
await testInfo.attach("End of Test Screenshot", {
|
||||||
|
body: await page.screenshot({ fullPage: true }),
|
||||||
|
contentType: 'image/png'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (testInfo.testCase)
|
||||||
|
await testInfo.testCase.disposeAll();
|
||||||
|
|
||||||
|
const seconds: string = String(testInfo.duration / 1000);
|
||||||
|
const minutes: string = (testInfo.duration / 1000 / 60).toFixed(2);
|
||||||
|
const validationErrors: string = Soft.hasFailedAssertions() ? ` with ${Soft.getFailureCount()} validation errors` : "";
|
||||||
|
|
||||||
|
if (originalStatus == "failed")
|
||||||
|
console.log(`${ConsoleColor.Red}Test failed after ${seconds} seconds, or roughly ${minutes} minutes${validationErrors}.${ConsoleColor.Reset}`);
|
||||||
|
else {
|
||||||
|
if (testInfo.status == "passed")
|
||||||
|
console.log(`${ConsoleColor.Green}Test finished successfully in ${seconds} seconds, or roughly ${minutes} minutes.${ConsoleColor.Reset}`);
|
||||||
|
else
|
||||||
|
console.log(`${ConsoleColor.Orange}Test finished in ${seconds} seconds, or roughly ${minutes} minutes${validationErrors}.${ConsoleColor.Reset}`);
|
||||||
|
}
|
||||||
|
console.log(page.url());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setup(): Promise<void> {
|
||||||
|
if (this.testData.policySoap && this.testData.claimDetails) {
|
||||||
|
const apiUtil = new CcisApiUtil();
|
||||||
|
|
||||||
|
// Create Policy
|
||||||
|
const req = MockPolicyData.generateCreateMockPolicyRequest(this.testData.claimDetails.policyNumber, this.testData.policySoap);
|
||||||
|
console.log('Policy Number: ' + this.testData.claimDetails.policyNumber + ' Damage Date: ' + this.testData.claimDetails.damageDate + ' Postal code: ' + this.testData.customerDetails?.address.postalCode!);
|
||||||
|
const res = await apiUtil.createFakeResponse(req);
|
||||||
|
console.log("Fake Policy creation status" + res.status); // For debug use console.dir(res);
|
||||||
|
|
||||||
|
// Create Claim Registration
|
||||||
|
const claimRegReq = MockPolicyData.generateCreateClaimRegistrationRequest(this.testData.claimDetails.policyNumber);
|
||||||
|
const claimRegRes = await apiUtil.createFakeResponse(claimRegReq);
|
||||||
|
console.log("Fake claim registration creation status: " + claimRegRes.status); //For debug use console.dir(claimRegRes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public setupPages(page: Page): void {
|
||||||
|
this.pages = {
|
||||||
|
bailoutPage: new BailoutPage(page),
|
||||||
|
capabilityQuestionsPage: new CapabilityQuestionsPage(page),
|
||||||
|
contactConfirmationPage: new ContactConfirmationPage(page),
|
||||||
|
contactDetailsPage: new ContactDetailsPage(page),
|
||||||
|
coverageStatementPage: new CoverageStatementPage(page),
|
||||||
|
duplicateCheckPage: new DuplicateCheckPage(page),
|
||||||
|
endorsementsPage: new EndorsementsPage(page),
|
||||||
|
orderConfirmationPage: new OrderConfirmationPage(page),
|
||||||
|
partQuestionsPage: new PartQuestionsPage(page),
|
||||||
|
paymentMethodPage: new PaymentMethodPage(page),
|
||||||
|
paymentPage: new PaymentPage(page),
|
||||||
|
paypalPage: new PaypalPage(page),
|
||||||
|
policyHolderDetailsPage: new PolicyHolderDetailsPage(page),
|
||||||
|
policyVehiclesPage: new PolicyVehiclesPage(page),
|
||||||
|
providerPreferencePage: new ProviderPreferencePage(page),
|
||||||
|
schedulePage: new SchedulePage(page),
|
||||||
|
serviceLocationPage: new ServiceLocationPage(page),
|
||||||
|
servicePackagesPage: new ServicePackagesPage(page),
|
||||||
|
tpaConfirmationPage: new TpaConfirmationPage(page),
|
||||||
|
tpaSearchPage: new TpaSearchPage(page),
|
||||||
|
tpaSubmitPage: new TpaSubmitPage(page),
|
||||||
|
vehicleDamagePage: new VehicleDamagePage(page),
|
||||||
|
vehicleLookupPage: new VehicleLookupPage(page),
|
||||||
|
vehiclePartQuestionsPage: new VehiclePartQuestionsPage(page),
|
||||||
|
vehicleSelectionPage: new VehicleSelectionPage(page),
|
||||||
|
vinLookupPage: new VinLookupPage(page),
|
||||||
|
vehicleLookupAddressPage: new VehicleLookupAddressPage(page),
|
||||||
|
vehicleLookupLicensePage: new VehicleLookupLicensePage(page),
|
||||||
|
welcomePage: new WelcomePage(page)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async dispose(): Promise<void> {
|
||||||
|
if (this.testData.policySoap && this.testData.claimDetails) {
|
||||||
|
const apiUtil = new CcisApiUtil();
|
||||||
|
|
||||||
|
// Delete policy
|
||||||
|
const policyDeleteRes = await apiUtil.deleteFakeResponse({
|
||||||
|
accountNumber: '550036', // TODO: Change when we have more clients
|
||||||
|
key: this.testData.claimDetails.policyNumber,
|
||||||
|
responseType: 'Policy'
|
||||||
|
});
|
||||||
|
console.log("Delete 'Fake policy' status: " + policyDeleteRes.status); //For debug use console.dir(policyDeleteRes);
|
||||||
|
|
||||||
|
// Delete Claim Registration
|
||||||
|
const crDeleteRes = await apiUtil.deleteFakeResponse({
|
||||||
|
accountNumber: '550036', // TODO: Change when we have more clients
|
||||||
|
key: this.testData.claimDetails.policyNumber,
|
||||||
|
responseType: 'ClaimRegistration'
|
||||||
|
});
|
||||||
|
console.log("Delete 'Fake Claim Registration policy' status: " + crDeleteRes.status); //For debug use console.dir(crDeleteRes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
playwright-tests/business-logic/types/Validations.ts
Normal file
29
playwright-tests/business-logic/types/Validations.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import PropertyUtils from "@impl/utils/PropertyUtils";
|
||||||
|
import IValidations from "./IValidations";
|
||||||
|
import { ValidationResult } from "./RuleEngine";
|
||||||
|
|
||||||
|
export default class Validations implements IValidations {
|
||||||
|
public readonly minicartLineItems: boolean;
|
||||||
|
public readonly minicartTotals: boolean;
|
||||||
|
public readonly cartSummary: boolean;
|
||||||
|
public readonly agreementPricing: boolean;
|
||||||
|
public readonly dealDescription: boolean;
|
||||||
|
public readonly allotmentStatus: boolean;
|
||||||
|
public readonly chevronStatus: boolean;
|
||||||
|
public readonly agreementDates: boolean;
|
||||||
|
public readonly lineItems: boolean;
|
||||||
|
public readonly accountingTotal: boolean;
|
||||||
|
|
||||||
|
public constructor(json: any, validationResults: ValidationResult[]) {
|
||||||
|
this.minicartLineItems = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.minicartLineItems);
|
||||||
|
this.minicartTotals = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.minicartTotals);
|
||||||
|
this.cartSummary = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.cartSummary);
|
||||||
|
this.dealDescription = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.dealDescription);
|
||||||
|
this.agreementPricing = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.agreementPricing);
|
||||||
|
this.allotmentStatus = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.allotmentStatus);
|
||||||
|
this.chevronStatus = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.chevronStatus);
|
||||||
|
this.agreementDates = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.agreementDates);
|
||||||
|
this.lineItems = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.lineItems);
|
||||||
|
this.accountingTotal = PropertyUtils.getValue<this>(json, Validations.name, validationResults, { isRequired: true }, x => x.accountingTotal);
|
||||||
|
}
|
||||||
|
}
|
||||||
179
playwright-tests/business-logic/validations/Soft.ts
Normal file
179
playwright-tests/business-logic/validations/Soft.ts
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
import LoggingUtils from '@impl/utils/LoggingUtils';
|
||||||
|
import { Page } from '@playwright/test';
|
||||||
|
import { TestInfo } from '@business-logic/types/Test';
|
||||||
|
import { TestInfoError } from "@playwright/test";
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { expect as pw_expect } from '@playwright/test';
|
||||||
|
|
||||||
|
export default class Soft {
|
||||||
|
private static _instance: Soft | null = null;
|
||||||
|
private testInfo: TestInfo;
|
||||||
|
private page: Page;
|
||||||
|
private failedAssertions: string[] = [];
|
||||||
|
private errorCounter: number = 0;
|
||||||
|
private errorsOnly: boolean = false;
|
||||||
|
|
||||||
|
private constructor(testInfo: TestInfo, page: Page) {
|
||||||
|
this.testInfo = testInfo;
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static initialize(testInfo: TestInfo, page: Page): void {
|
||||||
|
Soft._instance = new Soft(testInfo, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static setOptions(options: { errorsOnly: boolean }): void {
|
||||||
|
Soft.getInstance().errorsOnly = options.errorsOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getOptions(): { errorsOnly: boolean } {
|
||||||
|
return { errorsOnly: Soft.getInstance().errorsOnly };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getInstance(): Soft {
|
||||||
|
if (!Soft._instance)
|
||||||
|
throw new Error("Soft is not initialized. Call Soft.initialize(testInfo, page) first!");
|
||||||
|
return Soft._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handleAssertion(
|
||||||
|
matcherFull: string,
|
||||||
|
matcherDisplay: string,
|
||||||
|
matcherFunction: () => Promise<void>,
|
||||||
|
reason?: string): Promise<void> {
|
||||||
|
reason = reason ? reason : ''
|
||||||
|
const reasonText = reason ? `'${reason}' ` : '';
|
||||||
|
try {
|
||||||
|
await matcherFunction();
|
||||||
|
if (!this.errorsOnly)
|
||||||
|
console.log(LoggingUtils.logValidate(`Validation ${reasonText}passed: ${matcherDisplay}!`, true));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(LoggingUtils.logValidate(`Validation ${reasonText}failed: ${matcherDisplay}!`, false));
|
||||||
|
//const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
this.failedAssertions.push(`\n${++this.errorCounter}_Validation ${reasonText}failed:\n${LoggingUtils.replaceEmptyLinesWithMiddleDot(matcherFull)}!\n${error.stack}\n`);
|
||||||
|
// NOTE: I find it more helpful for the stack trace to be included here, so we know which line the validation is failing
|
||||||
|
try {
|
||||||
|
const screenshot: Buffer = await this.page.screenshot({ fullPage: true });
|
||||||
|
const name: string = LoggingUtils.sanitizeFileName(`${this.errorCounter}_Validation_${reason}${DateTime.now().valueOf()}`);
|
||||||
|
await this.testInfo.attach(name, {
|
||||||
|
body: screenshot,
|
||||||
|
contentType: 'image/png'
|
||||||
|
});
|
||||||
|
} catch(err){
|
||||||
|
// ohwell
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static expect(value: any, reason?: string): ExpectationChain {
|
||||||
|
return new ExpectationChain(value, Soft.getInstance(), reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getFailedAssertions(): string[] {
|
||||||
|
return Soft.getInstance().failedAssertions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static hasFailedAssertions(): boolean {
|
||||||
|
return Soft.getInstance().failedAssertions.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getFailureCount(): number {
|
||||||
|
return Soft.getInstance().failedAssertions.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static clearFailedAssertions(): void {
|
||||||
|
Soft.getInstance().failedAssertions = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SoftError implements TestInfoError {
|
||||||
|
public readonly message?: string | undefined;
|
||||||
|
constructor(msg: string) {
|
||||||
|
this.message = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExpectationChain {
|
||||||
|
constructor(private value: any, private soft: Soft, private reason?: string) { }
|
||||||
|
|
||||||
|
private formatValue(value: any): string {
|
||||||
|
return LoggingUtils.truncateString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toBe(expected: any): Promise<ExpectationChain> {
|
||||||
|
await this.soft.handleAssertion(
|
||||||
|
`expect(${this.value}).toBe(${expected})`,
|
||||||
|
`expect(${this.formatValue(this.value)}).toBe(${this.formatValue(expected)})`,
|
||||||
|
async () => await pw_expect(this.value).toBe(expected),
|
||||||
|
this.reason
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toEqual(expected: any): Promise<ExpectationChain> {
|
||||||
|
await this.soft.handleAssertion(
|
||||||
|
`expect(${JSON.stringify(this.value)}).toEqual(${expected})`,
|
||||||
|
`expect(${this.formatValue(JSON.stringify(this.value))}).toEqual(${this.formatValue(expected)})`,
|
||||||
|
async () => await pw_expect(this.value).toEqual(expected),
|
||||||
|
this.reason
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toContain(expected: any): Promise<ExpectationChain> {
|
||||||
|
await this.soft.handleAssertion(
|
||||||
|
`expect(${this.value}).toContain(${expected})`,
|
||||||
|
`expect(${this.formatValue(this.value)}).toContain(${this.formatValue(expected)})`,
|
||||||
|
async () => await pw_expect(this.value).toContain(expected),
|
||||||
|
this.reason
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toHaveText(expected: string): Promise<ExpectationChain> {
|
||||||
|
await this.soft.handleAssertion(
|
||||||
|
`expect(${this.value}).toHaveText(${expected})`,
|
||||||
|
`expect(${this.formatValue(this.value)}).toHaveText(${this.formatValue(expected)})`,
|
||||||
|
async () => {
|
||||||
|
if (typeof this.value.textContent !== 'function') {
|
||||||
|
throw new Error('value does not have a textContent method');
|
||||||
|
}
|
||||||
|
const text = await this.value.textContent();
|
||||||
|
await pw_expect(text).toHaveText(expected);
|
||||||
|
}, this.reason
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toBeGreaterThan(expected: number): Promise<ExpectationChain> {
|
||||||
|
await this.soft.handleAssertion(
|
||||||
|
`expect(${this.value}).toBeGreaterThan(${expected})`,
|
||||||
|
`expect(${this.formatValue(this.value)}).toBeGreaterThan(${this.formatValue(expected)})`,
|
||||||
|
async () => await pw_expect(this.value).toBeGreaterThan(expected),
|
||||||
|
this.reason
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toBeTruthy(): Promise<ExpectationChain> {
|
||||||
|
await this.soft.handleAssertion(
|
||||||
|
`expect(${this.value}).toBeTruthy`,
|
||||||
|
`expect(${this.formatValue(this.value)}).toBeTruthy`,
|
||||||
|
async () => await pw_expect(this.value).toBeTruthy(),
|
||||||
|
this.reason
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async toBeFalsy(): Promise<ExpectationChain> {
|
||||||
|
await this.soft.handleAssertion(
|
||||||
|
`expect(${this.value}).toBeFalsy`,
|
||||||
|
`expect(${this.formatValue(this.value)}).toBeFalsy`,
|
||||||
|
async () => await pw_expect(this.value).toBeFalsy(),
|
||||||
|
this.reason
|
||||||
|
);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
playwright-tests/eslint.config.js
Normal file
4
playwright-tests/eslint.config.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
// import tsEslint from "typescript-eslint"
|
||||||
|
const tsEslint = require('typescript-eslint');
|
||||||
|
module.exports =
|
||||||
|
tsEslint.configs.strict
|
||||||
21
playwright-tests/impl/api/AdminServiceApiUtil.ts
Normal file
21
playwright-tests/impl/api/AdminServiceApiUtil.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import axios, { type AxiosInstance } from 'axios';
|
||||||
|
import { buildQueryString, httpGet } from '@utils/HttpUtils'
|
||||||
|
import { IClientAuthentication, IClientSignatureRequest, IClientSignatureResponse } from '@business-logic/types/Authentication';
|
||||||
|
|
||||||
|
|
||||||
|
const adminServiceUrl = process.env.ADMIN_SERVICE_API_URL || '';
|
||||||
|
|
||||||
|
const axiosInstance: AxiosInstance = axios.create({
|
||||||
|
baseURL: adminServiceUrl,
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
});
|
||||||
|
|
||||||
|
const INSURANCE_SERVICE_ROUTE = 'insurance';
|
||||||
|
|
||||||
|
export const getClientAuthByClientTag = async (clientTag: string): Promise<IClientAuthentication> => {
|
||||||
|
return await httpGet<IClientAuthentication>(axiosInstance, `${INSURANCE_SERVICE_ROUTE}/client-info?clientTag=${clientTag}`);
|
||||||
|
}
|
||||||
|
export const getClientSignature = async (request: IClientSignatureRequest): Promise<IClientSignatureResponse> => {
|
||||||
|
const params = buildQueryString(request);
|
||||||
|
return await httpGet<IClientSignatureResponse>(axiosInstance, `${INSURANCE_SERVICE_ROUTE}/signature?${params.toString()}`);
|
||||||
|
}
|
||||||
47
playwright-tests/impl/api/ApiResponseInterceptUtil.ts
Normal file
47
playwright-tests/impl/api/ApiResponseInterceptUtil.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { IPartsOrQuestionsResponse } from "@business-logic/types/DigitalApi";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData";
|
||||||
|
import { expect, Response } from "@playwright/test";
|
||||||
|
|
||||||
|
export default class ApiResponseInterceptUtil {
|
||||||
|
readonly testData: Partial<ITestData>;
|
||||||
|
|
||||||
|
constructor(testData: Partial<ITestData>) {
|
||||||
|
this.testData = testData;
|
||||||
|
|
||||||
|
// Bind callback functions to the class instance so 'this' is usable within a callback
|
||||||
|
this.handleInterceptResponse = this.handleInterceptResponse.bind(this);
|
||||||
|
this.handlePartsOrQuestionsResponse = this.handlePartsOrQuestionsResponse.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleInterceptResponse(response: Response) {
|
||||||
|
if (!response.url().includes('safelite.io')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlPath = response.url().split('safelite.io')[1]; // get api path
|
||||||
|
|
||||||
|
switch(urlPath) {
|
||||||
|
case '/parts/api/v1/parts/parts-or-questions':
|
||||||
|
await this.handlePartsOrQuestionsResponse(response);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (response.url().endsWith('/parts/parts-or-questions') && response.status() === 200) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handlePartsOrQuestionsResponse(response: Response) {
|
||||||
|
if (this.testData.hasOemEndorsement) {
|
||||||
|
const partsRes = (await response.json()) as IPartsOrQuestionsResponse;
|
||||||
|
for ( const partOrQuestion of partsRes.partsOrQuestions) {
|
||||||
|
for (const part of partOrQuestion.parts) {
|
||||||
|
expect.soft(part.partNumber.endsWith('OEM'),
|
||||||
|
`handlePartsOrQuestionsResponse>> OEM endorsement was expected, but "${part.partType}" with part number "${part.partNumber}" is not OEM.`
|
||||||
|
).toEqual(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
playwright-tests/impl/api/CcisApiUtil.ts
Normal file
28
playwright-tests/impl/api/CcisApiUtil.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { IDeleteFakeResponseParams, IPostSaveFakeResponseRequestBody } from "@business-logic/types/CcisApi";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const authToken = process.env.CCIS_API_AUTH || '';
|
||||||
|
const ccisUrl = process.env.CCIS_API_URL || '';
|
||||||
|
|
||||||
|
export default class CcisApiUtil {
|
||||||
|
readonly baseUrl = ccisUrl;
|
||||||
|
readonly postCreateMockPolicyUrl = `${this.baseUrl}/ccis/api/v1/admin/fakeResponses/save`
|
||||||
|
readonly deleteFakeResponseUrl = `${this.baseUrl}/ccis/api/v1/admin/fakeResponses`
|
||||||
|
|
||||||
|
createFakeResponse(requestBody: IPostSaveFakeResponseRequestBody) {
|
||||||
|
return axios.post(this.postCreateMockPolicyUrl, requestBody, {
|
||||||
|
headers: {
|
||||||
|
'X-Mule-Origin-Verify': authToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteFakeResponse(params: IDeleteFakeResponseParams) {
|
||||||
|
const deleteUrl = `${this.deleteFakeResponseUrl}/${params.accountNumber}/${params.key}/${params.responseType}`;
|
||||||
|
return axios.delete(deleteUrl, {
|
||||||
|
headers: {
|
||||||
|
'X-Mule-Origin-Verify': authToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
26
playwright-tests/impl/utils/DateUtils.ts
Normal file
26
playwright-tests/impl/utils/DateUtils.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
export function formatDate(date: Date) {
|
||||||
|
const isoString = date.toISOString();
|
||||||
|
return isoString.slice(0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatTime(date: Date) {
|
||||||
|
return date.toLocaleTimeString('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextWeekday(date?: Date) {
|
||||||
|
if (!date) {
|
||||||
|
date = new Date();
|
||||||
|
date.setHours(8,0,0,0);
|
||||||
|
}
|
||||||
|
const dayOfWeek = date.getDay();
|
||||||
|
const daysToAdd = dayOfWeek === 5? 3: 1; // Add 3 days if today is friday. Otherwise add 1.
|
||||||
|
|
||||||
|
const nextDay = new Date(date);
|
||||||
|
nextDay.setDate(date.getDate() + daysToAdd);
|
||||||
|
|
||||||
|
return nextDay;
|
||||||
|
}
|
||||||
76
playwright-tests/impl/utils/EnumUtils.ts
Normal file
76
playwright-tests/impl/utils/EnumUtils.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
function isFlags(enumObj: object): boolean {
|
||||||
|
const values = Object.values(enumObj).filter(v => typeof v === "number");
|
||||||
|
return values.some(v => v !== 0 && (v & (v - 1)) === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidEnumValue(value: any, enumType: object): boolean {
|
||||||
|
if (isFlags(enumType)) {
|
||||||
|
const allFlags = Object.values(enumType).reduce((acc, val) => typeof val === "number" ? acc | val : acc, 0);
|
||||||
|
return typeof value === "number" && (value & allFlags) === value;
|
||||||
|
} else {
|
||||||
|
return Object.values(enumType).includes(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnumValues(enumObj: object): string[] | number[] {
|
||||||
|
if (!isFlags(enumObj))
|
||||||
|
return Object.values(enumObj);
|
||||||
|
return Object.values(enumObj).filter(value => typeof value === "number") as number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnumString<T extends { [key: string]: string | number }>(enumObj: T, flags: number): string {
|
||||||
|
if (flags === 0)
|
||||||
|
return Object.keys(enumObj).find(key => enumObj[key] === 0) || 'None';
|
||||||
|
|
||||||
|
const attributes = Object.entries(enumObj).filter(([key, value]) =>
|
||||||
|
typeof value === 'number' && value !== 0 && (flags & value) === value)
|
||||||
|
.map(([key]) => key);
|
||||||
|
|
||||||
|
return attributes.length === 1 ? attributes[0] : attributes.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateEnumProperty(json: any, property: string, enumType: object): number | string | null {
|
||||||
|
if (json.hasOwnProperty(property) && isValidEnumValue(json[property], enumType))
|
||||||
|
return json[property];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasFlag(value: number | string, flag: number | string, enumType: object): boolean {
|
||||||
|
if (isFlags(enumType))
|
||||||
|
return typeof value === "number" && typeof flag === "number" && (value & flag) === flag;
|
||||||
|
else
|
||||||
|
return value === flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addFlag(value: number, flag: number, enumType: object): number {
|
||||||
|
if (isFlags(enumType))
|
||||||
|
return value | flag;
|
||||||
|
else
|
||||||
|
throw new Error("Attempted to add flag on non-flags enum");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeFlag(value: number, flag: number, enumType: object): number {
|
||||||
|
if (isFlags(enumType))
|
||||||
|
return value & ~flag;
|
||||||
|
else
|
||||||
|
throw new Error("Attempted to remove flag on non-flags enum");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleFlag(value: number, flag: number, enumType: object): number {
|
||||||
|
if (isFlags(enumType))
|
||||||
|
return value ^ flag;
|
||||||
|
else
|
||||||
|
throw new Error("Attempted to toggle flag on non-flags enum");
|
||||||
|
}
|
||||||
|
|
||||||
|
const EnumUtils = {
|
||||||
|
hasFlag,
|
||||||
|
addFlag,
|
||||||
|
removeFlag,
|
||||||
|
toggleFlag,
|
||||||
|
validateEnumProperty,
|
||||||
|
getEnumValues,
|
||||||
|
getEnumString
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EnumUtils;
|
||||||
56
playwright-tests/impl/utils/FakerUtils.ts
Normal file
56
playwright-tests/impl/utils/FakerUtils.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
export default class FakerUtils {
|
||||||
|
private static NUMBERS = '0123456789';
|
||||||
|
private static UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
private static LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
private static ALPHABET = FakerUtils.UPPERCASE + FakerUtils.LOWERCASE;
|
||||||
|
private static ALPHANUMERIC = FakerUtils.NUMBERS + FakerUtils.ALPHABET;
|
||||||
|
|
||||||
|
private static generateRandomString(length: number, characters: string): string {
|
||||||
|
return Array.from(crypto.getRandomValues(new Uint8Array(length)))
|
||||||
|
.map(byte => characters[byte % characters.length])
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static generateRandomNumber(min: number, max: number): number {
|
||||||
|
const range = max - min + 1;
|
||||||
|
const bytesNeeded = Math.ceil(Math.log2(range) / 8);
|
||||||
|
const randomBytes = new Uint8Array(bytesNeeded);
|
||||||
|
crypto.getRandomValues(randomBytes);
|
||||||
|
const randomValue = randomBytes.reduce((acc, byte) => (acc << 8) + byte, 0);
|
||||||
|
return min + (randomValue % range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getRandomTestID(): string {
|
||||||
|
return FakerUtils.generateRandomString(8, FakerUtils.ALPHANUMERIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getRandomProperty(obj: Record<string, any>): string {
|
||||||
|
const keys = Object.keys(obj);
|
||||||
|
const randomIndex = FakerUtils.generateRandomNumber(0, keys.length - 1);
|
||||||
|
return keys[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getRandomTail(registrationPrefix: string = "XX", testID: string = ""): string {
|
||||||
|
return FakerUtils.formatString(FakerUtils.generateRandomString(8, FakerUtils.ALPHANUMERIC));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getRandomEmail(domainSuffix: string = "@test.com", testID: string = ""): string {
|
||||||
|
const retval = FakerUtils.formatString("{0}{1}", FakerUtils.generateRandomString(8, FakerUtils.ALPHANUMERIC), domainSuffix);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getRandomLastName(testID: string = " - "): string {
|
||||||
|
return FakerUtils.formatString(" - {0}", FakerUtils.generateRandomString(21, FakerUtils.ALPHABET));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getObjectName(prefix: string, testID: string = ""): string {
|
||||||
|
return FakerUtils.formatString("{0} - {1}", prefix, FakerUtils.generateRandomString(21, FakerUtils.ALPHANUMERIC));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static formatString(template: string, ...args: (string | (() => string))[]): string {
|
||||||
|
return template.replace(/\{(\d+)\}/g, (match, index) => {
|
||||||
|
const arg = args[parseInt(index)];
|
||||||
|
return typeof arg === 'function' ? arg() : arg || '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
22
playwright-tests/impl/utils/FileUtils.ts
Normal file
22
playwright-tests/impl/utils/FileUtils.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export function writeFileToLocalCache(fileNameWithExtension: string, fileContents: string) {
|
||||||
|
const folderPath = path.join(process.cwd(), '.debug', '.cache');
|
||||||
|
const filePath = path.join(folderPath, fileNameWithExtension);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create the folder if it doesn't exist
|
||||||
|
if (!fs.existsSync(folderPath)) {
|
||||||
|
fs.mkdirSync(folderPath, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the data to the file
|
||||||
|
fs.writeFileSync(filePath, fileContents);
|
||||||
|
|
||||||
|
console.log(`File "${filePath}" created successfully.`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating the file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
92
playwright-tests/impl/utils/HttpUtils.ts
Normal file
92
playwright-tests/impl/utils/HttpUtils.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import { Page } from '@playwright/test';
|
||||||
|
import { type AxiosInstance, type AxiosResponse } from 'axios';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
export async function httpGet<T>(client: AxiosInstance, url: string): Promise<T> {
|
||||||
|
const [isSuccess, response] = await handleHttp(client.get<T>(url));
|
||||||
|
if(isSuccess) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`An error occurred calling GET ${url}\nError:${response}`);
|
||||||
|
throw response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function httpPost<T, D>(client: AxiosInstance, url: string, data: D): Promise<T> {
|
||||||
|
const [isSuccess, response] = await handleHttp(client.post<T>(url, data));
|
||||||
|
if(isSuccess) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`An error occurred calling POST ${url}\nError:${response}`);
|
||||||
|
throw response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleHttp<T>(request: Promise<AxiosResponse<T>>): Promise<[isSuccess: true, data: T] | [isSuccess: false, error: Error]> {
|
||||||
|
return request.then(data => {
|
||||||
|
return [true, data.data] as [true, T]
|
||||||
|
}).catch((error: Error) => {
|
||||||
|
return [false, error] as [false, Error]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildQueryString<T>(data: T): URLSearchParams {
|
||||||
|
const params: Record<string, string> = {};
|
||||||
|
for (const key in data) {
|
||||||
|
const value = data[key];
|
||||||
|
params[key] = `${value}`;
|
||||||
|
}
|
||||||
|
return new URLSearchParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function forceAPIError(page: Page, endpoint: string) {
|
||||||
|
page.route('**/*', (route) => {
|
||||||
|
return route.request().url().includes(endpoint)
|
||||||
|
? route.abort()
|
||||||
|
: route.continue()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility method to return mock response based on endpoint and scenario
|
||||||
|
export function getMockedApiResponse(endpoint: string, scenario: string): object | null {
|
||||||
|
const mockResponsesDir = path.resolve(__dirname, '../../tests/mockResponses');
|
||||||
|
const configFilePath = path.join(mockResponsesDir, 'mockResponsesConfig.json');
|
||||||
|
|
||||||
|
if (fs.existsSync(configFilePath)) {
|
||||||
|
const config = JSON.parse(fs.readFileSync(configFilePath, 'utf-8'));
|
||||||
|
const scenarioConfig = config[scenario];
|
||||||
|
const commonConfig = config['common'];
|
||||||
|
|
||||||
|
let mockFilePath = scenarioConfig ? scenarioConfig[endpoint] : null;
|
||||||
|
if (!mockFilePath && commonConfig) {
|
||||||
|
mockFilePath = commonConfig[endpoint];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mockFilePath) {
|
||||||
|
const filePath = path.join(mockResponsesDir, mockFilePath);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const mockResponse = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
return JSON.parse(mockResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utility method for mocking API responses
|
||||||
|
export function mockApiResponse(page: Page, endpoint: string, scenario: string, mockTestingFlag: boolean) {
|
||||||
|
const mockResponse = getMockedApiResponse(endpoint, scenario);
|
||||||
|
page.route(`**/${endpoint}`, route => {
|
||||||
|
if (mockResponse && mockTestingFlag) {
|
||||||
|
route.fulfill({
|
||||||
|
status: 200,
|
||||||
|
contentType: 'application/json',
|
||||||
|
body: JSON.stringify(mockResponse)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
route.continue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
128
playwright-tests/impl/utils/LoggingUtils.ts
Normal file
128
playwright-tests/impl/utils/LoggingUtils.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import { ConsoleColor, ResultTypes } from "@business-logic/types/Enums";
|
||||||
|
|
||||||
|
export default class LoggingUtils {
|
||||||
|
|
||||||
|
public static CONSOLE_WIDTH: number = 100;
|
||||||
|
//public static ICON_OK: string = "✅";
|
||||||
|
public static ICON_OK: string = "\u2705";
|
||||||
|
//public static ICON_SKIP: string = "⏩";
|
||||||
|
public static ICON_SKIP: string = "\u23ED";
|
||||||
|
//public static ICON_WARNING: string = "⚠️";
|
||||||
|
public static ICON_WARNING: string = "\u26A0\uFE0F";
|
||||||
|
//public static ICON_FAIL: string = "❗";
|
||||||
|
public static ICON_FAIL: string = "\u2757";
|
||||||
|
|
||||||
|
public static log(message: string | null, color: ConsoleColor = ConsoleColor.Default): void {
|
||||||
|
if (message == null)
|
||||||
|
return;
|
||||||
|
console.log(`${color}%s${ConsoleColor.Reset}`, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static icon(value: boolean): string;
|
||||||
|
public static icon(value: ResultTypes): string;
|
||||||
|
public static icon(value: any): string {
|
||||||
|
if (typeof value === "boolean")
|
||||||
|
return value ? this.ICON_OK : this.ICON_FAIL;
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case ResultTypes.Failure:
|
||||||
|
return this.ICON_FAIL;
|
||||||
|
case ResultTypes.Success:
|
||||||
|
return this.ICON_OK;
|
||||||
|
case ResultTypes.Skipped:
|
||||||
|
return this.ICON_SKIP;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static logFunc(name: string, value: string | null = null, result: boolean | null = null): string {
|
||||||
|
var icon = this.ICON_SKIP;
|
||||||
|
if (result != null)
|
||||||
|
icon = result ? this.ICON_OK : this.ICON_FAIL;
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
return `${this.getShortDateTime()} [${this.centerPadString(`${name}: ${this.truncateString(value)}`)}] -> ${icon}`;
|
||||||
|
|
||||||
|
return `${this.getShortDateTime()} [${this.centerPadString(name)}] -> ${icon}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static logValidate(text: string, success: boolean) {
|
||||||
|
return `${this.getShortDateTime()} [${this.centerPadString(text)}] -> ${success ? this.ICON_OK : this.ICON_FAIL}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static truncateString(value: any, maxLength: number = this.CONSOLE_WIDTH): string {
|
||||||
|
let str = typeof value === 'string' ? value : String(value);
|
||||||
|
str = str.replace(/\s+/g, ' ').trim();
|
||||||
|
if (str.length <= maxLength) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return str.slice(0, maxLength - 2) + '..';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static sanitizeFileName(input: string): string {
|
||||||
|
// Remove characters that are invalid in both Windows and Linux file systems
|
||||||
|
let sanitized = input.replace(/[<>:"/\\|?*\x00-\x1F]/g, '');
|
||||||
|
|
||||||
|
// Remove leading and trailing spaces and dots
|
||||||
|
sanitized = sanitized.trim().replace(/^\.+|\.+$/g, '');
|
||||||
|
|
||||||
|
// Replace remaining dots and spaces with underscores
|
||||||
|
sanitized = sanitized.replace(/[\s.]+/g, '_');
|
||||||
|
|
||||||
|
// Ensure the name isn't empty after sanitization
|
||||||
|
if (sanitized.length === 0) {
|
||||||
|
sanitized = 'unnamed';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate to a reasonable maximum length (e.g., 255 characters)
|
||||||
|
sanitized = sanitized.slice(0, 255);
|
||||||
|
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static replaceEmptyLinesWithMiddleDot(input: string): string {
|
||||||
|
const emptyLinesRegex: RegExp = /(.+?)(\n\s*\n)+/g;
|
||||||
|
|
||||||
|
return input.replace(emptyLinesRegex, (_match, line) => {
|
||||||
|
return line + '·\n';
|
||||||
|
}).replace(/\n$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static centerPadString(str: string, length: number = this.CONSOLE_WIDTH): string {
|
||||||
|
if (str.length >= length) {
|
||||||
|
return this.truncateString(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
str = this.truncateString(str);
|
||||||
|
|
||||||
|
const totalPadding = length - str.length;
|
||||||
|
const leftPadding = Math.ceil(totalPadding / 2);
|
||||||
|
const rightPadding = Math.floor(totalPadding / 2);
|
||||||
|
|
||||||
|
return ' '.repeat(leftPadding) + str + ' '.repeat(rightPadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getShortDateTime() {
|
||||||
|
return new Date().toLocaleString('en-US', {
|
||||||
|
year: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static normalizeSalesForceType(input: string, prefix: string = "Apttus_Config2__", suffix: string = "__c") {
|
||||||
|
let result = input;
|
||||||
|
|
||||||
|
if (result.startsWith(prefix))
|
||||||
|
result = result.slice(prefix.length);
|
||||||
|
|
||||||
|
if (result.endsWith(suffix))
|
||||||
|
result = result.slice(0, -suffix.length);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
playwright-tests/impl/utils/ParsingUtils.ts
Normal file
34
playwright-tests/impl/utils/ParsingUtils.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string containing a representation of currency, and returns a typed number. Can handle
|
||||||
|
* different types of currency represenations.
|
||||||
|
*
|
||||||
|
* USD 12,345.65 -> 12345.65
|
||||||
|
* (USD 12345.00) -> -12345
|
||||||
|
*
|
||||||
|
* @param text String containing currency representation
|
||||||
|
* @param currencyCode Optionally define different currency code
|
||||||
|
* @returns Parsed currency with type number
|
||||||
|
*/
|
||||||
|
export function parseCurrency(text: string, currencyCode: string = "USD"): number {
|
||||||
|
// TODO: Add ability to handle null fields/not treat null as 0 - KK 9/12/24
|
||||||
|
// Base case, if text can be cast as a number then work is done
|
||||||
|
if (!isNaN(+text)) return +text;
|
||||||
|
// Remove parentheses and continue parsing, multiply return by -1 to preserve negative value
|
||||||
|
if (text.charAt(0) === '(') return -1 * parseCurrency(text.substring(1, text.length - 1));
|
||||||
|
// Remove currency code prefix and continue parsing
|
||||||
|
if (text.split(' ')[0] === currencyCode) return parseCurrency(text.split(' ')[1]);
|
||||||
|
// Remove commas and cast to number
|
||||||
|
return Number(text.split(',').join(''));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param text
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function parseNumberOrCurrency(text: string): Number | string {
|
||||||
|
if (!isNaN(+text)) return +text;
|
||||||
|
else if (text.charAt(0) === '(') return -1 * parseCurrency(text.substring(1, text.length - 1));
|
||||||
|
else if (text.split(' ')[0] === "USD") return parseCurrency(text);
|
||||||
|
else return text;
|
||||||
|
}
|
||||||
111
playwright-tests/impl/utils/PropertyUtils.ts
Normal file
111
playwright-tests/impl/utils/PropertyUtils.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import { ValidationResult } from "@business-logic/types/RuleEngine";
|
||||||
|
import EnumUtils from "./EnumUtils";
|
||||||
|
|
||||||
|
export type ExtractName<T> = { [K in keyof T]: () => K; };
|
||||||
|
|
||||||
|
type GetValueOptions = {
|
||||||
|
isRequired: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
function isNullOrWhiteSpace(input: unknown): boolean {
|
||||||
|
if (typeof input !== "string")
|
||||||
|
return input == null;
|
||||||
|
return input.trim().length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches: '() => _Enums.*****', e.g.: '() => _Enums.ModificationTypes'
|
||||||
|
// Matches: '() => *****', e.g.: '() => ModificationTypes'
|
||||||
|
// Returns: EnumName, e.g.: 'ModificationTypes'
|
||||||
|
function getEnumName(propertySelector: () => object): string {
|
||||||
|
const functionString = propertySelector.toString();
|
||||||
|
const match = functionString.match(/\(\s*\)\s*=>\s*(?:_?[A-Z]\w*\.)?(\w+)(?::)?/);
|
||||||
|
return match ? match[1] : "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches: 'x => x.*****', e.g.: 'x => x.name', 'x => x.tags'
|
||||||
|
// Returns: PropertyName, e.g.: 'name', 'tags'
|
||||||
|
function getNameof<T>(propertySelector: (obj: T) => any): string {
|
||||||
|
const propertyString = propertySelector.toString();
|
||||||
|
const match = propertyString.match(/(?:=>|return)\s*([\w\s.]+)/);
|
||||||
|
|
||||||
|
if (match && match[1]) {
|
||||||
|
const parts = match[1].split('.');
|
||||||
|
return parts[parts.length - 1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Invalid property selector: ${propertyString}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValueOrNull<T>(json: any, propertySelector: (obj: T) => any): any | null {
|
||||||
|
if (json == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const property = getNameof(propertySelector);
|
||||||
|
|
||||||
|
if (json.hasOwnProperty(property))
|
||||||
|
return json[property];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue<T>(json: any, typeName: string, validationResults: ValidationResult[], options: GetValueOptions, propertySelector: (obj: T) => any): any | null {
|
||||||
|
const retval = getValueOrNull(json, propertySelector);
|
||||||
|
|
||||||
|
if (retval == null)
|
||||||
|
validationResults.push(options.isRequired ? ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is null but is required!`) : ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is null but is NOT required.`));
|
||||||
|
else {
|
||||||
|
if (isNullOrWhiteSpace(retval))
|
||||||
|
validationResults.push(ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is empty!`));
|
||||||
|
else
|
||||||
|
validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasProperty<T>(json: any, typeName: string, validationResults: ValidationResult[], options: GetValueOptions, propertySelector: (obj: ExtractName<T>) => () => keyof T): boolean {
|
||||||
|
const retval = json.hasOwnProperty(getNameof(propertySelector));
|
||||||
|
|
||||||
|
if (retval)
|
||||||
|
validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
|
||||||
|
else
|
||||||
|
validationResults.push(options.isRequired ? ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is not defined but is required!`) : ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} is not defined but is NOT required!`));
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnumValueOrNull<T>(json: any, enumType: object, propertySelector: (obj: T) => any): any | null {
|
||||||
|
if (json == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const property = getNameof(propertySelector);
|
||||||
|
const retval = EnumUtils.validateEnumProperty(json, property, enumType);
|
||||||
|
|
||||||
|
if (retval != null)
|
||||||
|
return retval;
|
||||||
|
else if (Object.values(enumType).includes(json[property]))
|
||||||
|
return json[property];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnumValue<T>(json: any, typeName: string, validationResults: ValidationResult[], enumType: object, enumTypeInstance: () => object, options: GetValueOptions, propertySelector: (obj: T) => any): any | null {
|
||||||
|
const retval = getEnumValueOrNull(json, enumType, propertySelector);
|
||||||
|
|
||||||
|
if (retval == null)
|
||||||
|
validationResults.push(options.isRequired ? ValidationResult.FromFailure(`Value for ${typeName}.${getNameof(propertySelector)} [${getEnumName(enumTypeInstance)}] is null but is required!`) : ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} [${getEnumName(enumTypeInstance)}] is null but is NOT required.`));
|
||||||
|
else
|
||||||
|
validationResults.push(ValidationResult.FromSuccess(retval, `Value for ${typeName}.${getNameof(propertySelector)} is valid.`));
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { getEnumName, getEnumValueOrNull, getNameof, getValueOrNull };
|
||||||
|
|
||||||
|
const PropertyUtils = {
|
||||||
|
hasProperty,
|
||||||
|
getValue,
|
||||||
|
getEnumValue
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PropertyUtils;
|
||||||
32
playwright-tests/impl/utils/TaggingUtils.ts
Normal file
32
playwright-tests/impl/utils/TaggingUtils.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
export function formatTag(text: string): string {
|
||||||
|
return "@" + text.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, (match, index) => {
|
||||||
|
if (+match === 0) return ""; // Remove non-alphanumeric characters
|
||||||
|
return index === 0 ? match.toLowerCase() : match.toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description - Use this to add calculated/standardized/randomized tags to scenarioData prior to running. Randomly adds a smoke tag to 1 of the testCases.
|
||||||
|
* @param scenarioData - the data from your test case
|
||||||
|
* @returns scenarioData, but with added tags.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getRandomTestName(scenarioData: any) : string {
|
||||||
|
const testCaseNames: string[] = Object.keys(scenarioData);
|
||||||
|
const totalKeys = testCaseNames.length
|
||||||
|
const randomIndex = Math.floor(Math.random() * totalKeys) - 1;
|
||||||
|
const randomKey = testCaseNames[randomIndex];
|
||||||
|
|
||||||
|
return randomKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function metaTags(currentTestName: string, randomTestName: string) : string [] {
|
||||||
|
return currentTestName == randomTestName ? ["@smoke", "@standardRegression"] : ["@standardRegression"]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchAndReplaceContactDataTag(data: string[], replacement: string): string[] {
|
||||||
|
return data.map((value) => value.replace(/[{]{2}contact[}]{2}/, replacement));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchAndReplaceAccountDataTag(data: string[], replacement: string): string[] {
|
||||||
|
return data.map((value) => value.replace(/[{]{2}account[}]{2}/, replacement));
|
||||||
|
}
|
||||||
12
playwright-tests/impl/utils/ThrowUtils.ts
Normal file
12
playwright-tests/impl/utils/ThrowUtils.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { error } from "console";
|
||||||
|
|
||||||
|
|
||||||
|
export function throwIf(conditionFunction: () => boolean, errorMessage: string): void {
|
||||||
|
if (conditionFunction())
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function throwNotYetImplemented(nameOfThingNotImplemented: string) {
|
||||||
|
throw new Error(`${nameOfThingNotImplemented} has not yet been implemented.`)
|
||||||
|
}
|
||||||
273
playwright-tests/impl/utils/TimingUtils.ts
Normal file
273
playwright-tests/impl/utils/TimingUtils.ts
Normal file
|
|
@ -0,0 +1,273 @@
|
||||||
|
import { expect, Locator, Page } from "@playwright/test";
|
||||||
|
import LoggingUtils from "./LoggingUtils";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
|
||||||
|
export type TimeoutOpts = {
|
||||||
|
/**
|
||||||
|
* @description timeout Commonly referred to for an entire method; exists to allow developers to specify their own timeout whout overriding defaults
|
||||||
|
*/
|
||||||
|
|
||||||
|
timeout: number
|
||||||
|
/**
|
||||||
|
* @description timeout_tiny use for exceedingly small waits, as in, waiting for label to contain the test you just typed into it.
|
||||||
|
*/
|
||||||
|
timeoutTiny: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description timeout_short for relatively quick opperations, such as waiting for a dropdown to render
|
||||||
|
*/
|
||||||
|
timeoutShort: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description timeout_medium for moderately slow operations, such as a new modal rendering, or a calculated field value being updated, or an API Call
|
||||||
|
*/
|
||||||
|
timeoutMedium: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description timeout_long for high-risk, slow operations. Waiting for the minicart to load, waiting for login, or waiting for screen-to-screen navigation.
|
||||||
|
*/
|
||||||
|
timeoutLong: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description for when things are really, really bad.
|
||||||
|
*/
|
||||||
|
timeoutConga: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export const timeoutOptDefaults: TimeoutOpts = {
|
||||||
|
timeout: 60_000,
|
||||||
|
timeoutTiny: +(process.env.TIMEOUT_TINY ?? 500),
|
||||||
|
timeoutShort: +(process.env.TIMEOUT_SHORT ?? 5000),
|
||||||
|
timeoutMedium: +(process.env.TIMEOUT_MEDIUM ?? 30_000),
|
||||||
|
timeoutLong: +(process.env.TIMEOUT_LONG ?? 180_000),
|
||||||
|
timeoutConga: +(process.env.TIMEOUT_CONGA ?? 500_000),
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WaitUntilOpts = {
|
||||||
|
delayBetweenChecks: number,
|
||||||
|
continueOnTimeoutError: boolean,
|
||||||
|
anticipatedConditionResult: boolean,
|
||||||
|
conditionName: string,
|
||||||
|
beginWaitingMessage: string,
|
||||||
|
delayBetweenChecksMessage: string,
|
||||||
|
timeoutErrorMessage: string,
|
||||||
|
successMessage: string,
|
||||||
|
ignoreErrorsFromConditionFunction: boolean,
|
||||||
|
|
||||||
|
}
|
||||||
|
export const waitUntilOptDefaults: WaitUntilOpts = {
|
||||||
|
delayBetweenChecks: 3000,
|
||||||
|
continueOnTimeoutError: false,
|
||||||
|
anticipatedConditionResult: true,
|
||||||
|
conditionName: "",
|
||||||
|
beginWaitingMessage: "",
|
||||||
|
delayBetweenChecksMessage: "",
|
||||||
|
timeoutErrorMessage: "Timed Out",
|
||||||
|
successMessage: "",
|
||||||
|
ignoreErrorsFromConditionFunction: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description repeatedly execute an asynchronous conditional lambda until a given outcome occurs, or the method times-out. Useful for hedgning against GUI race conditions.
|
||||||
|
* @param conditionFunction the condition lambda. Example: ()=>{await return myPage.someButton.isVisible()}
|
||||||
|
* @param options standard Timeout and WaitUntil Options.
|
||||||
|
* @returns true or false - the outcome of the waituntil.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function waitUntil(conditionFunction: (...args: any[]) => Promise<boolean>, options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<boolean> {
|
||||||
|
const opts = { ...waitUntilOptDefaults, ...timeoutOptDefaults, ...options }
|
||||||
|
|
||||||
|
let timeoutAt = Date.now() + opts.timeout;
|
||||||
|
let waitUntilHasTimedOut = false;
|
||||||
|
let conditionHasBeenMet = false;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
const conditionResult = await conditionFunction()
|
||||||
|
conditionHasBeenMet = conditionResult == opts.anticipatedConditionResult
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
if (!opts.ignoreErrorsFromConditionFunction) {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waitUntilHasTimedOut = Date.now() > timeoutAt
|
||||||
|
if (!waitUntilHasTimedOut && !conditionHasBeenMet) {
|
||||||
|
await delay(opts.delayBetweenChecks)
|
||||||
|
}
|
||||||
|
else if (waitUntilHasTimedOut && !opts.continueOnTimeoutError) {
|
||||||
|
throw new Error(opts.timeoutErrorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!conditionHasBeenMet || waitUntilHasTimedOut)
|
||||||
|
return conditionHasBeenMet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Order Matters; WaitUntil each condition passes before moving to the next. All conditions must pass in the expected order.
|
||||||
|
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in sqeuance until all have passed.
|
||||||
|
* @param options standard Timeout and WaitUntil options
|
||||||
|
*/
|
||||||
|
export async function waitUntilValueStopsChanging(mercurialValueFunction: (...args: any[]) => Promise<any>, options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||||
|
const opts = { ...waitUntilOptDefaults, ...timeoutOptDefaults, ...options }
|
||||||
|
|
||||||
|
let lastFoundValue: any = undefined;
|
||||||
|
const valueHasStoppedChanging = async () => {
|
||||||
|
const newFoundValue = await mercurialValueFunction();
|
||||||
|
if (opts.delayBetweenChecksMessage.length > 0) console.log(`${opts.delayBetweenChecksMessage} - Last Value: ${lastFoundValue}`)
|
||||||
|
const valueIsStable = (newFoundValue != undefined) && (newFoundValue == lastFoundValue);
|
||||||
|
lastFoundValue = newFoundValue;
|
||||||
|
return valueIsStable;
|
||||||
|
}
|
||||||
|
await waitUntil(valueHasStoppedChanging, opts)
|
||||||
|
return lastFoundValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo EXPIRIMENTAL! NO UNIT TESTS YET! TODO, add - DF, 5/23
|
||||||
|
* @description Order Matters; WaitUntil each condition passes before moving to the next. All conditions must pass in the expected order.
|
||||||
|
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in sqeuance until all have passed.
|
||||||
|
* @param options standard Timeout and WaitUntil options
|
||||||
|
*/
|
||||||
|
export async function waitUntilEach(sequentialConditionFunctions: ((...args: any[]) => Promise<boolean>)[], options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||||
|
for (const conditionFunction of sequentialConditionFunctions) {
|
||||||
|
await waitUntil(conditionFunction, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo EXPIRIMENTAL! NO UNIT TESTS YET! TODO, add - DF, 5/23
|
||||||
|
* @description Order Matters; WaitUntil each condition passes before moving to the next. All conditions must pass in the expected order.
|
||||||
|
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in sequence until all have passed.
|
||||||
|
* @param options standard Timeout and WaitUntil options
|
||||||
|
*/
|
||||||
|
export async function waitUntilAll(sequentialConditionFunctions: ((...args: any[]) => Promise<boolean>)[], options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||||
|
for (const conditionFunction of sequentialConditionFunctions) {
|
||||||
|
await waitUntil(conditionFunction, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo EXPIRIMENTAL! NO UNIT TESTS YET! TODO, add - DF, 5/23
|
||||||
|
* @description Order DOES NOT Matter; WaitUntil ANY condition passes before completing. Use when multiple conditions can give confidence that sufficient waiting has occured.
|
||||||
|
* @param sequentialConditionFunctions an array of async, boolean lambdas to be executed in psudo-parallel until at least one has passed.
|
||||||
|
* @param options standard Timeout and WaitUntil options
|
||||||
|
*/
|
||||||
|
export async function waitUntilAny(multipleRequiredConditionFunctions: ((...args: any[]) => Promise<boolean>)[], options: Partial<TimeoutOpts & WaitUntilOpts> = {}): Promise<any> {
|
||||||
|
const anyConditionMet = async (): Promise<boolean> => {
|
||||||
|
return multipleRequiredConditionFunctions.filter(async (fun: (...args: any[]) => Promise<boolean>): Promise<boolean> => await Function.call(fun)).length > 0;
|
||||||
|
};
|
||||||
|
waitUntil(anyConditionMet, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param milliseconds delay duration
|
||||||
|
* @param logDelay defaults to false; if true, logs a waiting message.
|
||||||
|
*/
|
||||||
|
export async function delay(milliseconds: number, logDelay = false): Promise<void> {
|
||||||
|
if (logDelay) { console.log(`delaying ${milliseconds} milliseconds before continuing...`) }
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the page url
|
||||||
|
* @param partialUrl The string we are looking for in the url, to know we have transitioned to the correct page.
|
||||||
|
* @param timeout the amount of milliseconds to wait before giving up.
|
||||||
|
*/
|
||||||
|
export async function waitForUrlPartialMatch(page: Page, firstPartialUrl: string, timeout = 120_000) {
|
||||||
|
const startTime = Date.now();
|
||||||
|
while (Date.now() - startTime < timeout) {
|
||||||
|
if (page.url().includes(firstPartialUrl)) {
|
||||||
|
return; // URL matches the partial string, exit the function
|
||||||
|
}
|
||||||
|
await page.waitForTimeout(100); // Wait for 100 milliseconds before checking again
|
||||||
|
}
|
||||||
|
throw new Error(`Timed out waiting for URL to match '${firstPartialUrl}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run a function repeatedly until it returns true or the timeout is reached.
|
||||||
|
*
|
||||||
|
* This function executes the provided asynchronous block function in a loop until it returns true or the specified
|
||||||
|
* timeout duration has elapsed. Between each attempt, it waits for a specified delay.
|
||||||
|
*
|
||||||
|
* @param block - An asynchronous function that returns a boolean value. This function will be executed repeatedly until it returns true.
|
||||||
|
* @param timeout - The maximum duration to keep attempting to run the block function, in milliseconds. Default is 150000 (150 seconds).
|
||||||
|
* @param delayMs - The delay duration between each attempt, in milliseconds. Default is 3000 (3 seconds).
|
||||||
|
* @returns A promise that resolves to a boolean value indicating whether the block function eventually returned true.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Example usage:
|
||||||
|
* const blockFunction = async () => {
|
||||||
|
* // Some asynchronous condition check
|
||||||
|
* return await someConditionCheck();
|
||||||
|
* };
|
||||||
|
* const result = await runUntilTrue(blockFunction, 10000, 1000);
|
||||||
|
* console.log(result); // Outputs true if blockFunction returned true within the timeout, otherwise false.
|
||||||
|
*/
|
||||||
|
export async function runUntilTrue(block: () => Promise<boolean>, timeout: number = 150000, delayMs: number = 3000){
|
||||||
|
const startTime = DateTime.now();
|
||||||
|
let attempts = 0;
|
||||||
|
let evaluatesToTrue = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// If timeout duration has elapsed, stop making attempts
|
||||||
|
if (DateTime.now().diff(startTime).as('milliseconds') > timeout) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts++;
|
||||||
|
// If retrying, wait delay duration
|
||||||
|
if (attempts > 1) await delay(delayMs);
|
||||||
|
|
||||||
|
evaluatesToTrue = await block();
|
||||||
|
|
||||||
|
} while (!evaluatesToTrue);
|
||||||
|
|
||||||
|
return evaluatesToTrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO move this to impl/utils/WaitingUtils when that related pr is available in devleop branch - T.S. 5/20/24
|
||||||
|
export async function waitForEither(block1: () => Promise<any>, block2: () => Promise<any>, timeOut: number = 180_000): Promise<void> {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
const result1 = await block1();
|
||||||
|
const result2 = await block2();
|
||||||
|
|
||||||
|
// Check if either result is truthy (i.e., not falsy or undefined)
|
||||||
|
if (result1 || result2) {
|
||||||
|
// At least one block returned a truthy value, resolve the promise
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Handle errors thrown by either block
|
||||||
|
console.error("An error occurred:", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the timeout has been reached
|
||||||
|
if (Date.now() - startTime >= timeOut) {
|
||||||
|
throw new Error(`Timeout of ${timeOut} ms exceeded`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some delay before checking again
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000)); // Adjust delay as needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for a specific locator to show up on screen, then disappear. Typically used for things like progress bars.
|
||||||
|
* @param locator The locator we want to become visible and then become hidden
|
||||||
|
*/
|
||||||
|
export async function waitToAppearAndDisappear(locator: Locator): Promise<void> {
|
||||||
|
try {
|
||||||
|
await expect(locator).toBeVisible({ timeout: 60000 });
|
||||||
|
await expect(locator).toBeHidden({ timeout: 60000 });
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error)
|
||||||
|
console.log(LoggingUtils.logFunc(waitToAppearAndDisappear.name, err.message, false));
|
||||||
|
else
|
||||||
|
console.log(LoggingUtils.logFunc(waitToAppearAndDisappear.name, null, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
34
playwright-tests/impl/utils/TokenUtils.ts
Normal file
34
playwright-tests/impl/utils/TokenUtils.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { IClientAuthentication } from "@business-logic/types/Authentication";
|
||||||
|
import { Authentication } from "@business-logic/types/Enums";
|
||||||
|
|
||||||
|
export function buildToken(clientAuth: IClientAuthentication, formData: Map<string, string>): string {
|
||||||
|
if(clientAuth.authentication === undefined
|
||||||
|
|| clientAuth.authentication === Authentication.None
|
||||||
|
|| clientAuth.authentication === Authentication.Unknown
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = (formData.get('Token') ?? formData.get("Timestamp") ?? '');
|
||||||
|
if(clientAuth.authentication === Authentication.RSAToken) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const [id, value] of formData.entries()) {
|
||||||
|
token += `|${id.toLowerCase()}=${value}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimestamp(): string {
|
||||||
|
const time = new Date(Date.now());
|
||||||
|
const formatOptions :Intl.DateTimeFormatOptions = {
|
||||||
|
hour12: false,
|
||||||
|
dateStyle: "short",
|
||||||
|
timeStyle: "medium"
|
||||||
|
}
|
||||||
|
const string = time.toLocaleString('en-US', formatOptions);
|
||||||
|
let timestamp = string.replaceAll("/","").replaceAll(", ","").replaceAll(":","");
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
132
playwright-tests/impl/utils/TryUtils.ts
Normal file
132
playwright-tests/impl/utils/TryUtils.ts
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
import LoggingUtils from "./LoggingUtils";
|
||||||
|
import { delay, timeoutOptDefaults } from "./TimingUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description For situations where a Test Framework Exception (i.e., a locator timeout) could be incorrectly thrown based on the state of the Target-Application (i.e., a missing tail number). Use this to throw clearer 'Application Exception' errors under such circumstances.
|
||||||
|
* @param actionVerb What are you trying to do? Could be 'gotoCatalog', 'addAnEnhancement', etc. Logged as `Attempting to ${actionVerb}`
|
||||||
|
* @param failureExplenation Be descriptive. What is the context of the failure? If someone unfamiliar with the code base were to see this, how would they know if the error was caused by their code, or by an underlying problem with the Target Application?
|
||||||
|
* @param actionToAttempt a lambda for the flaky action.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function tryBusinessAction(actionVerb: string, failureExplenation: string, actionToAttempt: (...args: any[]) => Promise<any>): Promise<any> {
|
||||||
|
let actionResult: any;
|
||||||
|
console.log(`Attempting to ${actionVerb}...`)
|
||||||
|
try {
|
||||||
|
actionResult = await actionToAttempt();
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
e.message = e.message + ">>Failure Explenation>> " + failureExplenation;
|
||||||
|
throw (e);
|
||||||
|
} else {
|
||||||
|
throw (new Error("Unknown 'AttemptBusinessAction' State..."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`Successfully executed ${actionVerb}`)
|
||||||
|
return actionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Brute-Force Flaky GUI activities by reseting and retrying.
|
||||||
|
* @param actionToTry lambda for whatever flaky action you're trying to take
|
||||||
|
* @param resetAction lambda for backing out of the problem and returning to a known state. Often, refreshing a browser, or closing a popup.
|
||||||
|
* @param maxRetries number of times to retry the action
|
||||||
|
* @param delayBetweenRetries milliseconds between retries
|
||||||
|
*/
|
||||||
|
export async function tryResetAndRetry(
|
||||||
|
actionVerb: string,
|
||||||
|
actionToTry: (...args: any[]) => Promise<any>,
|
||||||
|
resetAction: (...args: any[]) => Promise<any>,
|
||||||
|
maxRetries = 2,
|
||||||
|
delayBetweenRetries = timeoutOptDefaults.timeoutMedium): Promise<any> {
|
||||||
|
|
||||||
|
for (let i = 1; i <= maxRetries; i++) {
|
||||||
|
try {
|
||||||
|
await actionToTry()
|
||||||
|
} catch {
|
||||||
|
await delay(delayBetweenRetries);
|
||||||
|
resetAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description For situations where a Test Framework Exception (i.e., a locator timeout) could be incorrectly thrown based on the state of the Target-Application (i.e., a missing tail number). Use this to throw clearer 'Application Exception' errors under such circumstances.
|
||||||
|
* @param actionVerb What are you trying to do? Could be 'gotoCatalog', 'addAnEnhancement', etc. Logged as `Attempting to ${actionVerb}`
|
||||||
|
* @param failureExplenation Be descriptive. What is the context of the failure? If someone unfamiliar with the code base were to see this, how would they know if the error was caused by their code, or by an underlying problem with the Target Application?
|
||||||
|
* @param actionToAttempt a lambda for the flaky action.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export async function tryBusinessActionWithRetries(actionVerb: string, failureExplenation: string, actionToAttempt: (...args: any[]) => Promise<any>, attempts = 3): Promise<any> {
|
||||||
|
let actionResult: any;
|
||||||
|
let isSuccessful: boolean = false;
|
||||||
|
//actionVerb is already a logFunc string
|
||||||
|
console.log(actionVerb);
|
||||||
|
|
||||||
|
for (let i = 0; i < attempts; i++) {
|
||||||
|
try {
|
||||||
|
actionResult = await actionToAttempt();
|
||||||
|
isSuccessful = true;
|
||||||
|
break; // Break loop if actionToAttempt is successful
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
e.message = e.message + ">>Failure Explanation>> " + failureExplenation;
|
||||||
|
} else {
|
||||||
|
throw (new Error("Unknown 'AttemptBusinessAction' State"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSuccessful) {
|
||||||
|
throw new Error(`Failed to ${actionVerb}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//actionVerb is already a logFunc string
|
||||||
|
console.log(actionVerb);
|
||||||
|
return actionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to execute a block of code with chances to retry.
|
||||||
|
* @param {Function} block The block of code to be executed.
|
||||||
|
* @param {string} [blockDescription=''] A text description of the block (optional).
|
||||||
|
* @param {number} [maxRetries=3] The maximum number of retry attempts (optional).
|
||||||
|
* @param {number} [delayMs=1000] The delay between retry attempts in milliseconds (optional).
|
||||||
|
*/
|
||||||
|
export async function retry<T>(block: () => Promise<T>, blockDescription: string = '', maxRetries: number = 3, delayMs: number = 1000): Promise<T> {
|
||||||
|
let retries: number = 0;
|
||||||
|
if (!blockDescription.length) {
|
||||||
|
blockDescription = block.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (retries < maxRetries) {
|
||||||
|
console.log(LoggingUtils.logFunc(retry.name, blockDescription));
|
||||||
|
try {
|
||||||
|
return await block();
|
||||||
|
} catch (error) {
|
||||||
|
if (retries === maxRetries - 1) {
|
||||||
|
throw new Error(`Max retries (${maxRetries}) exceeded. Last error: ${error}`);
|
||||||
|
}
|
||||||
|
// wait between retries
|
||||||
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
||||||
|
retries++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This should not be reached, but just in case
|
||||||
|
throw new Error(`Unexpected code execution. Max retries (${maxRetries}) exceeded.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tryWithRetries(actionBlock: Function, attempts = 3, waitInterval = 1000) {
|
||||||
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
||||||
|
try {
|
||||||
|
await actionBlock();
|
||||||
|
if (attempt > 1)
|
||||||
|
console.warn(`Had to retry but attempt ${attempt} succeeded!`);
|
||||||
|
break; // Exit the loop if the action is successful
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Attempt ${attempt} failed!`);
|
||||||
|
if (attempt < attempts) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, waitInterval));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
playwright-tests/pages/AfterpayPage.ts
Normal file
56
playwright-tests/pages/AfterpayPage.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { Locator, Page } from "@playwright/test";
|
||||||
|
import { BasePage } from "./BasePage";
|
||||||
|
import { IPaymentDetails } from "@business-logic/types/CustomerDetails";
|
||||||
|
|
||||||
|
export class AfterpayPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly submitButton: Locator;
|
||||||
|
|
||||||
|
// Login
|
||||||
|
readonly passwordTextBox: Locator;
|
||||||
|
|
||||||
|
// Card details
|
||||||
|
readonly cardholderNameTextBox: Locator;
|
||||||
|
readonly cardNumberTextBox: Locator;
|
||||||
|
readonly expirationDateTextBox: Locator;
|
||||||
|
readonly cvvTextBox: Locator;
|
||||||
|
|
||||||
|
readonly confirmButton: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.passwordTextBox = page.getByRole('textbox', { name: 'Please enter your password' });
|
||||||
|
this.submitButton = page.getByRole('button', { name: 'Continue' });
|
||||||
|
|
||||||
|
this.cardholderNameTextBox = page.getByTestId('payment-method-cardHolderName-input');
|
||||||
|
this.cardNumberTextBox = page.getByTestId('payment-method-cardNumber-input');
|
||||||
|
this.expirationDateTextBox = page.getByTestId('payment-method-cardExpiry-input');
|
||||||
|
this.cvvTextBox = page.getByTestId('payment-method-cardCvv-input');
|
||||||
|
|
||||||
|
this.confirmButton = page.getByRole('button', { name: 'Confirm' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(password: string) {
|
||||||
|
await this.passwordTextBox.fill(password);
|
||||||
|
await this.submitButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async populateCardDetails(paymentDetails: IPaymentDetails) {
|
||||||
|
await this.cardholderNameTextBox.fill('Roberts'); // TODO: Add cardholder name field
|
||||||
|
await this.cardNumberTextBox.fill(paymentDetails.cardNumber!);
|
||||||
|
await this.expirationDateTextBox.fill(`${paymentDetails.expirationMonth}/${paymentDetails.expirationYear}`);
|
||||||
|
await this.cvvTextBox.fill(paymentDetails.cvv!);
|
||||||
|
await this.submitButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeAfterpayPayment(paymentDetails: IPaymentDetails) {
|
||||||
|
await this.login(paymentDetails.password!);
|
||||||
|
|
||||||
|
await this.populateCardDetails(paymentDetails);
|
||||||
|
|
||||||
|
await this.confirmButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
49
playwright-tests/pages/BailoutPage.ts
Normal file
49
playwright-tests/pages/BailoutPage.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class BailoutPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly firstNameTextBox: Locator;
|
||||||
|
readonly lastNameTextBox: Locator;
|
||||||
|
readonly phoneNumberTextBox: Locator;
|
||||||
|
readonly emailAddressTextBox: Locator;
|
||||||
|
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=bailout-page';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.firstNameTextBox = this.page.locator('#firstNameField');
|
||||||
|
this.lastNameTextBox = this.page.locator('#lastNameField');
|
||||||
|
this.phoneNumberTextBox = this.page.locator('#phoneNumberField');
|
||||||
|
this.emailAddressTextBox = this.page.locator('#emailAddressField');
|
||||||
|
// this.page.waitForLoadState();
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateBailoutDetails(customerDetails: ICustomerDetails, bailoutCode: number) {
|
||||||
|
|
||||||
|
await this.firstNameTextBox.waitFor({state:'visible'});
|
||||||
|
await expect.soft(this.firstNameTextBox).toHaveValue(customerDetails.firstName);
|
||||||
|
await expect.soft(this.lastNameTextBox).toHaveValue(customerDetails.lastName);
|
||||||
|
await expect.soft(this.phoneNumberTextBox).toHaveValue(customerDetails.phoneNumber);
|
||||||
|
await expect.soft(this.emailAddressTextBox).toHaveValue(customerDetails.email);
|
||||||
|
await expect.soft(await this.getBailoutCode()).toEqual(bailoutCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateBailoutDetailsNotNull(){
|
||||||
|
await this.firstNameTextBox.waitFor({state:'visible'})
|
||||||
|
expect(this.firstNameTextBox.inputValue()).not.toBe('');
|
||||||
|
expect(this.lastNameTextBox.inputValue()).not.toBe('');
|
||||||
|
expect(this.phoneNumberTextBox.inputValue()).not.toBe('');
|
||||||
|
expect(this.emailAddressTextBox.inputValue()).not.toBe('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBailoutCode() {
|
||||||
|
const mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')'));
|
||||||
|
const bailoutCode = mainLocalStorage.applicationUser.pageData['bailout-page'].bailoutCode as number;
|
||||||
|
return bailoutCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
41
playwright-tests/pages/BasePage.ts
Normal file
41
playwright-tests/pages/BasePage.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
|
||||||
|
export class BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly continueButton: Locator;
|
||||||
|
readonly pageSpinner: Locator;
|
||||||
|
readonly buttonLoadSpin: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page){
|
||||||
|
this.page = page;
|
||||||
|
this.continueButton = page.locator('[id="infoBox"]').getByRole('button');
|
||||||
|
this.pageSpinner = page.getByRole('status');
|
||||||
|
this.buttonLoadSpin = page.getByRole('alert');
|
||||||
|
}
|
||||||
|
|
||||||
|
async nextPage() {
|
||||||
|
const startingUrl = this.page.url();
|
||||||
|
await expect(async () => {
|
||||||
|
const currentUrl = this.page.url();
|
||||||
|
if (currentUrl === startingUrl) {
|
||||||
|
await this.continueButton.click({ timeout: 1000 });
|
||||||
|
}
|
||||||
|
//this causes the schedule page to fail
|
||||||
|
//await expect(this.buttonLoadSpin).toHaveCount(0, {timeout: 180000});
|
||||||
|
expect(currentUrl).not.toEqual(startingUrl);
|
||||||
|
}).toPass({ timeout: 240_000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateURL(url:string){
|
||||||
|
await expect(this.pageSpinner).toHaveCount(0, {timeout: 60000});
|
||||||
|
await this.page.waitForURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillAndValidate(element: Locator, value: string){
|
||||||
|
await expect(async () => {
|
||||||
|
await element.clear();
|
||||||
|
await element.fill(value);
|
||||||
|
await expect(element).toHaveValue(value);
|
||||||
|
}).toPass();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
playwright-tests/pages/CapabilityQuestionsPage.ts
Normal file
10
playwright-tests/pages/CapabilityQuestionsPage.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Page } from "@playwright/test";
|
||||||
|
import { PartQuestionsPage } from "./PartQuestionsPage";
|
||||||
|
|
||||||
|
export default class CapabilityQuestionsPage extends PartQuestionsPage {
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=capability-questions';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
playwright-tests/pages/ContactConfirmationPage.ts
Normal file
17
playwright-tests/pages/ContactConfirmationPage.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class ContactConfirmationPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly ConfirmMessageLabel: Locator;
|
||||||
|
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=contact-confirmation';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.ConfirmMessageLabel = this.page.getByText("Your callback request has been sent!");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
45
playwright-tests/pages/ContactDetailsPage.ts
Normal file
45
playwright-tests/pages/ContactDetailsPage.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class ContactDetailsPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=contact-details';
|
||||||
|
|
||||||
|
// Contact details form
|
||||||
|
// TODO: Check if we can consolidate
|
||||||
|
readonly firstNameTextBox: Locator;
|
||||||
|
readonly lastNameTextBox: Locator;
|
||||||
|
readonly emailAddressTextBox: Locator;
|
||||||
|
readonly phoneNumberTextBox: Locator;
|
||||||
|
readonly notesTextBox: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
|
||||||
|
// Contact details form
|
||||||
|
this.firstNameTextBox = this.page.getByRole('textbox', { name: 'First name' });
|
||||||
|
this.lastNameTextBox = this.page.getByRole('textbox', { name: 'Last name' });
|
||||||
|
this.emailAddressTextBox = this.page.getByRole('textbox', { name: 'Email address' });
|
||||||
|
this.phoneNumberTextBox = this.page.getByRole('textbox', { name: 'Phone number' });
|
||||||
|
this.notesTextBox = this.page.getByRole('textbox', { name: 'Notes' });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getContactDetails() {
|
||||||
|
const customerDetails: Partial<ICustomerDetails> = {};
|
||||||
|
customerDetails.firstName = await this.firstNameTextBox.inputValue();
|
||||||
|
customerDetails.lastName = await this.lastNameTextBox.inputValue();
|
||||||
|
customerDetails.email = await this.emailAddressTextBox.inputValue();
|
||||||
|
customerDetails.phoneNumber = await this.phoneNumberTextBox.inputValue();
|
||||||
|
|
||||||
|
return customerDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillNotes(notes?: string) {
|
||||||
|
if (notes) {
|
||||||
|
await this.notesTextBox.fill(notes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
playwright-tests/pages/CoverageStatementPage.ts
Normal file
39
playwright-tests/pages/CoverageStatementPage.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
|
export class CoverageStatementPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly scheduleOnlineButton: Locator;
|
||||||
|
readonly cancelMyClaimButton: Locator;
|
||||||
|
readonly deductibleAmount: Locator;
|
||||||
|
readonly verfiyingCoverageText: Locator;
|
||||||
|
readonly continueToScheduleButton: Locator; // For ITAC/NoComp
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=coverage-statement';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.scheduleOnlineButton = this.page.getByText('Continue to schedule online');
|
||||||
|
this.cancelMyClaimButton = this.page.getByText('Cancel my claim');
|
||||||
|
this.deductibleAmount = this.page.getByText('$');
|
||||||
|
this.verfiyingCoverageText = this.page.getByRole('heading', { name: 'We’re verifying your coverage' });
|
||||||
|
this.continueToScheduleButton = page.locator('div[class*="button-content"]', {hasText:'Continue to schedule online'});
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async scheduleOnline(){
|
||||||
|
await this.scheduleOnlineButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelMyClaim(){
|
||||||
|
await this.cancelMyClaimButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateDeductibleAmount(customer){
|
||||||
|
await expect(this.deductibleAmount). toContainText(`$${customer.deductibleAmount}`, {timeout: 60000});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateUnverifiedText(){
|
||||||
|
await expect(this.verfiyingCoverageText).toBeEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
playwright-tests/pages/DuplicateCheckPage.ts
Normal file
20
playwright-tests/pages/DuplicateCheckPage.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
|
export class DuplicateCheckPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly newClaimButton: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=duplicate-check';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.newClaimButton = page.locator('label').filter({ hasText: 'Start a new claim' }).locator('div');
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async startNewClaim(){
|
||||||
|
await this.newClaimButton.click();
|
||||||
|
await this.page.waitForLoadState();
|
||||||
|
}
|
||||||
|
}
|
||||||
54
playwright-tests/pages/EndorsementsPage.ts
Normal file
54
playwright-tests/pages/EndorsementsPage.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IEndorsementDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { EndorsementType } from '@business-logic/types/Enums';
|
||||||
|
|
||||||
|
export class EndorsementsPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly educatorYesButton: Locator;
|
||||||
|
readonly educatorNoButton: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=policy-endorsements';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.educatorYesButton = this.page.locator('label[for="schoolProperty-Yes"]');
|
||||||
|
this.educatorNoButton = this.page.locator('label[for="schoolProperty-No"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyEndorsements(endorsements: IEndorsementDetails[]) {
|
||||||
|
for (const endorsement of endorsements) {
|
||||||
|
switch(endorsement.endorsementType) {
|
||||||
|
case EndorsementType.Educator:
|
||||||
|
if (endorsement.isOnPolicy) {
|
||||||
|
await expect.soft(this.educatorYesButton).toBeAttached();
|
||||||
|
} else {
|
||||||
|
await expect.soft(this.educatorYesButton).not.toBeAttached();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EndorsementType.EmployeeParking:
|
||||||
|
// TODO: Implement
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectEndorsements(endorsements: IEndorsementDetails[]) {
|
||||||
|
for (const endorsement of endorsements) {
|
||||||
|
if (endorsement.isOnPolicy) {
|
||||||
|
switch (endorsement.endorsementType) {
|
||||||
|
case EndorsementType.Educator:
|
||||||
|
if (endorsement.isClickYes) {
|
||||||
|
await this.educatorYesButton.click();
|
||||||
|
} else {
|
||||||
|
await this.educatorNoButton.click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EndorsementType.EmployeeParking:
|
||||||
|
// TODO: Implement
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
playwright-tests/pages/OrderConfirmationPage.ts
Normal file
115
playwright-tests/pages/OrderConfirmationPage.ts
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
import test, { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { PaymentType, ServicePackage } from '@business-logic/types/Enums';
|
||||||
|
import { ITestData } from '@business-logic/types/ITestData';
|
||||||
|
|
||||||
|
export class OrderConfirmationPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly serviceText: Locator;
|
||||||
|
readonly emailText: Locator;
|
||||||
|
readonly apptDateText: Locator;
|
||||||
|
readonly amountDueText: Locator;
|
||||||
|
readonly viewCartButton: Locator;
|
||||||
|
readonly deductibleText: Locator;
|
||||||
|
readonly subtotalText: Locator;
|
||||||
|
readonly finalAmountDue: Locator;
|
||||||
|
readonly cartServicePackageText: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=order-confirmation';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.serviceText = this.page.locator('[class="appointment-text text-center lh-base"]');
|
||||||
|
this.emailText = this.page.locator('[class="email-confirmation-text"]');
|
||||||
|
this.apptDateText = this.page.locator('[class="appointment-date-time text-center mt-4"]');
|
||||||
|
this.amountDueText = this.page.getByLabel('expand cart details');
|
||||||
|
this.viewCartButton = this.page.locator('#cart-dropdown-head');
|
||||||
|
this.deductibleText = this.page.locator('#deductible-value');
|
||||||
|
this.subtotalText = this.page.locator("#subtotal-value");
|
||||||
|
this.finalAmountDue = this.page.locator('#bottom-amount-due-value');
|
||||||
|
this.cartServicePackageText = this.page.locator('#cart-service-package');
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateOrderConfirmationPage(testData: Partial<ITestData>) {
|
||||||
|
// Destructure data we use
|
||||||
|
const { vehicleDetails, customerDetails, servicePackage, isItac,
|
||||||
|
isNoComp, isPolicyFound, claimDetails, paymentDetails } = testData;
|
||||||
|
await this.serviceText.waitFor({ state: "visible" });
|
||||||
|
await this.logOrderNumber();
|
||||||
|
|
||||||
|
// Grab text
|
||||||
|
const serviceTextValue = await this.serviceText.textContent();
|
||||||
|
const apptDateValue = await this.apptDateText.textContent();
|
||||||
|
const emailTextValue = await this.emailText.textContent();
|
||||||
|
const servicePackageValue = await this.cartServicePackageText.textContent();
|
||||||
|
const amountDueValue = await this.amountDueText.textContent();
|
||||||
|
const deductibleTextValue = (isItac || isNoComp)? null: await this.deductibleText.textContent();
|
||||||
|
const subtotalTextValue = await this.subtotalText.textContent();
|
||||||
|
const finalAmountDueValue = await this.finalAmountDue.textContent();
|
||||||
|
|
||||||
|
// Extract service package price
|
||||||
|
const servicePackageAmt = Number.parseFloat(servicePackageValue!.split('$')[1].replaceAll(',', ''));
|
||||||
|
|
||||||
|
// General Validations
|
||||||
|
expect.soft(serviceTextValue).toContain(`${vehicleDetails!.year} ${vehicleDetails!.make} ${vehicleDetails!.model}`);
|
||||||
|
expect.soft(apptDateValue).toContain(customerDetails!.apptDate);
|
||||||
|
expect.soft(emailTextValue).toContain(customerDetails!.email);
|
||||||
|
|
||||||
|
// Service package validations
|
||||||
|
await expect.soft(this.cartServicePackageText).toContainText(`${servicePackage}`)
|
||||||
|
if (servicePackage === ServicePackage.Premium || servicePackage === ServicePackage.Standard) {
|
||||||
|
expect.soft(servicePackageValue).toContain('New wiper blades');
|
||||||
|
}
|
||||||
|
if (servicePackage === ServicePackage.Premium) {
|
||||||
|
expect.soft(servicePackageValue).toContain('Rain Defense™ treatment');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Price validations
|
||||||
|
if (servicePackage === ServicePackage.GlassOnly) {
|
||||||
|
expect.soft(servicePackageAmt).toEqual(0);
|
||||||
|
} else {
|
||||||
|
expect.soft(servicePackageAmt).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPolicyFound) {
|
||||||
|
// Extract numbers
|
||||||
|
const amountDueAmt = Number.parseFloat(amountDueValue!.split('$')[1].replaceAll(',', ''));
|
||||||
|
const deductibleAmt = deductibleTextValue? Number.parseFloat(deductibleTextValue.split('$')[1].replaceAll(',', '')): 0;
|
||||||
|
const subtotalAmt = Number.parseFloat(subtotalTextValue!.split('$')[1].replaceAll(',', ''));
|
||||||
|
subtotalTextValue?.replaceAll(',', '')
|
||||||
|
const finalAmountDueAmt = Number.parseFloat(finalAmountDueValue!.split('$')[1].replaceAll(',', ''));
|
||||||
|
|
||||||
|
if (!(isItac || isNoComp)) {
|
||||||
|
expect.soft(subtotalAmt).toEqual(claimDetails!.policyDeductible + servicePackageAmt);
|
||||||
|
expect.soft(deductibleAmt).toEqual(claimDetails!.policyDeductible);
|
||||||
|
} else {
|
||||||
|
expect.soft(subtotalAmt).toBeGreaterThan(0);
|
||||||
|
expect.soft(deductibleAmt).toEqual(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paymentDetails!.paymentType === PaymentType.PayAtService && (claimDetails!.policyDeductible > 0 || servicePackageAmt > 0)) {
|
||||||
|
// Verify amount due > 0
|
||||||
|
expect.soft(amountDueAmt).toBeGreaterThan(0);
|
||||||
|
expect.soft(finalAmountDueAmt).toBeGreaterThan(0);
|
||||||
|
} else {
|
||||||
|
// Verify amount due 0
|
||||||
|
expect.soft(amountDueAmt).toEqual(0);
|
||||||
|
expect.soft(finalAmountDueAmt).toEqual(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expect.soft(amountDueValue).toContain('Verifying coverage');
|
||||||
|
expect.soft(subtotalTextValue).toEqual('Verifying coverage');
|
||||||
|
expect.soft(finalAmountDueValue).toEqual('Verifying coverage');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async logOrderNumber() {
|
||||||
|
const sessionStorage = JSON.parse(await this.page.evaluate('sessionStorage.getItem(\'submittedOrder\')'));
|
||||||
|
const workOrderNumber = sessionStorage.workOrderNumber;
|
||||||
|
await test.step(`SessionStorage Work Order Number:${workOrderNumber}`, async () => {
|
||||||
|
console.log(`SessionStorage Work Order Number:${workOrderNumber}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
35
playwright-tests/pages/PartQuestionsPage.ts
Normal file
35
playwright-tests/pages/PartQuestionsPage.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IPartQuestion } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class PartQuestionsPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=part-questions';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async validatePartQuestions(partQuestions: IPartQuestion[]) {
|
||||||
|
for (const pq of partQuestions) {
|
||||||
|
const partQuestionOptions = this.page.locator(`fieldset[aria-labelledby="${pq.partQuestionType}"]`);
|
||||||
|
if (pq.isOnPage) {
|
||||||
|
await expect(partQuestionOptions).toBeAttached();
|
||||||
|
} else {
|
||||||
|
await expect(partQuestionOptions).not.toBeAttached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectPartQuestionResponses(partQuestions: IPartQuestion[]) {
|
||||||
|
for (const pq of partQuestions) {
|
||||||
|
const partQuestionOptionButton = this.page.locator(`fieldset[aria-labelledby="${pq.partQuestionType}"]`).locator(`[buttonlabel="${pq.optionToSelect}"]`);
|
||||||
|
await partQuestionOptionButton.click();
|
||||||
|
if (pq.secondaryQuestionOptionToSelect != null) {
|
||||||
|
const secondaryQuestionButton = this.page.getByText(`${pq.secondaryQuestionOptionToSelect}`);
|
||||||
|
secondaryQuestionButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
playwright-tests/pages/PaymentMethodPage.ts
Normal file
82
playwright-tests/pages/PaymentMethodPage.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { PaymentType } from '@business-logic/types/Enums';
|
||||||
|
import { PaymentPage } from './PaymentPage';
|
||||||
|
import { AfterpayPage } from './AfterpayPage';
|
||||||
|
import { PaypalPage } from './PaypalPage';
|
||||||
|
|
||||||
|
export class PaymentMethodPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly payAtServiceButton: Locator;
|
||||||
|
readonly payNowButton: Locator;
|
||||||
|
readonly payInFourButton: Locator;
|
||||||
|
readonly paypalButton: Locator;
|
||||||
|
readonly paymentPage: PaymentPage;
|
||||||
|
readonly paypalPage: PaypalPage;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=payment-method';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.payAtServiceButton = this.page.locator('[buttonlabel="Pay at my appointment"]'); //this.page.getByText('Pay at time of service');
|
||||||
|
this.payNowButton = this.page.locator('[buttonlabel="Pay now"]');
|
||||||
|
this.payInFourButton = this.page.locator('[buttonlabel="Pay in 4 installments"]');
|
||||||
|
this.paypalButton = this.page.frameLocator('iframe[name="card-frame"]').locator('div[id="paypalParentDiv"]');
|
||||||
|
this.paymentPage = new PaymentPage(page);
|
||||||
|
this.paypalPage = new PaypalPage(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
async executePayment(paymentDetails: IPaymentDetails) {
|
||||||
|
const browserContext = this.page.context();
|
||||||
|
|
||||||
|
switch (paymentDetails.paymentType) {
|
||||||
|
case PaymentType.Credit:
|
||||||
|
await this.selectCreditCard();
|
||||||
|
await this.paymentPage.populateCreditCardDetails(paymentDetails);
|
||||||
|
break;
|
||||||
|
case PaymentType.Paypal:
|
||||||
|
await this.selectPaypal();
|
||||||
|
await this.paypalPage.completePaypalPurchase(paymentDetails);
|
||||||
|
break;
|
||||||
|
case PaymentType.AfterPay:
|
||||||
|
await this.payInFourButton.click();
|
||||||
|
await this.nextPage();
|
||||||
|
|
||||||
|
// Capture popup
|
||||||
|
const afterpayPopup = await browserContext.waitForEvent('page');
|
||||||
|
const afterpayPage = new AfterpayPage(afterpayPopup);
|
||||||
|
|
||||||
|
// Execute payment
|
||||||
|
await afterpayPage.executeAfterpayPayment(paymentDetails);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PaymentType.PayAtService:
|
||||||
|
await this.selectPayAtService();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error('PaymentMethodPage >> Logic for this payment method unimplemented');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectPaypal() {
|
||||||
|
await this.payNowButton.click();
|
||||||
|
await this.nextPage();
|
||||||
|
await this.paypalButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectCreditCard() {
|
||||||
|
await this.payNowButton.click();
|
||||||
|
await this.nextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectPayAtService() {
|
||||||
|
await this.payAtServiceButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
// async validateAmountDue(customer){
|
||||||
|
// await this.amountDueDropDown.click();
|
||||||
|
// await expect(this.deductibleAmountTextField).toContainText(`${customer.deductibleAmount}`);
|
||||||
|
// }
|
||||||
|
}
|
||||||
45
playwright-tests/pages/PaymentPage.ts
Normal file
45
playwright-tests/pages/PaymentPage.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class PaymentPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly cardNumberTextField: Locator;
|
||||||
|
readonly expirationMonthDropDown: Locator;
|
||||||
|
readonly expirationYearDropDown: Locator;
|
||||||
|
readonly cvvTextField: Locator;
|
||||||
|
readonly billingAddressTextField: Locator;
|
||||||
|
readonly cityTextField: Locator;
|
||||||
|
readonly stateDropDown: Locator;
|
||||||
|
readonly billingZipTextField: Locator;
|
||||||
|
readonly submitPaymentButton: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=payment-page';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.cardNumberTextField = page.frameLocator('iframe[name="card-frame"]').getByLabel('Card number*');
|
||||||
|
this.expirationMonthDropDown = page.frameLocator('iframe[name="card-frame"]').getByRole('combobox', { name: 'Expiration month' });
|
||||||
|
this.expirationYearDropDown = page.frameLocator('iframe[name="card-frame"]').getByRole('combobox', { name: 'Expiration year' });
|
||||||
|
this.cvvTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'CVV' });
|
||||||
|
this.billingAddressTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'Billing address' });
|
||||||
|
this.cityTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'City' });
|
||||||
|
this.stateDropDown = page.frameLocator('iframe[name="card-frame"]').getByRole('combobox', { name: 'State' });
|
||||||
|
this.billingZipTextField = page.frameLocator('iframe[name="card-frame"]').getByRole('textbox', { name: 'Billing ZIP code' });;
|
||||||
|
this.submitPaymentButton = page.frameLocator('iframe[name="card-frame"]').locator('#buttonContainer');
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async populateCreditCardDetails(paymentDetails: IPaymentDetails){
|
||||||
|
await this.cardNumberTextField.fill(paymentDetails.cardNumber || '');
|
||||||
|
await this.expirationMonthDropDown.selectOption(paymentDetails.expirationMonth!);
|
||||||
|
await this.expirationYearDropDown.selectOption(paymentDetails.expirationYear!);
|
||||||
|
await this.cvvTextField.fill(paymentDetails.cvv!);
|
||||||
|
await this.billingAddressTextField.fill(paymentDetails.billingAddress!.street);
|
||||||
|
await this.cityTextField.fill(paymentDetails.billingAddress!.city);
|
||||||
|
await this.stateDropDown.selectOption(paymentDetails.billingAddress!.state);
|
||||||
|
await this.billingZipTextField.fill(paymentDetails.billingAddress!.postalCode);
|
||||||
|
await this.submitPaymentButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
27
playwright-tests/pages/PaypalPage.ts
Normal file
27
playwright-tests/pages/PaypalPage.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IPaymentDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class PaypalPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly loginWithPasswordButton: Locator;
|
||||||
|
readonly passwordTextBox: Locator;
|
||||||
|
readonly paypalLoginButton: Locator;
|
||||||
|
readonly completePurchaseButton: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.loginWithPasswordButton = page.getByRole('link', { name: 'Log in with a password instead' });
|
||||||
|
this.passwordTextBox = page.getByPlaceholder('Password');
|
||||||
|
this.paypalLoginButton = page.getByRole('button', { name: 'Log In', exact: true });
|
||||||
|
this.completePurchaseButton = page.getByTestId('submit-button-initial');
|
||||||
|
}
|
||||||
|
|
||||||
|
async completePaypalPurchase(paymentDetails: IPaymentDetails){
|
||||||
|
await this.loginWithPasswordButton.click();
|
||||||
|
await this.passwordTextBox.fill(paymentDetails.password!);
|
||||||
|
await this.paypalLoginButton.click();
|
||||||
|
await this.completePurchaseButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
playwright-tests/pages/PolicyHolderDetailsPage.ts
Normal file
20
playwright-tests/pages/PolicyHolderDetailsPage.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { AddressForm } from './forms/AddressForm';
|
||||||
|
|
||||||
|
export class PolicyHolderDetailsPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly addressForm: AddressForm;
|
||||||
|
readonly url = process.env['BASE_URL']! + '/?issPage=policy-holder-details';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.addressForm = new AddressForm(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fillCustomerDetails(customerDetails: ICustomerDetails){
|
||||||
|
await this.addressForm.populateAddress(customerDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
playwright-tests/pages/PolicyVehiclesPage.ts
Normal file
27
playwright-tests/pages/PolicyVehiclesPage.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class PolicyVehiclesPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=policy-vehicles';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateVehicleIsOnPolicy(vehicleDetails: IVehicleDetails) {
|
||||||
|
const vehicleRegExp = new RegExp(`${vehicleDetails.year} .+ ${vehicleDetails.model}`, 'i');
|
||||||
|
await expect.soft(this.page.getByRole('radio', {name: vehicleRegExp})).toBeAttached();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectVehicle(vehicleDetails: IVehicleDetails){
|
||||||
|
const vehicleRegExp = new RegExp(`${vehicleDetails.year} .+ ${vehicleDetails.model}`, 'i');
|
||||||
|
await this.page.locator('label').filter({hasText: vehicleRegExp}).locator('div').click();
|
||||||
|
}
|
||||||
|
async selectVehicleNotListed(){
|
||||||
|
await this.page.getByText('Vehicle not listed').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
playwright-tests/pages/ProviderPreferencePage.ts
Normal file
50
playwright-tests/pages/ProviderPreferencePage.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import test, { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
|
export class ProviderPreferencePage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly scheduleWithSafelite: Locator;
|
||||||
|
readonly scheduleWithOther: Locator;
|
||||||
|
readonly acknowledgeAdasButton: Locator;
|
||||||
|
readonly gotItButton: Locator;
|
||||||
|
readonly acknowledgeCheckbox: Locator;
|
||||||
|
readonly stateLawModalHeading: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=provider-preference';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
// this.scheduleWithSafelite = this.page.locator('div').filter({ hasText: /Schedule online with |Safelite AutoGlass/ }).first();
|
||||||
|
this.scheduleWithSafelite = this.page.getByText(/Schedule online with|Safelite AutoGlass/).first();
|
||||||
|
this.scheduleWithOther = this.page.getByText(/Find another shop|Choose my own shop/).first();
|
||||||
|
this.acknowledgeAdasButton = this.page.getByLabel('I acknowledge that my vehicle');
|
||||||
|
this.gotItButton = this.page.getByRole('button', { name: 'Got it' });
|
||||||
|
this.acknowledgeCheckbox = this.page.locator('#tpaAcknowledgement');
|
||||||
|
this.stateLawModalHeading = this.page.getByRole('heading').filter({ hasText: /.* State Law/});
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectProvider(isSafelite = true) {
|
||||||
|
if (isSafelite) {
|
||||||
|
await this.scheduleWithSafelite.click();
|
||||||
|
await this.nextPage();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await this.scheduleWithOther.waitFor({ state: 'visible' });
|
||||||
|
await this.scheduleWithOther.click();
|
||||||
|
await this.continueButton.click();
|
||||||
|
//if(await this.acknowledgeAdasButton.isEnabled({timeout: 2500})){
|
||||||
|
// await this.acknowledgeAdasButton.click();
|
||||||
|
// await this.gotItButton.click();
|
||||||
|
// await this.page.waitForTimeout(1000);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async acknowledgeRecalNotificaiton() {
|
||||||
|
await this.acknowledgeAdasButton.click();
|
||||||
|
await this.gotItButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateStateLawModalIsVisible() {
|
||||||
|
await expect.soft(this.stateLawModalHeading).toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
58
playwright-tests/pages/SchedulePage.ts
Normal file
58
playwright-tests/pages/SchedulePage.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IAppointmentDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { formatDate, formatTime } from '@impl/utils/DateUtils';
|
||||||
|
import { ServiceLocation } from '@business-logic/types/Enums';
|
||||||
|
|
||||||
|
export class SchedulePage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=schedule-page';
|
||||||
|
readonly firstAvailableDate: Locator;
|
||||||
|
readonly firstAvailableTime: Locator;
|
||||||
|
readonly modalContinueButton: Locator;
|
||||||
|
readonly dropOffButton: Locator;
|
||||||
|
readonly dateText: Locator;
|
||||||
|
readonly viewMoreDatesLink: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.firstAvailableDate = this.page.locator('.selectable-day').locator('nth=0');
|
||||||
|
this.firstAvailableTime = this.page.locator('label').filter({ hasText: /AM|PM/ }).locator('div').locator('nth=0');
|
||||||
|
this.modalContinueButton = this.page.locator('#modalbtn');
|
||||||
|
this.dropOffButton = this.page.getByText('Drop off your vehicle', { exact: true });
|
||||||
|
this.dateText = this.page.locator('[class="modal-header mb-2 mt-2"]');
|
||||||
|
this.viewMoreDatesLink = this.page.getByText(/View more dates/).first();
|
||||||
|
}
|
||||||
|
|
||||||
|
async scheduleAppointment(appointmentDetails: IAppointmentDetails) {
|
||||||
|
const formattedDate = formatDate(appointmentDetails.appointmentDate!);
|
||||||
|
const formattedTime = formatTime(appointmentDetails.appointmentDate!);
|
||||||
|
const dateInput = this.page.locator(`div[id="${formattedDate}"]`);
|
||||||
|
const timeButton = this.page.locator(`div[aria-label="${formattedTime}"]`);
|
||||||
|
if (await dateInput.isVisible()) {
|
||||||
|
await dateInput.click();
|
||||||
|
} else {
|
||||||
|
await this.viewMoreDatesLink.click();
|
||||||
|
await dateInput.click();
|
||||||
|
}
|
||||||
|
await timeButton.click();
|
||||||
|
await this.modalContinueButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async scheduleFirstAppointment(serviceLocation: ServiceLocation) {
|
||||||
|
if (await this.firstAvailableDate.isVisible()) {
|
||||||
|
await this.firstAvailableDate.click();
|
||||||
|
} else {
|
||||||
|
await this.viewMoreDatesLink.click();
|
||||||
|
while(await this.firstAvailableDate.isHidden()){
|
||||||
|
await this.viewMoreDatesLink.click();
|
||||||
|
}
|
||||||
|
await this.firstAvailableDate.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceLocation === ServiceLocation.DropOff ? await this.dropOffButton.click() : await this.firstAvailableTime.click();
|
||||||
|
await this.modalContinueButton.click();
|
||||||
|
return (`${await this.dateText.allInnerTexts()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
135
playwright-tests/pages/ServiceLocationPage.ts
Normal file
135
playwright-tests/pages/ServiceLocationPage.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IAppointmentDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { ServiceLocation } from '@business-logic/types/Enums';
|
||||||
|
import { AddressForm } from './forms/AddressForm';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
|
export class ServiceLocationPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
|
||||||
|
readonly addressForm: AddressForm;
|
||||||
|
|
||||||
|
// Initial selection
|
||||||
|
readonly inShopButton: Locator;
|
||||||
|
readonly mobileButton: Locator;
|
||||||
|
readonly dropOffButton: Locator;
|
||||||
|
readonly RecalWarningMessage1: Locator;
|
||||||
|
readonly RecalWarningMessage2: Locator;
|
||||||
|
readonly militaryWarningMessage: Locator;
|
||||||
|
|
||||||
|
// For in-shop and drop off
|
||||||
|
readonly selectAShopOptions: Locator;
|
||||||
|
readonly firstAppointmentButton: Locator;
|
||||||
|
readonly changeZipButton: Locator;
|
||||||
|
readonly updateZipTextBox: Locator;
|
||||||
|
readonly saveZipButton: Locator;
|
||||||
|
|
||||||
|
// For mobile
|
||||||
|
readonly enterServiceAddressButton: Locator;
|
||||||
|
readonly serviceAddressTextBox: Locator;
|
||||||
|
readonly aptNumberTextBox: Locator;
|
||||||
|
readonly cityTextBox: Locator;
|
||||||
|
readonly stateDropDown: Locator;
|
||||||
|
readonly zipCodeTextBox: Locator;
|
||||||
|
readonly vehicleProtectedYesButton: Locator;
|
||||||
|
readonly vehicleProtectedNoButton: Locator;
|
||||||
|
readonly saveAddressButton: Locator;
|
||||||
|
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=service-location';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
|
||||||
|
this.addressForm = new AddressForm(page);
|
||||||
|
|
||||||
|
// Initial selection
|
||||||
|
this.inShopButton = this.page.locator('[buttonlabel="In-shop"]');
|
||||||
|
this.mobileButton = this.page.locator('[buttonlabel="Mobile"]')
|
||||||
|
this.dropOffButton = this.page.locator('[buttonlabel="Drop-off"]');
|
||||||
|
this.RecalWarningMessage1 = this.page.getByText(/We're not able to provide mobile service/);
|
||||||
|
this.RecalWarningMessage2 = this.page.getByText(/advanced safety system recalibration needs to be done in our shop./);
|
||||||
|
this.militaryWarningMessage = this.page.locator('[class*="widget-name-AlertMilitaryBaseZipWidget"]');
|
||||||
|
|
||||||
|
// For in-shop and drop off
|
||||||
|
this.selectAShopOptions = this.page.locator('[class="shop-question"]');
|
||||||
|
this.firstAppointmentButton = this.page.locator('div').filter({ hasText: /Appts/}).first();
|
||||||
|
this.changeZipButton = this.page.locator('[id="serviceZipLinkPromptId"]');
|
||||||
|
this.updateZipTextBox = this.page.getByRole('textbox', { name: 'Update your service ZIP code'});
|
||||||
|
this.saveZipButton = this.page.getByRole('button', { name: 'Save ZIP code' });
|
||||||
|
|
||||||
|
|
||||||
|
// For mobile
|
||||||
|
this.enterServiceAddressButton = this.page.getByRole('link', { name: 'Enter your service address' });
|
||||||
|
this.serviceAddressTextBox = this.page.getByRole('textbox', { name: 'Street Address' });
|
||||||
|
this.aptNumberTextBox = this.page.getByRole('textbox', { name: 'Apt. number'});
|
||||||
|
this.cityTextBox = this.page.getByRole('textbox', { name: 'City' });
|
||||||
|
this.stateDropDown = this.page.getByRole('combobox', { name: 'State' });
|
||||||
|
this.zipCodeTextBox = this.page.getByRole('textbox', { name: 'Zip code' });
|
||||||
|
this.vehicleProtectedYesButton = this.page.locator('label').filter({ hasText: 'Yes' }).locator('div');
|
||||||
|
this.vehicleProtectedNoButton = this.page.locator('label').filter({ hasText: 'No' }).locator('div');
|
||||||
|
this.saveAddressButton = this.page.getByRole('button', { name: 'Save Address' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectLocation(appointmentDetails: IAppointmentDetails){
|
||||||
|
if (appointmentDetails.alternateServiceZip) {
|
||||||
|
await this.changeZipButton.click();
|
||||||
|
await this.fillAndValidate(this.updateZipTextBox, appointmentDetails.alternateServiceZip);
|
||||||
|
await this.saveZipButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(appointmentDetails.serviceLocation) {
|
||||||
|
case ServiceLocation.Mobile:
|
||||||
|
await this.scheduleMobile(appointmentDetails);
|
||||||
|
break;
|
||||||
|
case ServiceLocation.InShop:
|
||||||
|
await this.scheduleInShop(appointmentDetails);
|
||||||
|
break;
|
||||||
|
case ServiceLocation.DropOff:
|
||||||
|
await this.scheduleDropOff(appointmentDetails);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async scheduleInShop(appointmentDetails?: IAppointmentDetails) {
|
||||||
|
await this.inShopButton.click();
|
||||||
|
if (appointmentDetails && appointmentDetails.shopAddress) {
|
||||||
|
await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check();
|
||||||
|
} else {
|
||||||
|
await this.firstAppointmentButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async scheduleMobile(appointmentDetails: IAppointmentDetails){
|
||||||
|
if (appointmentDetails.serviceAddress) {
|
||||||
|
await this.mobileButton.click();
|
||||||
|
await this.enterServiceAddressButton.click();
|
||||||
|
await this.addressForm.populateAddress({ address: appointmentDetails.serviceAddress! });
|
||||||
|
if (faker.datatype.boolean()) {
|
||||||
|
await this.vehicleProtectedYesButton.check();
|
||||||
|
} else {
|
||||||
|
await this.vehicleProtectedNoButton.check();
|
||||||
|
}
|
||||||
|
await this.saveAddressButton.click();
|
||||||
|
} else {
|
||||||
|
console.error('ServiceLocationPage >> Please supply an address')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async scheduleDropOff(appointmentDetails?: IAppointmentDetails){
|
||||||
|
await this.dropOffButton.click();
|
||||||
|
if (appointmentDetails && appointmentDetails.shopAddress) {
|
||||||
|
await this.selectAShopOptions.locator(`[buttonbodycopy="${appointmentDetails.shopAddress}"]`).check();
|
||||||
|
} else {
|
||||||
|
await this.firstAppointmentButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateRecalWarning(){
|
||||||
|
await expect(this.RecalWarningMessage1).toBeVisible();
|
||||||
|
await expect(this.RecalWarningMessage2).toBeVisible();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
26
playwright-tests/pages/ServicePackagesPage.ts
Normal file
26
playwright-tests/pages/ServicePackagesPage.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { ServicePackage } from '@business-logic/types/Enums';
|
||||||
|
|
||||||
|
export class ServicePackagesPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly standardPackageButton: Locator;
|
||||||
|
readonly premiumPackageButton: Locator;
|
||||||
|
readonly glassOnlyButton: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=service-packages';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.standardPackageButton = this.page.locator('li').filter({ hasText: 'Standard' });
|
||||||
|
this.premiumPackageButton = this.page.locator('li').filter({ hasText: 'Premium' });
|
||||||
|
this.glassOnlyButton = this.page.locator('li').filter({ hasText: 'Glass service' });
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectServicePackage(servicePackage: ServicePackage){
|
||||||
|
await this.page.getByText(servicePackage).click();
|
||||||
|
return (`${await this.page.locator('li').filter({ hasText: 'Premium' }).locator('[class="pricing-info"]').allInnerTexts()}`);
|
||||||
|
//return (`${await this.page.getByText(servicePackage).locator('[class="pricing-info"]').allInnerTexts()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
playwright-tests/pages/TpaConfirmationPage.ts
Normal file
19
playwright-tests/pages/TpaConfirmationPage.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
|
export class TpaConfirmationPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly successMessage: Locator;
|
||||||
|
readonly url = process.env['BASE_URL']! + '/?issPage=tpa-confirmation';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.successMessage = page.getByText('Success');
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateSuccessMessage() {
|
||||||
|
await expect(this.successMessage).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
24
playwright-tests/pages/TpaSearchPage.ts
Normal file
24
playwright-tests/pages/TpaSearchPage.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
|
export class TpaSearchPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly firstLocationButton: Locator;
|
||||||
|
readonly doNotSeeMyShopButton: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=tpa-search';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.firstLocationButton = page.locator("fieldset[aria-labelledby='chooseShop']").first();
|
||||||
|
this.doNotSeeMyShopButton = page.getByRole('link', { name: 'I don\'t see my shop' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectDoNotSeeMyShop() {
|
||||||
|
await this.doNotSeeMyShopButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectFirstLocation() {
|
||||||
|
await this.firstLocationButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
18
playwright-tests/pages/TpaSubmitPage.ts
Normal file
18
playwright-tests/pages/TpaSubmitPage.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
|
export class TpaSubmitPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly deductible: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=tpa-submit';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.deductible = page.getByText('Deductible $');
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateDeductible(expDeductible) {
|
||||||
|
await expect(this.deductible).toContainText(expDeductible);
|
||||||
|
}
|
||||||
|
}
|
||||||
150
playwright-tests/pages/VehicleDamagePage.ts
Normal file
150
playwright-tests/pages/VehicleDamagePage.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { SideDoorDamage, VehicleDamage, WindshieldDamage } from '@business-logic/types/Enums';
|
||||||
|
|
||||||
|
export class VehicleDamagePage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly windshieldChkBox: Locator;
|
||||||
|
readonly crackButton: Locator;
|
||||||
|
readonly chipButton: Locator;
|
||||||
|
readonly sideDoorButton: Locator;
|
||||||
|
readonly driverSideButton: Locator;
|
||||||
|
readonly passengerSideButton: Locator;
|
||||||
|
readonly driverQuarterPanelChkBox: Locator;
|
||||||
|
readonly driverFrontDoorChkBox: Locator;
|
||||||
|
readonly driverBackDoorChkBox: Locator;
|
||||||
|
readonly driverSlidingDoorChkBox: Locator;
|
||||||
|
readonly driverVentGlassChkBox: Locator;
|
||||||
|
readonly passengerQuarterPanelChkBox: Locator;
|
||||||
|
readonly passengerFrontDoorChkBox: Locator;
|
||||||
|
readonly passengerBackDoorChkBox: Locator;
|
||||||
|
readonly passengerVentGlassChkBox: Locator;
|
||||||
|
readonly rearWindowChkBox: Locator;
|
||||||
|
readonly separateApptsWarning: Locator;
|
||||||
|
readonly editVehicleButton: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=vehicle-damage';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.windshieldChkBox = this.page.locator('[buttonlabel="Windshield"]');
|
||||||
|
this.crackButton = this.page.locator('[buttonlabel="Crack"]');
|
||||||
|
this.chipButton = this.page.locator('[buttonlabel="Chip(s)"]');
|
||||||
|
this.sideDoorButton = this.page.locator('[buttonlabel="Side door"]');
|
||||||
|
this.driverSideButton = this.page.locator('[buttonlabel="Driver side"]');
|
||||||
|
this.passengerSideButton = this.page.locator('[buttonlabel="Passenger side"]');
|
||||||
|
this.driverQuarterPanelChkBox = this.page.locator('[aria-labelledby="driverSideOptions"]').locator('[buttonlabel="Quarter panel"]');
|
||||||
|
this.driverFrontDoorChkBox = this.page.locator('[aria-labelledby="driverSideOptions"]').locator('[buttonlabel="Front door"]');
|
||||||
|
this.driverBackDoorChkBox = this.page.locator('[aria-labelledby="driverSideOptions"]').locator('[buttonlabel="Back door"]');
|
||||||
|
this.driverVentGlassChkBox = this.page.locator('[aria-labelledby="driverSideOptions"]').locator('[buttonlabel="Vent glass"]');
|
||||||
|
this.driverSlidingDoorChkBox = this.page.locator('[aria-labelledby="driverSideOptions"]').locator('[buttonlabel="Sliding door"]');
|
||||||
|
this.passengerQuarterPanelChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Quarter panel"]');
|
||||||
|
this.passengerFrontDoorChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Front door"]');
|
||||||
|
this.passengerVentGlassChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Vent glass"]');
|
||||||
|
this.passengerBackDoorChkBox = this.page.locator('[aria-labelledby="passengerSideOptions"]').locator('[buttonlabel="Back door"]');
|
||||||
|
this.rearWindowChkBox = this.page.locator('[buttonlabel="Rear window"]');
|
||||||
|
this.separateApptsWarning = this.page.locator('[class*="widget-name-HasReplacementConflict"]');
|
||||||
|
this.editVehicleButton = this.page.getByRole('link', { name: 'Edit vehicle' });
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkSeparateApptsWarning() {
|
||||||
|
// Select conflict
|
||||||
|
await this.windshieldChkBox.check();
|
||||||
|
await this.chipButton.check();
|
||||||
|
await this.rearWindowChkBox.check();
|
||||||
|
|
||||||
|
// Expect warning
|
||||||
|
await expect.soft(this.separateApptsWarning).toBeAttached();
|
||||||
|
|
||||||
|
// Undo changes
|
||||||
|
await this.crackButton.check();
|
||||||
|
await this.windshieldChkBox.uncheck();
|
||||||
|
await expect.soft(this.crackButton).not.toBeVisible();
|
||||||
|
await this.rearWindowChkBox.uncheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectDamage(vehicleDamage: VehicleDamage[]) {
|
||||||
|
for (const damage of vehicleDamage) {
|
||||||
|
switch(damage) {
|
||||||
|
case VehicleDamage.WindshieldOneChip:
|
||||||
|
await this.windshieldChkBox.check();
|
||||||
|
await this.selectChips('1');
|
||||||
|
break;
|
||||||
|
case VehicleDamage.WindshieldTwoChips:
|
||||||
|
await this.windshieldChkBox.check();
|
||||||
|
await this.selectChips('2');
|
||||||
|
break;
|
||||||
|
case VehicleDamage.WindshieldThreeChips:
|
||||||
|
await this.windshieldChkBox.check();
|
||||||
|
await this.selectChips('3');
|
||||||
|
break;
|
||||||
|
case VehicleDamage.WindshieldCrack:
|
||||||
|
await this.windshieldChkBox.check();
|
||||||
|
await this.selectCrack();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.DriverFrontDoor:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.driverSideButton.check();
|
||||||
|
await this.driverFrontDoorChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.DriverRearDoor:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.driverSideButton.check();
|
||||||
|
await this.driverBackDoorChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.DriverQuarterPanel:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.driverSideButton.check();
|
||||||
|
await this.driverQuarterPanelChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.DriverVentGlass:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.driverSideButton.check();
|
||||||
|
await this.driverVentGlassChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.DriverSlidingDoor:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.driverSideButton.check();
|
||||||
|
await this.driverSlidingDoorChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.PassengerFrontDoor:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.passengerSideButton.check();
|
||||||
|
await this.passengerFrontDoorChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.PassengerRearDoor:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.passengerSideButton.check();
|
||||||
|
await this.passengerBackDoorChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.PassengerQuarterPanel:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.passengerSideButton.check();
|
||||||
|
await this.passengerQuarterPanelChkBox.click();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.PassengerVentGlass:
|
||||||
|
await this.sideDoorButton.check();
|
||||||
|
await this.passengerSideButton.check();
|
||||||
|
await this.passengerVentGlassChkBox.check();
|
||||||
|
break;
|
||||||
|
case VehicleDamage.RearWindow:
|
||||||
|
await this.selectRearWindowDamage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectCrack(){
|
||||||
|
await this.crackButton.check();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectChips(numChips: string){
|
||||||
|
const numChipsButton = this.page.getByText(numChips, {exact: true});
|
||||||
|
await this.chipButton.check();
|
||||||
|
await numChipsButton.check();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectRearWindowDamage(){
|
||||||
|
await this.rearWindowChkBox.check();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
playwright-tests/pages/VehicleLookupAddressPage.ts
Normal file
25
playwright-tests/pages/VehicleLookupAddressPage.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { AddressForm } from './forms/AddressForm';
|
||||||
|
import { VehicleSelectionForm } from './forms/VehicleSelectionForm';
|
||||||
|
import { ICustomerDetails, IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class VehicleLookupAddressPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly addressForm: AddressForm;
|
||||||
|
readonly vehicleSelectionForm: VehicleSelectionForm;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=address-vehicles';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.addressForm = new AddressForm(page);
|
||||||
|
this.vehicleSelectionForm = new VehicleSelectionForm(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
async lookupVehicleByAddress(customerDetails: ICustomerDetails, vehicleDetails: IVehicleDetails) {
|
||||||
|
await this.addressForm.populateAddress(customerDetails);
|
||||||
|
//await this.nextPage();
|
||||||
|
//await this.vehicleSelectionForm.selectVehicle(vehicleDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
playwright-tests/pages/VehicleLookupLicensePage.ts
Normal file
22
playwright-tests/pages/VehicleLookupLicensePage.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class VehicleLookupLicensePage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly licensePlateNumTextBox: Locator;
|
||||||
|
readonly licensePlateStateDrpDwn: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage='; // TODO: Input correct URL
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.licensePlateNumTextBox = page.getByRole('textbox', { name: 'License plate number'});
|
||||||
|
this.licensePlateStateDrpDwn = page.getByRole('combobox', { name: 'License plate state'});
|
||||||
|
}
|
||||||
|
|
||||||
|
async enterPlateDetails(vehicleDetails: IVehicleDetails){
|
||||||
|
await this.licensePlateNumTextBox.fill(vehicleDetails.licensePlateNumber || '');
|
||||||
|
await this.licensePlateStateDrpDwn.selectOption(vehicleDetails.licensePlateState!);
|
||||||
|
}
|
||||||
|
}
|
||||||
61
playwright-tests/pages/VehicleLookupPage.ts
Normal file
61
playwright-tests/pages/VehicleLookupPage.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { VehicleLookupType } from '@business-logic/types/Enums';
|
||||||
|
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { VinLookupPage } from './VinLookupPage';
|
||||||
|
import { VehicleLookupAddressPage } from './VehicleLookupAddressPage';
|
||||||
|
import { VehicleLookupLicensePage } from './VehicleLookupLicensePage';
|
||||||
|
|
||||||
|
export class VehicleLookupPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly vinLookupButton: Locator;
|
||||||
|
readonly addressLookupButton: Locator;
|
||||||
|
readonly licenseLookupButton: Locator;
|
||||||
|
readonly vinLookupPage: VinLookupPage;
|
||||||
|
readonly vehicleLookupAddressPage: VehicleLookupAddressPage;
|
||||||
|
readonly vehicleLookupLicensePage: VehicleLookupLicensePage;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=vehicle-lookup';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.vinLookupButton = page.getByLabel('Provide my VIN manually', { exact: true });
|
||||||
|
this.addressLookupButton = page.getByLabel('Provide my home address', { exact: true });
|
||||||
|
this.licenseLookupButton = page.getByLabel('Provide my license plate #', { exact: true });
|
||||||
|
this.vinLookupPage = new VinLookupPage(page);
|
||||||
|
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async vehicleLookup(vehicleDetails: IVehicleDetails) {
|
||||||
|
switch (vehicleDetails.vehicleLookupType) {
|
||||||
|
case VehicleLookupType.Address:
|
||||||
|
await this.selectAddressLookup();
|
||||||
|
await this.nextPage();
|
||||||
|
break;
|
||||||
|
case VehicleLookupType.LicensePlateNumber:
|
||||||
|
await this.selectLicenseLookup();
|
||||||
|
await this.nextPage();
|
||||||
|
break;
|
||||||
|
case VehicleLookupType.Vin:
|
||||||
|
await this.selectVinLookup();
|
||||||
|
await this.nextPage();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error('VehicleLookupPage >> DATA ISSUE: VehicleLookupType not provided');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectVinLookup(){
|
||||||
|
await this.vinLookupButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectAddressLookup(){
|
||||||
|
await this.addressLookupButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectLicenseLookup(){
|
||||||
|
await this.licenseLookupButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
10
playwright-tests/pages/VehiclePartsPage.ts
Normal file
10
playwright-tests/pages/VehiclePartsPage.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Page } from "@playwright/test";
|
||||||
|
import { PartQuestionsPage } from "./PartQuestionsPage";
|
||||||
|
|
||||||
|
export default class VehiclePartQuestionsPage extends PartQuestionsPage{
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=vehicle-parts';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
playwright-tests/pages/VehicleSelectionPage.ts
Normal file
35
playwright-tests/pages/VehicleSelectionPage.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class VehicleSelectionPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly yearDropdown: Locator;
|
||||||
|
readonly makeDropdown: Locator;
|
||||||
|
readonly modelDropdown: Locator;
|
||||||
|
readonly styleDropdown: Locator;
|
||||||
|
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=vehicle-selection';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.yearDropdown = this.page.locator('#yearQuestionField');
|
||||||
|
this.makeDropdown = this.page.locator('#makeQuestionField');
|
||||||
|
this.modelDropdown = this.page.locator('#modelQuestionField');
|
||||||
|
this.styleDropdown = this.page.locator('#styleQuestionField');
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectVehicle(vehicleDetails: IVehicleDetails) {
|
||||||
|
await this.yearDropdown.selectOption(vehicleDetails.year);
|
||||||
|
await this.yearDropdown.press('Tab');
|
||||||
|
await this.makeDropdown.selectOption(vehicleDetails.make);
|
||||||
|
await this.yearDropdown.press('Tab');
|
||||||
|
await this.modelDropdown.selectOption(vehicleDetails.model);
|
||||||
|
await this.yearDropdown.press('Tab');
|
||||||
|
if (vehicleDetails.style != undefined) {
|
||||||
|
await this.styleDropdown.selectOption(vehicleDetails.style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
playwright-tests/pages/VinLookupPage.ts
Normal file
26
playwright-tests/pages/VinLookupPage.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
|
||||||
|
export class VinLookupPage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly vinLookupTextBox: Locator;
|
||||||
|
readonly lookupVinForMe: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=vin-lookup'; // TODO: Input correct URL
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.vinLookupTextBox = page.getByRole('textbox', { name: 'Enter your VIN' });
|
||||||
|
this.lookupVinForMe = page.getByRole('link', { name: 'look up your VIN' });
|
||||||
|
// this.validateURL(this.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async enterVin(vin: string) {
|
||||||
|
await this.vinLookupTextBox.fill(vin);
|
||||||
|
}
|
||||||
|
|
||||||
|
async triggerBailout() {
|
||||||
|
await this.continueButton.click();
|
||||||
|
await this.lookupVinForMe.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
105
playwright-tests/pages/WelcomePage.ts
Normal file
105
playwright-tests/pages/WelcomePage.ts
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
import test, { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from './BasePage';
|
||||||
|
import { IClaimDetails, ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { getClientAuthByClientTag, getClientSignature } from '@impl/api/AdminServiceApiUtil';
|
||||||
|
import { ICertificateInfo, IClientSignatureRequest } from '@business-logic/types/Authentication';
|
||||||
|
import { buildToken, getTimestamp } from '@impl/utils/TokenUtils';
|
||||||
|
|
||||||
|
export class WelcomePage extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly policyNumber: Locator;
|
||||||
|
readonly policyZip: Locator;
|
||||||
|
readonly damageDate: Locator;
|
||||||
|
readonly damageCause: Locator;
|
||||||
|
readonly phoneNumber: Locator;
|
||||||
|
readonly emailAddress: Locator;
|
||||||
|
readonly city: Locator;
|
||||||
|
readonly state: Locator;
|
||||||
|
readonly cookieCloseButton: Locator;
|
||||||
|
url = process.env['BASE_URL']! + '/?issPage=welcome-page';
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.policyNumber = page.getByRole('textbox', { name: 'Policy number' });
|
||||||
|
this.policyZip = page.getByRole('textbox', { name: 'Policy ZIP' });
|
||||||
|
this.damageDate = page.getByRole('textbox', { name: 'When did the damage occur?<' });
|
||||||
|
this.damageCause = page.locator('#damageCauseQuestionField');
|
||||||
|
this.phoneNumber = page.getByRole('textbox', { name: 'Best number to reach you' });
|
||||||
|
this.emailAddress = page.getByRole('textbox', { name: 'Email address' });
|
||||||
|
this.city = page.getByRole('textbox', { name: 'In which city did the damage' });
|
||||||
|
this.state = page.locator('select[name="\\38 fdf9dc2e13e430eb57529499dceb3eb"]');
|
||||||
|
this.cookieCloseButton = page.getByRole('button', { name: 'Close' });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async goto(clientTag: string) {
|
||||||
|
await this.page.goto(process.env['BASE_URL']! + `/?issPage=entry-page&ClientTag=${clientTag}`);
|
||||||
|
await this.validateURL(this.url);
|
||||||
|
await this.cookieCloseButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async gotoWithAuthentication(clientTag: string) {
|
||||||
|
const clientAuth = await getClientAuthByClientTag(clientTag);
|
||||||
|
const certificate = clientAuth.certificateInfo[0] as ICertificateInfo;
|
||||||
|
let formData: Map<string, string> = new Map<string, string>();
|
||||||
|
formData.set("Timestamp", getTimestamp())
|
||||||
|
const request: IClientSignatureRequest = {
|
||||||
|
clientTag: clientTag,
|
||||||
|
token: buildToken(clientAuth, formData),
|
||||||
|
certificateFileName: certificate.name,
|
||||||
|
certificateKey: certificate.key,
|
||||||
|
certificateAlgorithm: certificate.algorithm,
|
||||||
|
certificateType: certificate.type
|
||||||
|
}
|
||||||
|
const result = await getClientSignature(request);
|
||||||
|
await this.page.goto(process.env['BASE_URL']! + `/?issPage=entry-page&ClientTag=${clientTag}&token=${request.token}&signature=${result.signature}`);
|
||||||
|
await this.validateURL(this.url);
|
||||||
|
await this.logReferralNumber();
|
||||||
|
await this.cookieCloseButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async hasCityInfo() {
|
||||||
|
await expect.soft(this.damageCause).toBeVisible();
|
||||||
|
return this.city.isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
async populatePage(customerDetails: ICustomerDetails, claimDetails: IClaimDetails, isFillCityInfo: boolean) {
|
||||||
|
await this.policyNumber.fill(claimDetails.policyNumber);
|
||||||
|
await this.policyZip.fill(customerDetails.address.postalCode);
|
||||||
|
await this.damageDate.click();
|
||||||
|
await this.damageDate.fill(claimDetails.damageDate);
|
||||||
|
await this.damageCause.selectOption(claimDetails.damageCause);
|
||||||
|
await this.damageCause.press('Tab');
|
||||||
|
await this.phoneNumber.fill(customerDetails.phoneNumber);
|
||||||
|
// Phone number fixed 12/5. Can't start with 1 or 0
|
||||||
|
await this.emailAddress.fill(customerDetails.email);
|
||||||
|
|
||||||
|
if (isFillCityInfo) {
|
||||||
|
await this.city.fill(customerDetails.address.city);
|
||||||
|
await this.state.selectOption(customerDetails.address.state);
|
||||||
|
}
|
||||||
|
await this.logReferralNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
async logReferralNumber() {
|
||||||
|
let mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')'));
|
||||||
|
let referralNumber = mainLocalStorage.order.referralNumber as number;
|
||||||
|
let referralSequenceNumber = mainLocalStorage.order.referralSequenceNumber as number;
|
||||||
|
if (referralNumber == null) {
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
if (!referralNumber == null) break;
|
||||||
|
await this.page.waitForTimeout(500);
|
||||||
|
mainLocalStorage = JSON.parse(await this.page.evaluate('localStorage.getItem(\'main\')'));
|
||||||
|
referralNumber = mainLocalStorage.order.referralNumber as number;
|
||||||
|
referralSequenceNumber = mainLocalStorage.order.referralSequenceNumber as number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Referral Number:${referralNumber} Referral Sequence Number:${referralSequenceNumber}`, async () => {
|
||||||
|
console.log(`Referral Number:${referralNumber}`);
|
||||||
|
console.log(`Referral Sequence Number:${referralSequenceNumber}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
58
playwright-tests/pages/forms/AddressForm.ts
Normal file
58
playwright-tests/pages/forms/AddressForm.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { expect, type Locator, type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from '../BasePage';
|
||||||
|
import { ICustomerDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
import { faker } from '@faker-js/faker/locale/en';
|
||||||
|
|
||||||
|
export class AddressForm extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
readonly streetAddressTextBox: Locator;
|
||||||
|
readonly cityTextBox: Locator;
|
||||||
|
readonly stateDrpDwn: Locator;
|
||||||
|
readonly zipCodeTextBox: Locator;
|
||||||
|
readonly firstNameTextBox: Locator;
|
||||||
|
readonly lastNameTextBox: Locator;
|
||||||
|
readonly addressNotFoundMsg: Locator;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
this.streetAddressTextBox = page.getByRole('textbox', { name: /Street (a|A)ddress$/ });
|
||||||
|
this.cityTextBox = page.getByRole('textbox', { name: 'City' });
|
||||||
|
this.stateDrpDwn = page.getByRole('combobox', { name: 'State' });
|
||||||
|
this.zipCodeTextBox = page.getByRole('textbox', { name: 'ZIP code' });
|
||||||
|
this.firstNameTextBox = page.getByRole('textbox', { name: 'First name' });
|
||||||
|
this.lastNameTextBox = page.getByRole('textbox', { name: 'Last name' });
|
||||||
|
this.addressNotFoundMsg = page.getByText('Address not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
async forceAddressFormToAppear() {
|
||||||
|
await expect(async () => {
|
||||||
|
await this.streetAddressTextBox.click();
|
||||||
|
await this.streetAddressTextBox.pressSequentially('7400 Safelite Way');
|
||||||
|
await this.streetAddressTextBox.press('Tab');
|
||||||
|
await expect(this.zipCodeTextBox).toBeVisible({ timeout: 100 });
|
||||||
|
}).toPass();
|
||||||
|
}
|
||||||
|
|
||||||
|
async populateAddress(customerDetails: Partial<ICustomerDetails>) {
|
||||||
|
|
||||||
|
if (customerDetails.address) {
|
||||||
|
// Force address form to appear
|
||||||
|
await this.forceAddressFormToAppear();
|
||||||
|
|
||||||
|
// Fill address
|
||||||
|
await this.fillAndValidate(this.streetAddressTextBox, customerDetails.address.street);
|
||||||
|
await this.fillAndValidate(this.zipCodeTextBox, customerDetails.address.postalCode)
|
||||||
|
await this.fillAndValidate(this.cityTextBox, customerDetails.address.city);
|
||||||
|
await this.stateDrpDwn.selectOption(customerDetails.address.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customerDetails.firstName) {
|
||||||
|
await this.fillAndValidate(this.firstNameTextBox, customerDetails.firstName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customerDetails.lastName) {
|
||||||
|
await this.fillAndValidate(this.lastNameTextBox, customerDetails.lastName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
playwright-tests/pages/forms/VehicleSelectionForm.ts
Normal file
16
playwright-tests/pages/forms/VehicleSelectionForm.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { type Page } from '@playwright/test';
|
||||||
|
import { BasePage } from '../BasePage';
|
||||||
|
import { IVehicleDetails } from '@business-logic/types/CustomerDetails';
|
||||||
|
|
||||||
|
export class VehicleSelectionForm extends BasePage {
|
||||||
|
readonly page: Page;
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
super(page);
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectVehicle(vehicleDetails: IVehicleDetails){
|
||||||
|
await this.page.locator('label').filter({hasText: vehicleDetails.model}).locator('div').first().click();
|
||||||
|
}
|
||||||
|
}
|
||||||
112
playwright-tests/playwright.config.ts
Normal file
112
playwright-tests/playwright.config.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import dotenv from 'dotenv-safe';
|
||||||
|
import { OrtoniReportConfig } from "ortoni-report";
|
||||||
|
|
||||||
|
if (!process.env.CI) {
|
||||||
|
// Environment variables are present in CI environment, no need to read from file
|
||||||
|
if (process.env.NODE_ENV == 'undefined' || process.env.NODE_ENV == null) {
|
||||||
|
dotenv.config({ path: `playwright-tests/.env.dev`, example: 'playwright-tests/.env.example' });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dotenv.config({ path: `playwright-tests/.env.${process.env.NODE_ENV}`, example: 'playwright-tests/.env.example' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// import dotenv from 'dotenv';
|
||||||
|
// import path from 'path';
|
||||||
|
// dotenv.config({ path: path.resolve(__dirname, '.env') });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
const reportConfig: OrtoniReportConfig = {
|
||||||
|
port: 1994,
|
||||||
|
open: "never",
|
||||||
|
folderPath: "test-results",
|
||||||
|
filename: "index.html",
|
||||||
|
logo: "../data/logo.png",
|
||||||
|
title: "Test Report",
|
||||||
|
showProject: false,
|
||||||
|
projectName: "ISS-Nextgen-Playwright-Report",
|
||||||
|
testType: `E2E- Environment: ${process.env.NODE_ENV} `,
|
||||||
|
preferredTheme: "light",
|
||||||
|
base64Image: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './tests',
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 1 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 2 : 5,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: [
|
||||||
|
['ortoni-report', reportConfig],
|
||||||
|
['list']
|
||||||
|
],
|
||||||
|
timeout: 120_000,
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
// baseURL: 'http://127.0.0.1:3000',
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
headless: process.env.CI ? true : false,
|
||||||
|
screenshot: "only-on-failure",
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'firefox',
|
||||||
|
// use: { ...devices['Desktop Firefox'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
// {
|
||||||
|
// name: 'webkit',
|
||||||
|
// use: { ...devices['Desktop Safari'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
// webServer: {
|
||||||
|
// command: 'npm run start',
|
||||||
|
// url: 'http://127.0.0.1:3000',
|
||||||
|
// reuseExistingServer: !process.env.CI,
|
||||||
|
// },
|
||||||
|
});
|
||||||
628
playwright-tests/tests/0000__M.test.ts
Normal file
628
playwright-tests/tests/0000__M.test.ts
Normal file
|
|
@ -0,0 +1,628 @@
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { expect, Page } from "@playwright/test";
|
||||||
|
import { addSmokeTagToRandomTest, prepareTest, test, TestInfo } from "@business-logic/types/Test";
|
||||||
|
import { RuleEngine, ValidationOptions } from "@business-logic/types/RuleEngine";
|
||||||
|
import { ICustomerDetails } from "@business-logic/types/CustomerDetails";
|
||||||
|
import essentialHpTestCases from "./0016_EssentialRepairInShop";
|
||||||
|
import essentialReplaceTestCases from "./0013_EssentialReplace";
|
||||||
|
import { BailoutCode, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import essentialTpaNotEnabledTestCases from "./0003_EssentialTpaNotEnabledBailout";
|
||||||
|
import essentialDoNotSeeShopTestCases from "./0011_EssentialDoNotSeeShopBailout";
|
||||||
|
import essentialVehicleNotFoundTestCases from "./0010_EssentialVehicleNotFoundBailout";
|
||||||
|
import essentialPartsServiceErrorBailout_0009 from "./0009_EssentialPartsServiceErrorBailout";
|
||||||
|
import essentialHeavyVehicleBailoutTestCases from "./0008_EssentialHeavyVehicleBailout";
|
||||||
|
import essentialRepairMobileTests from "./0014_EssentialRepairMobile";
|
||||||
|
import essentialReplacePartsQuestionsDropoff_0015 from "./0015_EssentialReplacePartsQuestionsDropoff";
|
||||||
|
import essentialUniqueGlassTests from "./0012_EssentialUniqueGlass";
|
||||||
|
import essentialRepairInShopAcuraTests from "./0002_EssentialRepairInShopAcura";
|
||||||
|
import essentialTpaEnabled_0004 from "./0004_EssentialTpaEnabled";
|
||||||
|
import essentialRepairMobileHyundaiTests from "./0007_EssentialRepairHyundaiMobile";
|
||||||
|
import essentialTpaNotEnabledReplace_0017 from "./0017_EssentialTpaNotEnabledBailoutReplace";
|
||||||
|
import essentialTpaEnabledReplace_0018 from "./0018_EssentialTpaEnabledReplace";
|
||||||
|
import essentialTpaEnabledReplaceRecal_0019 from "./0019_EssentialTpaEnabledReplaceRecal";
|
||||||
|
import advancedScenario0001TestCases from "./advanced/0001a_ReplaceInShopCredit";
|
||||||
|
import advancedScenario0003TestCases from "./advanced/0003a_MobileAfterpay";
|
||||||
|
import essentialReplaceDynamicAdasTests from "./0005_EssentialReplaceDynamicAdas";
|
||||||
|
import essentialReplaceStaticAdasTests from "./0001_EssentialReplaceStatisAdas";
|
||||||
|
import essentialVehicleLookupBailoutTests from "./0020_EssentialVehicleLookupBailout";
|
||||||
|
import essentialPriceServiceErrorBailoutTests from "./0021_EssentialPriceServiceErrorBailout";
|
||||||
|
import { forceAPIError, mockApiResponse } from "@impl/utils/HttpUtils";
|
||||||
|
import advancedScenario0002TestCases from "./advanced/0002a_ReplaceOemEndorsement";
|
||||||
|
import ApiResponseInterceptUtil from "@impl/api/ApiResponseInterceptUtil";
|
||||||
|
import advancedScenario0004aTestCases from "./advanced/0004a_NoDeductibleAdas";
|
||||||
|
import advancedScenario0006TestCases from "./advanced/0006a_RepairNoDeductibleMobile";
|
||||||
|
import advancedScenario0007TestCases from "./advanced/0007a_RepairStateLanguage";
|
||||||
|
import advancedScenario0008TestCases from "./advanced/0008a_RepairTpa";
|
||||||
|
import advancedScenario0011TestCases from "./advanced/0011a_ItacNoAdas";
|
||||||
|
import advancedScenario0012TestCases from "./advanced/0012a_ItacDropOff";
|
||||||
|
import advancedScenario0014TestCases from "./advanced/0014a_NoCompAdas";
|
||||||
|
import advancedScenario0013TestCases from "./advanced/0013a_ItacMobile";
|
||||||
|
import advancedScenario0015TestCases from "./advanced/0015a_NoCompPartQuestions";
|
||||||
|
import advancedScenario0016TestCases from "./advanced/0016a_NoCompAllGlass";
|
||||||
|
import advancedScenario0017TestCases from "./advanced/0017a_NoCompPremium";
|
||||||
|
import advancedScenario0018TestCases from "./advanced/0018a_NoCompGlassOnly";
|
||||||
|
import advancedScenario0019TestCases from "./advanced/0019a_NoCompEditVehicle";
|
||||||
|
import advancedScenario0020TestCases from "./advanced/0020a_NoCompChangeLoc";
|
||||||
|
import advancedScenario0005TestCases from "./advanced/0005a_CapabilityQuestions";
|
||||||
|
import advancedScenario0009TestCases from "./advanced/0009a_NoDeductibleFlorida";
|
||||||
|
import advancedScenario0010TestCases from "./advanced/0010a_RearGlass";
|
||||||
|
|
||||||
|
|
||||||
|
test.describe.parallel('ISS QA Automation Regression', () => {
|
||||||
|
const ruleEngine = new RuleEngine<TestCase>();
|
||||||
|
const options = new ValidationOptions();
|
||||||
|
addSmokeTagToRandomTest(essentialReplaceStaticAdasTests);
|
||||||
|
addSmokeTagToRandomTest(essentialRepairInShopAcuraTests);
|
||||||
|
addSmokeTagToRandomTest(essentialTpaNotEnabledTestCases);
|
||||||
|
addSmokeTagToRandomTest(essentialTpaEnabled_0004);
|
||||||
|
addSmokeTagToRandomTest(essentialReplaceDynamicAdasTests);
|
||||||
|
addSmokeTagToRandomTest(essentialRepairMobileHyundaiTests);
|
||||||
|
addSmokeTagToRandomTest(essentialVehicleNotFoundTestCases);
|
||||||
|
addSmokeTagToRandomTest(essentialHeavyVehicleBailoutTestCases);
|
||||||
|
addSmokeTagToRandomTest(essentialPartsServiceErrorBailout_0009);
|
||||||
|
addSmokeTagToRandomTest(essentialDoNotSeeShopTestCases);
|
||||||
|
addSmokeTagToRandomTest(essentialUniqueGlassTests);
|
||||||
|
addSmokeTagToRandomTest(essentialReplaceTestCases);
|
||||||
|
addSmokeTagToRandomTest(essentialRepairMobileTests);
|
||||||
|
addSmokeTagToRandomTest(essentialReplacePartsQuestionsDropoff_0015);
|
||||||
|
addSmokeTagToRandomTest(essentialHpTestCases);
|
||||||
|
addSmokeTagToRandomTest(essentialTpaNotEnabledReplace_0017);
|
||||||
|
addSmokeTagToRandomTest(essentialTpaEnabledReplace_0018);
|
||||||
|
addSmokeTagToRandomTest(essentialTpaEnabledReplaceRecal_0019);
|
||||||
|
addSmokeTagToRandomTest(essentialVehicleLookupBailoutTests);
|
||||||
|
addSmokeTagToRandomTest(essentialPriceServiceErrorBailoutTests);
|
||||||
|
|
||||||
|
//Scenario 1
|
||||||
|
for (const testCase of essentialReplaceStaticAdasTests) {
|
||||||
|
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 2
|
||||||
|
for (const testCase of essentialRepairInShopAcuraTests) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 3
|
||||||
|
for (const testCase of essentialTpaNotEnabledTestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 4
|
||||||
|
for (const testCase of essentialTpaEnabled_0004) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 5
|
||||||
|
for (const testCase of essentialReplaceDynamicAdasTests) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 7
|
||||||
|
for (const testCase of essentialRepairMobileHyundaiTests) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 8
|
||||||
|
for (const testCase of essentialHeavyVehicleBailoutTestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 9
|
||||||
|
for (const testCase of essentialPartsServiceErrorBailout_0009) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 10
|
||||||
|
for (const testCase of essentialVehicleNotFoundTestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 11
|
||||||
|
for (const testCase of essentialDoNotSeeShopTestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 12
|
||||||
|
for (const testCase of essentialUniqueGlassTests) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 13 //defect# SSR-2009
|
||||||
|
for (const testCase of essentialReplaceTestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 14
|
||||||
|
for (const testCase of essentialRepairMobileTests) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 15
|
||||||
|
for (const testCase of essentialReplacePartsQuestionsDropoff_0015) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 16
|
||||||
|
for (const testCase of essentialHpTestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 17
|
||||||
|
for (const testCase of essentialTpaNotEnabledReplace_0017) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 18
|
||||||
|
for (const testCase of essentialTpaEnabledReplace_0018) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 19
|
||||||
|
for (const testCase of essentialTpaEnabledReplaceRecal_0019) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
//Scenario 20
|
||||||
|
for (const testCase of essentialVehicleLookupBailoutTests) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine)); //skipping this until SSR-2004 is fixed
|
||||||
|
}
|
||||||
|
//Scenario 21
|
||||||
|
for (const testCase of essentialPriceServiceErrorBailoutTests) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Advanced Scenarios
|
||||||
|
|
||||||
|
// Scenario 0001a
|
||||||
|
for (const testCase of advancedScenario0001TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0002a
|
||||||
|
for (const testCase of advancedScenario0002TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0003a
|
||||||
|
// Note: payment will fail in dev. Payment (PIA) works fine in SYS
|
||||||
|
for (const testCase of advancedScenario0003TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0004a
|
||||||
|
for (const testCase of advancedScenario0004aTestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0005a
|
||||||
|
for (const testCase of advancedScenario0005TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0006a
|
||||||
|
for (const testCase of advancedScenario0006TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0007a
|
||||||
|
for (const testCase of advancedScenario0007TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0008a
|
||||||
|
for (const testCase of advancedScenario0008TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0009a
|
||||||
|
for (const testCase of advancedScenario0009TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
// Scenario 0010a
|
||||||
|
for (const testCase of advancedScenario0010TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0011a
|
||||||
|
for (const testCase of advancedScenario0011TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0012a
|
||||||
|
for (const testCase of advancedScenario0012TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0013a
|
||||||
|
for (const testCase of advancedScenario0013TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0014a
|
||||||
|
for (const testCase of advancedScenario0014TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0015a
|
||||||
|
for (const testCase of advancedScenario0015TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0016a
|
||||||
|
// FIXME:
|
||||||
|
for (const testCase of advancedScenario0016TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0017a
|
||||||
|
for (const testCase of advancedScenario0017TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0018a
|
||||||
|
for (const testCase of advancedScenario0018TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0019a
|
||||||
|
for (const testCase of advancedScenario0019TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 0020a
|
||||||
|
for (const testCase of advancedScenario0020TestCases) {
|
||||||
|
test(...prepareTest(testCase, run, options, ruleEngine));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test.afterEach(async ({ page, testInfo }) => {
|
||||||
|
await TestCase.afterEachMethod(page, testInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function run(page: Page, testInfo: TestInfo): Promise<void> {
|
||||||
|
|
||||||
|
await testInfo.testCase.setup();
|
||||||
|
testInfo.testCase.setupPages(page);
|
||||||
|
if (testInfo.testCase.testData.isAuthenticationRequired) {
|
||||||
|
await testInfo.testCase.pages.welcomePage.gotoWithAuthentication(testInfo.testCase.testData!.clientTag!);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await testInfo.testCase.pages.welcomePage.goto(testInfo.testCase.testData!.clientTag!);
|
||||||
|
}
|
||||||
|
await runWorkflow(page, testInfo.testCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runWorkflow(page: Page, testCase: TestCase) {
|
||||||
|
// Intercept API Responses
|
||||||
|
const apiResponseInterceptUtil = new ApiResponseInterceptUtil(testCase.testData);
|
||||||
|
page.on('response', apiResponseInterceptUtil.handleInterceptResponse);
|
||||||
|
|
||||||
|
|
||||||
|
// Destructure data for easy access
|
||||||
|
const { customerDetails, claimDetails, vehicleDetails, vehicleDamage,
|
||||||
|
appointmentDetails, isSafelite, endorsements,
|
||||||
|
partQuestions, paymentDetails, isNoComp, isItac, isRecalNotification,
|
||||||
|
isRecalWarning, servicePackage, hasStateLawPopup, otherVehiclesOnPolicy,
|
||||||
|
isSeparateApptsWarning, vehiclePartQuestions, editVehicleDetails,
|
||||||
|
hasMilitaryWarning, capabilityQuestions } = testCase.testData;
|
||||||
|
|
||||||
|
let { isPolicyFound } = testCase.testData; // Allow isPolicyFound to be re-assigned
|
||||||
|
|
||||||
|
// Destructure pages for easy access
|
||||||
|
const { welcomePage, duplicateCheckPage, policyHolderDetailsPage,
|
||||||
|
vehicleSelectionPage, vehicleDamagePage, coverageStatementPage,
|
||||||
|
providerPreferencePage, serviceLocationPage, servicePackagesPage,
|
||||||
|
schedulePage, contactDetailsPage, orderConfirmationPage, policyVehiclesPage,
|
||||||
|
endorsementsPage, vehicleLookupPage, partQuestionsPage, paymentMethodPage,
|
||||||
|
vehicleLookupAddressPage, vehicleLookupLicensePage, vinLookupPage,
|
||||||
|
bailoutPage, tpaSearchPage, tpaSubmitPage, tpaConfirmationPage,
|
||||||
|
vehiclePartQuestionsPage, capabilityQuestionsPage } = testCase.pages;
|
||||||
|
|
||||||
|
// Destructure bailout flags
|
||||||
|
const { isVehicleSelectBailout, isDoNotSeeMyShopBailout, isTpaNotEnabledBailout,
|
||||||
|
isRequestCallbackBailout, isHeavyTruckVehicleBailout, isPartsServiceErrorBailout,
|
||||||
|
isSafeliteNotTheProviderBailout, isVehicleLookupBailout, isPriceServiceErrorBailout } = testCase.testData.bailoutFlags || {};
|
||||||
|
|
||||||
|
const repairTypes: VehicleDamage[] = [
|
||||||
|
VehicleDamage.WindshieldOneChip,
|
||||||
|
VehicleDamage.WindshieldTwoChips,
|
||||||
|
VehicleDamage.WindshieldThreeChips
|
||||||
|
]
|
||||||
|
|
||||||
|
// Are we replacing or repairing?
|
||||||
|
const isReplace = !repairTypes.some(damageType => {
|
||||||
|
return vehicleDamage!.includes(damageType);
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasEndorsements = endorsements && endorsements.length > 0;
|
||||||
|
|
||||||
|
await test.step('WelcomePage >> Populate Customer Details', async () => {
|
||||||
|
await welcomePage.populatePage(customerDetails!, claimDetails!, await welcomePage.hasCityInfo());
|
||||||
|
|
||||||
|
// mockApiResponse(page, 'location/api/v1/location/zip/43016', 'common', testCase.testData!.mockTesting || false);
|
||||||
|
mockApiResponse(page, 'location/api/v1/location/zip/36116', 'common',testCase.testData!.isMockTesting || false);
|
||||||
|
mockApiResponse(page, 'coverage/api/v1/coverage/policy-information', 'scenario1', testCase.testData!.isMockTesting || false);
|
||||||
|
await welcomePage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (testCase.testData.isDuplicateClaim) {
|
||||||
|
// TODO: Scenarios where we want to resume from duplicate
|
||||||
|
// TODO: Dynamic for when we don't care if duplicate page appears
|
||||||
|
|
||||||
|
await test.step('DuplicateCheckPage >> Start New Claim', async () => {
|
||||||
|
await duplicateCheckPage.startNewClaim();
|
||||||
|
await duplicateCheckPage.nextPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPolicyFound) {
|
||||||
|
await test.step('PolicyVehiclesPage >> Select vehicle', async () => {
|
||||||
|
// Validate other vehicles on policy
|
||||||
|
if (otherVehiclesOnPolicy && otherVehiclesOnPolicy.length > 0) {
|
||||||
|
for (const vehicle of otherVehiclesOnPolicy) {
|
||||||
|
await policyVehiclesPage.validateVehicleIsOnPolicy(vehicle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select vehicle
|
||||||
|
await policyVehiclesPage.selectVehicle(vehicleDetails!);
|
||||||
|
await policyVehiclesPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isHeavyTruckVehicleBailout) {
|
||||||
|
await test.step('BailoutPage >> Heavy Vehicle Bailout', async () => {
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.HeavyTruckVehicle);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasEndorsements) {
|
||||||
|
await test.step('EndorsementsPage >> Select Endorsements', async () => {
|
||||||
|
await endorsementsPage.verifyEndorsements(endorsements);
|
||||||
|
await endorsementsPage.selectEndorsements(endorsements);
|
||||||
|
await endorsementsPage.nextPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
await test.step('PolicyHolderDetailsPage >> Enter customer data', async () => {
|
||||||
|
await policyHolderDetailsPage.fillCustomerDetails(customerDetails!);
|
||||||
|
await policyHolderDetailsPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isVehicleLookupBailout){
|
||||||
|
forceAPIError(page, '/vehicle/api/v1/vehicle/lookup')
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('VehicleDetailsPage >> Select Vehicle', async () => {
|
||||||
|
await vehicleSelectionPage.selectVehicle(vehicleDetails!);
|
||||||
|
await vehicleSelectionPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isVehicleLookupBailout) {
|
||||||
|
await test.step('BailoutPage >> Vehicle Lookup Bailout', async () => {
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.VehicleLookupError);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHeavyTruckVehicleBailout) {
|
||||||
|
await test.step('BailoutPage >> Heavy Vehicle Bailout', async () => {
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.HeavyTruckVehicle);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editVehicleDetails) {
|
||||||
|
isPolicyFound = false; // Flow proceeds as unverified
|
||||||
|
testCase.testData.isPolicyFound = false;
|
||||||
|
await test.step('VehicleDamagePage >> Click Edit Vehicle', async () => {
|
||||||
|
await vehicleDamagePage.editVehicleButton.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('VehicleDetailsPage >> Select edited vehicle', async () => {
|
||||||
|
await test.step('VehicleDetailsPage >> Select Vehicle', async () => {
|
||||||
|
await vehicleSelectionPage.selectVehicle(editVehicleDetails);
|
||||||
|
await vehicleSelectionPage.nextPage();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('VehicleDamagePage >> Select Damage', async () => {
|
||||||
|
if (isSeparateApptsWarning) {
|
||||||
|
await vehicleDamagePage.checkSeparateApptsWarning();
|
||||||
|
}
|
||||||
|
await vehicleDamagePage.selectDamage(vehicleDamage!);
|
||||||
|
await vehicleDamagePage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPartsServiceErrorBailout) {
|
||||||
|
await test.step('VehicleLookupPage >> Select Lookup Type', async () => {
|
||||||
|
await vehicleLookupPage.vehicleLookup(vehicleDetails!);
|
||||||
|
});
|
||||||
|
await test.step('VinLookupPage >> Lookup by VIN', async () => {
|
||||||
|
await vinLookupPage.enterVin(vehicleDetails!.vin!);
|
||||||
|
forceAPIError(page, '/parts/api/v1/parts')
|
||||||
|
await vinLookupPage.nextPage();
|
||||||
|
});
|
||||||
|
await test.step('BailoutPage >> Parts Service Error Bailout', async () => {
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.PartsServiceError);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReplace && !(isPolicyFound)) {
|
||||||
|
await test.step('VehicleLookupPage >> Select Lookup Type' + vehicleDetails?.vehicleLookupType, async () => {
|
||||||
|
await vehicleLookupPage.vehicleLookup(vehicleDetails!);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isVehicleSelectBailout) {
|
||||||
|
await test.step('BailoutPage >> Validate Bailout', async () => {
|
||||||
|
await vinLookupPage.enterVin(vehicleDetails!.vin!);
|
||||||
|
await vinLookupPage.triggerBailout();
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.VehicleNotFound);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (vehicleDetails!.vehicleLookupType!) {
|
||||||
|
case VehicleLookupType.Address:
|
||||||
|
await test.step('VehicleLookupAddressPage >> Lookup by address: ' + customerDetails!.address.street, async () => {
|
||||||
|
await vehicleLookupAddressPage.lookupVehicleByAddress(customerDetails!, vehicleDetails!);
|
||||||
|
await vehicleLookupAddressPage.nextPage();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case VehicleLookupType.LicensePlateNumber:
|
||||||
|
await test.step('VehicleLookupLicensePage >> Lookup by license plate: ' + vehicleDetails!.licensePlateNumber, async () => {
|
||||||
|
await vehicleLookupLicensePage.enterPlateDetails(vehicleDetails!);
|
||||||
|
await vehicleLookupLicensePage.nextPage();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case VehicleLookupType.Vin:
|
||||||
|
await test.step('VinLookupPage >> Lookup by VIN: ' + vehicleDetails!.vin!, async () => {
|
||||||
|
await vinLookupPage.enterVin(vehicleDetails!.vin!);
|
||||||
|
await vinLookupPage.nextPage();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capabilityQuestions && capabilityQuestions.length > 0) {
|
||||||
|
await capabilityQuestionsPage.validatePartQuestions(capabilityQuestions);
|
||||||
|
await capabilityQuestionsPage.selectPartQuestionResponses(capabilityQuestions);
|
||||||
|
await capabilityQuestionsPage.nextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partQuestions && partQuestions.length > 0) {
|
||||||
|
await partQuestionsPage.validatePartQuestions(partQuestions);
|
||||||
|
await partQuestionsPage.selectPartQuestionResponses(partQuestions);
|
||||||
|
await partQuestionsPage.nextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vehiclePartQuestions && vehiclePartQuestions.length > 0) {
|
||||||
|
await vehiclePartQuestionsPage.validatePartQuestions(vehiclePartQuestions);
|
||||||
|
await vehiclePartQuestionsPage.selectPartQuestionResponses(vehiclePartQuestions);
|
||||||
|
await vehiclePartQuestionsPage.nextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('CoverageStatementPage >> Next page', async () => {
|
||||||
|
// Confirm no coverage
|
||||||
|
if (isPolicyFound && (isItac || isNoComp)) {
|
||||||
|
await coverageStatementPage.continueToScheduleButton.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
await coverageStatementPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasStateLawPopup) {
|
||||||
|
await test.step('ProviderPreferencePage >> Dismiss state law popup', async () => {
|
||||||
|
await providerPreferencePage.validateStateLawModalIsVisible();
|
||||||
|
await providerPreferencePage.gotItButton.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPolicyFound || !(isItac || isNoComp)) {
|
||||||
|
await test.step('ProviderPreferencePage >> Select Provider ' + isSafelite ? "Safelite" : "Other shops(Non-Safelite)", async () => {
|
||||||
|
await providerPreferencePage.selectProvider(isSafelite);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// validations for Recal warning mesage
|
||||||
|
if (isRecalWarning) {
|
||||||
|
await serviceLocationPage.validateRecalWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
// if isRecalNotifidation flag true additional step to acknowledge Recal notification.
|
||||||
|
if (isRecalNotification) {
|
||||||
|
await providerPreferencePage.acknowledgeRecalNotificaiton();
|
||||||
|
}
|
||||||
|
// If No-Comp or ITAC, ProviderPreferencePage does not appear
|
||||||
|
if (!isPolicyFound || !(isItac || isNoComp)) {
|
||||||
|
|
||||||
|
|
||||||
|
if (!isSafelite) {
|
||||||
|
if (testCase.testData!.clientTag == '05CC1609-3631-4044-B45A-E78E13343B9A') { // Avoiding TPA flow for Federated Insureance due to Defect# SSR-1984 //Temporary fix until Defect# SSR-1984 is addressed.
|
||||||
|
await test.step('***** Performing Safelite flow for Federal Insurance due to defec# SSR-1984 *****', async () => { });
|
||||||
|
await test.step('TpaSearchPage >> TPA Search', async () => {
|
||||||
|
await tpaSearchPage.selectFirstLocation();
|
||||||
|
// await tpaSearchPage.nextPage();
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isTpaNotEnabledBailout) {
|
||||||
|
await test.step('validateBailoutDetails >> Bailout code : ' + BailoutCode.TPANotEnabled, async () => {
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.TPANotEnabled);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDoNotSeeMyShopBailout) {
|
||||||
|
await test.step('TpaSearchPage >> Select "Do Not See My Shop"', async () => {
|
||||||
|
await tpaSearchPage.selectDoNotSeeMyShop();
|
||||||
|
});
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.DoNotSeeMyShop);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('TpaSearchPage >> TPA Search', async () => {
|
||||||
|
await tpaSearchPage.selectFirstLocation();
|
||||||
|
await tpaSearchPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('TpaSubmitPage >> TPA Submit', async () => {
|
||||||
|
// TODO: Validations
|
||||||
|
await tpaSubmitPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('TpaConfirmationPage >> TPA Confirmation', async () => {
|
||||||
|
await tpaConfirmationPage.validateSuccessMessage();
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('ServiceLocationPage >> Select service location', async () => {
|
||||||
|
await serviceLocationPage.selectLocation(appointmentDetails!);
|
||||||
|
if (hasMilitaryWarning) {
|
||||||
|
await expect.soft(serviceLocationPage.militaryWarningMessage).toBeVisible();
|
||||||
|
}
|
||||||
|
await serviceLocationPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('SchedulePage >> Select day and time', async () => {
|
||||||
|
customerDetails!.apptDate = await schedulePage.scheduleFirstAppointment(appointmentDetails!.serviceLocation);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('ContactDetailsPage >> Validate contact details', async () => {
|
||||||
|
const expectedContactDetails: Partial<ICustomerDetails> = {
|
||||||
|
firstName: customerDetails!.firstName,
|
||||||
|
lastName: customerDetails!.lastName,
|
||||||
|
email: customerDetails!.email,
|
||||||
|
phoneNumber: customerDetails!.phoneNumber
|
||||||
|
};
|
||||||
|
const actualContactDetails = await contactDetailsPage.getContactDetails();
|
||||||
|
expect.soft(actualContactDetails).toEqual(expectedContactDetails);
|
||||||
|
|
||||||
|
await contactDetailsPage.fillNotes(customerDetails!.notes);
|
||||||
|
|
||||||
|
if (isPriceServiceErrorBailout) {
|
||||||
|
forceAPIError(page, '/price/api/v1/price/combined-quote');
|
||||||
|
}
|
||||||
|
|
||||||
|
await contactDetailsPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPriceServiceErrorBailout) {
|
||||||
|
await test.step('BailoutPage >> Price Service Error Bailout', async () => {
|
||||||
|
await bailoutPage.validateBailoutDetails(customerDetails!, BailoutCode.PricingResponseError);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('ServicePackagesPage >> Choose service package', async () => {
|
||||||
|
customerDetails!.packagePrice = await servicePackagesPage.selectServicePackage(servicePackage!);
|
||||||
|
await servicePackagesPage.nextPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isPolicyFound && claimDetails!.policyDeductible > 0) {
|
||||||
|
await test.step('PaymentMethodPage >> Execute Payment', async () => {
|
||||||
|
await paymentMethodPage.executePayment(paymentDetails!);
|
||||||
|
await paymentMethodPage.nextPage();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await test.step('PaymentMethodPage >> Skip to Order Confirmation', async () => {
|
||||||
|
await paymentMethodPage.nextPage();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step('OrderConfirmationPage >> Validate order', async () => {
|
||||||
|
await orderConfirmationPage.validateOrderConfirmationPage(testCase.testData);
|
||||||
|
});
|
||||||
|
}
|
||||||
70
playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts
Normal file
70
playwright-tests/tests/0001_EssentialReplaceStatisAdas.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialReplaceStaticAdasData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isSafelite: true,
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: faker.internet.email(),
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Knoxville',
|
||||||
|
state: 'Tennessee',
|
||||||
|
postalCode: '37996',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2019',
|
||||||
|
make: 'Toyota',
|
||||||
|
model: 'C-HR',
|
||||||
|
style: '4 door hatchback',
|
||||||
|
vin: 'NMTKHMBX5KR086519',
|
||||||
|
vehicleLookupType: VehicleLookupType.Vin,
|
||||||
|
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldCrack,
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.DropOff,
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialReplaceStaticAdasTests: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = { ...essentialReplaceStaticAdasData };
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0001 Essential Replace Statis ADAS Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Essentials','@0001', '@test_report'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0001');
|
||||||
|
essentialReplaceStaticAdasTests.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialReplaceStaticAdasTests;
|
||||||
70
playwright-tests/tests/0002_EssentialRepairInShopAcura.ts
Normal file
70
playwright-tests/tests/0002_EssentialRepairInShopAcura.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialRepairInShopAcuraData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: true,
|
||||||
|
servicePackage: ServicePackage.Premium,
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Fort Collins',
|
||||||
|
state: 'Colorado',
|
||||||
|
postalCode: '80526',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2020',
|
||||||
|
make: 'Acura',
|
||||||
|
model: 'ILX',
|
||||||
|
style: '4 door sedan',
|
||||||
|
vin: '19UDE2F38LA001705'
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldOneChip,
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialRepairInShopAcuraTests: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialRepairInShopAcuraData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0002 Essential Repair Mobile Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0002');
|
||||||
|
essentialRepairInShopAcuraTests.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialRepairInShopAcuraTests;
|
||||||
81
playwright-tests/tests/0003_EssentialTpaNotEnabledBailout.ts
Normal file
81
playwright-tests/tests/0003_EssentialTpaNotEnabledBailout.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialTpaNotEnabledData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: false,
|
||||||
|
bailoutFlags: {
|
||||||
|
isTpaNotEnabledBailout: true
|
||||||
|
},
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Dublin',
|
||||||
|
state: 'Ohio',
|
||||||
|
postalCode: '43016',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2022',
|
||||||
|
make: 'Honda',
|
||||||
|
model: 'Civic',
|
||||||
|
style: '4 door hatchback'
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldThreeChips,
|
||||||
|
// VehicleDamage.WindshieldCrack,
|
||||||
|
// VehicleDamage.DriverFrontDoor,
|
||||||
|
// VehicleDamage.DriverQuarterPanel,
|
||||||
|
// VehicleDamage.DriverRearDoor,
|
||||||
|
// VehicleDamage.PassengerFrontDoor,
|
||||||
|
// VehicleDamage.PassengerQuarterPanel,
|
||||||
|
// VehicleDamage.PassengerRearDoor,
|
||||||
|
// VehicleDamage.RearWindow
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
shopAddress: '6826 Sawmill Rd, Columbus, OH 43235',
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClientsWithTpaDisabled();
|
||||||
|
const essentialTpaNotEnabledTestCases: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialTpaNotEnabledData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0003 Essential TPA Not Enabled Bailout Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Bailout', '@TpaNotEnabled', '@Essentials'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0003');
|
||||||
|
essentialTpaNotEnabledTestCases.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialTpaNotEnabledTestCases;
|
||||||
83
playwright-tests/tests/0004_EssentialTpaEnabled.ts
Normal file
83
playwright-tests/tests/0004_EssentialTpaEnabled.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialTpaEnabledData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: false,
|
||||||
|
bailoutFlags: {
|
||||||
|
isTpaNotEnabledBailout: false
|
||||||
|
},
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
// phoneNumber: faker.phone.toString(),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
// street: faker.location.streetAddress(),
|
||||||
|
street: '134 Woodlands Place',
|
||||||
|
city: 'Dublin',
|
||||||
|
state: 'Florida',
|
||||||
|
postalCode: '32040',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2022',
|
||||||
|
make: 'Honda',
|
||||||
|
model: 'Civic',
|
||||||
|
style: '4 door hatchback'
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldThreeChips,
|
||||||
|
// VehicleDamage.WindshieldCrack,
|
||||||
|
// VehicleDamage.DriverFrontDoor,
|
||||||
|
// VehicleDamage.DriverQuarterPanel,
|
||||||
|
// VehicleDamage.DriverRearDoor,
|
||||||
|
// VehicleDamage.PassengerFrontDoor,
|
||||||
|
// VehicleDamage.PassengerQuarterPanel,
|
||||||
|
// VehicleDamage.PassengerRearDoor,
|
||||||
|
// VehicleDamage.RearWindow
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
shopAddress: '6826 Sawmill Rd, Columbus, OH 43235',
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClientsWithTpaEnabled();
|
||||||
|
const essentialTpaEnabled_0004: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialTpaEnabledData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0004 Essential TPA Enabled Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@TpaEnabled', '@Essentials','@0004'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0004');
|
||||||
|
essentialTpaEnabled_0004.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialTpaEnabled_0004;
|
||||||
78
playwright-tests/tests/0005_EssentialReplaceDynamicAdas.ts
Normal file
78
playwright-tests/tests/0005_EssentialReplaceDynamicAdas.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialReplaceDynamicAdasData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: true,
|
||||||
|
servicePackage: ServicePackage.Premium,
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Chicago',
|
||||||
|
state: 'Illinois',
|
||||||
|
postalCode: '60645',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2021',
|
||||||
|
make: 'BMW',
|
||||||
|
model: '740',
|
||||||
|
style: '4 door sedan',
|
||||||
|
vin: 'WBA7T2C01LGL17632',
|
||||||
|
vehicleLookupType: VehicleLookupType.Vin,
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldCrack,
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.Mobile,
|
||||||
|
serviceAddress: {
|
||||||
|
street: "2088 Haviland Road, Columbus, OH, USA",
|
||||||
|
city:"Vermillion",
|
||||||
|
state: "Ohio",
|
||||||
|
postalCode: "44089",
|
||||||
|
country: "undefined"
|
||||||
|
},
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialReplaceDynamicAdasTests: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialReplaceDynamicAdasData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0005 Essential Replace Dynamic ADAS Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0005');
|
||||||
|
essentialReplaceDynamicAdasTests.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialReplaceDynamicAdasTests;
|
||||||
77
playwright-tests/tests/0007_EssentialRepairHyundaiMobile.ts
Normal file
77
playwright-tests/tests/0007_EssentialRepairHyundaiMobile.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialRepairMobileHyundaiData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: true,
|
||||||
|
servicePackage: ServicePackage.Premium,
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Raleigh',
|
||||||
|
state: 'North Carolina',
|
||||||
|
postalCode: '27615',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2013',
|
||||||
|
make: 'Hyundai',
|
||||||
|
model: 'Sonata',
|
||||||
|
style: '4 door sedan',
|
||||||
|
vin: '5NPEC4AB6DH791034'
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldTwoChips,
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.Mobile,
|
||||||
|
serviceAddress: {
|
||||||
|
street: "2088 Haviland Road, Columbus, OH, USA",
|
||||||
|
city:"Vermillion",
|
||||||
|
state: "Ohio",
|
||||||
|
postalCode: "44089",
|
||||||
|
country: "undefined"
|
||||||
|
},
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialRepairMobileHyundaiTests: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialRepairMobileHyundaiData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0007 Essential Repair Hyundai Mobile Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0007');
|
||||||
|
essentialRepairMobileHyundaiTests.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialRepairMobileHyundaiTests;
|
||||||
81
playwright-tests/tests/0008_EssentialHeavyVehicleBailout.ts
Normal file
81
playwright-tests/tests/0008_EssentialHeavyVehicleBailout.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialHeavyVehicleBailoutData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: true,
|
||||||
|
bailoutFlags: {
|
||||||
|
isHeavyTruckVehicleBailout: true
|
||||||
|
},
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Dublin',
|
||||||
|
state: 'Ohio',
|
||||||
|
postalCode: '43016',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2017',
|
||||||
|
make: 'Freightliner',
|
||||||
|
model: '114sd',
|
||||||
|
style: 'conventional cab' // TODO: Check correctness of vehicle style
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
// VehicleDamage.WindshieldThreeChips,
|
||||||
|
VehicleDamage.WindshieldCrack,
|
||||||
|
// VehicleDamage.DriverFrontDoor,
|
||||||
|
// VehicleDamage.DriverQuarterPanel,
|
||||||
|
// VehicleDamage.DriverRearDoor,
|
||||||
|
// VehicleDamage.PassengerFrontDoor,
|
||||||
|
// VehicleDamage.PassengerQuarterPanel,
|
||||||
|
// VehicleDamage.PassengerRearDoor,
|
||||||
|
// VehicleDamage.RearWindow
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
shopAddress: '6826 Sawmill Rd, Columbus, OH 43235',
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialHeavyVehicleBailoutTestCases: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialHeavyVehicleBailoutData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0008 Essential Heavy Vehicle Bailout Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Bailout', '@HeavyVehicle', '@Essentials'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0008');
|
||||||
|
essentialHeavyVehicleBailoutTestCases.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialHeavyVehicleBailoutTestCases;
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialPartsServiceErrorData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: true,
|
||||||
|
bailoutFlags: {
|
||||||
|
isPartsServiceErrorBailout: true
|
||||||
|
},
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Tulare',
|
||||||
|
state: 'California',
|
||||||
|
postalCode: '93247',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2012',
|
||||||
|
make: 'Dodge',
|
||||||
|
model: 'Charger',
|
||||||
|
style: '4 door sedan',
|
||||||
|
vin: '2C3CDXBG6CH260654',
|
||||||
|
vehicleLookupType: VehicleLookupType.Vin
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
// VehicleDamage.WindshieldThreeChips,
|
||||||
|
VehicleDamage.WindshieldCrack,
|
||||||
|
// VehicleDamage.DriverFrontDoor,
|
||||||
|
// VehicleDamage.DriverQuarterPanel,
|
||||||
|
// VehicleDamage.DriverRearDoor,
|
||||||
|
// VehicleDamage.PassengerFrontDoor,
|
||||||
|
// VehicleDamage.PassengerQuarterPanel,
|
||||||
|
// VehicleDamage.PassengerRearDoor,
|
||||||
|
// VehicleDamage.RearWindow
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
shopAddress: '6826 Sawmill Rd, Columbus, OH 43235',
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialPartsServiceErrorBailout_0009: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialPartsServiceErrorData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0009 Essential Parts Service Error Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Bailout', '@PartsServiceError', '@Essentials','@0009'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0009');
|
||||||
|
essentialPartsServiceErrorBailout_0009.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialPartsServiceErrorBailout_0009;
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialVehicleNotFoundData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: true,
|
||||||
|
bailoutFlags: {
|
||||||
|
isVehicleSelectBailout: true
|
||||||
|
},
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Dublin',
|
||||||
|
state: 'Ohio',
|
||||||
|
postalCode: '43016',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2022',
|
||||||
|
make: 'Honda',
|
||||||
|
model: 'Civic',
|
||||||
|
style: '4 door hatchback',
|
||||||
|
vin: '0HGCR2E30FA099831',
|
||||||
|
vehicleLookupType: VehicleLookupType.Vin
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
// VehicleDamage.WindshieldThreeChips,
|
||||||
|
VehicleDamage.WindshieldCrack,
|
||||||
|
// VehicleDamage.DriverFrontDoor,
|
||||||
|
// VehicleDamage.DriverQuarterPanel,
|
||||||
|
// VehicleDamage.DriverRearDoor,
|
||||||
|
// VehicleDamage.PassengerFrontDoor,
|
||||||
|
// VehicleDamage.PassengerQuarterPanel,
|
||||||
|
// VehicleDamage.PassengerRearDoor,
|
||||||
|
// VehicleDamage.RearWindow
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
shopAddress: '6826 Sawmill Rd, Columbus, OH 43235',
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialVehicleNotFoundTestCases: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialVehicleNotFoundData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0010 Essential Vehicle Not Found Bailout Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Bailout', '@VehicleNotFound'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0001');
|
||||||
|
essentialVehicleNotFoundTestCases.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialVehicleNotFoundTestCases;
|
||||||
81
playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts
Normal file
81
playwright-tests/tests/0011_EssentialDoNotSeeShopBailout.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialDoNotSeeShopData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: false,
|
||||||
|
bailoutFlags: {
|
||||||
|
isDoNotSeeMyShopBailout: true
|
||||||
|
},
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Dublin',
|
||||||
|
state: 'Ohio',
|
||||||
|
postalCode: '43016',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2022',
|
||||||
|
make: 'Honda',
|
||||||
|
model: 'Civic',
|
||||||
|
style: '4 door hatchback'
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldThreeChips,
|
||||||
|
// VehicleDamage.WindshieldCrack,
|
||||||
|
// VehicleDamage.DriverFrontDoor,
|
||||||
|
// VehicleDamage.DriverQuarterPanel,
|
||||||
|
// VehicleDamage.DriverRearDoor,
|
||||||
|
// VehicleDamage.PassengerFrontDoor,
|
||||||
|
// VehicleDamage.PassengerQuarterPanel,
|
||||||
|
// VehicleDamage.PassengerRearDoor,
|
||||||
|
// VehicleDamage.RearWindow
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
shopAddress: '6826 Sawmill Rd, Columbus, OH 43235',
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClientsWithTpaEnabled();
|
||||||
|
const essentialDoNotSeeShopTestCases: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialDoNotSeeShopData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0011 Essential DoNotSeeShop Bailout Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Bailout', '@DoNotSeeShop', '@Essentials'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0011');
|
||||||
|
essentialDoNotSeeShopTestCases.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialDoNotSeeShopTestCases;
|
||||||
84
playwright-tests/tests/0012_EssentialUniqueGlass.ts
Normal file
84
playwright-tests/tests/0012_EssentialUniqueGlass.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, PartQuestionType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialUnqiqueGlassData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
isSafelite: true,
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Tacoma',
|
||||||
|
state: 'Washington',
|
||||||
|
postalCode: '98409',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2019',
|
||||||
|
make: 'Ram',
|
||||||
|
model: 'Promaster',
|
||||||
|
style: 'cargo van',
|
||||||
|
vin: '3C6TRVBG8KE566001',
|
||||||
|
vehicleLookupType: VehicleLookupType.Vin
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldCrack,
|
||||||
|
VehicleDamage.DriverSlidingDoor
|
||||||
|
],
|
||||||
|
vehiclePartQuestions: [
|
||||||
|
{
|
||||||
|
partQuestionType: PartQuestionType.WindshieldColor,
|
||||||
|
isOnPage: true,
|
||||||
|
optionToSelect: 'Green Tint'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partQuestionType: PartQuestionType.DriverSideColor,
|
||||||
|
isOnPage: true,
|
||||||
|
optionToSelect: 'Green Tint',
|
||||||
|
secondaryQuestionOptionToSelect: 'solar, driver side, rear'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.DropOff,
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialUniqueGlassTests: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialUnqiqueGlassData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0012 Essential Unique Glass Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0012');
|
||||||
|
essentialUniqueGlassTests.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialUniqueGlassTests;
|
||||||
102
playwright-tests/tests/0013_EssentialReplace.ts
Normal file
102
playwright-tests/tests/0013_EssentialReplace.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import { DamageType, PartQuestionType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialReplaceData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
vehiclePartQuestions: [
|
||||||
|
{
|
||||||
|
partQuestionType: PartQuestionType.WindshieldColor,
|
||||||
|
isOnPage: true,
|
||||||
|
optionToSelect: 'Green Tint'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partQuestionType: PartQuestionType.DriverFrontColor,
|
||||||
|
isOnPage: true,
|
||||||
|
optionToSelect: 'Green Tint'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partQuestionType: PartQuestionType.DriverRearColor,
|
||||||
|
isOnPage: true,
|
||||||
|
optionToSelect: 'Green Tint'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partQuestionType: PartQuestionType.PassengerFrontColor,
|
||||||
|
isOnPage: true,
|
||||||
|
optionToSelect: 'Green Tint'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
partQuestionType: PartQuestionType.PassengerRearColor,
|
||||||
|
isOnPage: true,
|
||||||
|
optionToSelect: 'Green Tint'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isSafelite: true,
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: 'Reed',
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: '10212 JEWEL CT',// DO NOT use fake address here as this scenario search vehicle by address //faker.location.streetAddress(),
|
||||||
|
city: 'CONROE',
|
||||||
|
state: 'Texas',
|
||||||
|
postalCode: '77385',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2015',
|
||||||
|
make: 'Ford',
|
||||||
|
model: 'F Series F150',
|
||||||
|
style: '2 door super cab',
|
||||||
|
vin: '5N1AN0NW9BC524974',
|
||||||
|
vehicleLookupType: VehicleLookupType.Address,
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldCrack,
|
||||||
|
VehicleDamage.DriverFrontDoor,
|
||||||
|
VehicleDamage.DriverRearDoor,
|
||||||
|
VehicleDamage.PassengerFrontDoor,
|
||||||
|
VehicleDamage.PassengerRearDoor,
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.InShop,
|
||||||
|
shopAddress: undefined,
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialReplaceTestCases: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialReplaceData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0013 Essential Replace Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Essentials'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0013');
|
||||||
|
essentialReplaceTestCases.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialReplaceTestCases;
|
||||||
77
playwright-tests/tests/0014_EssentialRepairMobile.ts
Normal file
77
playwright-tests/tests/0014_EssentialRepairMobile.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import ClientData from "@business-logic/data/ClientData";
|
||||||
|
import TestCase from "@business-logic/types/TestCase";
|
||||||
|
import { DamageType, ServiceLocation, ServicePackage, VehicleDamage, VehicleLookupType } from "@business-logic/types/Enums";
|
||||||
|
import { ITestData } from "@business-logic/types/ITestData"
|
||||||
|
import { faker } from "@faker-js/faker";
|
||||||
|
import { getNextWeekday } from "@impl/utils/DateUtils";
|
||||||
|
|
||||||
|
const nextWeekday = getNextWeekday();
|
||||||
|
|
||||||
|
const essentialRepairMobileData: Partial<ITestData> = {
|
||||||
|
clientTag: 'ALL_ESSENTIAL',
|
||||||
|
isDuplicateClaim: false,
|
||||||
|
isPolicyFound: false,
|
||||||
|
endorsements: [],
|
||||||
|
isReplace: false,
|
||||||
|
partQuestions: undefined,
|
||||||
|
isSafelite: true,
|
||||||
|
servicePackage: faker.helpers.enumValue(ServicePackage),
|
||||||
|
customerDetails: {
|
||||||
|
firstName: faker.person.firstName(),
|
||||||
|
lastName: faker.person.lastName(),
|
||||||
|
email: "itqatest@safelite.com",
|
||||||
|
phoneNumber: faker.helpers.fromRegExp(/[2-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]/),
|
||||||
|
notes: 'Automated Test',
|
||||||
|
address: {
|
||||||
|
street: faker.location.streetAddress(),
|
||||||
|
city: 'Dublin',
|
||||||
|
state: 'Ohio',
|
||||||
|
postalCode: '43016',
|
||||||
|
country: 'United States'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
claimDetails: {
|
||||||
|
policyNumber: faker.string.alphanumeric(5),
|
||||||
|
policyDeductible: -1, // Not advanced, so we don't care about deductible.
|
||||||
|
damageDate: '2024-10-10',
|
||||||
|
damageCause: DamageType.Other
|
||||||
|
},
|
||||||
|
vehicleDetails: {
|
||||||
|
year: '2021',
|
||||||
|
make: 'Subaru',
|
||||||
|
model: 'Outback',
|
||||||
|
style: '4 door station wagon',
|
||||||
|
vin: '4S4BTAFC7M3163249'
|
||||||
|
},
|
||||||
|
vehicleDamage: [
|
||||||
|
VehicleDamage.WindshieldThreeChips,
|
||||||
|
],
|
||||||
|
appointmentDetails: {
|
||||||
|
serviceLocation: ServiceLocation.Mobile,
|
||||||
|
serviceAddress: {
|
||||||
|
street: "2088 Haviland Road, Columbus, OH, USA",
|
||||||
|
city:"Vermillion",
|
||||||
|
state: "Ohio",
|
||||||
|
postalCode: "44089",
|
||||||
|
country: "undefined"
|
||||||
|
},
|
||||||
|
appointmentDate: nextWeekday
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const essentialClients = ClientData.getEssentialClients();
|
||||||
|
const essentialRepairMobileTests: TestCase[] = [];
|
||||||
|
for (const client of essentialClients) {
|
||||||
|
const data = {...essentialRepairMobileData};
|
||||||
|
data.clientTag = client.clientTag;
|
||||||
|
data.isAuthenticationRequired = client.clientFlags.isAuthenticationEnabled ?? false
|
||||||
|
const tc = new TestCase({
|
||||||
|
name: `0014 Essential Repair Mobile Client: "${client.accountName}"`,
|
||||||
|
tags: [`@${client.clientTag}`, `@${client.accountName}`, '@Essentials'],
|
||||||
|
testData: data
|
||||||
|
}, undefined, '0014');
|
||||||
|
essentialRepairMobileTests.push(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default essentialRepairMobileTests;
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue