├── .env ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── README.md ├── docs ├── _config.yml └── index.md ├── package-lock.json ├── package.json ├── playwright.config.ts ├── src ├── API │ ├── REST │ │ ├── constants │ │ │ └── RESTConstants.ts │ │ └── steps │ │ │ └── UserSteps.ts │ └── SOAP │ │ ├── constants │ │ └── SOAPConstants.ts │ │ └── steps │ │ └── AccountServiceSteps.ts ├── advantage │ ├── constants │ │ ├── CommonConstants.ts │ │ ├── ConfigurationConstants.ts │ │ ├── HomePageConstants.ts │ │ └── RegistrationPageConstants.ts │ ├── pages │ │ ├── ConfigurationPage.ts │ │ ├── HomePage.ts │ │ └── RegistrationPage.ts │ └── steps │ │ ├── ConfigurationSteps.ts │ │ ├── HomeSteps.ts │ │ └── RegistrationSteps.ts ├── database │ ├── constants │ │ └── DatabaseConstants.ts │ └── steps │ │ └── DatabaseStep.ts ├── framework │ ├── config │ │ └── base-test.ts │ ├── constants │ │ ├── BrowserConstants.ts │ │ ├── CommonConstants.ts │ │ ├── DBConstants.ts │ │ ├── ExcelConstants.ts │ │ └── HTMLConstants.ts │ ├── logger │ │ ├── Logger.ts │ │ └── TestListener.ts │ ├── manager │ │ ├── Browser.ts │ │ └── SuiteManager.ts │ ├── playwright │ │ ├── API │ │ │ ├── APIActions.ts │ │ │ ├── RESTRequest.ts │ │ │ ├── RESTResponse.ts │ │ │ ├── RequestHeader.ts │ │ │ ├── SOAPRequest.ts │ │ │ └── SOAPResponse.ts │ │ ├── actions │ │ │ ├── AlertActions.ts │ │ │ ├── CheckBoxActions.ts │ │ │ ├── DropDownActions.ts │ │ │ ├── EditBoxActions.ts │ │ │ ├── UIActions.ts │ │ │ └── UIElementActions.ts │ │ └── asserts │ │ │ └── Assert.ts │ ├── reporter │ │ ├── Allure.ts │ │ └── HTMLReporter.ts │ ├── template │ │ └── SuiteTemplate.ts │ └── utils │ │ ├── CLIUtil.ts │ │ ├── DBUtil.ts │ │ ├── DateUtil.ts │ │ ├── ExcelUtil.ts │ │ ├── PDFUtil.ts │ │ ├── StringUtil.ts │ │ └── XMLParserUtil.ts ├── resources │ ├── API │ │ ├── REST │ │ │ ├── ADD_USER.json │ │ │ ├── LOGIN.json │ │ │ └── UPDATE_USER.json │ │ └── SOAP │ │ │ ├── AccountCreateRequest.xml │ │ │ ├── AccountLoginRequest.xml │ │ │ ├── AccountLogoutRequest.xml │ │ │ └── CountrySearchRequest.xml │ └── data │ │ └── testData.xlsx └── tests │ ├── ContactUsTest.spec.ts │ ├── CreateAccountTest.spec.ts │ ├── DatabaseTest.spec.ts │ ├── DownloadTest.spec.ts │ ├── LoginTest.spec.ts │ ├── RESTUserTest.spec.ts │ └── SOAPAccountServiceTest.spec.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | BROWSER=chrome 2 | 3 | # These timeouts are set in minutes 4 | TEST_TIMEOUT=20 5 | BROWSER_LAUNCH_TIMEOUT=0 6 | ACTION_TIMEOUT=1 7 | NAVIGATION_TIMEOUT=2 8 | 9 | # Execution configurations 10 | RETRIES=0 11 | PARALLEL_THREAD=3 12 | 13 | # Test application configurations 14 | BASE_URL=http://advantageonlineshopping.com 15 | 16 | # API configurations 17 | SOAP_API_BASE_URL=http://www.advantageonlineshopping.com:80 18 | REST_API_BASE_URL=https://fakestoreapi.com 19 | 20 | # DB configurations 21 | DB_CONFIG=Server=localhost,1433;Database=AutomationDB;User Id=SA;Password=Auto2021 22 | # Sample DB2 CONFIG=> DATABASE=;HOSTNAME=;UID=;PWD=;PORT= 23 | # Sample ORACLE CONFIG=user:;password:;connectString::/ 24 | 25 | # Link to JIRA or any such tool for reporting 26 | LINK=https://github.com/microsoft/playwright/issues/ 27 | 28 | # Name of the test that needs to run locally 29 | TEST_NAME=debug 30 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/framework/config/BBConfig.ts -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "airbnb-base", 9 | "airbnb-typescript/base", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:playwright/playwright-test" 12 | ], 13 | "parser": "@typescript-eslint/parser", 14 | "parserOptions": { 15 | "project": "./tsconfig.json", 16 | "ecmaVersion": "latest", 17 | "sourceType": "module" 18 | }, 19 | "plugins": [ 20 | "@typescript-eslint" 21 | ], 22 | "rules": { 23 | "indent": "off", 24 | "@typescript-eslint/indent": "off", 25 | "linebreak-style": "off", 26 | "quotes": "off", 27 | "no-trailing-spaces": "off", 28 | "lines-between-class-members": "off", 29 | "@typescript-eslint/lines-between-class-members": "off", 30 | "@typescript-eslint/quotes": "off", 31 | "no-underscore-dangle": "off", 32 | "semi": [ 33 | "error", 34 | "always" 35 | ], 36 | "object-shorthand": "off", 37 | "no-console":"off", 38 | "class-methods-use-this": "off", 39 | "no-plusplus": "off", 40 | "@typescript-eslint/no-explicit-any": "off", 41 | "no-await-in-loop": "off", 42 | "function-paren-newline": "off", 43 | "function-call-argument-newline": "off", 44 | "import/no-extraneous-dependencies": "off", 45 | "max-len": ["warn", 46 | { 47 | "code": 120 , 48 | "ignoreComments": true, 49 | "ignoreTrailingComments": true, 50 | "ignoreTemplateLiterals": true, 51 | "ignoreUrls": true 52 | }] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Automation Test Execution 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | 10 | jobs: 11 | tests: 12 | name: Test Execution 13 | runs-on: windows-latest 14 | steps: 15 | - name: Checkout code from repository 16 | uses: actions/checkout@v4 17 | - name: Setting up Node.js 20 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | cache: 'npm' 22 | - name: Install dependencies 23 | run: npm ci && npx playwright install 24 | - name: Creating test suite 25 | run: npm run create:suite SHEET=Regression --if-present 26 | - name: Test execution 27 | run: npm test 28 | - name: Generating execution report 29 | if: always() 30 | run: npx ts-node ./src/framework/reporter/HTMLReporter.ts 31 | - name: Upload test results 32 | if: always() 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: test-results 36 | path: test-results 37 | - name: Upload allure-results artifact 38 | if: always() 39 | uses: actions/upload-artifact@master 40 | with: 41 | name: allure-results 42 | path: allure-results 43 | retention-days: 30 44 | 45 | generate_report: 46 | name: Allure Report 47 | runs-on: ubuntu-latest 48 | if: always() 49 | needs: [ tests ] 50 | steps: 51 | - name: Download Artifacts 52 | uses: actions/download-artifact@v4 53 | id: download 54 | with: 55 | name: allure-results 56 | path: allure-results 57 | 58 | - name: Get Allure history 59 | uses: actions/checkout@v4 60 | if: always() 61 | continue-on-error: true 62 | with: 63 | ref: gh-pages 64 | path: gh-pages 65 | 66 | - name: Allure Report action 67 | uses: simple-elf/allure-report-action@master 68 | if: always() 69 | id: allure-report 70 | with: 71 | allure_results: allure-results 72 | gh_pages: gh-pages 73 | allure_report: allure-report 74 | allure_history: allure-history 75 | 76 | - name: Deploy allure report to Github Pages 77 | if: always() 78 | uses: peaceiris/actions-gh-pages@v2 79 | env: 80 | PERSONAL_TOKEN: ${{ secrets.GITHUB_TOKEN }} 81 | PUBLISH_BRANCH: gh-pages 82 | PUBLISH_DIR: allure-history 83 | 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /test-results 2 | /allure-results 3 | /allure-report 4 | /node_modules 5 | node_modules 6 | **/*.test.ts 7 | /.vscode 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # playwright-sample-project 2 | 3 | ## **Overview:** 4 | 5 | This is a sample Automation project using Playwright and Typescript and uses playwright-testrunner to execute test cases. This is a Data Driven framework focused on separating the test scripts logic and the test data from each other. This allows us to create test automation scripts by passing different sets of test data. The test data set is kept in an external Excel Sheet. The test scripts connect to the external Excel sheet to get the test data. This framework significantly reduces the number of test scripts compared to a modular based framework when we need to test for multiple sets of data for same functionality. 6 | 7 | For Demo purpose UI test cases are created on [advantageonlineshopping.com](http://advantageonlineshopping.com/) site and API test cases are created on these [SOAP API](https://www.advantageonlineshopping.com/accountservice/ws/accountservice.wsdl) & [REST API](https://fakestoreapi.com) endpoints. 8 | 9 | ## Features 10 | 11 | - This framework has built in library to operate on UI, API (both SOAP & REST API) and DB (MSSQL, DB2 & Oracle). 12 | - Supports execution of tests in different browsers. 13 | - Test data is stored in an Excel sheet and from this Excel sheet user can control the test cases that needs to be run. 14 | - User also has full control to run test in different modes from the Excel sheet. 15 | - Allows transfer of data between test cases. 16 | - Has utility built in for file download, Read PDF files etc. 17 | - Generates Playwright's HTML Report, Allure Report & JUnit Report in HTML format for each exaction. 18 | - Allure & Playwright report including snapshots and video in case of test failure. 19 | - Test execution logs are captured in the log file. 20 | - You Can execute local tests in Playwright's UI Mode, that comes with a built-in watch mode. Which helps in running and debuging of tests. 21 | - All the playwright related config is controlled by playwright config file. 22 | - Environment variables can be modified at runtime and its controlled by .env file. 23 | - Easy and simple integration to CI/CD tools like Jenkins. 24 | 25 | #### Supported Browsers 26 | 1. Chrome - default browser 27 | 2. Firefox 28 | 3. MS Edge 29 | 4. WebKit - web browser engine used by Safari 30 | 31 | #### Run Mode Details 32 | | Mode | Execl Value |Description | 33 | | ------ | ------ | ------ | 34 | |Normal|Blank| Runs the tests sequentially| 35 | |Serial|serial| Runs the tests sequentially. On test failure, all subsequent tests are skipped| 36 | |Parallel|parallel| Runs the tests parallelly, this is ideal when tests in the scenario are independent of one another| 37 | 38 | #### Steps to use 39 | ##### 1. Installation 40 | 41 | Playwright framework requires [Node.js](https://nodejs.org/) v14+ to run. 42 | 43 | Code from github need to be [download](https://github.com/VinayKumarBM/playwright-sample-project/archive/refs/heads/master.zip) OR [cloned](https://github.com/VinayKumarBM/playwright-sample-project.git) using git command. 44 | 45 | Installing the dependencies. 46 | ```sh 47 | npm ci 48 | ``` 49 | ##### 2. Test creation 50 | - Create Test file with extenstion .spec.ts. Eg LoginTest.spec.ts 51 | - In the testData excel create a sheet with name of test. Eg. LoginTest 52 | - Create a execution sheet and make an entry of new test case. Eg. in the Regression sheet add a row for new test LoginTest and update other columns like run, mode etc. 53 | 54 | ##### 3. Execution 55 | To run test suite use below command. 56 | ```sh 57 | npm run create:suite SHEET= && npm test 58 | ``` 59 | **Note:** SheetName needs to be updated. 60 | 61 | To run individual test locally use below command. 62 | ```sh 63 | set TEST_NAME= && npm run local:test 64 | ``` 65 | **Note:** Using set command we are setting the local TestFileName. 66 | 67 | To run individual test locally in [UI Mode](https://playwright.dev/docs/test-ui-mode) use below command. 68 | ```sh 69 | set TEST_NAME= && npm run local:test:ui 70 | ``` 71 | **Note:** Using set command we are setting the local TestFileName. 72 | 73 | To change any environment configuration in .env file at run time use set command. 74 | Eg: To change browser to MS Edge use below command 75 | ```sh 76 | set BROWSER=edge 77 | ``` 78 | Similar command can be used to update other environment configuration 79 | 80 | To generate Allure report use below command 81 | ```sh 82 | npm run report 83 | ``` 84 | 85 | ##### 4. Report & Logs 86 | Playwright HTML report will be present inside 87 | ```sh 88 | test-results/results/index.html 89 | ``` 90 | Execution log will be present in the log file. 91 | ```sh 92 | test-results/logs/execution.log 93 | ``` 94 | ## ## 95 | **:pencil: If you find my work interesting don't forget to give a Star :star: & Follow me :busts_in_silhouette:** 96 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # playwright-sample-project 2 | 3 | ## **Overview:** 4 | 5 | This is a sample Playwright project using Typescript as scripting language and uses playwright-testrunner to execute test cases. This is a Data Driven framework focused on separating the test scripts logic and the test data from each other. This allows us to create test automation scripts by passing different sets of test data. The test data set is kept in an external Excel Sheet. The test scripts connect to the external Excel sheet to get the test data. This framework significantly reduces the number of test scripts compared to a modular based framework when we need to test for multiple sets of data for same functionality. 6 | 7 | For Demo purpose UI test cases are created on [advantageonlineshopping.com](http://advantageonlineshopping.com/) site and API test cases are created on these [SOAP API](https://www.advantageonlineshopping.com/accountservice/ws/accountservice.wsdl) & [REST API](https://fakestoreapi.com) endpoints. 8 | 9 | ## Features 10 | 11 | - This framework has built in library to operate on UI, API (both SOAP & REST API) and DB (MSSQL, DB2 & Oracle). 12 | - Supports execution of tests in different browsers. 13 | - Test data is stored in an Excel sheet and from this Excel sheet user can control the test cases that needs to be run. 14 | - User also has full control to run test in different modes from the Excel sheet. 15 | - Allows transfer of data between test cases. 16 | - Has utility built in for file download, Read PDF files etc. 17 | - Generates Playwright's HTML Report, Allure Report & JUnit Report in HTML format for each exaction. 18 | - Allure & Playwright report including snapshots and video in case of test failure. 19 | - Test execution logs are captured in the log file. 20 | - All the playwright related config is controlled by playwright config file. 21 | - Environment variables can be modified at runtime and its controlled by .env file. 22 | - Easy and simple integration to CI/CD tools like Jenkins. 23 | 24 | #### Supported Browsers 25 | 1. Chrome - default browser 26 | 2. Firefox 27 | 3. MS Edge 28 | 4. WebKit - web browser engine used by Safari 29 | 30 | #### Run Mode Details 31 | | Mode | Execl Value |Description | 32 | | ------ | ------ | ------ | 33 | |Normal|Blank| Runs the tests sequentially| 34 | |Serial|serial| Runs the tests sequentially. On test failure, all subsequent tests are skipped| 35 | |Parallel|parallel| Runs the tests parallelly, this is ideal when tests in the scenario are independent of one another| 36 | 37 | #### Steps to use 38 | ##### 1. Installation 39 | 40 | Playwright framework requires [Node.js](https://nodejs.org/) v14+ to run. 41 | 42 | Code from github need to be [download](https://github.com/VinayKumarBM/playwright-sample-project/archive/refs/heads/master.zip) OR [cloned](https://github.com/VinayKumarBM/playwright-sample-project.git) using git command. 43 | 44 | Installing the dependencies. 45 | ```sh 46 | npm ci 47 | ``` 48 | ##### 2. Test creation 49 | - Create Test file with extenstion .spec.ts. Eg LoginTest.spec.ts 50 | - In the testData excel create a sheet with name of test. Eg. LoginTest 51 | - Create a execution sheet and make an entry of new test case. Eg. in the Regression sheet add a row for new test LoginTest and update other columns like run, mode etc. 52 | 53 | ##### 3. Execution 54 | To run test suite use below command. 55 | ```sh 56 | npm run create:suite SHEET= && npm test 57 | ``` 58 | **Note:** SheetName needs to be updated. 59 | 60 | To run individual test locally use below command. 61 | ```sh 62 | set TEST_NAME= && npm run local:test 63 | ``` 64 | **Note:** Using set command we are setting the local TestFileName. 65 | 66 | To change any environment configuration in .env file at run time use set command. 67 | Eg: To change browser to MS Edge use below command 68 | ```sh 69 | set BROWSER=edge 70 | ``` 71 | Similar command can be used to update other environment configuration 72 | 73 | To generate Allure report use below command 74 | ```sh 75 | npm run report 76 | ``` 77 | 78 | ##### 4. Report & Logs 79 | Playwright HTML report will be present inside 80 | ```sh 81 | test-results/results/index.html 82 | ``` 83 | Execution log will be present in the log file. 84 | ```sh 85 | test-results/logs/execution.log 86 | ``` 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright-demo", 3 | "version": "1.0.0", 4 | "description": "Playwright Demo framework", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "" 9 | }, 10 | "scripts": { 11 | "lint": "eslint . --ext .ts", 12 | "report": "allure serve", 13 | "create:suite": "cd ./src/framework/manager & npx ts-node SuiteManager.ts", 14 | "test": "playwright test --project=suite", 15 | "local:test": "playwright test --project=local", 16 | "local:test:ui": "playwright test --project=local --ui" 17 | }, 18 | "keywords": [], 19 | "author": "Vinay Kumar B M", 20 | "license": "ISC", 21 | "devDependencies": { 22 | "@playwright/test": "1.37.0", 23 | "@types/easy-soap-request": "4.1.1", 24 | "@types/express": "^4.17.13", 25 | "@types/ibm_db": "2.0.10", 26 | "@types/mssql": "^7.1.4", 27 | "@types/oracledb": "5.2.3", 28 | "@types/pdf-parse": "^1.1.1", 29 | "@types/randomstring": "^1.1.8", 30 | "@types/string-format": "^2.0.0", 31 | "@types/xmldom": "^0.1.31", 32 | "@typescript-eslint/eslint-plugin": "^5.16.0", 33 | "@typescript-eslint/parser": "^5.16.0", 34 | "allure-playwright": "2.5.0", 35 | "dotenv": "16.0.0", 36 | "easy-soap-request": "^4.6.0", 37 | "eslint": "^8.12.0", 38 | "eslint-config-airbnb-typescript": "16.1.4", 39 | "eslint-plugin-playwright": "0.8.0", 40 | "ibm_db": "2.8.1", 41 | "jsonpath": "1.1.1", 42 | "moment": "2.29.1", 43 | "mssql": "^7.2.1", 44 | "oracledb": "5.3.0", 45 | "pdf-parse": "1.1.1", 46 | "randomstring": "1.2.2", 47 | "string-format": "2.0.0", 48 | "ts-node": "^10.4.0", 49 | "typescript": "^4.5.4", 50 | "winston": "^3.4.0", 51 | "xml-formatter": "2.6.1", 52 | "xmldom": "0.6.0", 53 | "xpath": "0.0.32", 54 | "@types/convert-excel-to-json": "1.7.1", 55 | "convert-excel-to-json": "1.7.0", 56 | "jasmine-xml2html-converter": "0.0.2", 57 | "fetch-to-curl": "0.5.2", 58 | "monocart-reporter": "1.0.7" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { PlaywrightTestConfig } from "@playwright/test"; 2 | import dotenv from 'dotenv'; 3 | import Browser from "./src/framework/manager/Browser"; 4 | 5 | dotenv.config(); 6 | 7 | const timeInMin: number = 60 * 1000; 8 | const config: PlaywrightTestConfig = { 9 | use: { 10 | browserName: Browser.type(process.env.BROWSER.toLowerCase()), 11 | headless: false, 12 | channel: Browser.channel(process.env.BROWSER.toLowerCase()), 13 | launchOptions: { 14 | args: ["--start-maximized", "--disable-extensions", "--disable-plugins"], 15 | headless: false, 16 | timeout: Number.parseInt(process.env.BROWSER_LAUNCH_TIMEOUT, 10), 17 | slowMo: 100, 18 | downloadsPath: "./test-results/downloads", 19 | }, 20 | viewport: null, 21 | ignoreHTTPSErrors: true, 22 | acceptDownloads: true, 23 | actionTimeout: Number.parseInt(process.env.ACTION_TIMEOUT, 10) * timeInMin, 24 | navigationTimeout: Number.parseInt(process.env.NAVIGATION_TIMEOUT, 10) * timeInMin, 25 | screenshot: { 26 | mode: "only-on-failure", 27 | fullPage: true, 28 | }, 29 | video: "retain-on-failure", 30 | }, 31 | testDir: "./src/tests", 32 | outputDir: "./test-results/failure", 33 | retries: Number.parseInt(process.env.RETRIES, 10), 34 | preserveOutput: "failures-only", 35 | reportSlowTests: null, 36 | timeout: Number.parseInt(process.env.TEST_TIMEOUT, 10) * timeInMin, 37 | workers: Number.parseInt(process.env.PARALLEL_THREAD, 10), 38 | reporter: [ 39 | ["dot"], 40 | ["allure-playwright", { 41 | detail: false, 42 | suiteTitle: false, 43 | environmentInfo: { 44 | OS: process.platform.toUpperCase(), 45 | BROWSER: process.env.BROWSER.toUpperCase(), 46 | BASE_URL: process.env.BASE_URL, 47 | }, 48 | }], 49 | ['html', { open: 'never', outputFolder: "./test-results/report" }], 50 | ["junit", { outputFile: "./test-results/results/results.xml" }], 51 | ["json", { outputFile: "./test-results/results/results.json" }], 52 | ["./src/framework/logger/TestListener.ts"], 53 | ['monocart-reporter', { 54 | name: "Automation Report", 55 | outputFile: './test-results/report/execution.html', 56 | }], 57 | ], 58 | projects: [ 59 | { 60 | name: "local", 61 | testMatch: `*${process.env.TEST_NAME.trim()}*`, 62 | }, 63 | { 64 | name: "suite", 65 | testMatch: "*.test.ts", 66 | }, 67 | ], 68 | }; 69 | export default config; 70 | -------------------------------------------------------------------------------- /src/API/REST/constants/RESTConstants.ts: -------------------------------------------------------------------------------- 1 | export default class RESTConstants { 2 | static readonly CONTENT_TYPE = 'Content-Type'; 3 | static readonly ACCEPT = 'Accept'; 4 | static readonly CONTENT_JSON = "application/json"; 5 | static readonly STATUS_CODE = "Status Code"; 6 | } 7 | -------------------------------------------------------------------------------- /src/API/REST/steps/UserSteps.ts: -------------------------------------------------------------------------------- 1 | import test, { Page } from "@playwright/test"; 2 | import APIActions from "@apiActions/APIActions"; 3 | import RESTResponse from "@apiActions/RESTResponse"; 4 | import Assert from "@asserts/Assert"; 5 | import RESTConstants from "@restConstants/RESTConstants"; 6 | 7 | export default class UserSteps { 8 | private api: APIActions; 9 | private BASE_URL = process.env.REST_API_BASE_URL; 10 | constructor(private page: Page) { 11 | this.api = new APIActions(this.page); 12 | } 13 | private get header() { 14 | return this.api.header.set(RESTConstants.CONTENT_TYPE, RESTConstants.CONTENT_JSON) 15 | .set(RESTConstants.ACCEPT, RESTConstants.CONTENT_JSON).get(); 16 | } 17 | 18 | public async get(endPoint: string, operation: string): Promise { 19 | let response: RESTResponse; 20 | await test.step(`Making call to GET ${operation}`, async () => { 21 | response = await this.api.rest.get(this.BASE_URL + endPoint, this.header, operation); 22 | }); 23 | return response; 24 | } 25 | 26 | public async post(endPoint: string, requestBodyFile: string, requestData: any, 27 | operation: string): Promise { 28 | let response: RESTResponse; 29 | await test.step(`Making POST call to ${operation}`, async () => { 30 | const requestJSON = await this.api.rest.createRequestBody(requestBodyFile, requestData); 31 | response = await this.api.rest.post(this.BASE_URL + endPoint, this.header, requestJSON, operation); 32 | }); 33 | return response; 34 | } 35 | 36 | public async put(endPoint: string, requestBodyFile: string, requestData: any, 37 | operation: string): Promise { 38 | let response: RESTResponse; 39 | await test.step(`Making PUT call to ${operation}`, async () => { 40 | const requestJSON = await this.api.rest.createRequestBody(requestBodyFile, requestData); 41 | response = await this.api.rest.put(this.BASE_URL + endPoint, this.header, requestJSON, operation); 42 | }); 43 | return response; 44 | } 45 | 46 | public async delete(endPoint: string, operation: string): Promise { 47 | let response: RESTResponse; 48 | await test.step(`Making DELETE call to ${operation}`, async () => { 49 | response = await this.api.rest.delete(this.BASE_URL + endPoint, this.header, operation); 50 | }); 51 | return response; 52 | } 53 | 54 | public async verifyStatusCode(response: RESTResponse, statusCode: string) { 55 | await test.step(`Verifying that status code is ${statusCode}`, async () => { 56 | await Assert.assertEquals(await response.getStatusCode(), statusCode, RESTConstants.STATUS_CODE); 57 | }); 58 | } 59 | 60 | public async extractResponseValue(response: RESTResponse, jsonPath: string, operation: string) { 61 | let value: string; 62 | await test.step(`Extract value from ${operation} response`, async () => { 63 | value = await response.getTagContentByJsonPath(jsonPath, operation); 64 | }); 65 | return value; 66 | } 67 | 68 | public async verifyContent(response: RESTResponse, jsonPath: string, expectedValue: string, description: string) { 69 | await test.step(`Verifying that ${description} has value ${expectedValue}`, async () => { 70 | const value = await response.getTagContentByJsonPath(jsonPath, description); 71 | await Assert.assertEquals(value, expectedValue, description); 72 | }); 73 | } 74 | 75 | public async verifyContentIsNotNull(response: RESTResponse, jsonPath: string, description: string) { 76 | await test.step(`Verifying that ${description} content is NOT NULL`, async () => { 77 | const value = await response.getTagContentByJsonPath(jsonPath, description); 78 | await Assert.assertNotNull(value, description); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/API/SOAP/constants/SOAPConstants.ts: -------------------------------------------------------------------------------- 1 | export default class SOAPConstants { 2 | static readonly CONTENT_TYPE = 'Content-Type'; 3 | static readonly SOAP_ACTION = 'SoapAction'; 4 | static readonly CONTENT_TEXT = "text/xml;charset=UTF-8"; 5 | } 6 | -------------------------------------------------------------------------------- /src/API/SOAP/steps/AccountServiceSteps.ts: -------------------------------------------------------------------------------- 1 | import test, { Page } from "@playwright/test"; 2 | import APIActions from "@apiActions/APIActions"; 3 | import SOAPResponse from "@apiActions/SOAPResponse"; 4 | import Assert from "@asserts/Assert"; 5 | import SOAPConstants from "@soapConstants/SOAPConstants"; 6 | 7 | export default class AccountServiceSteps { 8 | private api: APIActions; 9 | 10 | constructor(private page: Page) { 11 | this.api = new APIActions(this.page); 12 | } 13 | 14 | public async request(endPoint: string, requestBody: string, requestData: any, 15 | operation: string): Promise { 16 | let response: SOAPResponse; 17 | await test.step(`SOAP request to ${operation}`, async () => { 18 | const requestHeaders = this.api.header.set(SOAPConstants.CONTENT_TYPE, SOAPConstants.CONTENT_TEXT).get(); 19 | response = await this.api.soap.post(endPoint, requestHeaders, requestBody, requestData, operation); 20 | }); 21 | return response; 22 | } 23 | 24 | public async verifyResponse(response: SOAPResponse, xpath: string, result: string, operation: string) { 25 | await test.step(`Verifying that result of ${operation} is ${result}`, async () => { 26 | const actualResult = await response.getTagContentByXpath(xpath, operation); 27 | await Assert.assertEquals(actualResult, result.toString(), operation); 28 | }); 29 | } 30 | 31 | public async verifyResponseContains(response: SOAPResponse, xpath: string, result: string, operation: string) { 32 | await test.step(`Verifying that result of ${operation} contains ${result}`, async () => { 33 | const actualResult = await response.getTagContentByXpath(xpath, operation); 34 | await Assert.assertContains(actualResult, result, operation); 35 | }); 36 | } 37 | 38 | public async getResponseContent(response: SOAPResponse, xpath: string, operation: string) { 39 | let content: string; 40 | await test.step(`Getting content from response of ${operation}`, async () => { 41 | content = await response.getTagContentByXpath(xpath, operation); 42 | }); 43 | return content; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/advantage/constants/CommonConstants.ts: -------------------------------------------------------------------------------- 1 | export default class CommonConstants { 2 | static readonly FALSE = 'false'; 3 | static readonly TRUE = 'true'; 4 | static readonly TEN = 10; 5 | static readonly TWO = 2; 6 | static readonly DOWNLOADS_PATH = "./test-results/downloads/"; 7 | } 8 | -------------------------------------------------------------------------------- /src/advantage/constants/ConfigurationConstants.ts: -------------------------------------------------------------------------------- 1 | export default class ConfigurationConstants { 2 | static readonly AOS_BACKEND = "AOS Backend"; 3 | } 4 | -------------------------------------------------------------------------------- /src/advantage/constants/HomePageConstants.ts: -------------------------------------------------------------------------------- 1 | export default class HomePageConstants { 2 | static readonly USER_ICON = "User Icon"; 3 | static readonly USER_NAME = "User Name"; 4 | static readonly PASSWORD = "Password"; 5 | static readonly REMEMBER_ME_CHECKBOX = "Remember Me"; 6 | static readonly SIGN_IN_BUTTON = "Sign In Button"; 7 | static readonly SIGN_IN_ERROR_MESSAGE = "Error Message"; 8 | static readonly SIGN_OUT_LINK = "Sign Out Link"; 9 | static readonly HOME_PAGE = "Home Page"; 10 | static readonly CREATE_NEW_ACCOUNT_LINK = "Create New Account Link"; 11 | static readonly CATEGORY_DROPDOWN = "Category Dropdown"; 12 | static readonly PRODUCT_DROPDOWN = "Product Dropdown"; 13 | static readonly SUBJECT_TEXTAREA = "ContactUs Subject"; 14 | static readonly EMAIL_TEXTBOX = "ContactUs email"; 15 | static readonly SEND_BUTTON = "Send Button"; 16 | static readonly CONTACT_US_MESSAGE = "ContactUs Success Message"; 17 | static readonly SEARCH_ICON = "Search Icon"; 18 | static readonly SEARCH_TEXTBOX = "Search Box"; 19 | static readonly SEARCH_CLOSE_IMAGE = "Close Search"; 20 | static readonly ENTER_KEY: 'Enter'; 21 | static readonly HELP_ICON = "Help Icon"; 22 | static readonly MANAGEMENT_CONSOLE_LINK = "Management Console Link"; 23 | } 24 | -------------------------------------------------------------------------------- /src/advantage/constants/RegistrationPageConstants.ts: -------------------------------------------------------------------------------- 1 | export default class RegistrationPageConstants { 2 | static readonly USER_NAME = "User Name"; 3 | static readonly PASSWORD = "Password"; 4 | static readonly EMAIL = "Email"; 5 | static readonly CONFIRM_PASSWORD = "Confirm Password"; 6 | static readonly FIRST_NAME = "First Name"; 7 | static readonly LAST_NAME = "Last Name"; 8 | static readonly PHONE_NUMBER = "Phone Number"; 9 | static readonly COUNTRY = "Country"; 10 | static readonly CITY = "City"; 11 | static readonly ADDRESS = "Address"; 12 | static readonly STATE = "State"; 13 | static readonly POSTAL_CODE = "Postal Code"; 14 | static readonly PROMOTION = "Promotion"; 15 | static readonly TERMS_AND_CONDITIONS = "Terms & Conditions"; 16 | static readonly REGISTER_BUTTON = "Register Button"; 17 | static readonly ALREADY_HAVE_AN_ACCOUNT_LINK = "Already have an Account Link"; 18 | static readonly ERROR_MESSAGE = "Error Message"; 19 | } 20 | -------------------------------------------------------------------------------- /src/advantage/pages/ConfigurationPage.ts: -------------------------------------------------------------------------------- 1 | export default class ConfigurationPage { 2 | static readonly AOS_BACK_END_LINK = "//ul[@class='nav_user_guide_list']/li/a[text()='AOS back end']"; 3 | } 4 | -------------------------------------------------------------------------------- /src/advantage/pages/HomePage.ts: -------------------------------------------------------------------------------- 1 | export default class HomePage { 2 | static readonly USER_ICON = "#menuUser"; 3 | static readonly USER_NAME_TEXTBOX = "[name='username']"; 4 | static readonly PASSWORD_TEXTBOX = "[name='password']"; 5 | static readonly REMEMBER_ME_CHECKBOX = "[name='remember_me']"; 6 | static readonly SIGN_IN_BUTTON = "#sign_in_btn"; 7 | static readonly LOGGED_IN_USER = "a#menuUserLink>span.hi-user"; 8 | static readonly SIGN_IN_ERROR_MESSAGE = "#signInResultMessage.invalid"; 9 | static readonly SIGN_OUT_LINK = "#loginMiniTitle>[translate='Sign_out']"; 10 | static readonly CREATE_NEW_ACCOUNT_LINK = "[translate='CREATE_NEW_ACCOUNT']"; 11 | static readonly CATEGORY_DROPDOWN = "[name='categoryListboxContactUs']"; 12 | static readonly PRODUCT_DROPDOWN = "[name='productListboxContactUs']"; 13 | static readonly SUBJECT_TEXTAREA = "[name='subjectTextareaContactUs']"; 14 | static readonly EMAIL_TEXTBOX = "[name='emailContactUs']"; 15 | static readonly SEND_BUTTON = "#send_btn"; 16 | static readonly CONTACT_US_MESSAGE = ".roboto-regular.successMessage"; 17 | static readonly SEARCH_ICON = "#searchSection"; 18 | static readonly SEARCH_TEXTBOX = "#autoComplete"; 19 | static readonly SEARCH_CLOSE_IMAGE = "div.autoCompleteCover>div>img"; 20 | static readonly HELP_ICON = "#menuHelp"; 21 | static readonly MANAGEMENT_CONSOLE_LINK = "div#helpMiniTitle [translate='CONFIG_TOOL']"; 22 | } 23 | -------------------------------------------------------------------------------- /src/advantage/pages/RegistrationPage.ts: -------------------------------------------------------------------------------- 1 | export default class RegistrationPage { 2 | static readonly USER_NAME_TEXTBOX = "[name='usernameRegisterPage']"; 3 | static readonly PASSWORD_TEXTBOX = "[name='passwordRegisterPage']"; 4 | static readonly EMAIL_TEXTBOX = "[name='emailRegisterPage']"; 5 | static readonly PASSWORD_CONFIRM_TEXTBOX = "[name='confirm_passwordRegisterPage']"; 6 | static readonly FIRST_NAME_TEXTBOX = "[name='first_nameRegisterPage']"; 7 | static readonly LAST_NAME_TEXTBOX = "[name='last_nameRegisterPage']"; 8 | static readonly PHONE_NUMBER_TEXTBOX = "[name='phone_numberRegisterPage']"; 9 | static readonly COUNTRY_DROPDOWN = "[name='countryListboxRegisterPage']"; 10 | static readonly CITY_TEXTBOX = "[name='cityRegisterPage']"; 11 | static readonly ADDRESS_TEXTBOX = "[name='addressRegisterPage']"; 12 | static readonly STATE_TEXTBOX = "[name='state_/_province_/_regionRegisterPage']"; 13 | static readonly POSTAL_CODE_TEXTBOX = "[name='postal_codeRegisterPage']"; 14 | static readonly PROMOTION_CHECKBOX = "[name='allowOffersPromotion']"; 15 | static readonly PRIVACY_POLICY_CHECKBOX = "[name='i_agree']"; 16 | static readonly REGISTER_BUTTON = "#register_btn"; 17 | static readonly ALREADY_HAVE_AN_ACCOUNT_LINK = "[translate='ALREADY_HAVE_AN_ACCOUNT']"; 18 | static readonly MANDATORY_FIELD_ERROR_MESSAGE = "div.inputContainer>label.invalid"; 19 | } 20 | -------------------------------------------------------------------------------- /src/advantage/steps/ConfigurationSteps.ts: -------------------------------------------------------------------------------- 1 | import test, { Page } from "@playwright/test"; 2 | import ConfigurationPage from "@pages/ConfigurationPage"; 3 | import Assert from "@asserts/Assert"; 4 | import UIActions from "@uiActions/UIActions"; 5 | import PDFUtil from "@utils/PDFUtil"; 6 | import CommonConstants from "@uiConstants/CommonConstants"; 7 | import ConfigurationConstants from "@uiConstants/ConfigurationConstants"; 8 | 9 | export default class ConfigurationSteps { 10 | private ui: UIActions; 11 | 12 | constructor(private page: Page) { 13 | this.ui = new UIActions(page); 14 | } 15 | 16 | public async downloadAOSBackendPDF() { 17 | let fileName: string; 18 | await test.step(`Downloading the AOS backend PDF file`, async () => { 19 | fileName = await this.ui.downloadFile(ConfigurationPage.AOS_BACK_END_LINK, 20 | ConfigurationConstants.AOS_BACKEND); 21 | }); 22 | return fileName; 23 | } 24 | 25 | public async verifyPDFFilePageCount(fileName: string, pages: number) { 26 | await test.step(`Verify that ${fileName} file has ${pages} pages`, async () => { 27 | await Assert.assertEquals(await PDFUtil.getNumberOfPages(CommonConstants.DOWNLOADS_PATH + fileName), 28 | pages, fileName); 29 | }); 30 | } 31 | 32 | public async verifyPDFFileText(fileName: string, content: string) { 33 | await test.step(`Verify that ${fileName} has content ${content}`, async () => { 34 | await Assert.assertContains(await PDFUtil.getText(CommonConstants.DOWNLOADS_PATH + fileName), 35 | content, fileName); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/advantage/steps/HomeSteps.ts: -------------------------------------------------------------------------------- 1 | import test, { Page } from "@playwright/test"; 2 | import UIActions from "@uiActions/UIActions"; 3 | import Assert from "@asserts/Assert"; 4 | import CommonConstants from "@uiConstants/CommonConstants"; 5 | import HomePageConstants from "@uiConstants/HomePageConstants"; 6 | import HomePage from "@pages/HomePage"; 7 | 8 | export default class HomeSteps { 9 | private ui: UIActions; 10 | 11 | constructor(private page: Page) { 12 | this.ui = new UIActions(page); 13 | } 14 | /** 15 | * Launch the Application 16 | */ 17 | public async launchApplication() { 18 | await test.step(`Launching the application`, async () => { 19 | await this.ui.goto(process.env.BASE_URL, HomePageConstants.HOME_PAGE); 20 | }); 21 | } 22 | /** 23 | * Log into the application 24 | * @param userName 25 | * @param password 26 | */ 27 | public async login(userName: string, password: string) { 28 | await test.step(`Login to application credentials as ${userName} & ${password}`, async () => { 29 | await this.ui.element(HomePage.USER_ICON, HomePageConstants.USER_ICON).click(); 30 | await this.enterLoginDetails(userName, password); 31 | }); 32 | } 33 | /** 34 | * Enter login details 35 | * @param userName 36 | * @param password 37 | */ 38 | public async enterLoginDetails(userName: string, password: string) { 39 | await test.step(`Enter login credentials as ${userName} & ${password}`, async () => { 40 | await this.ui.editBox(HomePage.USER_NAME_TEXTBOX, HomePageConstants.USER_NAME).fill(userName); 41 | await this.ui.editBox(HomePage.PASSWORD_TEXTBOX, HomePageConstants.PASSWORD).fill(password); 42 | await this.ui.checkbox(HomePage.REMEMBER_ME_CHECKBOX, HomePageConstants.REMEMBER_ME_CHECKBOX).check(); 43 | await this.ui.element(HomePage.SIGN_IN_BUTTON, HomePageConstants.SIGN_IN_BUTTON).click(); 44 | }); 45 | } 46 | /** 47 | * Validate logged in user 48 | * @param userName 49 | */ 50 | public async validateLogin(userName: string) { 51 | await test.step(`Verify that user is successfully logged in as ${userName}`, async () => { 52 | const user = await this.ui.element(HomePage.LOGGED_IN_USER, HomePageConstants.USER_NAME).getTextContent(); 53 | await Assert.assertEquals(user, userName, HomePageConstants.USER_NAME); 54 | }); 55 | } 56 | /** 57 | * Validate invalid login 58 | * @param errorMessage 59 | */ 60 | public async validateInvalidLogin(errorMessage: string) { 61 | await test.step(`Verify that error message ${errorMessage}`, async () => { 62 | const user = await this.ui.element(HomePage.SIGN_IN_ERROR_MESSAGE, HomePageConstants.SIGN_IN_ERROR_MESSAGE) 63 | .getTextContent(); 64 | await Assert.assertEquals(user, errorMessage, HomePageConstants.SIGN_IN_ERROR_MESSAGE); 65 | }); 66 | } 67 | /** 68 | * Log out of the application 69 | */ 70 | public async logout() { 71 | await test.step(`Logged out of application`, async () => { 72 | await this.ui.element(HomePage.LOGGED_IN_USER, HomePageConstants.USER_NAME).click(); 73 | await this.ui.element(HomePage.SIGN_OUT_LINK, HomePageConstants.SIGN_OUT_LINK).click(); 74 | await this.ui.pauseInSecs(CommonConstants.TWO); 75 | }); 76 | } 77 | /** 78 | * Navigate to Create Account page 79 | */ 80 | public async navigateToCreateAccount() { 81 | await test.step(`Navigate to Create Account page`, async () => { 82 | await this.ui.element(HomePage.USER_ICON, HomePageConstants.USER_ICON).click(); 83 | await this.ui.element(HomePage.CREATE_NEW_ACCOUNT_LINK, HomePageConstants.CREATE_NEW_ACCOUNT_LINK).click(); 84 | }); 85 | } 86 | /** 87 | * Enters details into Contact Us 88 | * @param category 89 | * @param product 90 | * @param email 91 | * @param subject 92 | */ 93 | public async enterContactUsDetails(category: string, product: string, email: string, subject: string) { 94 | await test.step(`Entering Contact Us details`, async () => { 95 | await this.ui.dropdown(HomePage.CATEGORY_DROPDOWN, HomePageConstants.CATEGORY_DROPDOWN) 96 | .selectByVisibleText(category); 97 | await this.ui.dropdown(HomePage.PRODUCT_DROPDOWN, HomePageConstants.PRODUCT_DROPDOWN) 98 | .selectByVisibleText(product); 99 | await this.ui.editBox(HomePage.EMAIL_TEXTBOX, HomePageConstants.EMAIL_TEXTBOX).fill(email); 100 | await this.ui.editBox(HomePage.SUBJECT_TEXTAREA, HomePageConstants.SUBJECT_TEXTAREA).fill(subject); 101 | }); 102 | } 103 | /** 104 | * Click on Send button of Contact Us 105 | */ 106 | public async sendMessage() { 107 | await test.step(`Click on Send button of Contact Us`, async () => { 108 | await this.ui.element(HomePage.SEND_BUTTON, HomePageConstants.SEND_BUTTON).click(); 109 | }); 110 | } 111 | /** 112 | * Verify the success message of Contact Us 113 | * @param message 114 | */ 115 | public async verifySuccessMessage(message: string) { 116 | await test.step(`Verifying Success Message of Contact Us`, async () => { 117 | const actualMessage = await this.ui.element(HomePage.CONTACT_US_MESSAGE, 118 | HomePageConstants.CONTACT_US_MESSAGE).getTextContent(); 119 | await Assert.assertEquals(actualMessage, message, HomePageConstants.CONTACT_US_MESSAGE); 120 | }); 121 | } 122 | /** 123 | * Search for Product 124 | * @param product 125 | */ 126 | public async searchProduct(product: string) { 127 | await test.step(`Searching for product '${product}'`, async () => { 128 | await this.ui.element(HomePage.SEARCH_ICON, HomePageConstants.SEARCH_ICON).click(); 129 | await (await this.ui.editBox(HomePage.SEARCH_TEXTBOX, HomePageConstants.SEARCH_TEXTBOX).type(product)) 130 | .keyPress(HomePageConstants.ENTER_KEY); 131 | await this.ui.element(HomePage.SEARCH_CLOSE_IMAGE, HomePageConstants.SEARCH_CLOSE_IMAGE).click(); 132 | }); 133 | } 134 | /** 135 | * Navigate to Management Console screen 136 | */ 137 | public async navigateToManagementConsole() { 138 | let newPage: Page; 139 | await test.step(`Navigate to Management Console screen`, async () => { 140 | await this.ui.waitForLoadingImage(); 141 | await this.ui.element(HomePage.HELP_ICON, HomePageConstants.HELP_ICON).click(); 142 | newPage = await this.ui.switchToNewWindow(HomePage.MANAGEMENT_CONSOLE_LINK, 143 | HomePageConstants.MANAGEMENT_CONSOLE_LINK); 144 | }); 145 | return newPage; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/advantage/steps/RegistrationSteps.ts: -------------------------------------------------------------------------------- 1 | import test, { Page } from "@playwright/test"; 2 | import UIActions from "@uiActions/UIActions"; 3 | import Assert from "@asserts/Assert"; 4 | import StringUtil from "@utils/StringUtil"; 5 | import CommonConstants from "@uiConstants/CommonConstants"; 6 | import RegistrationPageConstants from "@uiConstants/RegistrationPageConstants"; 7 | import RegistrationPage from "@pages/RegistrationPage"; 8 | 9 | export default class RegistrationSteps { 10 | private ui: UIActions; 11 | 12 | constructor(private page: Page) { 13 | this.ui = new UIActions(page); 14 | } 15 | /** 16 | * Creating a new Account 17 | * @param email 18 | * @param password 19 | * @param confirmPassword 20 | * @param firstName 21 | * @param lastName 22 | * @param phoneNumber 23 | * @param country 24 | * @param city 25 | * @param address 26 | * @param state 27 | * @param postalCode 28 | * @param allowOffersPromotion 29 | * @returns 30 | */ 31 | public async createAccount(email: string, password: string, confirmPassword: string, firstName: string, 32 | lastName: string, phoneNumber: string, country: string, city: string, address: string, state: string, 33 | postalCode: string, allowOffersPromotion: string) { 34 | let userName: string; 35 | await test.step(`Create New Account`, async () => { 36 | userName = StringUtil.randomAlphabeticString(CommonConstants.TEN); 37 | await this.ui.editBox(RegistrationPage.USER_NAME_TEXTBOX, 38 | RegistrationPageConstants.USER_NAME).fill(userName); 39 | await this.ui.editBox(RegistrationPage.EMAIL_TEXTBOX, RegistrationPageConstants.EMAIL).fill(email); 40 | await this.ui.editBox(RegistrationPage.PASSWORD_TEXTBOX, RegistrationPageConstants.PASSWORD).fill(password); 41 | await this.ui.editBox(RegistrationPage.PASSWORD_CONFIRM_TEXTBOX, RegistrationPageConstants.CONFIRM_PASSWORD) 42 | .fill(confirmPassword); 43 | await this.ui.editBox(RegistrationPage.FIRST_NAME_TEXTBOX, RegistrationPageConstants.FIRST_NAME) 44 | .fill(firstName); 45 | await this.ui.editBox(RegistrationPage.LAST_NAME_TEXTBOX, RegistrationPageConstants.LAST_NAME) 46 | .fill(lastName); 47 | await this.ui.editBox(RegistrationPage.PHONE_NUMBER_TEXTBOX, RegistrationPageConstants.PHONE_NUMBER) 48 | .fill(phoneNumber); 49 | await this.ui.dropdown(RegistrationPage.COUNTRY_DROPDOWN, RegistrationPageConstants.COUNTRY) 50 | .selectByVisibleText(country); 51 | await this.ui.editBox(RegistrationPage.CITY_TEXTBOX, RegistrationPageConstants.CITY).fill(city); 52 | await this.ui.editBox(RegistrationPage.ADDRESS_TEXTBOX, RegistrationPageConstants.ADDRESS).fill(address); 53 | await this.ui.editBox(RegistrationPage.STATE_TEXTBOX, RegistrationPageConstants.STATE).fill(state); 54 | await this.ui.editBox(RegistrationPage.POSTAL_CODE_TEXTBOX, RegistrationPageConstants.POSTAL_CODE) 55 | .fill(postalCode); 56 | await Assert.assertTrue(await this.ui.checkbox(RegistrationPage.PROMOTION_CHECKBOX, 57 | RegistrationPageConstants.PROMOTION).isChecked(), RegistrationPageConstants.PROMOTION); 58 | if (allowOffersPromotion.toLowerCase() === CommonConstants.FALSE) { 59 | await this.ui.checkbox(RegistrationPage.PROMOTION_CHECKBOX, RegistrationPageConstants.PROMOTION) 60 | .uncheck(); 61 | } 62 | await this.ui.checkbox(RegistrationPage.PRIVACY_POLICY_CHECKBOX, 63 | RegistrationPageConstants.TERMS_AND_CONDITIONS).check(); 64 | }); 65 | return userName; 66 | } 67 | /** 68 | * Saves the registration details 69 | */ 70 | public async saveRegistration() { 71 | await test.step(`Save registration details`, async () => { 72 | await this.ui.element(RegistrationPage.REGISTER_BUTTON, RegistrationPageConstants.REGISTER_BUTTON).click(); 73 | }); 74 | } 75 | /** 76 | * Click on link Already having Account 77 | */ 78 | public async alreadyHaveAccount() { 79 | await test.step(`Click on Already have an account link`, async () => { 80 | await this.ui.element(RegistrationPage.ALREADY_HAVE_AN_ACCOUNT_LINK, 81 | RegistrationPageConstants.ALREADY_HAVE_AN_ACCOUNT_LINK).click(); 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/database/constants/DatabaseConstants.ts: -------------------------------------------------------------------------------- 1 | export default class DatabaseConstants { 2 | static readonly QUERY_EXECUTION = "Query Execution"; 3 | } 4 | -------------------------------------------------------------------------------- /src/database/steps/DatabaseStep.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { IRecordSet } from "mssql"; 3 | import Assert from "@asserts/Assert"; 4 | import DBUtil from "@utils/DBUtil"; 5 | import DatabaseConstants from "@dbConstants/DatabaseConstants"; 6 | 7 | export default class DatabaseStep { 8 | public async executeMSSQLQuery(query: string) { 9 | let result: { rows: IRecordSet; rowsAffected: number[]; }; 10 | await test.step('Executing query in MS SQL db', async () => { 11 | result = await DBUtil.executeMSSQLQuery(process.env.DB_CONFIG, query); 12 | console.log(result); 13 | }); 14 | return result; 15 | } 16 | 17 | public async executeDB2Query(query: string) { 18 | let result: { rows: any; rowsAffected: any; }; 19 | await test.step('Executing query in DB2 db', async () => { 20 | result = await DBUtil.executeDB2Query(process.env.DB_CONFIG, query); 21 | console.log(result); 22 | }); 23 | return result; 24 | } 25 | 26 | public async executeOracleQuery(query: string) { 27 | let result: { rows: unknown[]; rowsAffected: number; }; 28 | await test.step('Executing query in Oracle db', async () => { 29 | result = await DBUtil.executeOracleQuery(process.env.DB_CONFIG, query); 30 | console.log(result); 31 | }); 32 | return result; 33 | } 34 | 35 | public async verifyExecutionSuccess(rowsAffected: number) { 36 | await test.step('Verify query execution is success', async () => { 37 | await Assert.assertTrue(rowsAffected > 0, DatabaseConstants.QUERY_EXECUTION); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/framework/config/base-test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { test as base } from '@playwright/test'; 3 | 4 | export const test = base.extend<{ MyFixtures }, { gData: Map }>({ 5 | gData: [async ({ }, use) => { 6 | const data = new Map(); 7 | data.set("SPACE", " "); 8 | data.set("HYPHEN", "-"); 9 | data.set("UNDERSCORE", "_"); 10 | await use(data); 11 | }, { scope: 'worker' }], 12 | 13 | page: async ({ page, gData }, use) => { 14 | await use(page); 15 | }, 16 | }); 17 | export { expect } from '@playwright/test'; 18 | -------------------------------------------------------------------------------- /src/framework/constants/BrowserConstants.ts: -------------------------------------------------------------------------------- 1 | export default class BrowserConstants { 2 | static readonly CHROME = "chrome"; 3 | static readonly FIREFOX = "firefox"; 4 | static readonly WEBKIT = "webkit"; 5 | static readonly MSEDGE = "msedge"; 6 | static readonly EDGE = "edge"; 7 | static readonly CHROMIUM = "chromium"; 8 | static readonly BLANK = ""; 9 | } 10 | -------------------------------------------------------------------------------- /src/framework/constants/CommonConstants.ts: -------------------------------------------------------------------------------- 1 | export default class CommonConstants { 2 | static readonly SEMICOLON = ';'; 3 | static readonly BLANK = ''; 4 | static readonly ZERO = 0; 5 | static readonly ONE = 1; 6 | static readonly TWO = 2; 7 | static readonly THREE = 3; 8 | static readonly HALF = 0.5; 9 | static readonly ONE_THOUSAND = 1000; 10 | static readonly DOWNLOAD_PATH = "./test-results/downloads/"; 11 | static readonly SOAP_XML_REQUEST_PATH = "src/resources/API/SOAP/"; 12 | static readonly REST_JSON_REQUEST_PATH = "src/resources/API/REST/"; 13 | static readonly TEST_FOLDER_PATH = "../../tests/"; 14 | static readonly TEST_SUITE_FILE_FORMAT = ".test.ts"; 15 | static readonly PARALLEL_MODE = "parallel"; 16 | static readonly SERIAL_MODE = "serial"; 17 | static readonly REPORT_TITLE = "Test Execution Report"; 18 | static readonly RESULTS_PATH = "./test-results/results"; 19 | static readonly JUNIT_RESULTS_PATH = `${CommonConstants.RESULTS_PATH}/results.xml`; 20 | } 21 | -------------------------------------------------------------------------------- /src/framework/constants/DBConstants.ts: -------------------------------------------------------------------------------- 1 | export default class DBConstants { 2 | static readonly PROTOCOL = ';PROTOCOL=TCPIP'; 3 | static readonly CERTIFICATE = ';trustServerCertificate=true;encrypt=false'; 4 | static readonly USER = 'user:'; 5 | static readonly PASSWORD = 'password:'; 6 | static readonly CONNECTION_STRING = 'connectString:'; 7 | } 8 | -------------------------------------------------------------------------------- /src/framework/constants/ExcelConstants.ts: -------------------------------------------------------------------------------- 1 | export default class ExcelConstants { 2 | static readonly TEST_PATH = './src/resources/data/testData.xlsx'; 3 | static readonly SUITE_PATH = '../../resources/data/testData.xlsx'; 4 | static readonly YES = "YES"; 5 | } 6 | -------------------------------------------------------------------------------- /src/framework/constants/HTMLConstants.ts: -------------------------------------------------------------------------------- 1 | export default class HTMLConstants { 2 | static readonly LOADING_IMAGE = "body>.loader"; 3 | static readonly OPTION = "option"; 4 | static readonly SELECTED_OPTION = "option[selected='selected']"; 5 | } 6 | -------------------------------------------------------------------------------- /src/framework/logger/Logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | 3 | const Logger = winston.createLogger({ 4 | transports: [ 5 | new winston.transports.Console({ 6 | format: winston.format.combine( 7 | winston.format.uncolorize({ level: true, message: true, raw: true }), 8 | winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), 9 | winston.format.align(), 10 | winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`), 11 | ), 12 | }), 13 | new winston.transports.File({ 14 | filename: 'test-results/logs/execution.log', 15 | format: winston.format.combine( 16 | winston.format.uncolorize({ level: true, message: true, raw: true }), 17 | winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), 18 | winston.format.align(), 19 | winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`), 20 | ), 21 | }), 22 | ], 23 | }); 24 | export default Logger; 25 | -------------------------------------------------------------------------------- /src/framework/logger/TestListener.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | import { 3 | Reporter, TestCase, TestError, TestResult, TestStep, 4 | } from "@playwright/test/reporter"; 5 | import Logger from "./Logger"; 6 | 7 | const TEST_SEPARATOR = "##############################################################################"; 8 | const STEP_SEPARATOR = "------------------------------------------------------------------------------"; 9 | 10 | export default class TestListener implements Reporter { 11 | onTestBegin(test: TestCase, result: TestResult): void { 12 | this.printLogs(`Test: ${test.title} - Started`, TEST_SEPARATOR); 13 | } 14 | 15 | onTestEnd(test: TestCase, result: TestResult): void { 16 | if (result.status === 'failed') { 17 | Logger.error(`Test: ${test.title} - ${result.status}\n${result.error.stack}`); 18 | } 19 | this.printLogs(`Test: ${test.title} - ${result.status}`, TEST_SEPARATOR); 20 | } 21 | 22 | onStdOut(chunk: string | Buffer, test?: TestCase, result?: TestResult): void { 23 | Logger.info(chunk); 24 | } 25 | 26 | onStdErr(chunk: string | Buffer, test?: TestCase, result?: TestResult): void { 27 | Logger.error(chunk); 28 | } 29 | 30 | onStepBegin(test: TestCase, result: TestResult, step: TestStep): void { 31 | if (step.category === "test.step") { 32 | if (typeof step.parent !== "undefined") { 33 | Logger.info(step.title); 34 | } else { 35 | this.printLogs(`Started Step: ${step.title}`, STEP_SEPARATOR); 36 | } 37 | } 38 | } 39 | 40 | onStepEnd(test: TestCase, result: TestResult, step: TestStep): void { 41 | if (step.category === "test.step" && typeof step.parent === "undefined") { 42 | this.printLogs(`Completed Step: ${step.title}`, STEP_SEPARATOR); 43 | } 44 | } 45 | 46 | onError(error: TestError): void { 47 | Logger.error(`Message: ${error.message}`); 48 | Logger.error(`Stack: ${error.stack}`); 49 | Logger.error(`Value: ${error.value}`); 50 | } 51 | 52 | private printLogs(msg: string, separator: string) { 53 | Logger.info(separator); 54 | Logger.info(`${msg.toUpperCase()}`); 55 | Logger.info(separator); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/framework/manager/Browser.ts: -------------------------------------------------------------------------------- 1 | import BrowserConstants from "../constants/BrowserConstants"; 2 | 3 | export default class Browser { 4 | public static type(browser: string) { 5 | let browserType; 6 | if (browser === BrowserConstants.FIREFOX) { 7 | browserType = BrowserConstants.FIREFOX; 8 | } else if (browser === BrowserConstants.WEBKIT) { 9 | browserType = BrowserConstants.WEBKIT; 10 | } else { 11 | browserType = BrowserConstants.CHROMIUM; 12 | } 13 | return browserType; 14 | } 15 | 16 | public static channel(browser: string) { 17 | let browserChannel; 18 | if (browser === BrowserConstants.CHROME) { 19 | browserChannel = BrowserConstants.CHROME; 20 | } else if (browser === BrowserConstants.EDGE) { 21 | browserChannel = BrowserConstants.MSEDGE; 22 | } else { 23 | browserChannel = BrowserConstants.BLANK; 24 | } 25 | return browserChannel; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/framework/manager/SuiteManager.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-tabs */ 2 | /* eslint-disable no-restricted-syntax */ 3 | import fs from "fs"; 4 | import path from 'path'; 5 | import CommonConstants from "../constants/CommonConstants"; 6 | import SuiteTemplate from "../template/SuiteTemplate"; 7 | import CLIUtil from "../utils/CLIUtil"; 8 | import ExcelUtil from "../utils/ExcelUtil"; 9 | 10 | export default class SuiteManager { 11 | public static createSuite() { 12 | const sheet = CLIUtil.getValueOf("SHEET"); 13 | this.deleteFiles(CommonConstants.TEST_FOLDER_PATH); 14 | let testList = CommonConstants.BLANK; 15 | for (const { TestName, Mode } of ExcelUtil.getSuiteTests(sheet)) { 16 | let modeOfRun = CommonConstants.BLANK; 17 | if (Mode !== undefined && Mode !== null && Mode !== CommonConstants.BLANK) { 18 | modeOfRun = `\n\ttest.describe.configure({ mode: '${Mode.toLowerCase()}' });`; 19 | } 20 | testList += `\ntest.describe("${TestName}", () => {${modeOfRun} 21 | require("./${TestName}.spec.ts"); 22 | });`; 23 | } 24 | fs.writeFileSync(`${CommonConstants.TEST_FOLDER_PATH}${sheet}${CommonConstants.TEST_SUITE_FILE_FORMAT}`, 25 | SuiteTemplate.getTemplate(sheet, testList)); 26 | console.log(" Completed!! "); 27 | } 28 | 29 | private static deleteFiles(directory: string) { 30 | const files = fs.readdirSync(directory); 31 | for (const file of files) { 32 | if (file.includes(CommonConstants.TEST_SUITE_FILE_FORMAT)) { fs.unlinkSync(path.join(directory, file)); } 33 | } 34 | } 35 | } 36 | 37 | SuiteManager.createSuite(); 38 | -------------------------------------------------------------------------------- /src/framework/playwright/API/APIActions.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "@playwright/test"; 2 | import RequestHeader from "./RequestHeader"; 3 | import RESTRequest from "./RESTRequest"; 4 | import SOAPRequest from "./SOAPRequest"; 5 | 6 | export default class APIActions { 7 | constructor(private page: Page) { } 8 | /** 9 | * Returns REST Request instance 10 | * @returns 11 | */ 12 | public get rest(): RESTRequest { 13 | return new RESTRequest(this.page); 14 | } 15 | 16 | /** 17 | * Returns SOAP Request instance 18 | * @returns 19 | */ 20 | public get soap(): SOAPRequest { 21 | return new SOAPRequest(); 22 | } 23 | 24 | /** 25 | * Returns Request header instance 26 | * @returns 27 | */ 28 | public get header(): RequestHeader { 29 | return new RequestHeader(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/framework/playwright/API/RESTRequest.ts: -------------------------------------------------------------------------------- 1 | import { test, Page, APIResponse } from '@playwright/test'; 2 | import fs from 'fs'; 3 | import fetchToCurl from 'fetch-to-curl'; 4 | import CommonConstants from '../../constants/CommonConstants'; 5 | import StringUtil from '../../utils/StringUtil'; 6 | import RESTResponse from "./RESTResponse"; 7 | 8 | export default class RESTRequest { 9 | constructor(private page: Page) { } 10 | /** 11 | * Creates request body from JSON file by replacing the input parameters 12 | * @param jsonFileName 13 | * @param data 14 | * @returns 15 | */ 16 | public async createRequestBody(jsonFileName: string, data: any): Promise { 17 | let json = fs.readFileSync(CommonConstants.REST_JSON_REQUEST_PATH + jsonFileName, 'utf-8'); 18 | json = StringUtil.formatStringValue(json, data); 19 | return json; 20 | } 21 | /** 22 | * Make POST request and return response 23 | * @param endPoint 24 | * @param requestHeader 25 | * @param jsonAsString 26 | * @param description 27 | * @returns 28 | */ 29 | public async post(endPoint: string, requestHeader: any, jsonAsString: string, 30 | description: string): Promise { 31 | const headersAsJson = JSON.parse(JSON.stringify(requestHeader)); 32 | let restResponse: RESTResponse; 33 | await test.step(`Making POST request for ${description}`, async () => { 34 | this.printRequest(endPoint, headersAsJson, jsonAsString, 'post'); 35 | const response = await this.page.request.post(endPoint, 36 | { headers: headersAsJson, data: JSON.parse(jsonAsString) }); 37 | restResponse = await this.setRestResponse(response, description); 38 | }); 39 | return restResponse; 40 | } 41 | /** 42 | * Sets the API Response into RestResponse object 43 | * @param response 44 | * @param description 45 | * @returns RestResponse object 46 | */ 47 | private async setRestResponse(response: APIResponse, description: string): Promise { 48 | const body = await response.text(); 49 | const headers = response.headers(); 50 | const statusCode = response.status(); 51 | const restResponse: RESTResponse = new RESTResponse(headers, body, statusCode, description); 52 | console.log(`Response body: ${JSON.stringify(JSON.parse(body), undefined, 2)}`); 53 | return restResponse; 54 | } 55 | /** 56 | * Make Get request and return response 57 | * @param endPoint 58 | * @param requestHeader 59 | * @param description 60 | * @returns 61 | */ 62 | public async get(endPoint: string, requestHeader: any, description: string): Promise { 63 | const headersAsJson = JSON.parse(JSON.stringify(requestHeader)); 64 | let restResponse: RESTResponse; 65 | await test.step(`Making GET request for ${description}`, async () => { 66 | this.printRequest(endPoint, headersAsJson, null, 'get'); 67 | const response = await this.page.request.get(endPoint, { headers: headersAsJson }); 68 | restResponse = await this.setRestResponse(response, description); 69 | }); 70 | return restResponse; 71 | } 72 | /** 73 | * Make Put request and return response 74 | * @param endPoint 75 | * @param requestHeader 76 | * @param jsonAsString 77 | * @param description 78 | * @returns 79 | */ 80 | public async put(endPoint: string, requestHeader: any, jsonAsString: any, 81 | description: string): Promise { 82 | const headersAsJson = JSON.parse(JSON.stringify(requestHeader)); 83 | let restResponse: RESTResponse; 84 | await test.step(`Making PUT request for ${description}`, async () => { 85 | this.printRequest(endPoint, headersAsJson, jsonAsString, 'put'); 86 | const response = await this.page.request.put(endPoint, 87 | { headers: headersAsJson, data: JSON.parse(jsonAsString) }); 88 | restResponse = await this.setRestResponse(response, description); 89 | }); 90 | return restResponse; 91 | } 92 | /** 93 | * Make Patch request and return response 94 | * @param endPoint 95 | * @param requestHeader 96 | * @param jsonAsString 97 | * @param description 98 | * @returns 99 | */ 100 | public async patch(endPoint: string, requestHeader: any, jsonAsString: any, 101 | description: string): Promise { 102 | const headersAsJson = JSON.parse(JSON.stringify(requestHeader)); 103 | let restResponse: RESTResponse; 104 | await test.step(`Making PATCH request for ${description}`, async () => { 105 | this.printRequest(endPoint, headersAsJson, jsonAsString, 'patch'); 106 | const response = await this.page.request.patch(endPoint, 107 | { headers: headersAsJson, data: JSON.parse(jsonAsString) }); 108 | restResponse = await this.setRestResponse(response, description); 109 | }); 110 | return restResponse; 111 | } 112 | /** 113 | * Make Delete request and return response 114 | * @param endPoint 115 | * @param requestHeader 116 | * @param description 117 | * @returns 118 | */ 119 | public async delete(endPoint: string, requestHeader: any, description: string): Promise { 120 | const headersAsJson = JSON.parse(JSON.stringify(requestHeader)); 121 | let restResponse: RESTResponse; 122 | await test.step(`Making DELETE request for ${description}`, async () => { 123 | this.printRequest(endPoint, headersAsJson, null, 'delete'); 124 | const response = await this.page.request.delete(endPoint, { headers: headersAsJson }); 125 | restResponse = await this.setRestResponse(response, description); 126 | }); 127 | return restResponse; 128 | } 129 | /** 130 | * Prints the API request on console in curl format 131 | * @param endPoint 132 | * @param requestHeader 133 | * @param jsonRequestBody 134 | * @param method 135 | */ 136 | private printRequest(endPoint: string, requestHeader: any, jsonRequestBody: string, method: string) { 137 | let requestBody = jsonRequestBody; 138 | if (jsonRequestBody !== null) { 139 | requestBody = JSON.stringify(JSON.parse(jsonRequestBody), undefined, 2); 140 | } 141 | console.log("Request: ", fetchToCurl({ 142 | url: endPoint, 143 | headers: requestHeader, 144 | body: requestBody, 145 | method: method, 146 | })); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/framework/playwright/API/RESTResponse.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import jp from "jsonpath"; 3 | 4 | export default class RESTResponse { 5 | public constructor(private headers: any, private body: string, private status: number, 6 | private description: string) { } 7 | 8 | /** 9 | * Get content of tag in response body using JSON path 10 | * @param jsonPath 11 | * @param description 12 | * @returns 13 | */ 14 | public async getTagContentByJsonPath(jsonPath: string, description: string): Promise { 15 | let text: string; 16 | await test.step(`Getting content of ${description}`, async () => { 17 | // eslint-disable-next-line prefer-destructuring 18 | text = jp.query(JSON.parse(this.body), jsonPath)[0]; 19 | }); 20 | return text; 21 | } 22 | 23 | /** 24 | * Get header value by header key 25 | * @param key 26 | * @returns 27 | */ 28 | public async getHeaderValueByKey(key: string): Promise { 29 | let value: string; 30 | await test.step(`Getting header value of ${key}`, async () => { 31 | const jsonHeaders = await JSON.parse(JSON.stringify(this.headers)); 32 | value = jsonHeaders[key]; 33 | }); 34 | return value; 35 | } 36 | 37 | /** 38 | * Get response status code 39 | * @returns 40 | */ 41 | public async getStatusCode(): Promise { 42 | let status: number; 43 | await test.step(`Getting status code of ${this.description}`, async () => { 44 | status = this.status; 45 | }); 46 | return status; 47 | } 48 | 49 | /** 50 | * Get response body 51 | * @returns 52 | */ 53 | public async getBody(): Promise { 54 | let body: string; 55 | await test.step(`Getting response body of ${this.description}`, async () => { 56 | body = this.body; 57 | }); 58 | return body; 59 | } 60 | 61 | /** 62 | * Get response headers 63 | * @returns 64 | */ 65 | public async getHeaders(): Promise { 66 | let headers: string; 67 | await test.step(`Getting response Headers of ${this.description}`, async () => { 68 | headers = this.headers; 69 | }); 70 | return headers; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/framework/playwright/API/RequestHeader.ts: -------------------------------------------------------------------------------- 1 | export default class RequestHeader { 2 | private map = new Map(); 3 | 4 | public set(key: string, value: any): RequestHeader { 5 | this.map.set(key, value); 6 | return this; 7 | } 8 | 9 | public get() { 10 | return Object.fromEntries(this.map); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/framework/playwright/API/SOAPRequest.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import soapRequest from "easy-soap-request"; 3 | import format from "xml-formatter"; 4 | import fs from 'fs'; 5 | import SOAPResponse from "./SOAPResponse"; 6 | import StringUtil from "../../utils/StringUtil"; 7 | import CommonConstants from "../../constants/CommonConstants"; 8 | 9 | export default class SOAPRequest { 10 | /** 11 | * Creates request body by replacing the input parameters 12 | * @param xmlFileName 13 | * @param data 14 | * @returns 15 | */ 16 | private async createRequestBody(xmlFileName: string, data: any): Promise { 17 | let xml = fs.readFileSync(CommonConstants.SOAP_XML_REQUEST_PATH + xmlFileName, 'utf-8'); 18 | xml = StringUtil.formatStringValue(xml, data); 19 | console.log(`SOAP request : \n${format(xml, { collapseContent: true })}`); 20 | return xml; 21 | } 22 | 23 | /** 24 | * Make POST request and return response 25 | * @param endPoint 26 | * @param requestHeader 27 | * @param fileName 28 | * @param gData 29 | * @param data 30 | * @param description 31 | * @returns 32 | */ 33 | public async post(endPoint: string, requestHeader: any, fileName: string, 34 | requestData : any, description: string): Promise { 35 | let soapResponse: SOAPResponse; 36 | await test.step(`Making post request for ${description}`, async () => { 37 | const url = process.env.SOAP_API_BASE_URL + endPoint; 38 | console.log(`URL: ${url}`); 39 | const xml = await this.createRequestBody(fileName, requestData); 40 | const { response } = await soapRequest({ url: url, headers: requestHeader, xml: xml }); 41 | const { headers, body, statusCode } = response; 42 | soapResponse = new SOAPResponse(headers, body, statusCode, description); 43 | console.log(`SOAP Response: \n${format(body, { collapseContent: true })}`); 44 | }); 45 | return soapResponse; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/framework/playwright/API/SOAPResponse.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import XMLParserUtil from "../../utils/XMLParserUtil"; 3 | 4 | export default class SOAPResponse { 5 | public constructor(private headers: any, private body: any, private status: number, private description: string) { } 6 | /** 7 | * Get content of tag in response body using xpath 8 | * @param xPathExpression xpath for the tag 9 | * @param description 10 | */ 11 | public async getTagContentByXpath(xPathExpression: string, description: string): Promise { 12 | let text: string; 13 | await test.step(`Getting tag value of action ${description}`, async () => { 14 | text = XMLParserUtil.getTagContentByXpath(this.body, xPathExpression); 15 | }); 16 | return text; 17 | } 18 | 19 | /** 20 | * Get value of attribute in response body using xpath 21 | * @param xPathExpression xpath for the attribute 22 | * @param description 23 | */ 24 | public async getAttributeValueByXpath(xPathExpression: string, description: string): Promise { 25 | let text: string; 26 | await test.step(`Getting attribute value of action ${description}`, async () => { 27 | text = XMLParserUtil.getAttributeValueByXpath(this.body, xPathExpression); 28 | }); 29 | return text; 30 | } 31 | 32 | /** 33 | * Get header value by header key 34 | * @param key 35 | * @param description 36 | * @returns 37 | */ 38 | public async getHeaderValueByKey(key: string): Promise { 39 | let value:string; 40 | await test.step(`Getting header value of ${key}`, async () => { 41 | const jsonHeaders = await JSON.parse(JSON.stringify(this.headers)); 42 | value = jsonHeaders[key]; 43 | }); 44 | return value; 45 | } 46 | 47 | /** 48 | * Get response status code 49 | * @returns 50 | */ 51 | public async getStatusCode(): Promise { 52 | let status:number; 53 | await test.step(`Getting status code of ${this.description}`, async () => { 54 | status = this.status; 55 | }); 56 | return status; 57 | } 58 | 59 | /** 60 | * Get response body 61 | * @returns 62 | */ 63 | public async getBody(): Promise { 64 | let body:string; 65 | await test.step(`Getting response body of ${this.description}`, async () => { 66 | body = this.body; 67 | }); 68 | return body; 69 | } 70 | 71 | /** 72 | * Get response headers 73 | * @returns 74 | */ 75 | public async getHeaders(): Promise { 76 | let headers:string; 77 | await test.step(`Getting response Headers of ${this.description}`, async () => { 78 | headers = JSON.stringify(this.headers); 79 | }); 80 | return headers; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/framework/playwright/actions/AlertActions.ts: -------------------------------------------------------------------------------- 1 | import { Page } from "@playwright/test"; 2 | 3 | export default class AlertActions { 4 | constructor(private page: Page) {} 5 | 6 | /** 7 | * Accept alert and return alert message 8 | * @param promptText A text to enter in prompt. It is optional for alerts. 9 | * @returns alert message 10 | */ 11 | public async accept(promptText?: string): Promise { 12 | return this.page.waitForEvent("dialog").then(async (dialog) => { 13 | if (dialog.type() === "prompt") { 14 | await dialog.accept(promptText); 15 | } else { 16 | await dialog.accept(); 17 | } 18 | return dialog.message().trim(); 19 | }); 20 | } 21 | 22 | /** 23 | * Dismiss alert and return alert message 24 | * @returns alert message 25 | */ 26 | public async dismiss(): Promise { 27 | return this.page.waitForEvent("dialog").then(async (d) => { 28 | await d.dismiss(); 29 | return d.message().trim(); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/framework/playwright/actions/CheckBoxActions.ts: -------------------------------------------------------------------------------- 1 | import { test, Locator } from "@playwright/test"; 2 | 3 | export default class CheckBoxActions { 4 | private locator: Locator; 5 | private description: string; 6 | 7 | /** 8 | * Sets the locator with description 9 | * @param locator 10 | * @param description 11 | * @returns 12 | */ 13 | public setLocator(locator: Locator, description: string): CheckBoxActions { 14 | this.locator = locator; 15 | this.description = description; 16 | return this; 17 | } 18 | 19 | /** 20 | * check checkbox or radio button 21 | */ 22 | public async check() { 23 | await test.step(`Check ${this.description}`, async () => { 24 | await this.locator.check(); 25 | }); 26 | return this; 27 | } 28 | 29 | /** 30 | * uncheck checkbox or radio button 31 | */ 32 | public async uncheck() { 33 | await await test.step(`Uncheck ${this.description}`, async () => { 34 | await this.locator.uncheck(); 35 | }); 36 | return this; 37 | } 38 | 39 | /** 40 | * Returns the status of the checkbox 41 | * @returns 42 | */ 43 | public async isChecked(): Promise { 44 | let status: boolean; 45 | await test.step(`Checking status of checkbox ${this.description}`, async () => { 46 | const element = this.locator; 47 | await element.waitFor(); 48 | status = await element.isChecked(); 49 | }, 50 | ); 51 | return status; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/framework/playwright/actions/DropDownActions.ts: -------------------------------------------------------------------------------- 1 | import { test, Locator } from "@playwright/test"; 2 | import HTMLConstants from "../../constants/HTMLConstants"; 3 | 4 | export default class DropDownActions { 5 | private locator: Locator; 6 | private description: string; 7 | 8 | /** 9 | * Sets the locator with description 10 | * @param locator 11 | * @param description 12 | * @returns 13 | */ 14 | public setLocator(locator: Locator, description: string): DropDownActions { 15 | this.locator = locator; 16 | this.description = description; 17 | return this; 18 | } 19 | 20 | /** 21 | * Select the dropdown by value 22 | * @param value 23 | * @returns 24 | */ 25 | public async selectByValue(value: string) { 26 | await test.step(`Selecting value ${value} from ${this.description}`, async () => { 27 | await this.locator.selectOption({ value }); 28 | }); 29 | return this; 30 | } 31 | 32 | /** 33 | * Select the dropdown by Label 34 | * @param text 35 | * @returns 36 | */ 37 | public async selectByVisibleText(text: string) { 38 | await test.step(`Selecting text ${text} from ${this.description}`, async () => { 39 | await this.locator.selectOption({ label: text }); 40 | }); 41 | return this; 42 | } 43 | 44 | /** 45 | * Select the dropdown by index 46 | * @param index 47 | * @returns 48 | */ 49 | public async selectByIndex(index: number) { 50 | await test.step(`Selecting index ${index} of ${this.description}`, async () => { 51 | await this.locator.selectOption({ index }); 52 | }); 53 | return this; 54 | } 55 | 56 | /** 57 | * Gets all the options in dropdown 58 | * @param index 59 | * @returns 60 | */ 61 | public async getAllOptions(): Promise { 62 | let selectOptions: string[]; 63 | await test.step( 64 | `Getting all the options of ${this.description}`, 65 | async () => { 66 | selectOptions = await this.locator.locator(HTMLConstants.OPTION).allTextContents(); 67 | }, 68 | ); 69 | return selectOptions; 70 | } 71 | 72 | /** 73 | * Gets all the selected options in dropdown 74 | * @param index 75 | * @returns 76 | */ 77 | public async getAllSelectedOptions(): Promise { 78 | let selectOptions: string[]; 79 | await test.step( 80 | `Getting all the selected options of ${this.description}`, 81 | async () => { 82 | selectOptions = await this.locator 83 | .locator(HTMLConstants.SELECTED_OPTION) 84 | .allTextContents(); 85 | }, 86 | ); 87 | return selectOptions; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/framework/playwright/actions/EditBoxActions.ts: -------------------------------------------------------------------------------- 1 | import { test, Locator } from "@playwright/test"; 2 | import UIElementActions from "./UIElementActions"; 3 | 4 | export default class EditBoxActions extends UIElementActions { 5 | /** 6 | * Sets the selector with description 7 | * @param selector 8 | * @param description 9 | * @returns 10 | */ 11 | public setEditBox(selector: string, description: string): EditBoxActions { 12 | this.setElement(selector, description); 13 | return this; 14 | } 15 | 16 | /** 17 | * Sets the locator with description 18 | * @param locator 19 | * @returns 20 | */ 21 | public setLocator(locator: Locator, description: string): EditBoxActions { 22 | super.setLocator(locator, description); 23 | return this; 24 | } 25 | 26 | /** 27 | * Clear and enter text 28 | * @param value 29 | * @returns 30 | */ 31 | public async fill(value: string) { 32 | await test.step(`Entering ${this.description} as ${value}`, async () => { 33 | await this.getLocator().fill(value); 34 | }); 35 | return this; 36 | } 37 | 38 | /** 39 | * Types the value to text field 40 | * @param value 41 | * @returns 42 | */ 43 | public async type(value: string) { 44 | await test.step(`Typing ${this.description} as ${value}`, async () => { 45 | await this.getLocator().type(value); 46 | }); 47 | return this; 48 | } 49 | 50 | /** 51 | * Enter text and hit tab key 52 | * @param value 53 | * @returns 54 | */ 55 | public async fillAndTab(value: string) { 56 | await test.step(`Entering ${this.description} as ${value} and Tab`, async () => { 57 | await this.getLocator().fill(value); 58 | await this.getLocator().press("Tab"); 59 | }); 60 | return this; 61 | } 62 | 63 | /** 64 | * Typing text and hit tab key 65 | * @param value 66 | * @returns 67 | */ 68 | public async typeAndTab(value: string) { 69 | await test.step(`Entering ${this.description} as ${value} and Tab`, async () => { 70 | await this.getLocator().type(value); 71 | await this.getLocator().press("Tab"); 72 | }); 73 | return this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/framework/playwright/actions/UIActions.ts: -------------------------------------------------------------------------------- 1 | import { test, Page } from "@playwright/test"; 2 | import CommonConstants from "../../constants/CommonConstants"; 3 | import HTMLConstants from "../../constants/HTMLConstants"; 4 | import AlertActions from "./AlertActions"; 5 | import CheckBoxActions from "./CheckBoxActions"; 6 | import DropDownActions from "./DropDownActions"; 7 | import EditBoxActions from "./EditBoxActions"; 8 | import UIElementActions from "./UIElementActions"; 9 | 10 | export default class UIActions { 11 | private elementAction: UIElementActions; 12 | private editBoxAction: EditBoxActions; 13 | private checkboxAction: CheckBoxActions; 14 | private dropdownAction: DropDownActions; 15 | private alertAction: AlertActions; 16 | 17 | constructor(private page: Page) { 18 | this.elementAction = new UIElementActions(page); 19 | this.editBoxAction = new EditBoxActions(page); 20 | this.checkboxAction = new CheckBoxActions(); 21 | this.dropdownAction = new DropDownActions(); 22 | this.alertAction = new AlertActions(this.page); 23 | } 24 | 25 | /** 26 | * Returns page object 27 | * @returns 28 | */ 29 | public getPage(): Page { 30 | return this.page; 31 | } 32 | 33 | /** 34 | * Sets the page 35 | * @param page 36 | */ 37 | public setPage(page: Page) { 38 | this.page = page; 39 | this.elementAction = new UIElementActions(page); 40 | this.editBoxAction = new EditBoxActions(page); 41 | this.alertAction = new AlertActions(this.page); 42 | } 43 | 44 | /** 45 | * Close page 46 | * @returns 47 | */ 48 | public closePage() { 49 | this.page.close(); 50 | } 51 | 52 | /** 53 | * Returns the instance of Alert 54 | * @returns 55 | */ 56 | public alert() { 57 | return this.alertAction; 58 | } 59 | 60 | /** 61 | * Returns the instance of editbox actions 62 | * @param selector 63 | * @param description 64 | * @returns 65 | */ 66 | public editBox(selector: string, description: string) { 67 | return this.editBoxAction.setEditBox(selector, description); 68 | } 69 | 70 | /** 71 | * Returns the instance of UIElements actions 72 | * @param selector 73 | * @param description 74 | * @returns 75 | */ 76 | public element(selector: string, description: string) { 77 | return this.elementAction.setElement(selector, description); 78 | } 79 | 80 | /** 81 | * Returns the instance of Dropdown actions 82 | * @param selector 83 | * @param description 84 | * @returns 85 | */ 86 | public dropdown(selector: string, description: string) { 87 | return this.dropdownAction.setLocator( 88 | this.elementAction.setElement(selector, description).getLocator(), 89 | description, 90 | ); 91 | } 92 | 93 | /** 94 | * Returns the instance of CheckBox actions 95 | * @param selector 96 | * @param description 97 | * @returns 98 | */ 99 | public checkbox(selector: string, description: string) { 100 | return this.checkboxAction.setLocator( 101 | this.elementAction.setElement(selector, description).getLocator(), 102 | description, 103 | ); 104 | } 105 | 106 | /** 107 | * Navigate to specified URL 108 | * @param URL 109 | * @param description 110 | */ 111 | public async goto(URL: string, description: string) { 112 | await test.step(`Navigate to ${description}`, async () => { 113 | await this.page.goto(URL); 114 | }); 115 | } 116 | 117 | /** 118 | * Navigate to previous URL 119 | * @param description 120 | */ 121 | public async goBack(description: string) { 122 | await test.step(`Go to the previous ${description}`, async () => { 123 | await this.page.goBack(); 124 | }); 125 | } 126 | 127 | /** 128 | * Navigate to next URL 129 | * @param description 130 | */ 131 | public async goForward(description: string) { 132 | await test.step(`Go to the next ${description}`, async () => { 133 | await this.page.goForward(); 134 | }); 135 | } 136 | 137 | /** 138 | * Page Refresh 139 | */ 140 | public async pageRefresh() { 141 | await test.step(`Page Refresh`, async () => { 142 | await this.page.reload(); 143 | }); 144 | } 145 | 146 | /** 147 | * Press a key on web page 148 | * @param key 149 | * @param description 150 | */ 151 | public async keyPress(key: string, description: string) { 152 | await test.step(`Pressing ${description}`, async () => { 153 | await this.page.keyboard.press(key); 154 | }); 155 | } 156 | 157 | /** 158 | * Waits for the main frame navigation and returns the main resource response. 159 | */ 160 | public async waitForNavigation() { 161 | await test.step(`Waiting for navigation`, async () => { 162 | await this.page.waitForNavigation(); 163 | }); 164 | } 165 | 166 | /** 167 | * Returns when the required load state has been reached. 168 | */ 169 | public async waitForLoadState() { 170 | await test.step(`Waiting for load event`, async () => { 171 | await this.page.waitForLoadState(); 172 | }); 173 | } 174 | 175 | /** 176 | * Returns when the required dom content is in loaded state. 177 | */ 178 | public async waitForDomContentLoaded() { 179 | await test.step(`Waiting for load event`, async () => { 180 | await this.page.waitForLoadState("domcontentloaded", { timeout: 5000 }); 181 | }); 182 | } 183 | 184 | /** 185 | * Gets the handle of the new window 186 | * @param selector 187 | * @param description 188 | */ 189 | public async switchToNewWindow( 190 | selector: string, 191 | description: string, 192 | ): Promise { 193 | let [newPage] = [this.page]; 194 | await test.step(`Opening ${description} Window`, async () => { 195 | [newPage] = await Promise.all([ 196 | this.page.context().waitForEvent("page"), 197 | await this.elementAction.setElement(selector, description).click(), 198 | ]); 199 | await newPage.waitForLoadState("domcontentloaded"); 200 | }); 201 | return newPage; 202 | } 203 | 204 | /** 205 | * Clicks the an element, accepts the alert and returns the alert message 206 | * @param selector selector of the element 207 | * @param description description of element 208 | * @returns alert message 209 | */ 210 | public async acceptAlertOnElementClick( 211 | selector: string, 212 | description: string, 213 | ): Promise { 214 | const message = this.alert().accept(); 215 | return this.handleAlert(selector, description, message); 216 | } 217 | 218 | /** 219 | * Clicks the an element, dismisses the alert and returns the alert message 220 | * @param selector selector of the element 221 | * @param description description of element 222 | * @returns alert message 223 | */ 224 | public async dismissAlertOnElementClick( 225 | selector: string, 226 | description: string, 227 | ): Promise { 228 | const message = this.alert().dismiss(); 229 | return this.handleAlert(selector, description, message); 230 | } 231 | 232 | /** 233 | * Clicks the an element, accepts the alert prompt and returns the alert message 234 | * @param selector selector of the element 235 | * @param description description of element 236 | * @param promptText A text to enter in prompt. 237 | * @returns alert message 238 | */ 239 | public async acceptPromptOnElementClick( 240 | selector: string, 241 | description: string, 242 | promptText: string, 243 | ): Promise { 244 | const message = this.alert().accept(promptText); 245 | return this.handleAlert(selector, description, message); 246 | } 247 | 248 | private async handleAlert( 249 | selector: string, 250 | description: string, 251 | message: Promise, 252 | ) { 253 | await this.elementAction.setElement(selector, description).click(); 254 | return message; 255 | } 256 | 257 | /** 258 | * Gets the page Title 259 | * @returns 260 | */ 261 | public async getPageTitle(): Promise { 262 | let title: string; 263 | await test.step(`Getting Page Title`, async () => { 264 | title = await this.page.title(); 265 | }); 266 | return title; 267 | } 268 | 269 | /** 270 | * Downloads the file and returns the downloaded file name 271 | * @param selector element that results in file download 272 | * @param description description of the element 273 | * @returns downloaded file name 274 | */ 275 | public async downloadFile(selector: string, description: string): Promise { 276 | let fileName: string; 277 | await test.step(`Downloading ${description} file`, async () => { 278 | const [download] = await Promise.all([ 279 | this.page.waitForEvent('download'), 280 | await this.page.locator(selector).click({ modifiers: ["Alt"] }), 281 | ]); 282 | fileName = download.suggestedFilename(); 283 | const filePath = `${CommonConstants.DOWNLOAD_PATH}${fileName}`; 284 | await download.saveAs(filePath); 285 | await download.delete(); 286 | }); 287 | return fileName; 288 | } 289 | /** 290 | * Pause the execution in seconds 291 | * @param sec 292 | */ 293 | public async pauseInSecs(sec: number) { 294 | // eslint-disable-next-line no-promise-executor-return 295 | return new Promise((resolve) => setTimeout(resolve, sec * CommonConstants.ONE_THOUSAND)); 296 | } 297 | 298 | /** 299 | * Wait For Page loading image to disappear 300 | * @param page 301 | */ 302 | public async waitForLoadingImage() { 303 | await test.step("Waiting for Loading Image to disappear", async () => { 304 | try { 305 | await this.page.locator(HTMLConstants.LOADING_IMAGE).waitFor({ 306 | state: "visible", 307 | timeout: CommonConstants.ONE_THOUSAND * CommonConstants.THREE, 308 | }); 309 | } catch (error) { 310 | // console.log("Loading Image was not displayed"); 311 | } 312 | await this.page.locator(HTMLConstants.LOADING_IMAGE).waitFor({ state: "hidden" }); 313 | await this.pauseInSecs(CommonConstants.HALF); 314 | }); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/framework/playwright/actions/UIElementActions.ts: -------------------------------------------------------------------------------- 1 | import { test, Locator, Page } from "@playwright/test"; 2 | 3 | export default class UIElementActions { 4 | protected locator: Locator; 5 | protected description: string; 6 | protected selector: string; 7 | 8 | constructor(private page: Page) { } 9 | 10 | /** 11 | * Returns the first locator 12 | * @returns 13 | */ 14 | public getLocator(): Locator { 15 | return this.locator.first(); 16 | } 17 | 18 | /** 19 | * Returns the all the locators 20 | * @returns 21 | */ 22 | public getLocators(): Locator { 23 | return this.locator; 24 | } 25 | 26 | /** 27 | * Sets the locator using the selector * 28 | * @param selector 29 | * @param description 30 | * @returns 31 | */ 32 | public setElement(selector: string, description: string): UIElementActions { 33 | this.selector = selector; 34 | this.locator = this.page.locator(this.selector); 35 | this.description = description; 36 | return this; 37 | } 38 | 39 | /** 40 | * Sets the locator with description 41 | * @param locator 42 | * @param description 43 | * @returns 44 | */ 45 | public setLocator(locator: Locator, description: string): UIElementActions { 46 | this.locator = locator; 47 | this.description = description; 48 | return this; 49 | } 50 | 51 | /** 52 | * Click on element 53 | * @returns 54 | */ 55 | public async click() { 56 | await test.step(`Clicking on ${this.description}`, async () => { 57 | await this.getLocator().click(); 58 | }); 59 | return this; 60 | } 61 | 62 | /** 63 | * Double click on element 64 | * @returns 65 | */ 66 | public async doubleClick() { 67 | await test.step(`Double Clicking ${this.description}`, async () => { 68 | await this.getLocator().dblclick(); 69 | }); 70 | return this; 71 | } 72 | 73 | /** 74 | * scroll element into view, unless it is completely visible 75 | * @returns 76 | */ 77 | public async scrollIntoView() { 78 | await test.step(`Scroll to element ${this.description}`, async () => { 79 | await this.getLocator().scrollIntoViewIfNeeded(); 80 | }); 81 | return this; 82 | } 83 | 84 | /** 85 | * Wait for element to be invisible 86 | * @returns 87 | */ 88 | public async waitTillInvisible() { 89 | await test.step(`Waiting for ${this.description} to be invisible`, async () => { 90 | await this.getLocator().waitFor({ state: "hidden" }); 91 | }); 92 | return this; 93 | } 94 | 95 | /** 96 | * wait for element not to be present in DOM 97 | * @returns 98 | */ 99 | public async waitTillDetached() { 100 | await test.step(`Wait for ${this.description} to be detached from DOM`, async () => { 101 | await this.getLocator().waitFor({ state: "detached" }); 102 | }); 103 | return this; 104 | } 105 | 106 | /** 107 | * wait for element to be visible 108 | * @param wait time for element is visible 109 | * @returns 110 | */ 111 | public async waitTillVisible(sec: number) { 112 | await test.step(`Wait for ${this.description} to be visible in DOM`, async () => { 113 | await this.getLocator().waitFor({ state: "visible", timeout: sec * 1000 }); 114 | }); 115 | return this; 116 | } 117 | 118 | /** 119 | * wait for element to be attached to DOM 120 | * @returns 121 | */ 122 | public async waitForPresent() { 123 | await test.step(`Wait for ${this.description} to attach to DOM`, async () => { 124 | await this.getLocator().waitFor({ state: "attached" }); 125 | }); 126 | return this; 127 | } 128 | 129 | /** 130 | * This method hovers over the element 131 | */ 132 | public async hover() { 133 | await test.step(`Hovering on ${this.description}`, async () => { 134 | await this.getLocator().hover(); 135 | }); 136 | return this; 137 | } 138 | 139 | /** 140 | * Returns input.value for or