├── .env ├── .eslintrc.js ├── .github └── workflows │ └── playwright.yml ├── .gitignore ├── .mocharc.js ├── .prettierrc ├── DockerFile ├── Notes ├── README.md ├── config.ts ├── environments ├── dev.env ├── local.env └── qe.env ├── logs ├── combined.log └── error.log ├── package-lock.json ├── package.json ├── playwright.config.ts ├── src ├── globals │ ├── global-setup.ts │ ├── global-teardown.ts │ └── healthcheck.setup.ts ├── helper │ ├── Helper.ts │ ├── api │ │ └── apiHelper.ts │ ├── index.ts │ ├── terminal │ │ └── SSHHelper.ts │ └── web │ │ └── webHelper.ts └── utils │ ├── config │ ├── artillery │ │ └── artillery-script.yml │ ├── azure │ │ ├── azure-pipeline.yml │ │ ├── playwright.config.ts │ │ └── playwright.service.config.ts │ └── env │ │ ├── dev.env │ │ ├── local.env │ │ └── qa.env │ ├── constants.ts │ ├── dataStore.js │ ├── dbUtils.ts │ ├── error │ ├── ApiErrorHandler.ts │ └── ErrorManager.ts │ ├── reader │ ├── config.json │ └── jsonReader.ts │ ├── report │ ├── CustomReporterConfig.ts │ ├── Logger.ts │ └── sendEmail.ts │ └── utils.ts ├── testConfig.ts ├── tests ├── api │ └── example │ │ ├── api.spec.ts │ │ ├── api1.spec.ts │ │ ├── dashboard.spec.ts │ │ ├── post.spec.ts │ │ └── user.spec.ts ├── e2e │ └── moatazeldebsy │ │ └── form.spec.ts ├── fixtures │ ├── customFixtures.ts │ └── test-options.ts ├── load │ └── BrowerLoadTest.spec.js ├── mobile │ └── example │ │ ├── healthcheckAndroid.test.ts │ │ ├── healthcheckiOS.test.ts │ │ └── mobiletest.spec.js ├── resources │ └── uiMap │ │ ├── moataeldebsy.json │ │ └── orangeHRM.json └── web │ ├── IntercpetFailingAPI.js │ ├── example │ ├── alert.spec.ts │ ├── assertion.spec.ts │ ├── autoWait.spec.ts │ ├── blockunnecessary.spec.ts │ ├── demo-fixture.spec.ts │ ├── demo-todo-app.spec.ts │ ├── downloadfile.spec.ts │ ├── frame.spec.ts │ ├── globalLogin.spec.ts │ ├── locator.spec.ts │ ├── networkTest.spec.ts │ ├── route.spec.ts │ ├── runTestParallel.spect.ts │ ├── runTestSerial.spec.ts │ ├── saveHarFile.spec.ts │ ├── screenshot.spec.ts │ └── uploadfile.spec.ts │ └── orangeHrm.spec.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | #Browser variable 2 | HEADLESS_BROWSER=false 3 | 4 | #Paralleism, reties, timeouts 5 | WORKERS=10 6 | RETRY_FAILED=2 7 | MAX_TEST_RUNTIME=60000 8 | 9 | #Environment 10 | BASE_URL='https://moatazeldebsy.github.io/test-automation-practices' 11 | 12 | #Credentials 13 | USER_NAME='ABC' 14 | PASSWORd='ABC' 15 | 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | plugins: ["@typescript-eslint", "import", "prettier"], 4 | extends: [ 5 | "airbnb-typescript/base", 6 | "prettier", 7 | "plugin:@typescript-eslint/recommended", 8 | "plugin:@typescript-eslint/stylistic", 9 | "plugin:import/typescript", 10 | "eslint:recommended", 11 | ], 12 | parser: "@typescript-eslint/parser", 13 | parserOptions: { 14 | project: "./tsconfig.json", 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: [ main, master ] 5 | pull_request: 6 | branches: [ main, master ] 7 | jobs: 8 | test: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: 18 16 | - name: Install dependencies 17 | run: npm ci 18 | - name: Install Playwright Browsers 19 | run: npx playwright install --with-deps 20 | - name: Run Playwright tests 21 | run: npx playwright test 22 | - uses: actions/upload-artifact@v3 23 | if: always() 24 | with: 25 | name: playwright-report 26 | path: playwright-report/ 27 | retention-days: 30 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /blob-report/ 5 | /playwright/.cache/ 6 | logs/info.log 7 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | timeout: 20000, // define a 20 seconds timeout for all tests 3 | }; 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "userTabs": false, 5 | "semi": true, 6 | "singleQuote": false, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxSingleQuote": false, 10 | "arrowParens": "always", 11 | "proseWrap": "never", 12 | "htmlWhitespaceSensitivity": "strict", 13 | "endOfLine": "lf", 14 | "overrides": [ 15 | { 16 | "files": "*.json", 17 | "options": { 18 | "printWidth": 80, 19 | "singleQuote": false 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /DockerFile: -------------------------------------------------------------------------------- 1 | # Use the official Playwright Docker image 2 | FROM mcr.microsoft.com/playwright:focal 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app 6 | 7 | # Copy the package.json and package-lock.json (if available) 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy the entire project to the working directory 14 | COPY . . 15 | 16 | # Install Playwright browsers (Chromium, Firefox, WebKit) 17 | RUN npx playwright install --with-deps 18 | 19 | # Command to run the Playwright tests 20 | CMD ["npx", "playwright", "test"] -------------------------------------------------------------------------------- /Notes: -------------------------------------------------------------------------------- 1 | import { linkSync } from 'fs'; 2 | 3 | /** 4 | * Extends Playwright locator with a helper function to find, store, and manage elements using different locator strategies 5 | * @param {import('@playwright/test').Page} page - Playwright page instance 6 | * @returns {Object} Helper object with locator methods 7 | */ 8 | function createLocatorHelper(page) { 9 | // Internal storage for locators 10 | const locatorStorage = new Map(); 11 | 12 | return { 13 | /** 14 | * Finds a single element using the specified locator strategy 15 | * Checks if element is displayed and enabled 16 | * Returns null if element is not found 17 | * @param {string} type - Locator type ('css', 'xpath', 'text', 'testid', 'role', 'label') 18 | * @param {string} value - Locator value 19 | * @param {Object} [options] - Optional locator options 20 | * @returns {Promise<{locator: import('@playwright/test').Locator, isDisplayed: boolean, isEnabled: boolean} | null>} Playwright locator with status or null 21 | */ 22 | async findElement(type, value, options = {}) { 23 | try { 24 | let locator; 25 | switch (type.toLowerCase()) { 26 | case 'css': 27 | locator = page.locator(value, options); 28 | break; 29 | case 'xpath': 30 | locator = page.locator(`xpath=${value}`, options); 31 | break; 32 | case 'text': 33 | locator = page.getByText(value, options); 34 | break; 35 | case 'testid': 36 | locator = page.getByTestId(value, options); 37 | break; 38 | case 'role': 39 | locator = page.getByRole(value, options); 40 | break; 41 | case 'label': 42 | locator = page.getByLabel(value, options); 43 | break; 44 | default: 45 | console.warn(`Unsupported locator type: ${type}`); 46 | return null; 47 | } 48 | 49 | // Check if element exists 50 | const count = await locator.count(); 51 | if (count === 0) { 52 | console.warn(`Element not found with ${type} locator: ${value}`); 53 | return null; 54 | } 55 | 56 | // Check visibility and enabled status 57 | const isDisplayed = await locator.isVisible(); 58 | const isEnabled = await locator.isEnabled(); 59 | 60 | return { 61 | locator, 62 | isDisplayed, 63 | isEnabled 64 | }; 65 | } catch (error) { 66 | console.warn(`Error while finding element with ${type} locator: ${value}. Error: ${error.message}`); 67 | return null; 68 | } 69 | }, 70 | 71 | /** 72 | * Finds all elements using the specified locator strategy 73 | * @param {string} type - Locator type 74 | * @param {string} value - Locator value 75 | * @param {Object} [options] - Optional locator options 76 | * @returns {Promise} Playwright locator for all matching elements 77 | */ 78 | async findAllElements(type, value, options = {}) { 79 | try { 80 | const result = await this.findElement(type, value, options); 81 | if (!result) return []; 82 | return result.locator.all(); 83 | } catch (error) { 84 | console.warn(`Failed to find all elements with ${type} locator: ${value}. Error: ${error.message}`); 85 | return []; 86 | } 87 | }, 88 | 89 | /** 90 | * Waits for an element to be visible using the specified locator strategy 91 | * @param {string} type - Locator type 92 | * @param {string} value - Locator value 93 | * @param {Object} [options] - Optional locator options with timeout 94 | * @returns {Promise} Playwright locator 95 | */ 96 | async waitForElement(type, value, options = { timeout: 5000 }) { 97 | try { 98 | const result = await this.findElement(type, value); 99 | if (!result) { 100 | throw new Error(`Element not found with ${type} locator: ${value}`); 101 | } 102 | await result.locator.waitFor({ state: 'visible', ...options }); 103 | return result.locator; 104 | } catch (error) { 105 | throw new Error(`Failed to wait for element with ${type} locator: ${value}. Error: ${error.message}`); 106 | } 107 | }, 108 | 109 | /** 110 | * Stores a locator with a key for later use 111 | * @param {string} key - Unique key to identify the locator 112 | * @param {string} type - Locator type 113 | * @param {string} value - Locator value 114 | * @param {Object} [options] - Optional locator options 115 | * @returns {Promise} 116 | */ 117 | async storeLocator(key, type, value, options = {}) { 118 | try { 119 | const result = await this.findElement(type, value, options); 120 | if (!result) { 121 | throw new Error(`Cannot store null locator with key ${key}`); 122 | } 123 | locatorStorage.set(key, { type, value, options, locator: result.locator }); 124 | } catch (error) { 125 | throw new Error(`Failed to store locator with key ${key}. Error: ${error.message}`); 126 | } 127 | }, 128 | 129 | /** 130 | * Retrieves a stored locator by key 131 | * @param {string} key - Key used to store the locator 132 | * @returns {Promise} Stored Playwright locator 133 | */ 134 | async getStoredLocator(key) { 135 | const stored = locatorStorage.get(key); 136 | if (!stored) { 137 | throw new Error(`No locator found with key: ${key}`); 138 | } 139 | // Recreate locator to ensure it's fresh 140 | const result = await this.findElement(stored.type, stored.value, stored.options); 141 | return result ? result.locator : null; 142 | }, 143 | 144 | /** 145 | * Clears all stored locators 146 | */ 147 | clearStoredLocators() { 148 | locatorStorage.clear(); 149 | } 150 | }; 151 | } 152 | 153 | // Example usage: 154 | /* 155 | const { test, expect } = require('@playwright/test'); 156 | const { createLocatorHelper } = require('./playwright_locator_helper'); 157 | 158 | test('example test with enhanced findElement', async ({ page }) => { 159 | const locatorHelper = createLocatorHelper(page); 160 | await page.goto('https://example.com'); 161 | 162 | // Example with CSS locator 163 | const buttonResult = await locatorHelper.findElement('css', 'button.submit'); 164 | if (buttonResult) { 165 | console.log(`Button is displayed: ${buttonResult.isDisplayed}`); 166 | console.log(`Button is enabled: ${buttonResult.isEnabled}`); 167 | if (buttonResult.isDisplayed && buttonResult.isEnabled) { 168 | await buttonResult.locator.click(); 169 | } 170 | } else { 171 | console.log('Button not found'); 172 | } 173 | }); 174 | */ 175 | 176 | module.exports = { createLocatorHelper }; 177 | 178 | // Example usage: 179 | /* 180 | const { test, expect } = require('@playwright/test'); 181 | const { createLocatorHelper } = require('./playwright_locator_helper'); 182 | 183 | test('example test with stored locators', async ({ page }) => { 184 | const locatorHelper = createLocatorHelper(page); 185 | 186 | await page.goto('https://example.com'); 187 | 188 | // Store locators 189 | await locatorHelper.storeLocator('submitButton', 'css', 'button[type="submit"]'); 190 | await locatorHelper.storeLocator('usernameInput', 'css', '#username'); 191 | 192 | // Use stored locators 193 | const usernameInput = await locatorHelper.getStoredLocator('usernameInput'); 194 | await usernameInput.fill('testuser'); 195 | 196 | const submitButton = await locatorHelper.getStoredLocator('submitButton'); 197 | await submitButton.click(); 198 | 199 | // Clean up 200 | locatorHelper.clearStoredLocators(); 201 | }); 202 | */ 203 | 204 | module.exports = { createLocatorHelper }; 205 | 206 | Usage Example 207 | 208 | /** 209 | * Example usage of findElement function for different locator types 210 | * Assumes a webpage with common elements (e.g., a login form) 211 | */ 212 | const { test, expect } = require('@playwright/test'); 213 | const { createLocatorHelper } = require('./playwright_locator_helper'); 214 | 215 | test.describe('Locator Examples', () => { 216 | test.beforeEach(async ({ page }) => { 217 | // Navigate to a sample page (replace with your test URL) 218 | await page.goto('https://example.com/login'); 219 | }); 220 | 221 | test('Use CSS locator', async ({ page }) => { 222 | const locatorHelper = createLocatorHelper(page); 223 | 224 | // Find an input field by CSS selector 225 | const usernameInput = await locatorHelper.findElement('css', '#username'); 226 | await usernameInput.fill('testuser'); 227 | 228 | // Verify the input value 229 | await expect(usernameInput).toHaveValue('testuser'); 230 | }); 231 | 232 | test('Use XPath locator', async ({ page }) => { 233 | const locatorHelper = createLocatorHelper(page); 234 | 235 | // Find a submit button using XPath 236 | const submitButton = await locatorHelper.findElement('xpath', '//button[@type="submit"]'); 237 | await submitButton.click(); 238 | 239 | // Verify button is clickable (enabled) 240 | await expect(submitButton).toBeEnabled(); 241 | }); 242 | 243 | test('Use Text locator', async ({ page }) => { 244 | const locatorHelper = createLocatorHelper(page); 245 | 246 | // Find a link by its text content 247 | const forgotPasswordLink = await locatorHelper.findElement('text', 'Forgot Password'); 248 | await forgotPasswordLink.click(); 249 | 250 | // Verify navigation or element visibility 251 | await expect(page).toHaveURL(/forgot-password/); 252 | }); 253 | 254 | test('Use TestID locator', async ({ page }) => { 255 | const locatorHelper = createLocatorHelper(page); 256 | 257 | // Find an element by data-testid attribute 258 | const loginForm = await locatorHelper.findElement('testid', 'login-form'); 259 | 260 | // Verify the element is visible 261 | await expect(loginForm).toBeVisible(); 262 | }); 263 | 264 | test('Use Role locator', async ({ page }) => { 265 | const locatorHelper = createLocatorHelper(page); 266 | 267 | // Find a button by its role and name 268 | const loginButton = await locatorHelper.findElement('role', 'button', { name: 'Log In' }); 269 | await loginButton.click(); 270 | 271 | // Verify button interaction 272 | await expect(loginButton).toHaveAttribute('aria-pressed', 'true'); 273 | }); 274 | 275 | test('Use Label locator', async ({ page }) => { 276 | const locatorHelper = createLocatorHelper(page); 277 | 278 | // Find an input by its associated label 279 | const passwordInput = await locatorHelper.findElement('label', 'Password'); 280 | await passwordInput.fill('secure123'); 281 | 282 | // Verify input value 283 | await expect(passwordInput).toHaveValue('secure123'); 284 | }); 285 | }); 286 | 287 | ## phaatak package 288 | https://www.npmjs.com/package/phaatak Try this npm package for 289 | - managing multiple tabs 290 | - handling new windows 291 | - Saving and loading profiles 292 | - Stop the browser 293 | - close All tabs 294 | 295 | 296 | Example code - https://github.com/ghostinlinux/phaatak-playwright-demo/ 297 | 298 | ## Visual testing 299 | https://medium.com/@adam_pajda/playwright-visual-tests-with-git-lfs-and-docker-d537ddd6e86a 300 | 301 | Verify all broken links 302 | https://medium.com/@thananjayan1988/automatic-broken-link-detection-with-playwright-a241a6f41973 303 | 304 | How to link existing chrome browser in playwright run 305 | https://dev.to/mxschmitt/running-playwright-codegen-with-existing-chromium-profiles-5g7k 306 | 307 | Mobile App testing 308 | https://medium.com/@testerstalk/mobile-app-testing-with-playwright-a-step-by-step-guide-in-detail-cabf5def30a9 309 | 310 | Excel data comparision using Playwright 311 | https://medium.com/@testerstalk/how-to-download-validate-excel-file-in-playwright-b8acbb19a4e8 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Playwright-Framework-Template

2 | 3 | ## Introduction 4 | 5 | Playwright-Framework-Template - This project is based on Microsoft Playwright, Appium, Artillery which enables reliable end-to-end testing, Web testing, API testing, Mobile testing, load testing. 6 | 7 | _☝ If you liked the project, please give a ⭐ on [GitHub](https://github.com/abhaybharti/playwright-framework-template). It will motivate me to add more such project._ 8 | 9 | \_☝ If you want new feature to be added or you believe you've encountered a bug [Open an issue] (https://github.com/abhaybharti/playwright-framework-template/issues) . 10 | 11 | ## Features 12 | 13 | - Easy to Configure 14 | - Auto wait for all elements & checks 15 | - Generate HTML report 16 | - Generate detailed trace file which helps to debug 17 | - Generate snapshot for each step 18 | - Record video for test case execution 19 | - Support Web automation with support for chrome, Edge, Firefox and Safari 20 | - Support Mobile automation with support for Android native, web and hybrid apps automation 21 | - Support iOS native, web and hybrid apps automation 22 | - Support execution of Web tests on Microsoft Azure (can scale on demand) 23 | - Support API testing (GET, POST, PUT, DELETE HTTP methods) 24 | - Dynamic data handling using external JSON files 25 | - Support taking screenshots 26 | - Run functional test for load testing using Artillery 27 | - Support Serial and Parallel execution 28 | - Environment configuration using .env files 29 | 30 | ## Tech Stack/Libraries Used 31 | 32 | - [PlayWright](https://playwright.dev/) - for web automation 33 | - [PlayWright](https://playwright.dev/) - for Api automation 34 | - [Artillery](https://www.artillery.io/) - for load testing 35 | - [Appium](https://appium.io/docs/en/2.4/) - for mobile app automation 36 | - [ESLint](https://eslint.org/) - pinpoint issues and guide you in rectifying potential problems in both JavaScript and TypeScript. 37 | - [Prettier](https://prettier.io/) - for formatting code & maintain consistent style throughout codebase 38 | - [Dotenv](https://www.dotenv.org/) - to load environment variables from .env file 39 | - [Secrets](https://github.com/du5rte/secrets) - to load secrets from AWS Secrets Manager 40 | 41 | ## Getting Started 42 | 43 | ## Project Structure 44 | 45 | - `src` 46 | - `helper` 47 | - `/api/apiHelper.ts` contains functions for making HTTP requests 48 | - `/load/loadHelper.ts` contains functions generating load on UI/Api 49 | - `/mobile/mobileHelper.ts` contains functions for interacting with mobile apps 50 | - `/web/webHelper.ts` contains functions for interacting with browser 51 | - `tests` contains utility functions 52 | - `api` place to write api tests 53 | - `example` contains example api tests using this framework 54 | - `web` place to write web tests 55 | - `example` contains example api tests using this framework 56 | - `load` place to write load tests 57 | - `example` contains example api tests using this framework 58 | - `mobile` place to write mobile tests 59 | - `example` contains example api tests using this framework 60 | - `utils` contains utility functions 61 | - `config` contains config files 62 | - `report` contains report function files 63 | - `dataStore.js` acts as a in-memory data store. It is used to save data that needs to be shared between different test case 64 | - `test-results` contains test results 65 | - `har` contains har file generated by playwright tests 66 | - `logs` contains logs 67 | 68 | ### Prerequisite 69 | 70 | - `nodejs`: Download and install Node JS from 71 | > `https://nodejs.org/en/download` 72 | - `Visual Studio Code/Aqua/IntelliJ`: Download and install code editor 73 | 74 | ### Installation 75 | 76 | - clone the repo using below URL 77 | 78 | > `https://github.com/abhaybharti/playwright-framework-template.git` 79 | 80 | - Navigate to folder and install npm packages using: 81 | 82 | > `npm install` 83 | 84 | - For first time installation use below command to download required browsers: 85 | 86 | > `npx playwright install` 87 | 88 | - In case you want to do fresh setup of playwright 89 | - Create a folder & run command `npm init playwright@latest` 90 | - select `TypeScript` & select default for other options 91 | 92 | ## Usage 93 | 94 | - For browser configuration, change required parameters in playwright.config.ts 95 | - To run your suite on MS Azure, copy the below code in `azure-pipeline.yml` and `playwright.service.config.ts` to root folder, update following key & run your suite 96 | - PLAYWRIGHT_SERVICE_ACCESS_TOKEN 97 | - PLAYWRIGHT_SERVICE_URL=XXX 98 | 99 | ## How to generate Playwright code (Playwright Test Generator) 100 | 101 | - run command `npx playwright codegen` 102 | - Browser gets opened & navigate to web app & perform test actions 103 | 104 | Playwright test generator generates tests and pick locator for you. It uses role,text and test ID locators. 105 | 106 | To pick a locator, run the `codegen` command followed by URL, `npx playwright codegen https://opensource-demo.orangehrmlive.com/web/index.php/auth/login` 107 | 108 | ## Writing Tests 109 | 110 | - Create test files in `src/tests` folder 111 | 112 | ## Sample Test 113 | 114 | ### Unit/Integration Testing 115 | 116 | ### Sample Web Test 117 | 118 | > Note: Refer to [sample-web-test](https://github.com/abhaybharti/playwright-framework-template/tree/master/src/tests/web/example) 119 | 120 | Pls go through different `\*.ts` file, which has tests example for different purpose. 121 | 122 | #### Locator Example 123 | 124 | > Note: Refer to [sample-web-test](https://github.com/abhaybharti/playwright-framework-template/tree/master/src/tests/web/example/locator.spec.ts) 125 | 126 | ### Sample Web Load Test 127 | 128 | ### Sample Api Test 129 | 130 | > Note: Refer to [sample-api-test](https://github.com/abhaybharti/playwright-framework-template/tree/master/src/tests/api/example) 131 | 132 | Pls go through different `\*.ts` files, which has tests example for different api tests. 133 | 134 | ### Sample API Load Test 135 | 136 | ### Sample Mobile Test 137 | 138 | ## Run Test 139 | 140 | ### To Run Web Test 141 | 142 | - `npx playwright test (name-of-file.spec.ts) --headed` to run test in ui mode 143 | - `npx playwright test (name-of-file.spec.ts) --headed --config=playwright.config.chrome.ts` to run test in ui mode on chrome browser 144 | - `npx playwright test (name-of-file.spec.ts) --headed --config=playwright.config.firefox.ts` to run test in ui mode on firefox browser 145 | - `npx playwright test (name-of-file.spec.ts) --headed --config=playwright.config.edge.ts` to run test in ui mode on edge browser 146 | 147 | ### To Run Api Test 148 | 149 | ### To Run Mobile Test 150 | 151 | ### To Run Test Multiple Times in Parallel 152 | 153 | `npx playwright test --workers=5 --headed --repeat-each=5` 154 | 155 | - This will run test 5 times, at a time 5 instance will run. `--workers=5` will run 5 instances 156 | 157 | ### To Run Test Multiple Times in Sequence 158 | 159 | `npx playwright test --workers=1 --headed --repeat-each=5` 160 | 161 | - This will run test 5 times, at a time single instance will run, `--repeat-each=5` will run 5 times 162 | 163 | ### To Run Load Test using Artillery & PlayWright Suite 164 | 165 | `artillery run artillery-script.yml --output report.json` 166 | 167 | ### Grouping and Organizing Test Suite in PlayWright 168 | 169 | `npx playwright test --grep @smoke` This will run only test tagged as @smoke 170 | 171 | ## Debug And Analyze 172 | 173 | ### View Trace Result of PlayWright Execution 174 | 175 | - Open `https://trace.playwright.dev` 176 | - Upload `trace.zip` file to above site, it will show trace details 177 | 178 | ### Run test in debug mode 179 | 180 | `npx playwright test UIBasictest.spec.js --debug` 181 | 182 | This will start running script in debug mode & open PlayWright inspector 183 | 184 | ### How to generate load test report using artillery + Playwright 185 | 186 | `artillery report report.json --output report.html` 187 | 188 | #### Run & Generate Report 189 | 190 | ### Best Practices in Test Authoring: 191 | 192 | - `Create isolated test cases`: Each test case should be independent. 193 | - `Write Meaningful Test Case Titles`: Make your test case titles descriptive and meaningful. 194 | - `Follow the AAA (Arrange-Act-Assert) Pattern`: Align your Test-Driven Development (TDD) approach with the clarity of Arrange, Act, and Assert. 195 | - `Maintain Cleanliness`: Separate additional logic from tests for a tidy and focused codebase. 196 | 197 | ## GitHub Actions - created workflow to run test 198 | 199 | ## Contributing 200 | 201 | We love our contributors! Here's how you can contribute: 202 | 203 | - [Open an issue](https://github.com/abhaybharti/playwright-framework-template/issues) if you believe you've encountered a bug. 204 | - Make a [pull request](https://github.com/abhaybharti/playwright-framework-template/pulls) to add new features/make quality-of-life improvements/fix bugs. 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | Alternative: Use ts-node (Run Without Compilation) 213 | To execute TypeScript files directly without manual compilation, install ts-node: 214 | 215 | sh 216 | Copy 217 | Edit 218 | npm install -g ts-node 219 | Then, run your TypeScript file directly: 220 | 221 | sh 222 | Copy 223 | Edit 224 | ts-node app.ts 225 | 226 | App to run UI test- 227 | https://moatazeldebsy.github.io/test-automation-practices/#/ 228 | 229 | https://github.com/AtulKrSharma/PlaywrightInjectTagsWithCaching/blob/main/playwright.config.ts -------------------------------------------------------------------------------- /config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv" 2 | import Joi from "joi" 3 | import {z} from "zod" 4 | 5 | //Load enviornment variables from .env 6 | dotenv.config() 7 | 8 | 9 | //Define the schema for validation 10 | // const envSchema = Joi.object({ 11 | // //Enironment 12 | // BASE_URL : Joi.string().uri().required(), 13 | // USER_NAME: Joi.string().required(), 14 | // PASSWORD: Joi.string().required(), 15 | 16 | // //Playwright settings 17 | // WORKERS : Joi.number().integer().min(1).required(), 18 | // RETRY_FAILED : Joi.number().integer().min(0).required(), 19 | // MAX_TEST_RUNTIME : Joi.number().integer().min(1000).required(), 20 | // HEADLESS_BROWSER : Joi.boolean().required() 21 | // }).unknown(true) //Allow other unknow enviornment variables 22 | 23 | // Define the schema for validation using Zod 24 | // const envSchema = z.object({ 25 | // // Environment 26 | // BASE_URL: z.string().url().nonempty(), 27 | // USER_NAME: z.string().nonempty(), 28 | // PASSWORD: z.string().nonempty(), 29 | 30 | // // Playwright settings 31 | // WORKERS: z.number().int().min(1), 32 | // RETRY_FAILED: z.number().int().min(0), 33 | // MAX_TEST_RUNTIME: z.number().int().min(1000), 34 | // HEADLESS_BROWSER: z.boolean() 35 | // }).passthrough(); //Allow unknonn environment variables 36 | 37 | //.strict() //Allow only known environment variables 38 | 39 | //To Do - Convert above validation schema in zod 40 | 41 | 42 | //Validate environment variables 43 | // const envVars = envSchema.validate(process.env,{allowUnknown:true,abortEarly:false}) 44 | // const envVars = envSchema.safeParse(process.env) 45 | 46 | // if (envVars.error){ 47 | // throw new Error(`Enviornment validation error : ${envVars.error.message}`) 48 | // } 49 | 50 | //Config class with validate enviornment variables 51 | export class Config{ 52 | 53 | static readonly BASE_URL :string = process.env.BASE_URL || ''; 54 | static readonly USER_NAME :string = process.env.USER_NAME || ''; 55 | static readonly PASSWORD :string = process.env.PASSWORD || ''; 56 | 57 | static readonly WORKERS :number = parseInt(process.env.WORKERS || '1'); 58 | static readonly RETRY_FAILED :number = parseInt(process.env.RETRY_FAILED || '0'); 59 | static readonly MAX_TEST_RUNTIME :number = parseInt(process.env.MAX_TEST_RUNTIME || '1000'); 60 | static readonly HEADLESS_BROWSER :boolean = process.env.HEADLESS_BROWSER === 'true'; 61 | } 62 | 63 | export const MCP_CONFIG = { 64 | mcpServers: { 65 | playwright: { 66 | url: "http://localhost:8931/sse" 67 | } 68 | } 69 | }; -------------------------------------------------------------------------------- /environments/dev.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/environments/dev.env -------------------------------------------------------------------------------- /environments/local.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/environments/local.env -------------------------------------------------------------------------------- /environments/qe.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/environments/qe.env -------------------------------------------------------------------------------- /logs/combined.log: -------------------------------------------------------------------------------- 1 | {"level":"info","message":"Test Suite Started : , 0 tests","timestamp":"2025-03-12T14:44:11.928Z"} 2 | {"level":"error","message":"TestError : Fixture \"api\" has unknown parameter \"apiRequest\".","timestamp":"2025-03-12T14:44:11.936Z"} 3 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"browserContext\".","timestamp":"2025-03-12T14:44:11.937Z"} 4 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-03-12T14:51:04.602Z"} 5 | {"level":"info","message":"Test Case Started : Verify homepage","timestamp":"2025-03-12T14:51:07.354Z"} 6 | {"level":"info","message":"Test Case Completed : Verify homepage Status : failed","timestamp":"2025-03-12T14:51:17.087Z"} 7 | {"level":"info","message":"Test Case Started : Verify homepage","timestamp":"2025-03-12T14:51:21.134Z"} 8 | {"level":"info","message":"Test Case Completed : Verify homepage Status : failed","timestamp":"2025-03-12T14:51:29.828Z"} 9 | {"level":"info","message":"Test Case Started : Verify homepage","timestamp":"2025-03-12T14:51:32.532Z"} 10 | {"level":"info","message":"Test Case Completed : Verify homepage Status : failed","timestamp":"2025-03-12T14:51:40.528Z"} 11 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-03-12T14:56:35.496Z"} 12 | {"level":"info","message":"Test Case Started : Verify homepage","timestamp":"2025-03-12T14:56:38.069Z"} 13 | {"level":"info","message":"Test Case Completed : Verify homepage Status : passed","timestamp":"2025-03-12T14:56:45.668Z"} 14 | {"level":"info","message":"Test Suite Started : , 0 tests","timestamp":"2025-04-27T16:51:57.567Z"} 15 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:51:58.696Z"} 16 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:51:58.705Z"} 17 | {"level":"info","message":"Test Suite Started : , 0 tests","timestamp":"2025-04-27T16:52:03.098Z"} 18 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:52:03.796Z"} 19 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:52:03.958Z"} 20 | {"level":"info","message":"Test Suite Started : , 0 tests","timestamp":"2025-04-27T16:53:08.241Z"} 21 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:08.475Z"} 22 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:08.498Z"} 23 | {"level":"info","message":"Test Suite Started : , 0 tests","timestamp":"2025-04-27T16:53:12.564Z"} 24 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:12.604Z"} 25 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:12.630Z"} 26 | {"level":"info","message":"Test Suite Started : , 0 tests","timestamp":"2025-04-27T16:54:52.217Z"} 27 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:54:52.227Z"} 28 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:54:52.233Z"} 29 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-04-27T17:02:52.652Z"} 30 | {"level":"info","message":"Test Case Started : Launch twitter and take screenshot @form","timestamp":"2025-04-27T17:02:59.727Z"} 31 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-04-27T17:06:41.824Z"} 32 | {"level":"info","message":"Test Case Started : Launch twitter and take screenshot @form","timestamp":"2025-04-27T17:06:48.265Z"} 33 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-04-27T17:07:44.451Z"} 34 | {"level":"info","message":"Test Case Started : Launch twitter and take screenshot @form","timestamp":"2025-04-27T17:07:52.065Z"} 35 | {"level":"info","message":"Test Suite Started : , 0 tests","timestamp":"2025-04-27T17:15:24.846Z"} 36 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-04-27T17:16:10.475Z"} 37 | {"level":"info","message":"Test Case Started : Launch twitter and take screenshot @form","timestamp":"2025-04-27T17:16:20.281Z"} 38 | {"level":"info","message":"Test Case Completed : Launch twitter and take screenshot @form Status : interrupted","timestamp":"2025-04-27T17:19:37.923Z"} 39 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-04-27T17:20:00.365Z"} 40 | {"level":"info","message":"Test Case Started : Launch twitter and take screenshot @form","timestamp":"2025-04-27T17:20:07.844Z"} 41 | {"level":"info","message":"Test Case Completed : Launch twitter and take screenshot @form Status : interrupted","timestamp":"2025-04-27T17:28:41.027Z"} 42 | {"level":"info","message":"Test Suite Started : , 1 tests","timestamp":"2025-04-27T17:29:06.243Z"} 43 | {"level":"info","message":"Test Case Started : Launch twitter and take screenshot @form","timestamp":"2025-04-27T17:29:12.396Z"} 44 | {"level":"info","message":"Test Case Completed : Launch twitter and take screenshot @form Status : passed","timestamp":"2025-04-27T17:30:39.967Z"} 45 | -------------------------------------------------------------------------------- /logs/error.log: -------------------------------------------------------------------------------- 1 | {"level":"error","message":"TestError : Fixture \"api\" has unknown parameter \"apiRequest\".","timestamp":"2025-03-12T14:44:11.936Z"} 2 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"browserContext\".","timestamp":"2025-03-12T14:44:11.937Z"} 3 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:51:58.696Z"} 4 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:51:58.705Z"} 5 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:52:03.796Z"} 6 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:52:03.958Z"} 7 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:08.475Z"} 8 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:08.498Z"} 9 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:12.604Z"} 10 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:53:12.630Z"} 11 | {"level":"error","message":"TestError : Fixture \"json\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:54:52.227Z"} 12 | {"level":"error","message":"TestError : Fixture \"web\" has unknown parameter \"config\".","timestamp":"2025-04-27T16:54:52.233Z"} 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright-framework-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo %NODE_ENV% && npx playwright test --headed --config=playwright.config.ts", 8 | "test:local": "cross-env NODE_ENV='local' npm run test", 9 | "test:dev": "cross-env NODE_ENV='dev' npm run test", 10 | "test:sit": "cross-env NODE_ENV='sit' npm run test", 11 | "mcp": "ts-node mcp-server.ts", 12 | "test:mcp": "playwright test", 13 | "ci":"npx playwright test --project=chromium --workers=1", 14 | "flaky":"npx playwright test --project=chromium --repeat-each=20", 15 | "sanity":"npx playwright test --grep @Sanity --workers=1", 16 | "smoke":"npx playwright test --grep @Smoke --workers=1", 17 | "api":"npx playwright test --grep @Api --workers=1", 18 | "regresion":"npx playwright test --grep @Regressoin --workers=1" 19 | 20 | }, 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "devDependencies": { 25 | "@playwright/test": "^1.51.0", 26 | "@types/node": "^20.17.24", 27 | "@types/ssh2": "^1.15.4", 28 | "@typescript-eslint/eslint-plugin": "^6.21.0", 29 | "@typescript-eslint/parser": "^6.21.0", 30 | "eslint": "^8.57.0", 31 | "eslint-config-airbnb-base": "^15.0.0", 32 | "eslint-config-prettier": "^9.1.0", 33 | "eslint-plugin-import": "^2.25.2", 34 | "eslint-plugin-prettier": "^5.2.1", 35 | "playwright-ajv-schema-validator": "^1.0.2", 36 | "prettier": "^3.3.3", 37 | "pw-api-plugin": "^2.0.2", 38 | "ts-node": "^10.9.2", 39 | "typescript": "^5.5.4" 40 | }, 41 | "dependencies": { 42 | "@types/mocha": "^10.0.6", 43 | "@types/webdriverio": "^5.0.0", 44 | "ajv": "^8.12.0", 45 | "appium": "^2.4.1", 46 | "dotenv": "^16.5.0", 47 | "joi": "^17.13.3", 48 | "mocha": "^10.2.0", 49 | "moment": "^2.30.1", 50 | "nodejs-nodemailer-outlook": "^1.2.4", 51 | "ssh2": "^1.16.0", 52 | "webdriverio": "^8.28.0", 53 | "winston": "^3.11.0", 54 | "zod": "^3.24.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | import * as dotenv from "dotenv"; 3 | import { Config } from "./config"; 4 | import { on } from "events"; 5 | //import OrtoniReport from "ortoni-report"; 6 | 7 | switch (process.env.NODE_ENV) { 8 | case "local": 9 | dotenv.config({ path: "./environments/local.env" }); 10 | break; 11 | case "dev": 12 | dotenv.config({ path: "./environments/local.env" }); 13 | break; 14 | case "qa": 15 | dotenv.config({ path: "./environments/qa.env" }); 16 | break; 17 | case "prod": 18 | dotenv.config({ path: "./environments/prod.env" }); 19 | 20 | break; 21 | 22 | default: 23 | break; 24 | } 25 | export default defineConfig({ 26 | testDir: "./tests", 27 | testMatch: ["**/*.ts", "**/*.js"], 28 | timeout: 180 * 1000, 29 | expect: { timeout: 180 * 1000 }, 30 | /* Run tests in files in parallel */ 31 | fullyParallel: true, 32 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 33 | forbidOnly: !!process.env.CI, 34 | /* Retry on CI only */ 35 | retries: process.env.CI ? 2 : Config.RETRY_FAILED, 36 | /* Opt out of parallel tests on CI. */ 37 | workers: process.env.CI ? 1 : Config.WORKERS, 38 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 39 | reporter: [ 40 | [`./src/utils/report/CustomReporterConfig.ts`], 41 | ["html", { open: "always", host: "127.0.0.1", port: 5723 }], 42 | //["OrtoniReport", { outputFolder: "reports" }], 43 | ], 44 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 45 | use: { 46 | /* Base URL to use in actions like `await page.goto('/')`. */ 47 | // baseURL: 'http://127.0.0.1:3000', 48 | 49 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 50 | trace: "retain-on-failure", 51 | headless: Config.HEADLESS_BROWSER, 52 | baseURL: Config.BASE_URL, 53 | screenshot: "on", 54 | video: "on", 55 | }, 56 | 57 | /* Configure projects for major browsers */ 58 | projects: [ 59 | // { 60 | // name: "chromium", 61 | // use: { ...devices["Desktop Chrome"] }, 62 | // }, 63 | 64 | // { 65 | // name: "firefox", 66 | // use: { ...devices["Desktop Firefox"] }, 67 | // }, 68 | 69 | // { 70 | // name: "webkit", 71 | // use: { ...devices["Desktop Safari"] }, 72 | // }, 73 | 74 | /* Test against mobile viewports. */ 75 | // { 76 | // name: 'Mobile Chrome', 77 | // use: { ...devices['Pixel 5'] }, 78 | // }, 79 | // { 80 | // name: 'Mobile Safari', 81 | // use: { ...devices['iPhone 12'] }, 82 | // }, 83 | 84 | /* Test against branded browsers. */ 85 | { 86 | name: "Microsoft Edge", 87 | use: { 88 | ...devices["Desktop Edge"], 89 | channel: "msedge", 90 | //viewport: { width: 1920, height: 1080 }, 91 | viewport: devices["Desktop Edge"].viewport, //set viewport dynamically 92 | // baseURL:'https://restful-booker.herokuapp.com', 93 | ignoreHTTPSErrors: true, 94 | acceptDownloads: true, 95 | }, 96 | }, 97 | // {viewport: devices["Desktop Edge"].viewport, 98 | // name: "Chrome", 99 | // use: { 100 | // ...devices["Desktop Chrome"], 101 | // channel: "chrome", 102 | // screenshot: "on", 103 | // trace: "on", 104 | // video: "on", 105 | // headless: false, 106 | // viewport: { width: 1920, height: 1080 }, 107 | // }, 108 | // }, 109 | ], 110 | 111 | 112 | /* Run your local dev server before starting the tests */ 113 | // webServer: { 114 | // command: 'npm run start', 115 | // url: 'http://127.0.0.1:3000', 116 | // reuseExistingServer: !process.env.CI, 117 | // }, 118 | }); 119 | -------------------------------------------------------------------------------- /src/globals/global-setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/globals/global-setup.ts -------------------------------------------------------------------------------- /src/globals/global-teardown.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/globals/global-teardown.ts -------------------------------------------------------------------------------- /src/globals/healthcheck.setup.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/globals/healthcheck.setup.ts -------------------------------------------------------------------------------- /src/helper/Helper.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Helper { 3 | async isValidDate(date: string) { 4 | if (Date.parse(date)) { 5 | return true; 6 | } else { 7 | return false; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/helper/api/apiHelper.ts: -------------------------------------------------------------------------------- 1 | import { Page, expect } from "@playwright/test"; 2 | import { Helper } from "@src/helper/Helper"; 3 | import { pwApi, test } from 'pw-api-plugin'; 4 | import { ApiError } from "@src/utils/error/ErrorManager"; 5 | import { validateSchema } from 'playwright-ajv-schema-validator'; 6 | 7 | enum methodType { 8 | GET = "get", 9 | POST = "post", 10 | DELETE = "delete", 11 | PUT = "put", 12 | PATCH = "patch", 13 | HEAD = "head", 14 | } 15 | 16 | const BASE_URL = "https://restful-booker.herokuapp.com"; 17 | 18 | export class ApiHelper extends Helper { 19 | private readonly apiRequest: pwApi; 20 | private readonly webPage: Page; 21 | 22 | 23 | 24 | /** 25 | * The constructor function initializes a new context for the API. 26 | * @param {any} apiContext - The `apiContext` parameter is an object that represents the context of an 27 | * API. It is used to store and manage information related to the API, such as authentication 28 | * credentials, request headers, and other configuration settings. 29 | */ 30 | constructor(webPage: Page,apiRequest: pwApi) { 31 | super(); 32 | this.apiRequest = apiRequest; 33 | this.webPage = webPage; 34 | } 35 | 36 | 37 | /** 38 | * The function `hitApiEndPoint` is an asynchronous function that takes in an operation type, an 39 | * endpoint, a payload, and a status code, and then invokes the corresponding API method based on the 40 | * operation type. 41 | * @param {string} method - The `operationType` parameter is a string that specifies the type of 42 | * operation to be performed on the API endpoint. It can have one of the following values: "get", 43 | * "post", "delete", or "put". 44 | * @param {string} endPoint - The `endPoint` parameter is a string that represents the URL or endpoint 45 | * of the API that you want to hit. It specifies the location where the API is hosted and the specific 46 | * resource or action you want to perform. 47 | * @param {object} body - The `payload` parameter is an object that contains the data to be sent in 48 | * the request body for POST and PUT operations. It can include any relevant information that needs to 49 | * be sent to the API endpoint. 50 | * @param {number} statusCode - The `statusCode` parameter is the expected HTTP status code that the 51 | * API endpoint should return. 52 | */ 53 | async hitApiEndPoint( 54 | method: methodType, 55 | endPoint: string, 56 | body: object, 57 | statusCode: number 58 | ):Promise { 59 | try { 60 | switch (method.toLowerCase()) { 61 | case methodType.GET: 62 | return await this.invokeGetApi(endPoint, statusCode); 63 | case methodType.POST: 64 | return await this.invokePostApi(endPoint, body, statusCode); 65 | case methodType.DELETE: 66 | return await this.invokeDeleteApi(endPoint, statusCode); 67 | case methodType.PUT: 68 | return await this.invokePutApi(endPoint, body, statusCode); 69 | default: 70 | throw new Error(`Unsupported operation type: ${method}`); 71 | } 72 | } catch (error) { 73 | throw new ApiError( 74 | `Unsupported operation type: ${method}`, 75 | ); 76 | } 77 | } 78 | 79 | async invokeGetApi(endPoint: string, statusCode: number = 200) { 80 | try { 81 | console.log(`Making GET request to endPoint: ${BASE_URL}${endPoint}`); 82 | const responseGet = await pwApi.get ({request:this.apiRequest, page:this.webPage},`${BASE_URL}${endPoint}`); 83 | 84 | expect(responseGet.status(),`${endPoint}, Expected Status : ${statusCode}, Actual Status : ${responseGet.status()}`).toBe(statusCode); 85 | return await responseGet.json(); 86 | } catch (error) { 87 | console.log(error); 88 | throw new ApiError("Get request failed"); 89 | } 90 | } 91 | 92 | async invokeDeleteApi(endPoint: string, statusCode: number = 200) { 93 | let response; 94 | try { 95 | console.log( 96 | `Making DELETE request to endPoint: ${BASE_URL}${endPoint}` 97 | ); 98 | response = await pwApi.delete({request:this.apiRequest, page:this.webPage},`${BASE_URL}${endPoint}`); 99 | expect( 100 | response.status(), 101 | `API : ${BASE_URL}${endPoint} , Expected status : ${statusCode}, Actual status : ${response.status()}` 102 | ).toBe(statusCode); 103 | return await response.json(); 104 | } catch (error) { 105 | return error; 106 | } 107 | } 108 | 109 | /** 110 | * The function `invokePostApi` is an asynchronous function that sends a POST request to an API 111 | * endpoint with a payload and returns the response data. 112 | * @param {string} endPoint - The `endPoint` parameter is a string that represents the URL or endpoint 113 | * of the API you want to call. 114 | * @param {object} payload - The `payload` parameter is an object that contains the data to be sent in 115 | * the request body. It is typically used to send data to the server for processing or to update a 116 | * resource. 117 | * @returns the response data as a JSON object if the response status is 200. If there is an error, it 118 | * will return the error object. 119 | */ 120 | async invokePostApi( 121 | endPoint: string, 122 | payload: object, 123 | statusCode: number = 200 124 | ) { 125 | let response; 126 | try { 127 | let tempPayload = JSON.stringify(payload); 128 | console.log( 129 | `Making POST request to endPoint: ${BASE_URL}${endPoint} payload :${tempPayload} ` 130 | ); 131 | response = await pwApi.post({request:this.apiRequest, page:this.webPage},`${BASE_URL}${endPoint}`, { 132 | data: payload, 133 | }); 134 | expect( 135 | response.status(), 136 | `API : ${BASE_URL}${endPoint} , Expected status : ${statusCode}, Actual status : ${response.status()}` 137 | ).toBe(statusCode); 138 | return await response.json(); 139 | } catch (error) { 140 | return error; 141 | } 142 | } 143 | async invokePutApi( 144 | endPoint: string, 145 | payload: object, 146 | statusCode: number = 200 147 | ) { 148 | let response; 149 | try { 150 | console.log( 151 | `Making PUT request to endPoint: ${BASE_URL}${endPoint} payload :${payload} ` 152 | ); 153 | response = await pwApi.put({request:this.apiRequest, page:this.webPage},`${BASE_URL}${endPoint}`, { 154 | data: payload, 155 | }); 156 | expect( 157 | response.status(), 158 | `API : ${BASE_URL}${endPoint} , Expected status : ${statusCode}, Actual status : ${response.status()}` 159 | ).toBe(statusCode); 160 | return await response.json(); 161 | } catch (error) { 162 | return error; 163 | } 164 | } 165 | } 166 | 167 | 168 | -------------------------------------------------------------------------------- /src/helper/index.ts: -------------------------------------------------------------------------------- 1 | export {ApiHelper} from "../helper/api/apiHelper" 2 | export {SshHelper} from "../helper/terminal/SSHHelper" 3 | export {WebHelper} from "../helper/web/webHelper" -------------------------------------------------------------------------------- /src/helper/terminal/SSHHelper.ts: -------------------------------------------------------------------------------- 1 | import { Client, ConnectConfig } from 'ssh2'; 2 | 3 | export class SshHelper { 4 | private client: Client; 5 | private isConnected: boolean = false; 6 | 7 | constructor() { 8 | this.client = new Client(); 9 | } 10 | 11 | /** 12 | * Establish SSH Connection 13 | * @param config - SSH connection details 14 | */ 15 | public async setUpSshSession(config: ConnectConfig): Promise { 16 | return new Promise((resolve, reject) => { 17 | this.client 18 | .on('ready', () => { 19 | console.log('SSH Connection Established'); 20 | this.isConnected = true; 21 | resolve(); 22 | }) 23 | .on('error', (err) => { 24 | console.error('SSH Connection Error:', err); 25 | reject(err); 26 | }) 27 | .connect(config); 28 | }); 29 | } 30 | 31 | /** 32 | * Run Command on Remote Linux Machine 33 | * @param command - Command to execute 34 | * @returns Command output as a string 35 | */ 36 | public async runCommand(command: string): Promise { 37 | return new Promise((resolve, reject) => { 38 | if (!this.isConnected) { 39 | return reject(new Error('SSH session is not connected')); 40 | } 41 | 42 | this.client.exec(command, (err, stream) => { 43 | if (err) return reject(err); 44 | 45 | let output = ''; 46 | stream 47 | .on('close', (code:string, signal:string) => { 48 | console.log(`Command execution completed. Exit code: ${code}, Signal: ${signal}`); 49 | resolve(output); 50 | }) 51 | .on('data', (data:string) => { 52 | output += data.toString(); 53 | }) 54 | .stderr.on('data', (data) => { 55 | console.error('Error:', data.toString()); 56 | }); 57 | }); 58 | }); 59 | } 60 | 61 | /** 62 | * Close the SSH Connection 63 | */ 64 | public closeConnection(): void { 65 | if (this.isConnected) { 66 | this.client.end(); 67 | this.isConnected = false; 68 | console.log('SSH Connection Closed'); 69 | } 70 | } 71 | } 72 | 73 | //Example code 74 | 75 | // import { SSHHelper } from './SSHHelper'; 76 | 77 | // async function main() { 78 | // const sshHelper = new SSHHelper(); 79 | 80 | // const config = { 81 | // host: '192.168.1.100', // Replace with your Linux device's IP 82 | // port: 22, // Default SSH port 83 | // username: 'your-username', 84 | // password: 'your-password', // Use SSH keys for better security 85 | // }; 86 | 87 | // try { 88 | // // Step 1: Establish SSH Connection 89 | // await sshHelper.setUpSshSession(config); 90 | 91 | // // Step 2: Run Commands 92 | // const output = await sshHelper.runCommand('ls -l'); 93 | // console.log('Command Output:', output); 94 | 95 | // } catch (error) { 96 | // console.error('Error:', error); 97 | // } finally { 98 | // // Step 3: Close Connection 99 | // sshHelper.closeConnection(); 100 | // } 101 | // } 102 | 103 | // main(); 104 | -------------------------------------------------------------------------------- /src/helper/web/webHelper.ts: -------------------------------------------------------------------------------- 1 | import { test, BrowserContext, Page, expect, Locator } from "@playwright/test"; 2 | 3 | import fs from "fs"; 4 | import { Helper } from "@src/helper/Helper"; 5 | import { JsonReader } from "@src/utils/reader/jsonReader"; 6 | import logger from '@src/utils/report/Logger' 7 | 8 | interface ChangedValueParams { 9 | elementName: string; 10 | valueToUse?: string; // Optional parameter for values 11 | } 12 | 13 | export class WebHelper extends Helper { 14 | readonly webPage: Page; 15 | readonly browserContext: BrowserContext; 16 | readonly json: JsonReader; 17 | 18 | 19 | constructor(webPage: Page, browserContext: BrowserContext, jsonPath: string) { 20 | super(); 21 | this.webPage = webPage; 22 | this.browserContext = browserContext; 23 | this.json = new JsonReader(jsonPath); 24 | } 25 | 26 | 27 | 28 | async changeValueOnUi( 29 | params: ChangedValueParams 30 | ): Promise { 31 | 32 | let { elementName, valueToUse } = params; 33 | 34 | const objType: string = await this.json.getJsonValue(elementName, "objType") 35 | const locatorType: string = await this.json.getJsonValue(elementName, "locatorType") 36 | const locatorValue: string = await this.json.getJsonValue(elementName, "locatorValue") 37 | 38 | const elementInfo = await this.findElement(locatorType, locatorValue); 39 | 40 | if (null === elementInfo) { 41 | console.log(`Element ${locatorType} ${locatorValue} is not found`) 42 | return null; 43 | } 44 | 45 | let testData: string[] = []; 46 | if (typeof valueToUse === 'undefined') { 47 | testData = this.json.getJsonValue(elementName, "saveTestData").split("|"); 48 | } 49 | 50 | 51 | try { 52 | let result: string; 53 | 54 | 55 | switch (objType.toLowerCase()) { 56 | case 'toggle': 57 | break; 58 | case 'checkbox': 59 | let currentStatus = await this.getCheckBoxStatus(elementInfo) ? "true" : "false"; 60 | if (currentStatus !== valueToUse) { 61 | await this.setCheckBoxStatus(elementInfo, valueToUse); 62 | } 63 | 64 | break; 65 | case 'textbox': 66 | case 'textarea': 67 | let currentValue = await this.getText(elementInfo) 68 | if (testData.length !== 0) { 69 | valueToUse = this.getValueFromArray(testData, currentValue); 70 | } 71 | 72 | if (currentValue !== valueToUse) { 73 | await this.enterText(elementInfo, valueToUse || ''); 74 | } 75 | let afterChangeValue = await this.getText(elementInfo); 76 | break; 77 | case 'dropdown': 78 | break; 79 | case 'radiobutton': 80 | break; 81 | 82 | default: 83 | throw new Error(`Unsupported Object type: ${objType}`); 84 | } 85 | } catch (error) { 86 | throw new Error(`Unsupported operation type: ${objType}`); 87 | } 88 | } 89 | 90 | 91 | 92 | 93 | /** 94 | * Finds a single element using the specified locator strategy 95 | * Checks if element is displayed and enabled 96 | * Returns null if element is not found 97 | * @param { string } type - Locator type('css', 'xpath', 'text', 'testid', 'role', 'label') 98 | * @param { string } value - Locator value 99 | * @returns { Promise<{ locator: import('@playwright/test').Locator, isDisplayed: boolean, isEnabled: boolean } | null> } Playwright locator with status or null 100 | */ 101 | async findElement(type: string, value: string): Promise { 102 | try { 103 | let locator: Locator; 104 | switch (type.toLowerCase()) { 105 | case 'css': 106 | locator = this.webPage.locator(value); 107 | break; 108 | case 'xpath': 109 | locator = this.webPage.locator(`xpath=${value}`); 110 | break; 111 | case 'text': 112 | locator = this.webPage.getByText(value); 113 | break; 114 | case 'testid': 115 | locator = this.webPage.getByTestId(value); 116 | break; 117 | case 'role': 118 | // locator = this.webPage.getByRole(value as Role,{name:value,exact:false}); 119 | // break; 120 | case 'label': 121 | locator = this.webPage.getByLabel(value,); 122 | break; 123 | default: 124 | console.warn(`Unsupported locator type: ${type}`); 125 | return null; 126 | } 127 | 128 | // Check if element exists 129 | const count = await locator.count(); 130 | if (count === 0) { 131 | console.warn(`Element not found with ${type} locator: ${value}`); 132 | return null; 133 | } 134 | 135 | // Check visibility and enabled status 136 | const isDisplayed = await locator.isVisible(); 137 | const isEnabled = await locator.isEnabled(); 138 | 139 | if (!locator) { 140 | console.log(`Element ${type} ${value} is not found`); 141 | return null; 142 | } 143 | 144 | if (!isDisplayed || !isEnabled) { 145 | console.log(`Element ${type} ${value} is not visible or enabled`); 146 | return null; 147 | } 148 | return locator; 149 | 150 | } catch (error) { 151 | const errorMessage = error instanceof Error ? error.message : String(error); 152 | console.warn(`Error while finding element with ${type} locator: ${value}. Error: ${errorMessage}`); 153 | return null; 154 | } 155 | } 156 | 157 | /** 158 | * The `delay` function is an asynchronous function that waits for a specified amount of time before 159 | * resolving. 160 | * @param {number} time - The `time` parameter is a number that represents the duration of the delay 161 | * in seconds. 162 | * @returns a Promise that resolves to void. 163 | */ 164 | async delay(time: number): Promise { 165 | return new Promise(function (resolve) { 166 | setTimeout(resolve, time * 1000); 167 | }); 168 | } 169 | 170 | /** 171 | * The function clicks on an element on a web page based on its text content. 172 | * @param {string} text - The text parameter is a string that represents the text content of an element 173 | * that you want to click on. It is used to locate the element on the web page. 174 | * @param {boolean} [exact=true] - The `exact` parameter is a boolean value that determines whether the 175 | * text should be matched exactly or not. If `exact` is set to `true`, the `clickByText` function will 176 | * only click on elements that have an exact match with the provided text. If `exact` is set to ` 177 | */ 178 | async clickByText(text: string, exact: boolean = true): Promise { 179 | await this.webPage.getByText(text, { exact: exact }).click(); 180 | } 181 | 182 | async rightClickButton(locator: string): Promise { 183 | await this.webPage.locator(locator).click({ button: "right" }); 184 | } 185 | 186 | async leftClickButton(locator: string): Promise { 187 | await this.webPage.locator(locator).click({ button: "left" }); 188 | } 189 | 190 | async navigateToUrl(url: string): Promise { 191 | await this.webPage.goto(url); 192 | } 193 | 194 | async verifyDragAndDrop( 195 | source: string, 196 | target: string, 197 | verifyText: string 198 | ): Promise { 199 | let draggable = await this.webPage.locator(source); 200 | let droppable = await this.webPage.locator(target); 201 | await draggable.hover(); 202 | await this.webPage.mouse.down(); 203 | await droppable.hover(); 204 | await this.webPage.mouse.up(); 205 | await expect(droppable).toContainText(verifyText); 206 | } 207 | 208 | async verifyToolTip(locator: string, hoverText: string): Promise { 209 | let el = await this.webPage.locator(locator); 210 | el.hover(); 211 | await expect(el).toContainText(hoverText); 212 | } 213 | 214 | async verifyFileDownload(): Promise { 215 | //TBD 216 | } 217 | 218 | async verifyNewTab(newTabUrlExpected: string): Promise { 219 | //TBD 220 | } 221 | 222 | async verifyNewWindow(newWindowUrlExpected: string): Promise { 223 | //TBD 224 | } 225 | async verifyFrameText(): Promise { 226 | //TBD 227 | } 228 | async verifyNestedFrame(): Promise { 229 | //TBD 230 | } 231 | 232 | /** 233 | * The function asserts that the current page URL matches the expected URL. 234 | * @param {string} url - The `url` parameter is a string that represents the expected URL of a web 235 | * page. 236 | */ 237 | async assertPageURL(url: string): Promise { 238 | console.log(`Asserts that page url is ${url}.`); 239 | await expect(this.webPage).toHaveURL(url); 240 | } 241 | 242 | /** 243 | * The function asserts that the page title matches the expected title. 244 | * @param {string} title - The title parameter is a string that represents the expected title of the 245 | * web page. 246 | */ 247 | async assertPageTitle(title: string): Promise { 248 | console.log(`Asserts that page title is ${title}.`); 249 | await expect(this.webPage).toHaveTitle(title); 250 | } 251 | /** 252 | * The function opens a new tab in a browser context, navigates to a specified URL, and returns the 253 | * page object representing the new tab. 254 | * @param {string} url - A string representing the URL of the webpage that you want to open in a new 255 | * tab. 256 | * @returns a Promise that resolves to a Page object. 257 | */ 258 | async openNewTab(url: string): Promise { 259 | const pageOne = await this.browserContext.newPage(); 260 | await pageOne.goto(url); 261 | return pageOne; 262 | } 263 | 264 | /** 265 | * The function takes a screenshot of a web page and saves it as an image file. 266 | * @param {string} imageName - The imageName parameter is a string that specifies the name of the 267 | * screenshot image file. If no value is provided, it defaults to "screenshot.png". 268 | */ 269 | async takeFullPageScreenshot( 270 | imageName: string = `screenshot.png` 271 | ): Promise { 272 | await this.webPage.screenshot({ path: `${imageName}`, fullPage: true }); 273 | } 274 | async takePageScreenshot( 275 | imageName: string = `screenshot.png` 276 | ): Promise { 277 | await this.webPage.screenshot({ path: `${imageName}` }); 278 | } 279 | 280 | /** 281 | * The function takes a locator and an optional image name as parameters, finds the element on a web 282 | * page using the locator, and takes a screenshot of the element. 283 | * @param {string} locator - The `locator` parameter is a string that represents the element you want 284 | * to take a screenshot of. It can be a CSS selector, an XPath expression, or any other valid locator 285 | * strategy supported by the `this.webPage.locator` method. 286 | * @param {string} imageName - The `imageName` parameter is a string that specifies the name of the 287 | * screenshot image file. If no value is provided, it defaults to "screenshot.png". 288 | */ 289 | async takeScreenshotOfElement( 290 | locator: string, 291 | imageName: string = `screenshot.png` 292 | ): Promise { 293 | const el = await this.webPage.locator(locator); 294 | await el.screenshot({ path: `${imageName}` }); 295 | } 296 | 297 | async clipScreenshot(imageName: string = `screenshot.png`): Promise { 298 | await this.webPage.screenshot({ 299 | path: `${imageName}`, 300 | clip: { x: 0, y: 0, width: 800, height: 800 }, 301 | }); 302 | } 303 | 304 | /** 305 | * The function checks if an element on a web page contains the expected text. 306 | * @param {string} target - A string representing the target element to locate on the web page. 307 | * @param {string} expectedText - The expected text that you want the element to contain. 308 | */ 309 | async elementContainText( 310 | target: string, 311 | expectedText: string 312 | ): Promise { 313 | console.log( 314 | `Asserts that element ${target} contains text ${expectedText}.` 315 | ); 316 | const el = await this.webPage.locator(target); 317 | await expect(el).toContainText(expectedText); 318 | } 319 | 320 | /** 321 | * The function checks if an element on a web page has the expected text. 322 | * @param {string} target - The target parameter is a string that represents the locator for the 323 | * element you want to check for text. It could be a CSS selector, an XPath expression, or any other 324 | * valid locator strategy supported by the testing framework you are using. 325 | * @param {string} expectedText - The expected text that the element should have. 326 | */ 327 | async elementHasText(target: string, expectedText: string): Promise { 328 | console.log( 329 | `Asserts that element ${target} has expected text ${expectedText}.` 330 | ); 331 | const el = await this.webPage.locator(target); 332 | await expect(el).toHaveText(expectedText); 333 | } 334 | 335 | /** 336 | * The function asserts that a specified element is visible on a web page. 337 | * @param {string} target - The `target` parameter is a string that represents the locator of the 338 | * element you want to check for visibility. It could be a CSS selector, an XPath expression, or any 339 | * other valid locator that can be used to identify the element on the web page. 340 | */ 341 | async elementIsVisible(target: string): Promise { 342 | console.log(`Asserts that element ${target} is visible.`); 343 | expect(await this.webPage.locator(target)).toBeVisible(); 344 | } 345 | 346 | /** 347 | * The function asserts that a specified element is not visible on a web page. 348 | * @param {string} target - The target parameter is a string that represents the locator or selector 349 | * for the element that you want to check for visibility. It can be a CSS selector, an XPath 350 | * expression, or any other valid locator that can be used to identify the element on the web page. 351 | */ 352 | async elementIsNotVisible(target: string): Promise { 353 | console.log(`Asserts that element ${target} is not visible.`); 354 | expect(await this.webPage.locator(target)).toBeHidden(); 355 | } 356 | 357 | async elementHasAttributeAndValue( 358 | target: string, 359 | attribute: string, 360 | attributeVal: string 361 | ): Promise { 362 | console.log( 363 | `Asserts that '${target}' has a specific attribute '${attribute}' with the expected value '${attributeVal}'.` 364 | ); 365 | //expect(await (target).toHaveAttribute(attribute, attributeVal)); 366 | } 367 | 368 | /** 369 | * The `blockRequest` function blocks all requests in a given browser context that do not start with a 370 | * specified request name. 371 | * @param {BrowserContext} context - The `context` parameter is an instance of a `BrowserContext` 372 | * object. It represents a browser context in Puppeteer, which is a container for a set of pages and 373 | * allows for fine-grained control over browser behavior. 374 | * @param {string} requestName - The `requestName` parameter is a string that represents the name of 375 | * the request you want to block. 376 | * Call this method in your tests 377 | */ 378 | async blockRequest(context: BrowserContext, requestName: string) { 379 | await context.route("**/*", (request) => { 380 | request.request().url().startsWith(`${requestName}`) 381 | ? request.abort() 382 | : request.continue(); 383 | return; 384 | }); 385 | } 386 | 387 | /** 388 | * The function will setup a listener for alert box, if dialog appears during the test then automatically accepting them. 389 | * Alert box contains only Ok button 390 | */ 391 | async acceptAlertBox(): Promise { 392 | console.log(`Handle Alert Box by clicking on Ok button`); 393 | this.webPage.on("dialog", async (dialog) => dialog.dismiss()); 394 | } 395 | 396 | /** 397 | * The function will setup a listener for Confirm box, if dialog appears during the test then automatically call accept/dismiss method. 398 | * Confirm box contains Ok/Cancel button 399 | */ 400 | async acceptConfirmBox(): Promise { 401 | console.log(`Accept Confirm Box by clicking on Ok button`); 402 | this.webPage.on("dialog", async (dialog) => dialog.accept()); 403 | } 404 | 405 | async dismissConfirmBox(): Promise { 406 | console.log(`Dismiss Confirm Box by clicking on Cancel button`); 407 | this.webPage.on("dialog", async (dialog) => dialog.dismiss()); 408 | } 409 | 410 | /** 411 | * The function will setup a listener for Prompt box, if dialog appears during the test then automatically call accept/dismiss method. 412 | * Prompt box contains text box where user can enter text and submit (using Ok/Cancel button) it. 413 | */ 414 | async handlePromptBox(txtVal: string): Promise { 415 | console.log(`Enter text message in Prompt Box and click on Ok button`); 416 | this.webPage.on("dialog", async (dialog) => dialog.accept(txtVal)); 417 | } 418 | 419 | waitForDialogMessage(page: Page) { 420 | return new Promise((resolve) => { 421 | page.on("dialog", (dialog) => { 422 | resolve(dialog.message()); 423 | }); 424 | }); 425 | } 426 | 427 | /** 428 | * The function will read text message from Alert and return. 429 | */ 430 | async getAlertText(): Promise { 431 | console.log(`Read text message from Alert box`); 432 | let dialogMessage: string; 433 | dialogMessage = await this.waitForDialogMessage( 434 | this.webPage 435 | ).then.toString(); 436 | console.log(dialogMessage); 437 | return dialogMessage; 438 | } 439 | 440 | /** 441 | * The function `getFrame` takes a frame locator as input and calls a method on the `webPage` object 442 | * to locate the frame. 443 | * @param {string} frameLocator - The frameLocator parameter is a string that represents the locator 444 | * or identifier of the frame you want to retrieve. 445 | */ 446 | async getFrame(frameLocator: string) { 447 | return this.webPage.frameLocator(frameLocator); 448 | } 449 | 450 | /** 451 | * The function `getStringFromShadowDom` retrieves the text content from a specified element within 452 | * the Shadow DOM. 453 | * @param {string} locator - The `locator` parameter is a string that represents a CSS selector used 454 | * to locate an element within the Shadow DOM. 455 | * @returns a Promise that resolves to a string. 456 | */ 457 | async getStringFromShadowDom(locator: string): Promise { 458 | return (await this.webPage.locator(locator).textContent()) as string; 459 | } 460 | 461 | /** 462 | * The `downLoadFile` function downloads a file by clicking on a specified locator and waits for the 463 | * download event to occur. 464 | * @param {string} locator - The locator parameter is a string that represents the selector used to 465 | * locate the element on the web page that triggers the file download. It could be an ID, class name, 466 | * CSS selector, or any other valid selector that can be used with the `this.webPage.locator()` 467 | * method to locate the element 468 | * @param {string} expectedFileName - The expectedFileName parameter is a string that represents the 469 | * name of the file that is expected to be downloaded. 470 | * @param {string} savePath - The `savePath` parameter is a string that represents the path where the 471 | * downloaded file will be saved on the local machine. 472 | */ 473 | async downLoadFile( 474 | locator: string, 475 | expectedFileName: string, 476 | savePath: string 477 | ) { 478 | //start download 479 | const [download] = await Promise.all([ 480 | this.webPage.waitForEvent("download"), 481 | this.webPage.locator(locator).click(), 482 | ]); 483 | const suggestedFileName = download.suggestedFilename() 484 | const filePath = 'download/'+suggestedFileName 485 | await download.saveAs(filePath); 486 | expect(fs.existsSync(filePath)).toBeTruthy(); 487 | return filePath; 488 | } 489 | 490 | /** 491 | * The function uploads a file to a web page using the specified file path, file upload locator, and 492 | * upload button locator. 493 | * @param {string} filePath - The file path is the path to the file that you want to upload. It 494 | * should be a string that specifies the location of the file on your computer. 495 | * @param {string} fileUploadLocator - The fileUploadLocator parameter is a string that represents 496 | * the locator of the file upload input element on the web page. This locator is used to identify the 497 | * element where the file will be uploaded to. 498 | * @param {string} uploadBtnLocator - The `uploadBtnLocator` parameter is a string that represents 499 | * the locator of the upload button on the web page. It is used to locate and interact with the 500 | * upload button element on the page. 501 | */ 502 | async uploadFile( 503 | filePath: string, 504 | fileUploadLocator: string, 505 | uploadBtnLocator: string 506 | ) { 507 | if (!fs.existsSync(filePath)) { 508 | console.log(`File ${filePath} does not exist`); 509 | throw new Error(`File not found :${filePath}`); 510 | } 511 | 512 | await this.webPage.setInputFiles(`${fileUploadLocator}`, filePath); 513 | await this.webPage.locator(`${uploadBtnLocator}`).click(); 514 | } 515 | 516 | /** 517 | * The function intercepts a specific route in a browser context, logs the request and response, and 518 | * continues with the intercepted request. 519 | * @param {string} interceptRoute - The interceptRoute parameter is a string that represents the route 520 | * that you want to intercept. It is used to match against the URL of incoming requests and determine 521 | * if the route should be intercepted. 522 | */ 523 | async interceptRouteAndContinue(interceptRoute: string) { 524 | await this.browserContext.route(interceptRoute, async (route) => { 525 | //Arrange & Log the request 526 | const response = await route.fetch(); 527 | const json = await response.json(); 528 | console.log(JSON.stringify(json, null, 10)); 529 | 530 | //continue with the intercepted request 531 | await route.continue(); 532 | }); 533 | } 534 | 535 | /** 536 | * The function intercepts a specific route and aborts it. 537 | * @param {string} interceptRoute - The `interceptRoute` parameter is a string that represents the 538 | * URL pattern that you want to intercept and abort. It is used to match against the URLs of incoming 539 | * network requests. 540 | */ 541 | async interceptRouteAndAbort(interceptRoute: string) { 542 | await this.browserContext.route(interceptRoute, async (route) => { 543 | route.abort(); //abort the route 544 | }); 545 | } 546 | /** 547 | * The function intercepts a specified route and modifies the response data with the provided JSON 548 | * data. 549 | * @param {string} interceptRoute - The `interceptRoute` parameter is a string that represents the 550 | * route that you want to intercept. It is the URL or path that you want to intercept and modify the 551 | * response for. For example, if you want to intercept the route "/api/data", you would pass 552 | * "/api/data" as the 553 | * @param {string} modifiedJsonData - The `modifiedJsonData` parameter is a string representing the 554 | * modified JSON data that you want to use as the response body for the intercepted route. 555 | */ 556 | async interceptRouteAndChangeData( 557 | interceptRoute: string, 558 | modifiedJsonData: string 559 | ) { 560 | await this.browserContext.route(interceptRoute, async (route) => { 561 | const modifiedResponse = [`${modifiedJsonData}`]; 562 | return route.fulfill({ 563 | status: 200, 564 | contentType: "application/json", 565 | body: JSON.stringify(modifiedResponse), 566 | }); 567 | }); 568 | } 569 | 570 | async changeElementValue(): Promise { } 571 | 572 | async verifyValueFromUi(): Promise { } 573 | 574 | async getAttribute(locator: string, attributeName: string): Promise { 575 | const value = await this.webPage 576 | .locator(locator) 577 | .getAttribute(attributeName); 578 | return value ?? ""; 579 | } 580 | 581 | async getText(el: Locator): Promise { 582 | // Check if it's an input element 583 | const isInput = await el.evaluate(el => 584 | el.tagName === 'INPUT' || 585 | el.tagName === 'TEXTAREA' || 586 | el.tagName === 'SELECT' 587 | ); 588 | 589 | if (isInput) { 590 | // For input elements, get the value 591 | const value = await el.inputValue(); 592 | return value ?? ""; 593 | } else { 594 | // For other elements, get the text content 595 | const value = await el.textContent(); 596 | return value ?? ""; 597 | } 598 | } 599 | 600 | async press(key: string): Promise { 601 | await this.webPage.keyboard.press(key); 602 | } 603 | 604 | // async addStep(stepDescription: string, stepFunction: any): Promise { 605 | // return await test.step(stepDescription, stepFunction); 606 | // } 607 | 608 | // async attachScreenshot( 609 | // locator: string, 610 | // fileName: string, 611 | // testInfo: TestInfo 612 | // ): Promise { 613 | // const file = testInfo.outputPath(fileName); 614 | // const pathFile = path.dirname(file); 615 | // const pathAttachments = path.join(pathFile, "attachments"); 616 | // const attachmentFile = path.join(pathAttachments, fileName); 617 | // const screenshot = await webPage 618 | // .locator(locator) 619 | // .isVisible() 620 | // .screenshot({ path: file }); 621 | // await fs.promise.writeFile(file, screenshot); 622 | // if (!fs.existsSync(pathAttachments)) { 623 | // fs.mkdirSync(pathAttachments, { recursive: true }); 624 | // } 625 | // await fs.promises.writeFile(attachmentFile, screenshot); 626 | // await testInfo.attach(fileName, { contentType: "image/png", path: file }); 627 | // } 628 | 629 | async enterText(el: Locator, value: string) { 630 | await el.clear(); 631 | await el.fill(value); 632 | } 633 | 634 | async setCheckBoxStatus(el: Locator, state: string = 'true') { 635 | let isChecked = await el.isChecked(); 636 | let tryCount = 0; 637 | while (`${isChecked}` !== state) { 638 | if (`${isChecked}` !== `${state}` && `${state}` === `true`) { 639 | await el.check(); // Check the checkbox 640 | console.log('checkbox was not checked, now checked.'); 641 | } 642 | 643 | if (`${isChecked}` !== `${state}` && `${state}` === `false`) { 644 | await el.check(); // unCheck the checkbox 645 | console.log('checkbox was checked, now unchecked.'); 646 | } 647 | isChecked = await el.isChecked(); 648 | tryCount++; 649 | 650 | if (tryCount == 3){ 651 | return false; //exit the loop 652 | } 653 | } 654 | } 655 | 656 | async getCheckBoxStatus(el: Locator): Promise { 657 | return await el.isChecked(); 658 | } 659 | 660 | getValueFromArray(testData: string[], preVal: string) { 661 | const currentIndex = testData.indexOf(preVal); 662 | const nextIndex = (currentIndex + 1) % testData.length; 663 | return testData[nextIndex]; 664 | } 665 | } 666 | -------------------------------------------------------------------------------- /src/utils/config/artillery/artillery-script.yml: -------------------------------------------------------------------------------- 1 | config: 2 | target: https://opensource-demo.orangehrmlive.com/web/index.php/auth/login 3 | phases: 4 | - duration: 100 5 | arrivalRate: 1 6 | maxVusers: 1 7 | engines: 8 | playwright: 9 | showAllPageMetrics: true, 10 | extendedMetrics: true 11 | aggregateByName: true 12 | launchOptions: 13 | headless: false 14 | processor: ./tests/load/BrowerLoadTest.spec.js 15 | scenarios: 16 | - name: "Basic Load" 17 | engine: playwright 18 | flowFunction: "browserloadTest" 19 | -------------------------------------------------------------------------------- /src/utils/config/azure/azure-pipeline.yml: -------------------------------------------------------------------------------- 1 | # Starter pipeline 2 | # Start with a minimal pipeline that you can customize to build and deploy your code. 3 | # Add steps that build, run tests, deploy, and more: 4 | # https://aka.ms/yaml 5 | 6 | trigger: 7 | - none 8 | 9 | pool: 10 | vmImage: ubuntu-latest 11 | 12 | variables: 13 | "system.debug": false 14 | 15 | steps: 16 | - task: NodeTool@0 17 | inputs: 18 | versionSource: "spec" 19 | versionSpec: ">16.x" 20 | 21 | - task: PowerShell@2 22 | enabled: true 23 | displayName: "Install dependencies" 24 | inputs: 25 | targetType: "inline" 26 | script: "npm ci" 27 | workingDirectory: "$(System.DefaultWorkingDirectory)/samples/get-started" 28 | 29 | - task: PowerShell@2 30 | enabled: true 31 | displayName: "Run Playwright tests" 32 | env: 33 | PLAYWRIGHT_SERVICE_ACCESS_TOKEN: $(Secret) 34 | inputs: 35 | targetType: "inline" 36 | script: "npx playwright test -c playwright.service.config.ts --workers=20" 37 | workingDirectory: "$(System.DefaultWorkingDirectory)/samples/get-started" 38 | 39 | - task: PublishPipelineArtifact@1 40 | inputs: 41 | targetPath: "$(System.DefaultWorkingDirectory)/samples/get-started/playwright-report/" 42 | artifact: "Playwright tests" 43 | publishLocation: "pipeline" 44 | -------------------------------------------------------------------------------- /src/utils/config/azure/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from "@playwright/test"; 2 | import * as dotenv from "dotenv"; 3 | 4 | switch (process.env.NODE_ENV) { 5 | case "local": 6 | dotenv.config({ path: "./environments/local.env" }); 7 | break; 8 | case "dev": 9 | dotenv.config({ path: "./environments/local.env" }); 10 | break; 11 | case "qa": 12 | dotenv.config({ path: "./environments/qa.env" }); 13 | break; 14 | case "prod": 15 | dotenv.config({ path: "./environments/prod.env" }); 16 | 17 | break; 18 | 19 | default: 20 | break; 21 | } 22 | export default defineConfig({ 23 | testDir: "./src", 24 | /* Run tests in files in parallel */ 25 | fullyParallel: true, 26 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 27 | forbidOnly: !!process.env.CI, 28 | /* Retry on CI only */ 29 | retries: process.env.CI ? 2 : 0, 30 | /* Opt out of parallel tests on CI. */ 31 | workers: process.env.CI ? 1 : undefined, 32 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 33 | reporter: "html", 34 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 35 | use: { 36 | /* Base URL to use in actions like `await page.goto('/')`. */ 37 | // baseURL: 'http://127.0.0.1:3000', 38 | 39 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 40 | trace: "on-first-retry", 41 | }, 42 | 43 | /* Configure projects for major browsers */ 44 | projects: [ 45 | // { 46 | // name: "chromium", 47 | // use: { ...devices["Desktop Chrome"] }, 48 | // }, 49 | 50 | // { 51 | // name: "firefox", 52 | // use: { ...devices["Desktop Firefox"] }, 53 | // }, 54 | 55 | // { 56 | // name: "webkit", 57 | // use: { ...devices["Desktop Safari"] }, 58 | // }, 59 | 60 | /* Test against mobile viewports. */ 61 | // { 62 | // name: 'Mobile Chrome', 63 | // use: { ...devices['Pixel 5'] }, 64 | // }, 65 | // { 66 | // name: 'Mobile Safari', 67 | // use: { ...devices['iPhone 12'] }, 68 | // }, 69 | 70 | /* Test against branded browsers. */ 71 | { 72 | name: "Microsoft Edge", 73 | use: { ...devices["Desktop Edge"], channel: "msedge" }, 74 | }, 75 | // { 76 | // name: 'Google Chrome', 77 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 78 | // }, 79 | ], 80 | 81 | /* Run your local dev server before starting the tests */ 82 | // webServer: { 83 | // command: 'npm run start', 84 | // url: 'http://127.0.0.1:3000', 85 | // reuseExistingServer: !process.env.CI, 86 | // }, 87 | }); 88 | -------------------------------------------------------------------------------- /src/utils/config/azure/playwright.service.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file enables Playwright client to connect to remote browsers. 3 | * It should be placed in the same directory as playwright.config.ts. 4 | */ 5 | 6 | import { defineConfig } from "@playwright/test"; 7 | import config from "./playwright.config"; 8 | import dotenv from "dotenv"; 9 | 10 | // Define environment on the dev box in .env file: 11 | // .env: 12 | // PLAYWRIGHT_SERVICE_ACCESS_TOKEN=XXX 13 | // PLAYWRIGHT_SERVICE_URL=XXX 14 | 15 | // Define environment in your GitHub workflow spec. 16 | // env: 17 | // PLAYWRIGHT_SERVICE_ACCESS_TOKEN: ${{ secrets.PLAYWRIGHT_SERVICE_ACCESS_TOKEN }} 18 | // PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }} 19 | // PLAYWRIGHT_SERVICE_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}-${{ github.sha }} 20 | 21 | dotenv.config(); 22 | 23 | // Name the test run if it's not named yet. 24 | process.env.PLAYWRIGHT_SERVICE_RUN_ID = process.env.PLAYWRIGHT_SERVICE_RUN_ID || new Date().toISOString(); 25 | 26 | // Can be 'linux' or 'windows'. 27 | const os = process.env.PLAYWRIGHT_SERVICE_OS || "linux"; 28 | 29 | export default defineConfig(config, { 30 | // Define more generous timeout for the service operation if necessary. 31 | // timeout: 60000, 32 | // expect: { 33 | // timeout: 10000, 34 | // }, 35 | workers: 20, 36 | 37 | // Enable screenshot testing and configure directory with expectations. 38 | // https://learn.microsoft.com/azure/playwright-testing/how-to-configure-visual-comparisons 39 | ignoreSnapshots: false, 40 | snapshotPathTemplate: `{testDir}/__screenshots__/{testFilePath}/${os}/{arg}{ext}`, 41 | 42 | use: { 43 | // Specify the service endpoint. 44 | connectOptions: { 45 | wsEndpoint: `${process.env.PLAYWRIGHT_SERVICE_URL}?cap=${JSON.stringify({ 46 | // Can be 'linux' or 'windows'. 47 | os, 48 | runId: process.env.PLAYWRIGHT_SERVICE_RUN_ID, 49 | })}`, 50 | timeout: 30000, 51 | headers: { 52 | "x-mpt-access-key": process.env.PLAYWRIGHT_SERVICE_ACCESS_TOKEN!, 53 | }, 54 | // Allow service to access the localhost. 55 | exposeNetwork: "", 56 | }, 57 | }, 58 | // Tenmp workaround for config merge bug in OSS https://github.com/microsoft/playwright/pull/28224 59 | projects: config.projects ? config.projects : [{}], 60 | }); 61 | -------------------------------------------------------------------------------- /src/utils/config/env/dev.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/utils/config/env/dev.env -------------------------------------------------------------------------------- /src/utils/config/env/local.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/utils/config/env/local.env -------------------------------------------------------------------------------- /src/utils/config/env/qa.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/utils/config/env/qa.env -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/utils/constants.ts -------------------------------------------------------------------------------- /src/utils/dataStore.js: -------------------------------------------------------------------------------- 1 | /* 2 | This components acts as a in-memory data store. It is used to save data that needs to be shared between different test case*/ 3 | let store = {}; 4 | 5 | function saveData(key, data) { 6 | store[key] = data; 7 | } 8 | 9 | function getData(key) { 10 | return store[key]; 11 | } 12 | module.exports = { saveData, getData }; 13 | -------------------------------------------------------------------------------- /src/utils/dbUtils.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/utils/dbUtils.ts -------------------------------------------------------------------------------- /src/utils/error/ApiErrorHandler.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/utils/error/ApiErrorHandler.ts -------------------------------------------------------------------------------- /src/utils/error/ErrorManager.ts: -------------------------------------------------------------------------------- 1 | import { logError } from '../report/Logger'; 2 | 3 | // Define an interface for the expected error object 4 | interface ApiErrorResponse { 5 | response?: { 6 | status?: number; 7 | data?: any; // Replace 'any' with a more specific type if known 8 | }; 9 | } 10 | 11 | export class ApiError extends Error { 12 | constructor( 13 | message: string, 14 | public statusCode?: number, 15 | public response?: any 16 | ) { 17 | super(message); 18 | this.name = 'ApiError'; 19 | } 20 | } 21 | 22 | export const handleError = (error: any): never => { 23 | if (error instanceof ApiError) { 24 | logError(`API Error: ${error.message}`, { 25 | statusCode: error.statusCode, 26 | response: error.response 27 | }); 28 | } else { 29 | logError('Unexpected error occurred', error); 30 | } 31 | throw error; 32 | }; 33 | 34 | export const wrapAsync = async ( 35 | fn: () => Promise, 36 | errorMessage: string 37 | ): Promise => { 38 | try { 39 | return await fn(); 40 | } catch (error) { 41 | const apiError = error as ApiErrorResponse; // Cast error to the expected type 42 | throw new ApiError( 43 | errorMessage, 44 | apiError?.response?.status, 45 | apiError?.response?.data 46 | ); 47 | } 48 | }; -------------------------------------------------------------------------------- /src/utils/reader/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "John Doe", 3 | "age": 30, 4 | "email": "johndoe@example.com", 5 | "address": { 6 | "street": "123 Main St", 7 | "city": "New York", 8 | "state": "NY", 9 | "zip": "10001" 10 | }, 11 | "phone_numbers": [ 12 | { 13 | "type": "home", 14 | "number": "555-1234" 15 | }, 16 | { 17 | "type": "work", 18 | "number": "555-5678" 19 | } 20 | ], 21 | "is_active": true, 22 | "roles": ["admin", "editor"], 23 | "preferences": { 24 | "newsletter": true, 25 | "notifications": { 26 | "email": true, 27 | "sms": false 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/reader/jsonReader.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as path from 'path'; 3 | 4 | export class JsonReader { 5 | private filePath: string; 6 | public jsonData: any; 7 | 8 | constructor(filePath: string) { 9 | // Resolve the path relative to the project root 10 | // Adjust the path resolution based on where the file is located 11 | this.filePath = path.resolve(__dirname, '..', '..', '..', 'tests', 'resources', 'uiMap', filePath); 12 | console.log('Reading JSON from:', this.filePath); 13 | this.readJsonFile(); 14 | } 15 | 16 | /** 17 | * Reads a JSON file from the given path and returns its content as an object. 18 | * @returns Parsed JSON object or null if an error occurs. 19 | */ 20 | readJsonFile(): any { 21 | try { 22 | if (!fs.existsSync(this.filePath)) { 23 | console.error(`File not found: ${this.filePath}`); 24 | return false; 25 | } 26 | 27 | const fileContent = fs.readFileSync(this.filePath, 'utf-8'); 28 | this.jsonData = JSON.parse(fileContent); 29 | console.log(this.jsonData); 30 | } catch (error) { 31 | console.error("Error reading or parsing JSON file:", error); 32 | this.jsonData = null; 33 | return false; 34 | } 35 | } 36 | 37 | /** 38 | * Retrieves a value from a nested JSON object using a JSON path hierarchy. 39 | * @param jsonObj - The JSON object. 40 | * @param path - The dot-separated JSON path (e.g., "user.address.city"). 41 | * @returns The value at the specified path or undefined if not found. 42 | */ 43 | /** 44 | * Retrieves a value from a nested JSON object using a JSON path hierarchy. 45 | * @param path - The dot-separated JSON path to the parent object (e.g., "user.profile.address"). 46 | * @param key - The specific key to retrieve from the parent object (e.g., "city"). 47 | * @returns The value at the specified key in the parent object or undefined if not found. 48 | */ 49 | getJsonValue(path: string, key: string): any { 50 | if (!this.jsonData) { 51 | console.log("No Json Data loaded") 52 | return undefined; 53 | } 54 | 55 | // First navigate to the parent object using the path 56 | const parentObject = path.split('.').reduce((acc, part) => { 57 | if (acc && acc[part] !== undefined) { 58 | return acc[part]; 59 | } 60 | return undefined; 61 | }, this.jsonData); 62 | 63 | // If we found the parent object, return the value for the specific key 64 | if (parentObject && parentObject[key] !== undefined) { 65 | return parentObject[key]; 66 | } 67 | console.log(`Path or key not found: ${path}.${key}`); 68 | 69 | return undefined; 70 | } 71 | } 72 | 73 | 74 | // Example usage 75 | 76 | 77 | // function readData() { 78 | // const jsonReader = new JsonReader('./orangeHRM.json'); // Update with your actual JSON file path 79 | // // const jsonData = jsonReader.readJsonFile(); 80 | // if (jsonReader.jsonData) { 81 | // const value = jsonReader.getJsonValue("App.Login.OrangeHRMLogo", 'locatorValue'); // Example: Reading "city" from "user.address.city" 82 | // console.log("Extracted Value:", value); 83 | // } 84 | // } 85 | 86 | // readData(); -------------------------------------------------------------------------------- /src/utils/report/CustomReporterConfig.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FullConfig, 3 | FullResult, 4 | Reporter, 5 | Suite, 6 | TestCase, 7 | TestError, 8 | TestResult, 9 | TestStep, 10 | } from "@playwright/test/reporter"; 11 | import logger from './Logger' 12 | 13 | 14 | // Writes logs to console 15 | // logger.add(console); 16 | 17 | export default class CustomReporterConfig implements Reporter { 18 | constructor(options: { customOption?: string } = {}) { 19 | console.log(`playwright-framework-template ${options.customOption}`); 20 | } 21 | 22 | onBegin(config: FullConfig, suite: Suite): void { 23 | logger.log(`info`, 24 | `Test Suite Started : ${suite.title} , ${suite.allTests().length} tests` 25 | ); 26 | } 27 | onTestBegin(test: TestCase): void { 28 | logger.log(`info`,`Test Case Started : ${test.title}`); 29 | } 30 | 31 | onTestEnd(test: TestCase, result: TestResult): void { 32 | logger.log(`info`,`Test Case Completed : ${test.title} Status : ${result.status}`); 33 | } 34 | 35 | onStepBegin(test: TestCase, result: TestResult, step: TestStep): void { 36 | if (step.category === `test.step`) { 37 | logger.log(`info`,`Executing Step : ${step.title}`); 38 | } 39 | } 40 | 41 | onError(error: TestError): void { 42 | logger.log(`error`,`TestError : ${error.message}`); 43 | } 44 | 45 | onEnd( 46 | result: FullResult 47 | ): void | Promise< 48 | | void 49 | | { status?: "passed" | "failed" | "timedout" | "interrupted" | undefined } 50 | | undefined 51 | > { 52 | console.log(`Test Suite Completed : ${result.status}`); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/utils/report/Logger.ts: -------------------------------------------------------------------------------- 1 | import winston from 'winston'; 2 | 3 | const logger = winston.createLogger({ 4 | level: 'info', 5 | format: winston.format.combine( 6 | winston.format.timestamp(), 7 | winston.format.json() 8 | ), 9 | transports: [ 10 | new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), 11 | new winston.transports.File({ filename: 'logs/combined.log' }), 12 | new winston.transports.Console({ 13 | format: winston.format.combine( 14 | winston.format.colorize(), 15 | winston.format.simple() 16 | ) 17 | }) 18 | ] 19 | }); 20 | 21 | export const logInfo = (message: string, meta?: any) => { 22 | logger.info(message, meta); 23 | }; 24 | 25 | export const logError = (message: string, error?: any) => { 26 | logger.error(message, { error }); 27 | }; 28 | 29 | export const logDebug = (message: string, meta?: any) => { 30 | logger.debug(message, meta); 31 | }; 32 | 33 | export const logFunctionCall = (funcName: string, args: Record)=> { 34 | const params = JSON.stringify(args); 35 | logger.info(`Function: ${funcName} | Parameters: ${params}`); 36 | } 37 | 38 | 39 | 40 | export default logger; 41 | -------------------------------------------------------------------------------- /src/utils/report/sendEmail.ts: -------------------------------------------------------------------------------- 1 | var nodeoutlook = require("nodejs-nodemailer-outlook"); 2 | 3 | function sendEmailReport() { 4 | nodeoutlook.sendEmail({ 5 | auth: { 6 | user: "xxxxx", 7 | pass: "password", 8 | }, 9 | from: "emailid", 10 | to: "emailid", 11 | subject: "subject", 12 | html: "Pls open attached HTML file", 13 | replyTo: "abhaybharti@gmail.com", 14 | attachments: [ 15 | { filename: "index.html", path: "../../../playwright-report/index.html" }, 16 | ], 17 | onError: (e: any) => console.log(e), 18 | onSuccess: (i: any) => console.log(i), 19 | }); 20 | } 21 | sendEmailReport(); 22 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abhaybharti/playwright-framework-template/16aa11a48535b8c4922f42629fcbffb9d126e0a7/src/utils/utils.ts -------------------------------------------------------------------------------- /testConfig.ts: -------------------------------------------------------------------------------- 1 | export const testConfig = { 2 | qa: `https://demoqa.com`, 3 | dev: ``, 4 | qaApi: `https://reqres.in`, 5 | devApi: ``, 6 | username: `demouat@gmail.com`, 7 | password: `U2FsdGVkX18/eMdsOJpvI4hJZ/w7hNgwSRFaDvAcZx4=`, 8 | waitForElement: 120000, 9 | dbUsername: ``, 10 | dbPassword: ``, 11 | dbServerName: ``, 12 | dbPort: ``, 13 | dbName: `` 14 | } -------------------------------------------------------------------------------- /tests/api/example/api.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, APIRequestContext } from "@playwright/test"; 2 | 3 | import { ApiHelper } from "../../../helper/api/apiHelper"; 4 | 5 | let token: string; 6 | let bookingId: string; 7 | 8 | test.beforeAll(async ({ request }) => { 9 | //1. Hit /Auth Api and provide username/password as body 10 | //2. fetch token value from JSON response 11 | //3. save in token variable 12 | 13 | const apiHelper = await new ApiHelper(request); 14 | const responseMsg = await apiHelper.invokePostApi("/auth", { 15 | username: "admin", 16 | password: "password123", 17 | }); 18 | 19 | expect(responseMsg.token); 20 | 21 | token = responseMsg.token; 22 | console.log(token); 23 | }); 24 | 25 | test("Get booking list and verify response -- No Authentication Required ", async ({ 26 | request, 27 | }) => { 28 | /* Test Flow 29 | 1. Hit API endpoint 30 | 2. Verify API status code 31 | 3. Verify JSON Response 32 | 4. Verify JSON Schema 33 | */ 34 | test.info().annotations.push({ 35 | type: "purpose", 36 | description: 37 | "This will make GET call to https://restful-booker.herokuapp.com/booking with no authentication", 38 | }); 39 | const apiHelper = await new ApiHelper(request); // 40 | const responseMsg = await apiHelper.invokeGetApi("/booking"); 41 | console.log(JSON.stringify(responseMsg)); 42 | for (let index = 0; index < responseMsg.length; index++) { 43 | test.info().annotations.push({ 44 | type: "value", 45 | description: `BookingId : ${responseMsg[index].bookingid}`, 46 | }); 47 | expect(responseMsg[index].bookingid).not.toBeNull(); 48 | } 49 | }); 50 | 51 | test("Get Booking Details using BookingID --> 1914", async ({ request }) => { 52 | /* Test Flow 53 | 1. Hit API endpoint 54 | 2. Verify API status code 55 | 3. Verify JSON Response 56 | 4. Verify JSON Schema 57 | */ 58 | test.info().annotations.push({ 59 | type: "purpose", 60 | description: 61 | "This will make GET call to https://restful-booker.herokuapp.com/booking/:id and verify keys/values in response", 62 | }); 63 | 64 | const apiHelper = await new ApiHelper(request); // 65 | const bookingDetails = await apiHelper.invokeGetApi("/booking/1914"); 66 | console.log(JSON.stringify(bookingDetails)); 67 | 68 | expect(bookingDetails.firstname).toBe("John"); 69 | expect(bookingDetails.lastname).toBe("Allen"); 70 | expect(bookingDetails.totalprice).toBe(111); 71 | expect(bookingDetails.depositpaid).toBeTruthy(); 72 | expect(apiHelper.isValidDate(bookingDetails.bookingdates.checkin)).toBe(true); 73 | expect(apiHelper.isValidDate(bookingDetails.bookingdates.checkout)).toBe( 74 | true 75 | ); 76 | expect(bookingDetails.additionalneeds).toBe("super bowls"); 77 | }); 78 | 79 | test("Get booking list, pass to booking/:id API and verify response -- No Authentication Required ", async ({ 80 | request, 81 | }) => { 82 | /* Test Flow 83 | 1. Hit API endpoint 84 | 2. Verify API status code 85 | 3. Verify JSON Response 86 | 4. Verify JSON Schema 87 | */ 88 | test.info().annotations.push({ 89 | type: "purpose", 90 | description: 91 | "This will make GET call to https://restful-booker.herokuapp.com/booking with no authentication", 92 | }); 93 | const apiHelper = await new ApiHelper(request); // 94 | const responseMsg = await apiHelper.invokeGetApi("/booking"); 95 | console.log(JSON.stringify(responseMsg)); 96 | for (let index = 0; index < responseMsg.length; index++) { 97 | test.info().annotations.push({ 98 | type: "value", 99 | description: `BookingId : ${responseMsg[index].bookingid}`, 100 | }); 101 | expect(responseMsg[index].bookingid).not.toBeNull(); 102 | let bookingDetail = await apiHelper.invokeGetApi( 103 | `/booking/${responseMsg[index].bookingid}` 104 | ); 105 | console.log(JSON.stringify(bookingDetail)); 106 | } 107 | }); 108 | //API used for writing test code - https://restful-booker.herokuapp.com/apidoc/index.html 109 | -------------------------------------------------------------------------------- /tests/api/example/api1.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import { api } from './utils/apiHelper'; 3 | import { handleError } from './utils/errorHandler'; 4 | 5 | interface Post { 6 | userId: number; 7 | id: number; 8 | title: string; 9 | body: string; 10 | } 11 | 12 | interface User { 13 | id: number; 14 | name: string; 15 | username: string; 16 | email: string; 17 | } 18 | 19 | test.describe('JSONPlaceholder API Tests', () => { 20 | test('should get all posts', async () => { 21 | try { 22 | const response = await api.get('/posts'); 23 | expect(response.status).toBe(200); 24 | expect(Array.isArray(response.data)).toBeTruthy(); 25 | expect(response.data.length).toBeGreaterThan(0); 26 | 27 | const firstPost = response.data[0]; 28 | expect(firstPost).toHaveProperty('id'); 29 | expect(firstPost).toHaveProperty('title'); 30 | expect(firstPost).toHaveProperty('body'); 31 | expect(firstPost).toHaveProperty('userId'); 32 | } catch (error) { 33 | handleError(error); 34 | } 35 | }); 36 | 37 | test('should create a new post', async () => { 38 | try { 39 | const newPost = { 40 | title: 'Test Post', 41 | body: 'This is a test post', 42 | userId: 1 43 | }; 44 | 45 | const response = await api.post('/posts', newPost); 46 | expect(response.status).toBe(201); 47 | expect(response.data).toHaveProperty('id'); 48 | expect(response.data.title).toBe(newPost.title); 49 | expect(response.data.body).toBe(newPost.body); 50 | } catch (error) { 51 | handleError(error); 52 | } 53 | }); 54 | 55 | test('should update a post', async () => { 56 | try { 57 | const updatedPost = { 58 | title: 'Updated Post', 59 | body: 'This post has been updated', 60 | userId: 1 61 | }; 62 | 63 | const response = await api.put('/posts/1', updatedPost); 64 | expect(response.status).toBe(200); 65 | expect(response.data.title).toBe(updatedPost.title); 66 | expect(response.data.body).toBe(updatedPost.body); 67 | } catch (error) { 68 | handleError(error); 69 | } 70 | }); 71 | 72 | test('should delete a post', async () => { 73 | try { 74 | const response = await api.delete<{}>('/posts/1'); 75 | expect(response.status).toBe(200); 76 | } catch (error) { 77 | handleError(error); 78 | } 79 | }); 80 | 81 | test('should handle 404 error gracefully', async () => { 82 | try { 83 | await api.get('/nonexistent-endpoint'); 84 | } catch (error) { 85 | expect(error.statusCode).toBe(404); 86 | } 87 | }); 88 | }); -------------------------------------------------------------------------------- /tests/api/example/dashboard.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test, APIRequestContext } from "@playwright/test"; 2 | import moment from "moment"; 3 | import { ApiHelper } from "../../../helper/api/apiHelper"; 4 | 5 | let token: string; 6 | let bookingId: string; 7 | 8 | test.beforeAll(async ({ request }) => { 9 | //1. Hit /Auth Api and provide username/password as body 10 | //2. fetch token value from JSON response 11 | //3. save in token variable 12 | 13 | const apiHelper = await new ApiHelper(request); 14 | }); 15 | 16 | const testSuiteTestScriptObject = { 17 | Product_Buy_Flow: 18 | "Verify_that_on_the_product_page,_the_user_can_select_the_desired_attribute_of_the_product_eg_size_color_etc,Verify_that_the_user_can_add_to_the_cart_one_or_more_products,Verify_that_users_can_add_products_to_the_wishlist,Verify_that_the_user_can_see_the_previously_added_products_on_the_cart_page_after_signing_in_to_the_application,Verify_that_the_user_can_successfully_buy_more_than_one_products_that_were_added_to_his/her_cart,Verify_that_the_user_cannot_add_more_than_the_available_inventory_of_the_product,Verify_that_the_limit_to_the_number_of_products_a_user_can_buy_is_working_correctly_Also_an_error_message_gets_displayed_preventing_the_user_from_buying_more_than_the_limit,Verify_that_the_delivery_can_be_declined_during_checkout_for_the_places_where_shipping_is_not_available,Verify_that_the_Cash_on_Delivery_option_of_payment_is_working_fine,Verify_that_the_different_prepaid_methods_of_payments_are_working_fine,Verify_that_product_return_functionality_works_correctly", 19 | User_Registration: 20 | "Verify_that_all_the_required_fields__username,_email,_password,_confirm_password,_etc_are_present_on_the_registration_page,Verify_that_on_passing_valid_values,_a_user_should_get_registered_and_the_same_should_be_allowed_to_log_in_to_the_application,Verify_that_if_a_user_tries_to_register_an_existing_username_then_an_error_message_should_get_displayed,Verify_that_the_required/mandatory_fields_are_marked_with_the_‘*’_symbol,Verify_that_for_a_better_user_interface__dropdowns,_radio_buttons,_checkboxes,_etc_fields_are_displayed_wherever_possible_instead_of_just_text_boxes,Verify_the_page_has_both_submit_and_cancel/reset_buttons_at_the_end,Verify_that_clicking_submits_button_after_entering_all_the_required_fields,_submits_the_data_to_the_server,Verify_that_clicking_the_cancel/reset_button_after_entering_all_the_required_fields,_cancels_the_submit_request,_and_reset_all_the_fields,Verify_that_if_no_value_is_passed_to_the_mandatory_fields_and_submit_button_is_clicked_then_it_leads_to_a_validation_error,Verify_that_the_user_can_leave_the_optional_fields_blank_and_on_clicking_the_submit_button_no_validation_error_appears,Verify_that_whenever_possible_validation_should_take_place_on_the_client_side_For_example,_if_a_user_presses_submit_button_without_entering_a_username_and_password_then_this_validation_should_take_place_on_the_client_side_instead_of_sending_blank_entries_to_the_server,Check_the_upper_limit_of_the_different_textbox_fields,Verify_validation_on_the_date_and_email_fields_Only_valid_dates_and_valid_email_Ids_should_be_allowed,Check_validation_on_numeric_fields_by_entering_alphabets_and_special_characters,Check_that_leading_and_trailing_spaces_are_trimmed_ie_in_case,_the_user_appends_space_before_and_after_a_field,_then_the_same_should_get_trimmed_before_getting_stored_on_the_server", 21 | Portal_Validation: 22 | "Verify_that_the_company_logo_and_name_are_clearly_visible,Verify_that_the_user_is_able_to_navigate_through_all_the_products_across_different_categories,Verify_that_all_the_links_and_banners_are_redirecting_to_the_correct_product/category_pages_and_none_of_the_links_are_broken,Verify_that_all_the_information_displayed__product_name,_category_name,_price,_and_product_description_is_clearly_visible,Verify_that_all_the_images__product_and_banner_are_clearly_visible,Verify_that_category_pages_have_a_relevant_product_listed,_specific_to_the_category,Verify_that_the_correct_count_of_total_products_is_displayed_on_the_category_pages,Search__Verify_that_on_searching,_all_the_products_satisfying_the_search_criteria_are_visible_on_the_search_result_page,Search__Verify_that_on_searching,_products_get_displayed_on_the_basis_of_their_relevancy,Search__Verify_that_the_count_of_products_is_correctly_displayed_on_the_search_result_page_for_a_particular_search_term,Filtering__Verify_that_filtering_functionality_correctly_filters_products_based_on_the_filter_applied,Filtering__Verify_that_filtering_works_correctly_on_category_pages,Filtering__Verify_that_filtering_works_correctly_on_the_search_result_page,Filtering__Verify_that_the_correct_count_of_total_products_is_displayed_after_a_filter_is_applied,Filtering__Verify_that_the_correct_count_and_products_get_displayed_on_applying_multiple_filters,Sorting__Verify_that_all_the_sort_options_work_correctly_On_sorting_the_products_based_on_the_sort_option_chosen,Sorting__Verify_that_sorting_works_correctly_on_the_category_pages,Sorting__Verify_that_sorting_works_correctly_on_the_search_result_page,Sorting__Verify_that_sorting_works_correctly_on_the_pages_containing_the_filtered_result_after_applying_filters,Sorting__Verify_that_the_product_count_remains_the_same_irrespective_of_the_sorting_option_applied", 23 | }; 24 | 25 | // for (const suiteName in testSuiteTestScriptObject) { 26 | // let testCaseList = testSuiteTestScriptObject[suiteName].split(","); 27 | // for (let iLoop = 0; testCaseList[iLoop]; iLoop++) { 28 | // //console.log(testCaseList[iLoop]); 29 | // test(`${testCaseList[iLoop]}`, async ({ request }) => { 30 | // const apiHelper = await new ApiHelper(request); // 31 | // let todayDate = new Date(); 32 | // let env = "SIT"; 33 | // let runid = `ecom_run_${moment().format("YYYY-MM-DD")}`; //ecom_run_02_17_2024 34 | // let payLoad = { 35 | // runid: runid, 36 | // suite: suiteName, 37 | // testcasename: testCaseList[iLoop], 38 | // status: "PASS", 39 | // env: "SIT", 40 | // failurereason: "", 41 | // duration: 10, 42 | // browser: null, 43 | // timestamp: moment().format("YYYY-MM-DD HH:mm:ss"), //"2024-03-17 16:40:48", 44 | // reportpath: "abc.html", 45 | // subscriptionkey: "clttt86dt0000mxs8a9thes2h", 46 | // testid: `${runid}|${suiteName}|${testCaseList[iLoop]}|${env}`, 47 | // executiondate: `${moment().format("YYYY-MM-DD")}`, 48 | // }; 49 | // console.log("payload : ", payLoad); 50 | // const responseMsg = await apiHelper.invokePostApi( 51 | // "/api/testcase/updateTestCaseExecution", 52 | // payLoad 53 | // ); 54 | // }); 55 | // } 56 | // } 57 | -------------------------------------------------------------------------------- /tests/api/example/post.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import { ApiHelper } from '../../../helper/api/apiHelper'; 3 | 4 | test.describe('Posts API', () => { 5 | let apiHelper: ApiHelper; 6 | 7 | test.beforeEach(async ({ request }) => { 8 | apiHelper = new ApiHelper(request); 9 | }); 10 | 11 | test('should get all posts', async () => { 12 | const response = await apiHelper.get('/posts'); 13 | expect(response.status).toBe(200); 14 | expect(Array.isArray(response.data)).toBeTruthy(); 15 | expect(response.data.length).toBeGreaterThan(0); 16 | }); 17 | 18 | test('should get a single post', async () => { 19 | const response = await apiHelper.get('/posts/1'); 20 | expect(response.status).toBe(200); 21 | expect(response.data.id).toBe(1); 22 | expect(response.data.title).toBeTruthy(); 23 | expect(response.data.body).toBeTruthy(); 24 | }); 25 | 26 | test('should create a new post', async () => { 27 | const postData = { 28 | title: 'Test Post', 29 | body: 'This is a test post', 30 | userId: 1 31 | }; 32 | const response = await apiHelper.post('/posts', postData); 33 | expect(response.status).toBe(201); 34 | expect(response.data.title).toBe(postData.title); 35 | expect(response.data.body).toBe(postData.body); 36 | expect(response.data.userId).toBe(postData.userId); 37 | expect(response.data.id).toBeTruthy(); 38 | }); 39 | 40 | test('should update a post', async () => { 41 | const updateData = { 42 | title: 'Updated Post', 43 | body: 'This post has been updated', 44 | userId: 1 45 | }; 46 | const response = await apiHelper.put('/posts/1', updateData); 47 | expect(response.status).toBe(200); 48 | expect(response.data.title).toBe(updateData.title); 49 | expect(response.data.body).toBe(updateData.body); 50 | }); 51 | 52 | test('should delete a post', async () => { 53 | const response = await apiHelper.delete('/posts/1'); 54 | expect(response.status).toBe(200); 55 | }); 56 | }); -------------------------------------------------------------------------------- /tests/api/example/user.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | import { ApiHelper } from '../../../helper/api/apiHelper'; 3 | 4 | test.describe('Users API', () => { 5 | let apiHelper: ApiHelper; 6 | 7 | test.beforeEach(async ({ request }) => { 8 | apiHelper = new ApiHelper(request); 9 | }); 10 | 11 | test('should get all users', async () => { 12 | const response = await apiHelper.get('/users'); 13 | expect(response.status).toBe(200); 14 | expect(Array.isArray(response.data)).toBeTruthy(); 15 | expect(response.data.length).toBeGreaterThan(0); 16 | }); 17 | 18 | test('should get a single user', async () => { 19 | const response = await apiHelper.get('/users/1'); 20 | expect(response.status).toBe(200); 21 | expect(response.data.id).toBe(1); 22 | expect(response.data.name).toBeTruthy(); 23 | expect(response.data.email).toBeTruthy(); 24 | }); 25 | 26 | test('should create a new user', async () => { 27 | const userData = { 28 | name: 'John Doe', 29 | email: 'john@example.com', 30 | username: 'johndoe', 31 | website: 'example.com' 32 | }; 33 | const response = await apiHelper.post('/users', userData); 34 | expect(response.status).toBe(201); 35 | expect(response.data.name).toBe(userData.name); 36 | expect(response.data.email).toBe(userData.email); 37 | expect(response.data.id).toBeTruthy(); 38 | }); 39 | 40 | test('should update a user', async () => { 41 | const updateData = { 42 | name: 'Jane Doe', 43 | email: 'jane@example.com' 44 | }; 45 | const response = await apiHelper.patch('/users/1', updateData); 46 | expect(response.status).toBe(200); 47 | expect(response.data.name).toBe(updateData.name); 48 | expect(response.data.email).toBe(updateData.email); 49 | }); 50 | }); -------------------------------------------------------------------------------- /tests/e2e/moatazeldebsy/form.spec.ts: -------------------------------------------------------------------------------- 1 | import {test} from "@tests/fixtures/customFixtures" 2 | 3 | test("Enter value in forms @form", async ({ page,web }) => { 4 | await page.goto("https://moatazeldebsy.github.io/test-automation-practices/#/forms"); 5 | await web.changeValueOnUi({elementName:"Basic.Forms.UsernameText"}) 6 | }); 7 | 8 | 9 | -------------------------------------------------------------------------------- /tests/fixtures/customFixtures.ts: -------------------------------------------------------------------------------- 1 | import { test as base, BrowserContext } from '@playwright/test' 2 | import { ApiHelper, SshHelper, WebHelper } from "../../src/helper"; 3 | import { pwApi } from 'pw-api-plugin'; 4 | 5 | import { } from "@playwright/test"; 6 | import { JsonReader } from '@src/utils/reader/jsonReader'; 7 | 8 | type MyMixtures = { 9 | context: BrowserContext 10 | } 11 | 12 | export const test = base.extend<{ api: ApiHelper; web: WebHelper; ssh: SshHelper; MyMixtures: any;json:JsonReader; config: { jsonPath: string } }>({ 13 | config: async ({}, use) => { 14 | // Get JSON path from environment variable or use default 15 | const jsonPath = process.env.JSON_PATH || 'moataeldebsy.json'; 16 | await use({ jsonPath }); 17 | }, 18 | api: async ({ page}, use) => { 19 | await use(new ApiHelper(page,pwApi)) 20 | }, 21 | 22 | web: async ({ page, browser,config }, use) => { 23 | console.log(config.jsonPath) 24 | const context = await browser.newContext(); 25 | await use(new WebHelper(page, context,config.jsonPath)); 26 | }, 27 | ssh: async ({ }, use) => { 28 | await use(new SshHelper()) 29 | }, 30 | json:async({config},use)=>{ 31 | console.log('Initializing JSON reader with:', config.jsonPath); 32 | await use(new JsonReader(config.jsonPath)) 33 | 34 | // npx playwright test --config:jsonPath=./alternative-config.json -- command to pass json filename 35 | 36 | //npx playwright test --config:jsonPath=./moataeldebsy.json --debug ./tests/e2e/moatazeldebsy/form.spec.ts 37 | } 38 | }) 39 | 40 | -------------------------------------------------------------------------------- /tests/fixtures/test-options.ts: -------------------------------------------------------------------------------- 1 | //This file is used for merging all extended test fixtures 2 | import { mergeTests, test as base } from "@playwright/test"; 3 | import {test as customFixture} from "./customFixtures"; 4 | 5 | const test = mergeTests(customFixture); 6 | 7 | const expect = base.expect; 8 | export {test,expect}; -------------------------------------------------------------------------------- /tests/load/BrowerLoadTest.spec.js: -------------------------------------------------------------------------------- 1 | const withTransactionTimer = async (transactionName, events, userActions) => { 2 | const startedTime = Date.now(); 3 | await userActions(); 4 | const difference = Date.now() - startedTime; 5 | events.emit("histogram", transactionName, difference); 6 | }; 7 | 8 | async function browserloadTest(page, userContext, events) { 9 | await withTransactionTimer("login", events, async () => { 10 | await page.goto( 11 | "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login" 12 | ); 13 | await page.getByPlaceholder("Username").click(); 14 | await page.getByPlaceholder("Username").fill("Admin"); 15 | await page.getByPlaceholder("Password").fill("admin123"); 16 | await page.getByRole("button", { name: "Login" }).click(); 17 | await page.waitForLoadState("domcontentloaded"); 18 | }); 19 | 20 | await withTransactionTimer("OpenAdminPage", events, async () => { 21 | await page.getByRole("link", { name: "Admin" }).click(); 22 | await page.waitForLoadState("domcontentloaded"); 23 | }); 24 | 25 | await withTransactionTimer("OpenPimPage", events, async () => { 26 | await page.getByRole("link", { name: "PIM" }).click(); 27 | await page.waitForLoadState("domcontentloaded"); 28 | }); 29 | 30 | await withTransactionTimer("OpenTimePage", events, async () => { 31 | await page.getByRole("link", { name: "Time" }).click(); 32 | await page.waitForLoadState("domcontentloaded"); 33 | }); 34 | 35 | await withTransactionTimer("OpenRecruitmentPage", events, async () => { 36 | await page.getByRole("link", { name: "Recruitment" }).click(); 37 | await page.waitForLoadState("domcontentloaded"); 38 | }); 39 | 40 | await withTransactionTimer("OpenMyInfoPage", events, async () => { 41 | await page.getByRole("link", { name: "My Info" }).click(); 42 | await page.waitForLoadState("domcontentloaded"); 43 | }); 44 | 45 | await withTransactionTimer("OpenPerformancePage", events, async () => { 46 | await page.getByRole("link", { name: "Performance" }).click(); 47 | await page.waitForLoadState("domcontentloaded"); 48 | }); 49 | } 50 | 51 | module.exports = { browserloadTest }; 52 | -------------------------------------------------------------------------------- /tests/mobile/example/healthcheckAndroid.test.ts: -------------------------------------------------------------------------------- 1 | import { AppiumHelper } from "../../../helper/mobile/appiumHelper"; 2 | 3 | const androidCapabilities = { 4 | platformName: "Android", 5 | "appium:automationName": "UiAutomator2", 6 | "appium:deviceName": "Android", 7 | "appium:appPackage": "com.android.settings", 8 | "appium:appActivity": ".Settings", 9 | "appium:locale": "US", 10 | "appium:language": "en", 11 | }; 12 | 13 | // describe("Healthcheck Android Appium connection", function () { 14 | // let app: AppiumHelper; 15 | 16 | // before(async () => { 17 | // app = new AppiumHelper(); 18 | // await app.init(androidCapabilities); 19 | // }); 20 | 21 | // after(async () => { 22 | // await app.quit(); 23 | // }); 24 | 25 | // it("checks battery level on Settings App", async () => { 26 | // const imageButton = await app.findElement("android.widget.ImageButton"); 27 | // await imageButton.click(); 28 | // const editText = await app.findElement( 29 | // "android=new UiSelector().className(android.widget.EditText)" 30 | // ); 31 | // await editText.click(); 32 | // await editText.setValue("bat"); 33 | // const linearLayout = await app.findElement( 34 | // "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout[2]/android.widget.ScrollView/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.FrameLayout/android.support.v7.widget.RecyclerView/android.widget.LinearLayout[3]/android.widget.LinearLayout" 35 | // ); 36 | // await linearLayout.click(); 37 | // const progressBar = await app.findElement( 38 | // "android=new UiSelector().className(android.widget.ProgressBar)" 39 | // ); 40 | // await progressBar.isDisplayed(); 41 | // }); 42 | // }); 43 | -------------------------------------------------------------------------------- /tests/mobile/example/healthcheckiOS.test.ts: -------------------------------------------------------------------------------- 1 | import { AppiumHelper } from "../../../helper/mobile/appiumHelper"; 2 | 3 | const iOSVersion = "16.4"; 4 | 5 | const capabilities = { 6 | platformName: "iOS", 7 | "appium:platformVersion": iOSVersion, 8 | "appium:deviceName": "iPhone 13 Pro Max", 9 | "appium:automationName": "XCUITest", 10 | "appium:app": "com.apple.Preferences", 11 | "appium:locale": "US", 12 | "appium:language": "en", 13 | }; 14 | 15 | // describe("Healthcheck iOS Appium connection", function () { 16 | // let app: AppiumHelper; 17 | 18 | // before(async () => { 19 | // app = new AppiumHelper(); 20 | // await app.init(capabilities); 21 | // }); 22 | 23 | // after(async () => { 24 | // await app.quit(); 25 | // }); 26 | 27 | // it("checks iOS version number on Settings App", async () => { 28 | // // Go the the "General" section 29 | // const generalElement = await app.findElement( 30 | // '//XCUIElementTypeCell[@name="General"]' 31 | // ); 32 | // await generalElement.click(); 33 | 34 | // // Go the the "About" section 35 | // const aboutElement = await app.findElement( 36 | // '//XCUIElementTypeCell[@name="About"]' 37 | // ); 38 | // await aboutElement.click(); 39 | 40 | // // Go the the "iOS Version" section 41 | // const versionElement = await app.findElement( 42 | // '//XCUIElementTypeCell[@name="iOS Version"]' 43 | // ); 44 | // await versionElement.click(); 45 | 46 | // // Check the version is the on expected 47 | // const iosVersionElement = await app.findElement( 48 | // `//XCUIElementTypeButton[contains(@name, "iOS ${iOSVersion}")]` 49 | // ); 50 | // const isDisplayed = await iosVersionElement.isDisplayed(); 51 | 52 | // if (!isDisplayed) { 53 | // throw new Error( 54 | // `Could not find iOS version label ${iOSVersion} on the device` 55 | // ); 56 | // } 57 | // }); 58 | // }); 59 | -------------------------------------------------------------------------------- /tests/mobile/example/mobiletest.spec.js: -------------------------------------------------------------------------------- 1 | import { remote } from "webdriverio"; 2 | 3 | const capabilities = { 4 | platformName: "Android", 5 | "appium:automationName": "UiAutomator2", 6 | "appium:deviceName": "Android", 7 | "appium:appPackage": "com.android.settings", 8 | "appium:appActivity": ".Settings", 9 | }; 10 | 11 | const wdOpts = { 12 | hostname: process.env.APPIUM_HOST || "localhost", 13 | port: parseInt(process.env.APPIUM_PORT, 10) || 4723, 14 | logLevel: "info", 15 | capabilities, 16 | }; 17 | 18 | // async function runTest() { 19 | // const driver = await remote(wdOpts); 20 | // try { 21 | // const batteryItem = await driver.$('//*[@text="Battery"]'); 22 | // await batteryItem.click(); 23 | // } finally { 24 | // await driver.pause(1000); 25 | // await driver.deleteSession(); 26 | // } 27 | // } 28 | 29 | // runTest().catch(console.error); 30 | -------------------------------------------------------------------------------- /tests/resources/uiMap/moataeldebsy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Basic": { 3 | "Forms": { 4 | "Username": { 5 | "objType": "Label", 6 | "locatorType": "xpath", 7 | "locatorValue": "//label[@for='username']", 8 | "label": "Username" 9 | }, 10 | "UsernameText": { 11 | "objType": "textbox", 12 | "locatorType": "xpath", 13 | "locatorValue": "//input[@data-test='username-input']", 14 | "saveTestData": "A|B", 15 | "invalidTestData": "", 16 | "validTestData": "" 17 | }, 18 | "Email": { 19 | "objType": "Label", 20 | "locatorType": "xpath", 21 | "locatorValue": "//label[@for='email']", 22 | "label": "Email" 23 | }, 24 | "EmailText": { 25 | "objType": "textbox", 26 | "locatorType": "xpath", 27 | "locatorValue": "//input[@data-test='email-input']", 28 | "saveTestData": "", 29 | "invalidTestData": "", 30 | "validTestData": "" 31 | }, 32 | "Password": { 33 | "objType": "Label", 34 | "locatorType": "xpath", 35 | "locatorValue": "//label[@for='password']", 36 | "label": "Password" 37 | }, 38 | "PasswordText": { 39 | "objType": "textbox", 40 | "locatorType": "xpath", 41 | "locatorValue": "//input[@data-test='password-input']", 42 | "saveTestData": "", 43 | "invalidTestData": "", 44 | "validTestData": "" 45 | }, 46 | "SignIn": { 47 | "objType": "button", 48 | "locatorType": "xpath", 49 | "locatorValue": "//button[@data-test='submit-button']", 50 | "label": "Sign In" 51 | } 52 | }, 53 | "Table": {} 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/resources/uiMap/orangeHRM.json: -------------------------------------------------------------------------------- 1 | { 2 | "App": { 3 | "Login": { 4 | "objType": "page", 5 | "OrangeHRMLogo": { 6 | "objType": "img", 7 | "locatorType": "xpath", 8 | "locatorValue": "//img[@alt='company-branding']" 9 | }, 10 | "LoginInfo": {}, 11 | "UsernameText": { 12 | "objType": "text", 13 | "locatorType": "xpath", 14 | "locatorValue": "//label[normalize-space()='Username']", 15 | "label": "Username" 16 | }, 17 | "Username": { 18 | "objType": "input", 19 | "locatorType": "xpath", 20 | "locatorValue": "//input[@placeholder='Username']", 21 | "label": "", 22 | "validTestData": "a,abcde", 23 | "invalidTestData": ",abcdef,%%%%,$$$$", 24 | "saveTestData": "admin,test", 25 | "fieldErrorMessage": "Required,invalid error" 26 | }, 27 | "PasswordText": { 28 | "objType": "text", 29 | "locatorType": "xpath", 30 | "locatorValue": "//label[normalize-space()='Password']", 31 | "label": "Password" 32 | }, 33 | "Password": { 34 | "objType": "input", 35 | "locatorType": "xpath", 36 | "locatorValue": "//input[@placeholder='Password']", 37 | "label": "", 38 | "validTestData": "", 39 | "invalidTestData": "", 40 | "saveTestData": "", 41 | "fieldErrorMessage": "" 42 | }, 43 | "Login": { 44 | "elementType": "button", 45 | "locatorType": "xpath", 46 | "locatorValue": "//button[normalize-space()='Login']", 47 | "labelName": "Login" 48 | }, 49 | "Forgotyourpassword": { 50 | "elementType": "link", 51 | "locatorType": "xpath", 52 | "locatorValue": "//p[normalize-space()='Forgot your password?']", 53 | "labelName": "Forgot your password?" 54 | } 55 | }, 56 | "Admin": { 57 | "objType": "sidebar", 58 | "locatorType": "xpath", 59 | "locatorValue": "//a[@class='oxd-main-menu-item active']//span[1]", 60 | "label": "Admin", 61 | "UserManagement": { 62 | "objType": "tab", 63 | "locatorType": "xpath", 64 | "locatorValue": "//span[normalize-space()='User Management']", 65 | "label": "User Management", 66 | "Users": { 67 | "objType": "subtab", 68 | "locatorType": "xpath", 69 | "locatorValue": "//a[text()='Users']", 70 | "SystemUsers": { 71 | "objType": "section", 72 | "locatorType": "xpath", 73 | "locatorValue": "//*[text()='System Users']", 74 | "UsernameText": { 75 | "objType": "text", 76 | "locatorType": "xpath", 77 | "locatorValue": "//label[text()='Username']", 78 | "label": "Username", 79 | "Username": { 80 | "objType": "input", 81 | "locatorType": "xpath", 82 | "locatorValue": "//input[@class='oxd-input oxd-input--active']", 83 | "label": "", 84 | "validTestData": "", 85 | "invalidTestData": "", 86 | "saveTestData": "", 87 | "fieldErrorMessage": "", 88 | "UserRoleText": { 89 | "objType": "text", 90 | "locatorType": "xpath", 91 | "locatorValue": "//label[text()='User Role']", 92 | "label": "User Role", 93 | "UserRole": { 94 | "objType": "input", 95 | "locatorType": "xpath", 96 | "locatorValue": "//div[@class='oxd-select-text-input']", 97 | "label": "", 98 | "validTestData": "", 99 | "invalidTestData": "", 100 | "saveTestData": "", 101 | "fieldErrorMessage": "", 102 | "EmployeeNameText": { 103 | "objType": "text", 104 | "locatorType": "xpath", 105 | "locatorValue": "//label[text()='Employee Name']", 106 | "label": "Employee Name", 107 | "EmployeeName": { 108 | "objType": "input", 109 | "locatorType": "xpath", 110 | "locatorValue": "//input[@placeholder='Type for hints...']", 111 | "label": "", 112 | "validTestData": "", 113 | "invalidTestData": "", 114 | "saveTestData": "", 115 | "fieldErrorMessage": "", 116 | "StatusText": { 117 | "objType": "text", 118 | "locatorType": "xpath", 119 | "locatorValue": "//label[text()='Status']", 120 | "label": "User Role", 121 | "Status": { 122 | "objType": "input", 123 | "locatorType": "xpath", 124 | "locatorValue": "//div[@class='oxd-select-text-input']", 125 | "label": "", 126 | "validTestData": "", 127 | "invalidTestData": "", 128 | "saveTestData": "", 129 | "fieldErrorMessage": "", 130 | "Reset": { 131 | "elementType": "button", 132 | "locatorType": "xpath", 133 | "locatorValue": "//button[text()=' Reset ']", 134 | "labelName": "Reset", 135 | "Search": { 136 | "elementType": "button", 137 | "locatorType": "xpath", 138 | "locatorValue": "//button[text()=' Search ']", 139 | "labelName": "Search", 140 | "Add": { 141 | "elementType": "button", 142 | "locatorType": "xpath", 143 | "locatorValue": "//button[text()=' Add ']", 144 | "labelName": "Add", 145 | "RecordsTable": { 146 | "objType": "table/grid", 147 | "locatorType": "xpath", 148 | "locatorValue": "//div[@role='table']" 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /tests/web/IntercpetFailingAPI.js: -------------------------------------------------------------------------------- 1 | //Create a setup.ts File (Global Hook) 2 | //Create a new file named setup.ts inside your tests/ folder (or any preferred directory). This file will extend Playwright’s test configuration. 3 | 4 | import { test as base } from '@playwright/test'; 5 | 6 | // Extend Playwright's test object to include API monitoring in all tests 7 | export const test = base.extend<{ page: any }>({ 8 | page: async ({ browser }, use) => { 9 | const context = await browser.newContext(); 10 | const page = await context.newPage(); 11 | 12 | // Listen to all network requests 13 | page.on('request', request => { 14 | console.log(`➡️ Request: ${request.method()} ${request.url()}`); 15 | }); 16 | 17 | // Intercept API responses and check for failures 18 | page.on('response', async response => { 19 | if (!response.ok()) { 20 | console.error(`❌ Failed API Call: ${response.url()}`); 21 | console.error(`Status Code: ${response.status()}`); 22 | 23 | const request = response.request(); 24 | const requestData = request.postData(); 25 | const responseBody = await response.text(); 26 | 27 | console.error(`📌 Request Data: ${requestData || 'No Body'}`); 28 | console.error(`📌 Response Body: ${responseBody || 'No Response Body'}`); 29 | } 30 | }); 31 | 32 | await use(page); // Makes `page` available for all tests 33 | await page.close(); 34 | } 35 | }); 36 | 37 | export { expect } from '@playwright/test'; 38 | 39 | 40 | // Use the Extended Test in Your Spec Files 41 | // Modify all your spec files (*.spec.ts) to import the extended test object from setup.ts instead of Playwright's default test. 42 | 43 | // Example: tests/example.spec.ts 44 | 45 | import { expect } from '../setup'; // Import from setup.ts 46 | 47 | test.describe('UI Test Suite with API Monitoring', () => { 48 | test('Test Case 1 - Navigate to Homepage', async ({ page }) => { 49 | await page.goto('https://example.com'); 50 | await page.click('button#submit'); 51 | await page.waitForTimeout(2000); 52 | }); 53 | 54 | test('Test Case 2 - Login Flow', async ({ page }) => { 55 | await page.goto('https://example.com/login'); 56 | await page.fill('input[name="username"]', 'testUser'); 57 | await page.fill('input[name="password"]', 'password123'); 58 | await page.click('button#login'); 59 | await page.waitForTimeout(2000); 60 | }); 61 | }); 62 | 63 | 64 | //Prompt 65 | //Write playwright script using typescript which should log all API requests failures across test files when UI test is running. -------------------------------------------------------------------------------- /tests/web/example/alert.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "@playwright/test"; 2 | import { WebHelper } from "../../../helper/web/webHelper"; 3 | 4 | test("Test 1 : Subscribe on dialog and call dismiss by clicking on Ok button", async ({ 5 | page, 6 | browser, 7 | }) => { 8 | const context = await browser.newContext(); 9 | const webHelper = new WebHelper(page, context); 10 | 11 | const expectedText = "I am JS Alert"; 12 | //setup listener to handle alert box 13 | webHelper.acceptAlertBox(); 14 | 15 | //write code to open alert box 16 | await webHelper.clickByText("click to open alert box"); 17 | 18 | //Assert 19 | expect(await webHelper.getAlertText()).toBe(expectedText); 20 | }); 21 | 22 | test("Test 2 : Subscribe on dialog and call accept by clicking on Ok button and dismiss by clicking on Cancel button", async ({ 23 | page, 24 | browser, 25 | }) => { 26 | const context = await browser.newContext(); 27 | const webHelper = new WebHelper(page, context); 28 | 29 | const expectedText = "I am JS Confirm box"; 30 | //setup listener to click on Ok button on confirm box 31 | webHelper.acceptConfirmBox(); 32 | 33 | //write code to open alert box 34 | await webHelper.clickByText("click to open Confirm box"); 35 | 36 | //Assert 37 | expect(await webHelper.getAlertText()).toBe(expectedText); 38 | 39 | //setup listener to click on Cancel button on confirm box 40 | webHelper.dismissConfirmBox(); 41 | 42 | //write code to open alert box 43 | await webHelper.clickByText("click to open Confirm box"); 44 | 45 | //Assert 46 | expect(await webHelper.getAlertText()).toBe(expectedText); 47 | }); 48 | 49 | test("Test 3 : Subscribe on Prompt, enter text in input box and call accept by clicking on Ok button", async ({ 50 | page, 51 | browser, 52 | }) => { 53 | const context = await browser.newContext(); 54 | const webHelper = new WebHelper(page, context); 55 | 56 | const expectedText = "I am JS Prompt box"; 57 | //setup listener to click on Ok button on confirm box 58 | webHelper.handlePromptBox(expectedText); 59 | 60 | //write code to open alert box 61 | await webHelper.clickByText("click to open Prompt box"); 62 | 63 | //Assert 64 | expect(await webHelper.getAlertText()).toBe(expectedText); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/web/example/assertion.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, request } from "@playwright/test"; 2 | import { ApiHelper } from "helper/api/apiHelper"; 3 | 4 | /* 5 | These assertions will retry until the assertion passes, or the assertion timeout is reached. 6 | */ 7 | test("Using playwright Auto-Retrying Assertion", async ({ page }) => { 8 | await page.goto("https://www.google.com"); 9 | await expect(page).toHaveTitle("Google"); 10 | await expect(page.getByTestId("status")).toHaveText("PASS"); 11 | const locator = await page.locator("selector"); 12 | 13 | const apiContext = await request.newContext(); 14 | const apiHelper = new ApiHelper(apiContext); 15 | const response = apiHelper.invokeGetApi("url"); 16 | 17 | await expect(locator).toBeAttached(); //Element is attached 18 | await expect(locator).toBeChecked(); //Checkbox is checked 19 | await expect(locator).toBeDisabled(); //Element is disabled 20 | await expect(locator).toBeEditable(); //Element is editable 21 | await expect(locator).toBeEmpty(); //Container is empty 22 | await expect(locator).toBeEnabled(); //Element is enabled 23 | await expect(locator).toBeFocused(); //Element is focused 24 | await expect(locator).toBeHidden(); //Element is not visible 25 | await expect(locator).toBeInViewport(); //Element intersects viewport 26 | await expect(locator).toBeVisible(); //Element is visible 27 | await expect(locator).toContainText("xyz"); //Element contains text 28 | await expect(locator).toHaveAttribute("class"); //Element has a DOM attribute 29 | await expect(locator).toHaveClass("icon"); //Element has a class property 30 | await expect(locator).toHaveCount(1); //List has exact number of children 31 | // await expect(locator).toHaveCSS() //Element has CSS property 32 | await expect(locator).toHaveId("id"); //Element has an ID 33 | // await expect(locator).toHaveJSProperty() //Element has a JavaScript property 34 | await expect(locator).toHaveScreenshot(); //Element has a screenshot 35 | await expect(locator).toHaveText("ABC"); //Element matches text 36 | await expect(locator).toHaveValue("ABC"); //Input has a value 37 | //await expect(locator).toHaveValues("ABC") //Select has options selected 38 | await expect(page).toHaveScreenshot(); //Page has a screenshot 39 | await expect(page).toHaveTitle("ABC"); //Page has a title 40 | await expect(page).toHaveURL("ABC"); //Page has a URL 41 | //await expect(response).toBeOK() //Response has an OK status 42 | }); 43 | 44 | /* 45 | These assertions will test any condition but do not auto-retry. Using these assertions can lead to a flaky test 46 | 47 | */ 48 | test("Using playwright Non-Retrying Assertion", async ({ page }) => { 49 | await page.goto("https://www.google.com"); 50 | await expect(page).toHaveTitle("Google"); 51 | const value = ""; 52 | expect(value).toBe("ABC"); //Value is the same 53 | expect(value).toBeCloseTo(100); // Number is approximately equal 54 | expect(value).toBeDefined(); //Value is not undefined 55 | expect(value).toBeFalsy(); //Value is falsy, e.g. false, 0, null, etc. 56 | expect(value).toBeGreaterThan(5); // Number is more than 57 | expect(value).toBeGreaterThanOrEqual(5); // Number is more than or equal 58 | expect(value).toBeInstanceOf(Object); //Object is an instance of a class 59 | expect(value).toBeLessThan(19); //Number is less than 60 | expect(value).toBeLessThanOrEqual(19); // Number is less than or equal 61 | expect(value).toBeNaN(); //Value is NaN 62 | expect(value).toBeNull(); //Value is null 63 | expect(value).toBeTruthy(); // Value is truthy, i.e. not false, 0, null, etc. 64 | expect(value).toBeUndefined(); // Value is undefined 65 | expect(value).toContain("ABC"); //String contains a substring 66 | expect(value).toContain("ABC"); //Array or set contains an element 67 | expect(value).toContainEqual("ABC"); //Array or set contains a similar element 68 | expect(value).toEqual(5); //Value is similar - deep equality and pattern matching 69 | expect(value).toHaveLength(10); //Array or string has length 70 | expect(value).toHaveProperty("name"); // Object has a property 71 | expect(value).toMatch(/name/i); //String matches a regular expression 72 | // expect(value).toMatchObject(); // Object contains specified properties 73 | // expect(value).toStrictEqual(); //Value is similar, including property types 74 | expect(value).toThrow(); //Function throws an error 75 | // expect(value).any(); //Matches any instance of a class/primitive 76 | // expect(value).anything(); //Matches anything 77 | // expect(value).arrayContaining(); //Array contains specific elements 78 | // expect(value).closeTo(); //Number is approximately equal 79 | // expect(value).objectContaining(); // Object contains specific properties 80 | // expect(value).stringContaining(); //String contains a substring 81 | // expect(value).stringMatching(); //String matches a regular expression 82 | }); 83 | -------------------------------------------------------------------------------- /tests/web/example/autoWait.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Request, Response } from "@playwright/test"; 2 | 3 | //Different wait available in Playwright 4 | // 1. waitForTimeout -- Thread.sleep() 5 | 6 | // 2. waitForRequest 7 | test("test waitForRequest demo", async ({ page }) => { 8 | const request: Request = await page.waitForRequest(/ohrm_logo.png/); 9 | console.log(request.url()); 10 | }); 11 | // 3. waitForResponse 12 | test("test waitForResponse demo", async ({ page }) => { 13 | const response: Response = await page.waitForResponse(/ohrm_logo.png/); 14 | console.log(response.request().url()); 15 | }); 16 | 17 | // 4. waitForUrl 18 | // 5. waitForLoadState 19 | // 6. waitForSelector 20 | 21 | test("test waitForSelector demo", async ({ page }) => { 22 | //use below approach than using waitForSelector 23 | await expect(page.getByAltText("OrangeHRM")).toBeVisible({ timeout: 3_000 }); 24 | }); 25 | // 7. waitForFunction 26 | test("test waitForFunction demo", async ({ page }) => { 27 | await page.waitForFunction(() => { 28 | window.scrollBy(0, 600); 29 | }); 30 | }); 31 | 32 | // 8. waitForEvent 33 | 34 | test("test waitForEvent demo", async ({ page }) => { 35 | await page.waitForEvent("domcontentloaded"); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/web/example/blockunnecessary.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | 3 | /* 4 | Blocking Unnecessary requests, can significantly speed up your tests 5 | */ 6 | test("Block Unnecessary Requests", async ({ page }) => { 7 | await page.route("**/analytics/**", (route) => route.abort()); 8 | }); 9 | -------------------------------------------------------------------------------- /tests/web/example/demo-fixture.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from "../../fixtures/customFixtures"; 2 | 3 | test.describe('Demo website',()=>{ 4 | 5 | test('Verify homepage', async({web})=>{ 6 | await web.navigateToUrl("https://google.co.in"); 7 | }) 8 | }) -------------------------------------------------------------------------------- /tests/web/example/demo-todo-app.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | import { WebHelper } from "../../../helper/web/webHelper"; 3 | 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto("https://demo.playwright.dev/todomvc"); 6 | }); 7 | 8 | test("Sample Web Test", async ({ page, browser }) => { 9 | const browserContext = await browser.newContext(); 10 | 11 | const webHelper = new WebHelper(page, browserContext); 12 | }); 13 | 14 | const TODO_ITEMS = [ 15 | "buy some cheese", 16 | "feed the cat", 17 | "book a doctors appointment", 18 | ]; 19 | 20 | test.describe("New Todo", () => { 21 | test("should allow me to add todo items", async ({ page }) => { 22 | // create a new todo locator 23 | const newTodo = page.getByPlaceholder("What needs to be done?"); 24 | 25 | // Create 1st todo. 26 | await newTodo.fill(TODO_ITEMS[0]); 27 | await newTodo.press("Enter"); 28 | 29 | // Make sure the list only has one todo item. 30 | await expect(page.getByTestId("todo-title")).toHaveText([TODO_ITEMS[0]]); 31 | 32 | // Create 2nd todo. 33 | await newTodo.fill(TODO_ITEMS[1]); 34 | await newTodo.press("Enter"); 35 | 36 | // Make sure the list now has two todo items. 37 | await expect(page.getByTestId("todo-title")).toHaveText([ 38 | TODO_ITEMS[0], 39 | TODO_ITEMS[1], 40 | ]); 41 | 42 | await checkNumberOfTodosInLocalStorage(page, 2); 43 | }); 44 | 45 | test("should clear text input field when an item is added", async ({ 46 | page, 47 | }) => { 48 | // create a new todo locator 49 | const newTodo = page.getByPlaceholder("What needs to be done?"); 50 | 51 | // Create one todo item. 52 | await newTodo.fill(TODO_ITEMS[0]); 53 | await newTodo.press("Enter"); 54 | 55 | // Check that input is empty. 56 | await expect(newTodo).toBeEmpty(); 57 | await checkNumberOfTodosInLocalStorage(page, 1); 58 | }); 59 | 60 | test("should append new items to the bottom of the list", async ({ 61 | page, 62 | }) => { 63 | // Create 3 items. 64 | await createDefaultTodos(page); 65 | 66 | // create a todo count locator 67 | const todoCount = page.getByTestId("todo-count"); 68 | 69 | // Check test using different methods. 70 | await expect(page.getByText("3 items left")).toBeVisible(); 71 | await expect(todoCount).toHaveText("3 items left"); 72 | await expect(todoCount).toContainText("3"); 73 | await expect(todoCount).toHaveText(/3/); 74 | 75 | // Check all items in one call. 76 | await expect(page.getByTestId("todo-title")).toHaveText(TODO_ITEMS); 77 | await checkNumberOfTodosInLocalStorage(page, 3); 78 | }); 79 | }); 80 | 81 | test.describe("Mark all as completed", () => { 82 | test.beforeEach(async ({ page }) => { 83 | await createDefaultTodos(page); 84 | await checkNumberOfTodosInLocalStorage(page, 3); 85 | }); 86 | 87 | test.afterEach(async ({ page }) => { 88 | await checkNumberOfTodosInLocalStorage(page, 3); 89 | }); 90 | 91 | test("should allow me to mark all items as completed", async ({ page }) => { 92 | // Complete all todos. 93 | await page.getByLabel("Mark all as complete").check(); 94 | 95 | // Ensure all todos have 'completed' class. 96 | await expect(page.getByTestId("todo-item")).toHaveClass([ 97 | "completed", 98 | "completed", 99 | "completed", 100 | ]); 101 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 102 | }); 103 | 104 | test("should allow me to clear the complete state of all items", async ({ 105 | page, 106 | }) => { 107 | const toggleAll = page.getByLabel("Mark all as complete"); 108 | // Check and then immediately uncheck. 109 | await toggleAll.check(); 110 | await toggleAll.uncheck(); 111 | 112 | // Should be no completed classes. 113 | await expect(page.getByTestId("todo-item")).toHaveClass(["", "", ""]); 114 | }); 115 | 116 | test("complete all checkbox should update state when items are completed / cleared", async ({ 117 | page, 118 | }) => { 119 | const toggleAll = page.getByLabel("Mark all as complete"); 120 | await toggleAll.check(); 121 | await expect(toggleAll).toBeChecked(); 122 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 123 | 124 | // Uncheck first todo. 125 | const firstTodo = page.getByTestId("todo-item").nth(0); 126 | await firstTodo.getByRole("checkbox").uncheck(); 127 | 128 | // Reuse toggleAll locator and make sure its not checked. 129 | await expect(toggleAll).not.toBeChecked(); 130 | 131 | await firstTodo.getByRole("checkbox").check(); 132 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 133 | 134 | // Assert the toggle all is checked again. 135 | await expect(toggleAll).toBeChecked(); 136 | }); 137 | }); 138 | 139 | test.describe("Item", () => { 140 | test("should allow me to mark items as complete", async ({ page }) => { 141 | // create a new todo locator 142 | const newTodo = page.getByPlaceholder("What needs to be done?"); 143 | 144 | // Create two items. 145 | for (const item of TODO_ITEMS.slice(0, 2)) { 146 | await newTodo.fill(item); 147 | await newTodo.press("Enter"); 148 | } 149 | 150 | // Check first item. 151 | const firstTodo = page.getByTestId("todo-item").nth(0); 152 | await firstTodo.getByRole("checkbox").check(); 153 | await expect(firstTodo).toHaveClass("completed"); 154 | 155 | // Check second item. 156 | const secondTodo = page.getByTestId("todo-item").nth(1); 157 | await expect(secondTodo).not.toHaveClass("completed"); 158 | await secondTodo.getByRole("checkbox").check(); 159 | 160 | // Assert completed class. 161 | await expect(firstTodo).toHaveClass("completed"); 162 | await expect(secondTodo).toHaveClass("completed"); 163 | }); 164 | 165 | test("should allow me to un-mark items as complete", async ({ page }) => { 166 | // create a new todo locator 167 | const newTodo = page.getByPlaceholder("What needs to be done?"); 168 | 169 | // Create two items. 170 | for (const item of TODO_ITEMS.slice(0, 2)) { 171 | await newTodo.fill(item); 172 | await newTodo.press("Enter"); 173 | } 174 | 175 | const firstTodo = page.getByTestId("todo-item").nth(0); 176 | const secondTodo = page.getByTestId("todo-item").nth(1); 177 | const firstTodoCheckbox = firstTodo.getByRole("checkbox"); 178 | 179 | await firstTodoCheckbox.check(); 180 | await expect(firstTodo).toHaveClass("completed"); 181 | await expect(secondTodo).not.toHaveClass("completed"); 182 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 183 | 184 | await firstTodoCheckbox.uncheck(); 185 | await expect(firstTodo).not.toHaveClass("completed"); 186 | await expect(secondTodo).not.toHaveClass("completed"); 187 | await checkNumberOfCompletedTodosInLocalStorage(page, 0); 188 | }); 189 | 190 | test("should allow me to edit an item", async ({ page }) => { 191 | await createDefaultTodos(page); 192 | 193 | const todoItems = page.getByTestId("todo-item"); 194 | const secondTodo = todoItems.nth(1); 195 | await secondTodo.dblclick(); 196 | await expect(secondTodo.getByRole("textbox", { name: "Edit" })).toHaveValue( 197 | TODO_ITEMS[1] 198 | ); 199 | await secondTodo 200 | .getByRole("textbox", { name: "Edit" }) 201 | .fill("buy some sausages"); 202 | await secondTodo.getByRole("textbox", { name: "Edit" }).press("Enter"); 203 | 204 | // Explicitly assert the new text value. 205 | await expect(todoItems).toHaveText([ 206 | TODO_ITEMS[0], 207 | "buy some sausages", 208 | TODO_ITEMS[2], 209 | ]); 210 | await checkTodosInLocalStorage(page, "buy some sausages"); 211 | }); 212 | }); 213 | 214 | test.describe("Editing", () => { 215 | test.beforeEach(async ({ page }) => { 216 | await createDefaultTodos(page); 217 | await checkNumberOfTodosInLocalStorage(page, 3); 218 | }); 219 | 220 | test("should hide other controls when editing", async ({ page }) => { 221 | const todoItem = page.getByTestId("todo-item").nth(1); 222 | await todoItem.dblclick(); 223 | await expect(todoItem.getByRole("checkbox")).toBeHidden(); 224 | await expect( 225 | todoItem.locator("label", { 226 | hasText: TODO_ITEMS[1], 227 | }) 228 | ).toBeHidden(); 229 | await checkNumberOfTodosInLocalStorage(page, 3); 230 | }); 231 | 232 | test("should save edits on blur", async ({ page }) => { 233 | const todoItems = page.getByTestId("todo-item"); 234 | await todoItems.nth(1).dblclick(); 235 | await todoItems 236 | .nth(1) 237 | .getByRole("textbox", { name: "Edit" }) 238 | .fill("buy some sausages"); 239 | await todoItems 240 | .nth(1) 241 | .getByRole("textbox", { name: "Edit" }) 242 | .dispatchEvent("blur"); 243 | 244 | await expect(todoItems).toHaveText([ 245 | TODO_ITEMS[0], 246 | "buy some sausages", 247 | TODO_ITEMS[2], 248 | ]); 249 | await checkTodosInLocalStorage(page, "buy some sausages"); 250 | }); 251 | 252 | test("should trim entered text", async ({ page }) => { 253 | const todoItems = page.getByTestId("todo-item"); 254 | await todoItems.nth(1).dblclick(); 255 | await todoItems 256 | .nth(1) 257 | .getByRole("textbox", { name: "Edit" }) 258 | .fill(" buy some sausages "); 259 | await todoItems 260 | .nth(1) 261 | .getByRole("textbox", { name: "Edit" }) 262 | .press("Enter"); 263 | 264 | await expect(todoItems).toHaveText([ 265 | TODO_ITEMS[0], 266 | "buy some sausages", 267 | TODO_ITEMS[2], 268 | ]); 269 | await checkTodosInLocalStorage(page, "buy some sausages"); 270 | }); 271 | 272 | test("should remove the item if an empty text string was entered", async ({ 273 | page, 274 | }) => { 275 | const todoItems = page.getByTestId("todo-item"); 276 | await todoItems.nth(1).dblclick(); 277 | await todoItems.nth(1).getByRole("textbox", { name: "Edit" }).fill(""); 278 | await todoItems 279 | .nth(1) 280 | .getByRole("textbox", { name: "Edit" }) 281 | .press("Enter"); 282 | 283 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 284 | }); 285 | 286 | test("should cancel edits on escape", async ({ page }) => { 287 | const todoItems = page.getByTestId("todo-item"); 288 | await todoItems.nth(1).dblclick(); 289 | await todoItems 290 | .nth(1) 291 | .getByRole("textbox", { name: "Edit" }) 292 | .fill("buy some sausages"); 293 | await todoItems 294 | .nth(1) 295 | .getByRole("textbox", { name: "Edit" }) 296 | .press("Escape"); 297 | await expect(todoItems).toHaveText(TODO_ITEMS); 298 | }); 299 | }); 300 | 301 | test.describe("Counter", () => { 302 | test("should display the current number of todo items", async ({ page }) => { 303 | // create a new todo locator 304 | const newTodo = page.getByPlaceholder("What needs to be done?"); 305 | 306 | // create a todo count locator 307 | const todoCount = page.getByTestId("todo-count"); 308 | 309 | await newTodo.fill(TODO_ITEMS[0]); 310 | await newTodo.press("Enter"); 311 | await expect(todoCount).toContainText("1"); 312 | 313 | await newTodo.fill(TODO_ITEMS[1]); 314 | await newTodo.press("Enter"); 315 | await expect(todoCount).toContainText("2"); 316 | 317 | await checkNumberOfTodosInLocalStorage(page, 2); 318 | }); 319 | }); 320 | 321 | test.describe("Clear completed button", () => { 322 | test.beforeEach(async ({ page }) => { 323 | await createDefaultTodos(page); 324 | }); 325 | 326 | test("should display the correct text", async ({ page }) => { 327 | await page.locator(".todo-list li .toggle").first().check(); 328 | await expect( 329 | page.getByRole("button", { name: "Clear completed" }) 330 | ).toBeVisible(); 331 | }); 332 | 333 | test("should remove completed items when clicked", async ({ page }) => { 334 | const todoItems = page.getByTestId("todo-item"); 335 | await todoItems.nth(1).getByRole("checkbox").check(); 336 | await page.getByRole("button", { name: "Clear completed" }).click(); 337 | await expect(todoItems).toHaveCount(2); 338 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 339 | }); 340 | 341 | test("should be hidden when there are no items that are completed", async ({ 342 | page, 343 | }) => { 344 | await page.locator(".todo-list li .toggle").first().check(); 345 | await page.getByRole("button", { name: "Clear completed" }).click(); 346 | await expect( 347 | page.getByRole("button", { name: "Clear completed" }) 348 | ).toBeHidden(); 349 | }); 350 | }); 351 | 352 | test.describe("Persistence", () => { 353 | test("should persist its data", async ({ page }) => { 354 | // create a new todo locator 355 | const newTodo = page.getByPlaceholder("What needs to be done?"); 356 | 357 | for (const item of TODO_ITEMS.slice(0, 2)) { 358 | await newTodo.fill(item); 359 | await newTodo.press("Enter"); 360 | } 361 | 362 | const todoItems = page.getByTestId("todo-item"); 363 | const firstTodoCheck = todoItems.nth(0).getByRole("checkbox"); 364 | await firstTodoCheck.check(); 365 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 366 | await expect(firstTodoCheck).toBeChecked(); 367 | await expect(todoItems).toHaveClass(["completed", ""]); 368 | 369 | // Ensure there is 1 completed item. 370 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 371 | 372 | // Now reload. 373 | await page.reload(); 374 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 375 | await expect(firstTodoCheck).toBeChecked(); 376 | await expect(todoItems).toHaveClass(["completed", ""]); 377 | }); 378 | }); 379 | 380 | test.describe("Routing", () => { 381 | test.beforeEach(async ({ page }) => { 382 | await createDefaultTodos(page); 383 | // make sure the app had a chance to save updated todos in storage 384 | // before navigating to a new view, otherwise the items can get lost :( 385 | // in some frameworks like Durandal 386 | await checkTodosInLocalStorage(page, TODO_ITEMS[0]); 387 | }); 388 | 389 | test("should allow me to display active items", async ({ page }) => { 390 | const todoItem = page.getByTestId("todo-item"); 391 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 392 | 393 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 394 | await page.getByRole("link", { name: "Active" }).click(); 395 | await expect(todoItem).toHaveCount(2); 396 | await expect(todoItem).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 397 | }); 398 | 399 | test("should respect the back button", async ({ page }) => { 400 | const todoItem = page.getByTestId("todo-item"); 401 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 402 | 403 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 404 | 405 | await test.step("Showing all items", async () => { 406 | await page.getByRole("link", { name: "All" }).click(); 407 | await expect(todoItem).toHaveCount(3); 408 | }); 409 | 410 | await test.step("Showing active items", async () => { 411 | await page.getByRole("link", { name: "Active" }).click(); 412 | }); 413 | 414 | await test.step("Showing completed items", async () => { 415 | await page.getByRole("link", { name: "Completed" }).click(); 416 | }); 417 | 418 | await expect(todoItem).toHaveCount(1); 419 | await page.goBack(); 420 | await expect(todoItem).toHaveCount(2); 421 | await page.goBack(); 422 | await expect(todoItem).toHaveCount(3); 423 | }); 424 | 425 | test("should allow me to display completed items", async ({ page }) => { 426 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 427 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 428 | await page.getByRole("link", { name: "Completed" }).click(); 429 | await expect(page.getByTestId("todo-item")).toHaveCount(1); 430 | }); 431 | 432 | test("should allow me to display all items", async ({ page }) => { 433 | await page.getByTestId("todo-item").nth(1).getByRole("checkbox").check(); 434 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 435 | await page.getByRole("link", { name: "Active" }).click(); 436 | await page.getByRole("link", { name: "Completed" }).click(); 437 | await page.getByRole("link", { name: "All" }).click(); 438 | await expect(page.getByTestId("todo-item")).toHaveCount(3); 439 | }); 440 | 441 | test("should highlight the currently applied filter", async ({ page }) => { 442 | await expect(page.getByRole("link", { name: "All" })).toHaveClass( 443 | "selected" 444 | ); 445 | 446 | //create locators for active and completed links 447 | const activeLink = page.getByRole("link", { name: "Active" }); 448 | const completedLink = page.getByRole("link", { name: "Completed" }); 449 | await activeLink.click(); 450 | 451 | // Page change - active items. 452 | await expect(activeLink).toHaveClass("selected"); 453 | await completedLink.click(); 454 | 455 | // Page change - completed items. 456 | await expect(completedLink).toHaveClass("selected"); 457 | }); 458 | }); 459 | 460 | async function createDefaultTodos(page) { 461 | // create a new todo locator 462 | const newTodo = page.getByPlaceholder("What needs to be done?"); 463 | 464 | for (const item of TODO_ITEMS) { 465 | await newTodo.fill(item); 466 | await newTodo.press("Enter"); 467 | } 468 | } 469 | 470 | /** 471 | * @param {import('@playwright/test').Page} page 472 | * @param {number} expected 473 | */ 474 | async function checkNumberOfTodosInLocalStorage(page, expected) { 475 | return await page.waitForFunction((e) => { 476 | return JSON.parse(localStorage["react-todos"]).length === e; 477 | }, expected); 478 | } 479 | 480 | /** 481 | * @param {import('@playwright/test').Page} page 482 | * @param {number} expected 483 | */ 484 | async function checkNumberOfCompletedTodosInLocalStorage(page, expected) { 485 | return await page.waitForFunction((e) => { 486 | return ( 487 | JSON.parse(localStorage["react-todos"]).filter((i) => i.completed) 488 | .length === e 489 | ); 490 | }, expected); 491 | } 492 | 493 | /** 494 | * @param {import('@playwright/test').Page} page 495 | * @param {string} title 496 | */ 497 | async function checkTodosInLocalStorage(page, title) { 498 | return await page.waitForFunction((t) => { 499 | return JSON.parse(localStorage["react-todos"]) 500 | .map((i) => i.title) 501 | .includes(t); 502 | }, title); 503 | } 504 | -------------------------------------------------------------------------------- /tests/web/example/downloadfile.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | import { WebHelper } from "../../../helper/web/webHelper"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | 6 | test("File Download Test ", async ({ page, browser }) => { 7 | const context = await browser.newContext(); 8 | const webHelper = new WebHelper(page, context); 9 | 10 | //Arrange 11 | const expectedFileName = "fileToDownload.txt"; 12 | const downloadFolderPath = path.resolve(__dirname, "../test-data"); // path to the folder where the downloaded file will be saved 13 | const savePath = path.join(downloadFolderPath, expectedFileName); 14 | 15 | //Action 16 | await webHelper.downLoadFile( 17 | expectedFileName, 18 | downloadFolderPath, 19 | "locatorOfDownloadLink" 20 | ); 21 | 22 | //Assert 23 | expect(fs.existsSync(savePath)).toBeTruthy(); 24 | 25 | //Clean up : remove downloaded file 26 | fs.unlinkSync(savePath); 27 | }); 28 | -------------------------------------------------------------------------------- /tests/web/example/frame.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { Console, log } from "console"; 3 | import { WebHelper } from "../../../helper/web/webHelper"; 4 | 5 | test("iframe", async ({ page }) => { 6 | await page.goto("http://rahulshettyacademy.com/AutomationPractice/"); 7 | const framesPage = await page.frameLocator("#courses-iframe"); 8 | framesPage.locator("li a[href*='lifetime-access]:visible").click(); 9 | const textCheck = await framesPage.locator(".text h2").textContent(); 10 | console.log(textCheck); 11 | }); 12 | 13 | test("Test 2 : Operation on frame", async ({ page, browser }) => { 14 | const context = await browser.newContext(); 15 | const webHelper = new WebHelper(page, context); 16 | const frameOne = await webHelper.getFrame("iframe[name='courses-iframe']"); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/web/example/globalLogin.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, BrowserContext } from "@playwright/test"; 2 | import { WebHelper } from "../../../helper/web/webHelper.js"; 3 | 4 | let webContext: any; 5 | 6 | /* The `test.beforeAll()` function is a hook provided by the Playwright test framework. It is used to 7 | run a setup function before all the tests in a test file. In this hook, we will login into application and save login details in state.json file*/ 8 | test.beforeAll( 9 | "Login into web app through browser and save login detail in JSON", 10 | async ({ browser }) => { 11 | const browserContext = await browser.newContext(); 12 | const webPage = await browserContext.newPage(); 13 | const webHelper = new WebHelper(webPage, browserContext); 14 | webHelper.navigateToUrl("www.gmail.com"); 15 | 16 | //write code to login in gmail 17 | 18 | await browserContext.storageState({ path: "state.json" }); 19 | webContext = await browserContext.newContext({ 20 | storageState: "state.json", 21 | }); 22 | await webPage.close(); 23 | } 24 | ); 25 | /* The code you provided is a test case that logs into a web application using the saved login state. */ 26 | test("Login into web app using saved login state", async () => { 27 | const webPage = await webContext.newPage(); 28 | 29 | const webHelper = new WebHelper(webPage, webContext); 30 | webHelper.navigateToUrl("www.gmail.com"); // Browser will open page using login details saved in test.beforeAll() step 31 | }); 32 | -------------------------------------------------------------------------------- /tests/web/example/locator.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | /* playwright built in locators 4 | example code is run on website -> https://opensource-demo.orangehrmlive.com/web/index.php/auth/login 5 | In case you want to read more about role refer -> https://www.w3.org/TR/wai-aria-1.2/#role_definitions*/ 6 | 7 | test("test locator getByRole() @locator", async ({ page: page }) => { 8 | /*supported role in playwright: 9 | "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"| 10 | "caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"| 11 | "deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"| 12 | "group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"| 13 | "menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"| 14 | "presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|" 15 | "searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"| 16 | "tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem" 17 | */ 18 | 19 | await page.goto( 20 | "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login" 21 | ); 22 | await page.getByRole("textbox", { name: "username" }).fill("Admin"); 23 | await page.getByRole("textbox", { name: "password" }).fill("admin123"); 24 | await page.getByRole("button", { name: "Login" }).click({ 25 | button: "left", 26 | }); 27 | }); 28 | 29 | test("test locator getByPlaceholder() @locator", async ({ page }) => { 30 | await page.goto( 31 | "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login" 32 | ); 33 | await page.getByPlaceholder("Username").fill("Adminplaceholder"); 34 | await page.getByPlaceholder("Password").fill("admin123"); 35 | 36 | await page.getByPlaceholder(/Username/).fill("Adminregexp"); 37 | await page.getByPlaceholder(/Password/).fill("admin123"); 38 | 39 | await page.getByPlaceholder(/username/i).fill("Admin_reg_ex_ignorecase"); 40 | await page.getByPlaceholder(/Password/i).fill("admin123"); 41 | 42 | await page.getByRole("button", { name: "Login" }).click({ 43 | button: "left", 44 | }); 45 | 46 | await page.getByText("Invalid credentials").click(); 47 | expect(await page.getByText("Invalid credentials").count()).toBe(1); 48 | }); 49 | 50 | test("test locator getByText() @locator", async ({ page }) => {}); 51 | test("test locator getByLabel() @locator", async ({ page }) => {}); 52 | 53 | test("test locator getByAltText() @locator", async ({ page }) => {}); 54 | test("test locator getByTitle() @locator", async ({ page }) => {}); 55 | test("test locator getByTestId() @locator", async ({ page }) => {}); 56 | -------------------------------------------------------------------------------- /tests/web/example/networkTest.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { ApiHelper } from "../../../helper/api/apiHelper"; 3 | 4 | let token: string; 5 | 6 | test.beforeAll(async ({ browser }) => { 7 | const context = await browser.newContext(); 8 | const apiContent = await context.newPage(); 9 | const apiHelper = new ApiHelper(apiContent); 10 | 11 | //save token value returned from getToken() function in token variable 12 | token = await apiHelper.getToken(); 13 | }); 14 | 15 | test("Network Test -> Inject token generated through API into browser", async ({ 16 | page, 17 | }) => { 18 | //executed JavaScript to inject token into browser 19 | page.addInitScript((value) => { 20 | window.localStorage.setItem("token", value); 21 | }, token); 22 | 23 | //when script hits URL, browser will open as logged in using above generated token 24 | await page.goto("https://www.xxxx.com"); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/web/example/route.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | import { Console, log } from "console"; 3 | 4 | let fakePayloadOrders = { data: [], message: "No Users" }; 5 | 6 | test("Intercept Network call and Fake/Mock API response", async ({ page }) => { 7 | await page.goto("http://xxxx.com"); 8 | await page.route("http://xxxx.com/abc/ird", async (route) => { 9 | //go the response 10 | const response = await page.request.fetch(route.request()); 11 | let body = JSON.stringify(fakePayloadOrders); 12 | 13 | //send response to browser and override respond body 14 | route.fulfill({ 15 | status: 200, 16 | body: body, 17 | }); 18 | }); 19 | }); 20 | 21 | test("Intercept Network request", async ({ page }) => { 22 | await page.goto("http://xxxx.com"); 23 | 24 | //intercept request send to server & respond by url value passed in route.continue 25 | await page.route("http://xxxx.com/abc/ird", async (route) => { 26 | route.continue({ 27 | url: "http://xxxx.com/abc/123455", 28 | }); 29 | }); 30 | }); 31 | 32 | test.only("Capture Request and Response", async ({ page }) => { 33 | page.on("request", (request) => { 34 | console.log(request.url(), request.method()); 35 | }); 36 | page.on("requestfinished", (data) => { 37 | console.log(data); 38 | }); 39 | 40 | page.on("websocket", (data) => { 41 | console.log(data.url()); 42 | }); 43 | 44 | page.on("console", async (msg) => { 45 | const values = []; 46 | for (const arg of msg.args()) values.push(await arg.jsonValue()); 47 | console.log(...values); 48 | }); 49 | 50 | page.on("response", (response) => { 51 | // console.log(response?.status(), response?.body()); 52 | console.log(response?.status()); 53 | }); 54 | 55 | page.on("requestfailed", (request) => { 56 | console.log(request.url() + " " + request.failure()?.errorText); 57 | }); 58 | await page.goto("https://www.flipkart.com"); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/web/example/runTestParallel.spect.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | 3 | //1st way to set run tests in parallel 4 | test.describe.configure({ mode: "parallel" }); 5 | 6 | //2nd way to set run tests in parallel 7 | test.describe.parallel("Run All Tests in Parallel", async () => { 8 | test("TestOne", async ({ page }) => {}); 9 | 10 | test("TestTwo", async ({ page }) => {}); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/web/example/runTestSerial.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | 3 | //1st way to define test suite to run tests in sequence 4 | test.describe.configure({ mode: "serial" }); 5 | 6 | //2nd way to define test suite to run tests in sequence 7 | test.describe.serial("Run all test in serial", async () => { 8 | test("TestOne", async ({ page }) => {}); 9 | 10 | test("TestTwo", async ({ page }) => {}); 11 | 12 | test("TestThree", async ({ page }) => {}); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/web/example/saveHarFile.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | 3 | test(`Generate HAR file`, async ({ page: Page }, testInfo) => { 4 | // To record HAR file, use "update:true", below code will create a directory named har and 5 | //store all the har related files in it 6 | await Page.routeFromHAR("har/example.har", { update: true }); 7 | 8 | /* The `await testInfo.attach(`HAR FILE`, { path: `../../../../har/example.har` });` line is 9 | attaching the generated HAR file to the test report. It allows the HAR file to be easily 10 | accessible and viewable alongside the test results. The `path` parameter specifies the location of 11 | the HAR file. */ 12 | await testInfo.attach(`HAR FILE`, { path: `../../../../har/example.har` }); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/web/example/screenshot.spec.ts: -------------------------------------------------------------------------------- 1 | import test from "@playwright/test"; 2 | 3 | test("Take Screenshot of Page", async ({ page }) => { 4 | //Take screenshot of full page 5 | await page.screenshot({ path: "screenshot.png" }); 6 | }); 7 | 8 | test("Take Screenshot of Element", async ({ page }) => { 9 | //Take screenshot of an element 10 | await page.goto("http://xxx.com"); 11 | await page.getByTestId("todo-item").screenshot({ path: "screenshot.png" }); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/web/example/uploadfile.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "@playwright/test"; 2 | import { WebHelper } from "../../../helper/web/webHelper"; 3 | import path from "path"; 4 | 5 | test("File Upload Test ", async ({ page, browser }) => { 6 | const context = await browser.newContext(); 7 | const webHelper = new WebHelper(page, context); 8 | 9 | //Arrange 10 | const fileName = "uploadFile.txt"; 11 | const filePath = path.resolve(__dirname, `../test-data/${fileName}`); 12 | 13 | //Action 14 | await webHelper.uploadFile(fileName, filePath, "locatorOfUploadLink"); 15 | 16 | //Assert 17 | expect(await webHelper.elementHasText("locatorOfUploadLink", fileName)); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/web/orangeHrm.spec.ts: -------------------------------------------------------------------------------- 1 | //test 1 -> Login successful - Verify welcome message on Dashboard 2 | //test 2 - Message Invalid credentials 3 | //test 3 -> Error message password can not be empty 4 | //test 4 -> Error message username can not be empty 5 | //test 5 -> Verify Access to HR Administration Panel 6 | //Navigate to HR Administration panel (login as HR admin) 7 | //test 6 -> Verify Employee addition (login as HR admin) 8 | // Navigate to Employee Management section 9 | // Add a new Employee with required details 10 | // Save the employee record 11 | //test 7 -> Verify Employee deletion (login as HR admin) 12 | // Navigate to Employee Management section 13 | //select an existing employee and initiate the deletion process 14 | // confirm the deletion action 15 | //test 8 -> Verify Leave Application Submission (login as Employee) 16 | // Navigate to the leave section 17 | // Apply for leave by providing required details 18 | // submit the leave application 19 | //test 9 -> Verify Leave Approval(login as HR admin) 20 | // Navigate to leave Approval section 21 | // Review pending leave application 22 | // Approve or reject the leave 23 | //test 10 -> Verify Time Tracking Record Creation 24 | //Navigate to Time Tracking module 25 | // Click on Add Time Record button 26 | // Fill the details & Save the record 27 | //test 11 -> Verify Time Tracking Record Deletion 28 | //Navigate to Time Tracking module 29 | // find an existing time tracking record 30 | // Click on delete button 31 | //confirm the deletion 32 | //test 12 -> Validate attendance marking for an employee 33 | //Navigate to attendance module 34 | //Select an employee from the employee list 35 | //Mark attendance for that employee for the current date 36 | //test 13 -> Verify Job posting Creation (login as HR admin) 37 | //Navigate to Recruitment section 38 | //Create a new job posting 39 | //test 14 -> Verify Application Submission(login as job applicant) 40 | //Navigate to career section 41 | //Apply for specific job posting by submitting the application 42 | 43 | //test 15 -> Verify New Employee Onboarding(login as HR admin) 44 | //Navigate to Onboarding section 45 | //Initiate the onboarding process for a newly added employee 46 | //test 16 -> Verify Training Program Creation (login as HR admin) 47 | //Navigate to training section 48 | //create a new training program with all details 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 4 | "module": "commonjs" /* Specify what module code is generated. */, 5 | "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */, 6 | "paths": { 7 | "@src/*": ["src/*"], 8 | "@tests/*": ["tests/*"], 9 | "@pages/*": ["tests/pages/*"] 10 | }, 11 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 12 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 13 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 14 | "strict": true /* Enable all strict type-checking options. */, 15 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 16 | }, 17 | "include": ["src", "./.eslintrc.js", "./playwright.config.ts", "tests"], 18 | "exclude": ["node_modules", "dist"] 19 | } 20 | --------------------------------------------------------------------------------