├── .dockerignore ├── .github └── workflows │ └── playwright.yml ├── .gitignore ├── Dockerfile ├── README.md ├── base_fwk ├── common │ ├── CommonPage.ts │ └── CommonScenario.ts └── fixtures │ └── baseTest.ts ├── docker-compose.yml ├── package-lock.json ├── package.json ├── pageObjects ├── CartPage │ ├── CartPage.ts │ └── CartPageLocators.ts ├── DashBoardPage │ ├── DashBoardLocators.ts │ └── DashBoardPage.ts ├── LoginPage │ ├── LoginPage.ts │ └── LoginPageLocators.ts ├── OrdersHistoryPage │ ├── OrdersHistoryPage.ts │ └── OrdersHistoryPageLocators.ts └── OrdersReviewPage │ ├── OrdersReviewPage.ts │ └── OrdersReviewPageLocators.ts ├── playwright.config.ts └── tests ├── apiOperationTesting.spec.ts ├── demo-todo-app.spec.ts ├── dialogHandling.spec.ts ├── fwkTesting.spec.ts ├── networkMocking.spec.ts ├── productCheckout.spec.ts └── testData.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.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 tests/fwkTesting.spec.ts 22 | - uses: actions/upload-artifact@v3 23 | if: always() 24 | with: 25 | name: playwright-report 26 | path: playwright-report/ 27 | retention-days: 30 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /playwright/.cache/ 5 | allure-results/ 6 | allure-report/ 7 | html-report/ 8 | practice.ts 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Get the latest version of Playwright 2 | FROM mcr.microsoft.com/playwright:v1.49.1-noble 3 | 4 | RUN mkdir /tests 5 | COPY . /tests 6 | WORKDIR /tests 7 | 8 | RUN npm install && \ 9 | npx @playwright/test install 10 | 11 | CMD ["npm", "test"] 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Playwright Automation framework (Page object model) 2 | 3 | This framework is designed to be used as a boilerplate template to start automation testing quickly for any web application. The page object model is used to structure the test. 4 | 5 | ## Built With 6 | 7 | - [Playwright](https://playwright.dev) 8 | - [Typescript](https://www.typescriptlang.org/) 9 | - [Docker](https://www.docker.com/) 10 | 11 | ## Installation 12 | 13 | Prerequisites: 14 | NodeJS 14(or above) and Java 8 (or above) 15 | 16 | - Clone the repo using the below URL 17 | https://github.com/jagota-s/playwright-typescript-pom.git 18 | - Navigate to the folder and install npm packages using: 19 | ```bash 20 | - npm install 21 | ``` 22 | 23 | - Install Playwright browsers 24 | ```bash 25 | - npx @playwright/test install 26 | ``` 27 | 28 | ## Usage 29 | 30 | - Run all the spec files present in the "./tests" directory by using the below command 31 | ```bash 32 | npm run test 33 | ``` 34 | - Run specific spec file 35 | ```bash 36 | npx playwright test tests/{specfile_name.ts} 37 | ``` 38 | 39 | - To generate allure report 40 | ```bash 41 | npm run test:reporter 42 | npm run open:allure-report 43 | ``` 44 | 45 | ## Docker 46 | - Directly use docker compose file with below command 47 | ````bash 48 | docker-compose -f docker-compose.yml up 49 | ```` 50 | OR use below 51 | - To run tests in docker containers, install docker and use the below commands to compose the docker image from the docker file; 52 | ```bash 53 | docker build -t {give image name} . 54 | ``` 55 | - To create the container and launch it use: 56 | ```bash 57 | docker run -it -d {same image name as in the previous command} 58 | ``` 59 | - Check the container is up and running; copy the container id 60 | ```bash 61 | docker ps -a 62 | ``` 63 | - Login in to the running container 64 | ```bash 65 | docker exec -it {container id} bash 66 | ``` 67 | - Run the commands as per need in the docker bash; 68 | ```bash 69 | npm run test 70 | ``` 71 | 72 | ## GitHub Actions 73 | 74 | - The workflow file is in directory .github/workflows named playwright.yml 75 | - Every push or pull request action will trigger the workflow 76 | - Results can be seen on the 'Actions' tab in the repo. 77 | 78 | 79 | ## Contributing 80 | 81 | Pull requests are welcome. For significant changes, please open an issue first 82 | to discuss what you would like to change. 83 | 84 | -------------------------------------------------------------------------------- /base_fwk/common/CommonPage.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from "@playwright/test"; 2 | import { CommonScenario } from "./CommonScenario"; 3 | 4 | 5 | export class CommonPage { 6 | private dataMap = new Map(); 7 | constructor(public page: Page, readonly scenario: CommonScenario) { 8 | } 9 | 10 | public getValue(key: string) { 11 | const value = this.scenario.getValue(key); 12 | return value; 13 | } 14 | 15 | public setValue(key: string, value: string) { 16 | this.scenario.setValue(key, value); 17 | } 18 | 19 | async takeScreenshot(name: string) { 20 | await this.scenario.takeScreenshot(name); 21 | } 22 | } -------------------------------------------------------------------------------- /base_fwk/common/CommonScenario.ts: -------------------------------------------------------------------------------- 1 | import AxeBuilder from "@axe-core/playwright"; 2 | import { test, expect, Page, TestInfo } from "@playwright/test"; 3 | export class CommonScenario { 4 | private myMap = new Map(); 5 | constructor(public page: Page, public testinfo: TestInfo) { 6 | } 7 | 8 | async takeScreenshot(name: string) { 9 | this.testinfo.attach(`${this.testinfo.title}_${name} `, { 10 | contentType: "image/png", 11 | body: await this.page.screenshot({ 12 | fullPage: true 13 | }) 14 | }); 15 | } 16 | 17 | 18 | async hooks() { 19 | console.log("hook from the scenario page"); 20 | } 21 | 22 | 23 | setValue(key: string, value: string) { 24 | this.myMap.set(key, value); 25 | } 26 | 27 | getValue(key: string) { 28 | return this.myMap.get(key); 29 | } 30 | 31 | async a11yAnalysis() { 32 | const accessibilityScanResults = await new AxeBuilder({ page: this.page }).analyze(); // 4 33 | const issues = accessibilityScanResults.violations.length; 34 | console.log("a11y issues found: " + issues); 35 | await this.testinfo.attach('accessibility-scan-results', { 36 | body: JSON.stringify(accessibilityScanResults, null, 2), 37 | contentType: 'application/json' 38 | }); 39 | } 40 | } -------------------------------------------------------------------------------- /base_fwk/fixtures/baseTest.ts: -------------------------------------------------------------------------------- 1 | import { LoginPage } from "../../pageObjects/LoginPage/LoginPage" 2 | import { DashboardPage } from '../../pageObjects/DashBoardPage/DashBoardPage'; 3 | import { OrdersHistoryPage } from '../../pageObjects/OrdersHistoryPage/OrdersHistoryPage'; 4 | import { OrdersReviewPage } from '../../pageObjects/OrdersReviewPage/OrdersReviewPage'; 5 | import { CartPage } from '../../pageObjects/CartPage/CartPage'; 6 | import { Page, test as baseTest } from "@playwright/test"; 7 | import { CommonScenario } from "../common/CommonScenario"; 8 | import { CommonPage } from "../common/CommonPage"; 9 | 10 | // declaring the objects type for autocompletion 11 | interface PageObjects { 12 | loginPage: LoginPage; 13 | dashboardPage: DashboardPage; 14 | ordersHistoryPage: OrdersHistoryPage; 15 | ordersReviewPage: OrdersReviewPage; 16 | cartPage: CartPage; 17 | commonScenarioPage: CommonScenario; 18 | commonPage: CommonPage, 19 | } 20 | // intializing all the page objects you have in your app 21 | // and import them as fixture in spec file 22 | const test = baseTest.extend({ 23 | commonScenarioPage: async ({ page }, use, testinfo) => { 24 | await use(new CommonScenario(page, testinfo)); 25 | }, 26 | loginPage: async ({ page, commonScenarioPage }, use) => { 27 | await use(new LoginPage(page, commonScenarioPage)); 28 | }, 29 | dashboardPage: async ({ page, commonScenarioPage }, use) => { 30 | await use(new DashboardPage(page, commonScenarioPage)); 31 | }, 32 | ordersHistoryPage: async ({ page, commonScenarioPage }, use) => { 33 | await use(new OrdersHistoryPage(page, commonScenarioPage)); 34 | }, 35 | ordersReviewPage: async ({ page, commonScenarioPage }, use) => { 36 | await use(new OrdersReviewPage(page, commonScenarioPage)); 37 | }, 38 | cartPage: async ({ page, commonScenarioPage }, use) => { 39 | await use(new CartPage(page, commonScenarioPage)); 40 | }, 41 | // allPages: async ({ page, commonScenarioPage }, use) => { 42 | // await use({ 43 | // loginPage: new LoginPage(page, commonScenarioPage), 44 | // dashboardPage: new DashboardPage(page, commonScenarioPage) 45 | 46 | // } as PageObjects); 47 | // } 48 | /* , 49 | commonPage: async ({ page }, use) => { 50 | await use(new CommonPage(page)); 51 | } */ 52 | 53 | }); 54 | // this describe block is applicable to all the tests using baseTest 55 | // test.describe('two tests', () => { 56 | // console.log("in describe"); 57 | 58 | // }); 59 | // hooks as fixtures 60 | // let authenticatedPage: Page; 61 | test.beforeEach(async ({ browser }) => { 62 | // console.log('beforeEach tests'); 63 | }); 64 | 65 | test.afterEach(async ({ page}) => { 66 | await page.close(); 67 | }); 68 | 69 | // export default and name export so spec files can use it 70 | export default test; 71 | export const expect = test.expect; 72 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | playwright-test: 4 | image: playwright 5 | build: . 6 | container_name: playwright_docker 7 | volumes: 8 | - ${PWD}:/tests 9 | command: npm run test 10 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "playwright", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "@axe-core/playwright": "4.6.0", 13 | "@playwright/test": "^1.46.0", 14 | "@types/node": "^22.2.0", 15 | "allure-playwright": "^2.0.0-beta.20" 16 | } 17 | }, 18 | "node_modules/@axe-core/playwright": { 19 | "version": "4.6.0", 20 | "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.6.0.tgz", 21 | "integrity": "sha512-q9K4GVJ1fH8FQqErgs01dwzhOJ03vZDfMg+vO9Er05BxQOCp9Rm8oyB3byVzC7oNlxFaPU1qQ8zLwZYypHmchw==", 22 | "dev": true, 23 | "dependencies": { 24 | "axe-core": "^4.6.1" 25 | }, 26 | "peerDependencies": { 27 | "playwright": ">= 1.0.0" 28 | } 29 | }, 30 | "node_modules/@playwright/test": { 31 | "version": "1.46.0", 32 | "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz", 33 | "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==", 34 | "dev": true, 35 | "dependencies": { 36 | "playwright": "1.46.0" 37 | }, 38 | "bin": { 39 | "playwright": "cli.js" 40 | }, 41 | "engines": { 42 | "node": ">=18" 43 | } 44 | }, 45 | "node_modules/@types/node": { 46 | "version": "22.2.0", 47 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz", 48 | "integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==", 49 | "dev": true, 50 | "dependencies": { 51 | "undici-types": "~6.13.0" 52 | } 53 | }, 54 | "node_modules/allure-js-commons": { 55 | "version": "2.0.0-beta.20", 56 | "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-2.0.0-beta.20.tgz", 57 | "integrity": "sha512-ZUSa75s1LEhoIOTgNJhB463t+GURPKkB92HNqSWvFImxU0SojdT7JbgaQ4Ku+qhY5VFMsWmZJt6TAYo5t7ZR4A==", 58 | "dev": true, 59 | "dependencies": { 60 | "mkdirp": "^1.0.4", 61 | "properties": "^1.2.1", 62 | "uuid": "^8.3.0" 63 | } 64 | }, 65 | "node_modules/allure-playwright": { 66 | "version": "2.0.0-beta.20", 67 | "resolved": "https://registry.npmjs.org/allure-playwright/-/allure-playwright-2.0.0-beta.20.tgz", 68 | "integrity": "sha512-RELEsiK4b4h+K4E2GzYqp3yEOR4OA+gUQi/PInIFtaDPKG4BYo0b28iqox+xrDILNce7NhmMC8Q/uqBGl33B4w==", 69 | "dev": true, 70 | "dependencies": { 71 | "allure-js-commons": "2.0.0-beta.20" 72 | } 73 | }, 74 | "node_modules/axe-core": { 75 | "version": "4.6.3", 76 | "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", 77 | "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", 78 | "dev": true, 79 | "engines": { 80 | "node": ">=4" 81 | } 82 | }, 83 | "node_modules/fsevents": { 84 | "version": "2.3.2", 85 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 86 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 87 | "dev": true, 88 | "hasInstallScript": true, 89 | "optional": true, 90 | "os": [ 91 | "darwin" 92 | ], 93 | "engines": { 94 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 95 | } 96 | }, 97 | "node_modules/mkdirp": { 98 | "version": "1.0.4", 99 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 100 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 101 | "dev": true, 102 | "bin": { 103 | "mkdirp": "bin/cmd.js" 104 | }, 105 | "engines": { 106 | "node": ">=10" 107 | } 108 | }, 109 | "node_modules/playwright": { 110 | "version": "1.46.0", 111 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz", 112 | "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==", 113 | "dev": true, 114 | "dependencies": { 115 | "playwright-core": "1.46.0" 116 | }, 117 | "bin": { 118 | "playwright": "cli.js" 119 | }, 120 | "engines": { 121 | "node": ">=18" 122 | }, 123 | "optionalDependencies": { 124 | "fsevents": "2.3.2" 125 | } 126 | }, 127 | "node_modules/playwright-core": { 128 | "version": "1.46.0", 129 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz", 130 | "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==", 131 | "dev": true, 132 | "bin": { 133 | "playwright-core": "cli.js" 134 | }, 135 | "engines": { 136 | "node": ">=18" 137 | } 138 | }, 139 | "node_modules/properties": { 140 | "version": "1.2.1", 141 | "resolved": "https://registry.npmjs.org/properties/-/properties-1.2.1.tgz", 142 | "integrity": "sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ==", 143 | "dev": true, 144 | "engines": { 145 | "node": ">=0.10" 146 | } 147 | }, 148 | "node_modules/undici-types": { 149 | "version": "6.13.0", 150 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", 151 | "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", 152 | "dev": true 153 | }, 154 | "node_modules/uuid": { 155 | "version": "8.3.2", 156 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 157 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 158 | "dev": true, 159 | "bin": { 160 | "uuid": "dist/bin/uuid" 161 | } 162 | } 163 | }, 164 | "dependencies": { 165 | "@axe-core/playwright": { 166 | "version": "4.6.0", 167 | "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.6.0.tgz", 168 | "integrity": "sha512-q9K4GVJ1fH8FQqErgs01dwzhOJ03vZDfMg+vO9Er05BxQOCp9Rm8oyB3byVzC7oNlxFaPU1qQ8zLwZYypHmchw==", 169 | "dev": true, 170 | "requires": { 171 | "axe-core": "^4.6.1" 172 | } 173 | }, 174 | "@playwright/test": { 175 | "version": "1.46.0", 176 | "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz", 177 | "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==", 178 | "dev": true, 179 | "requires": { 180 | "playwright": "1.46.0" 181 | } 182 | }, 183 | "@types/node": { 184 | "version": "22.2.0", 185 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.2.0.tgz", 186 | "integrity": "sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==", 187 | "dev": true, 188 | "requires": { 189 | "undici-types": "~6.13.0" 190 | } 191 | }, 192 | "allure-js-commons": { 193 | "version": "2.0.0-beta.20", 194 | "resolved": "https://registry.npmjs.org/allure-js-commons/-/allure-js-commons-2.0.0-beta.20.tgz", 195 | "integrity": "sha512-ZUSa75s1LEhoIOTgNJhB463t+GURPKkB92HNqSWvFImxU0SojdT7JbgaQ4Ku+qhY5VFMsWmZJt6TAYo5t7ZR4A==", 196 | "dev": true, 197 | "requires": { 198 | "mkdirp": "^1.0.4", 199 | "properties": "^1.2.1", 200 | "uuid": "^8.3.0" 201 | } 202 | }, 203 | "allure-playwright": { 204 | "version": "2.0.0-beta.20", 205 | "resolved": "https://registry.npmjs.org/allure-playwright/-/allure-playwright-2.0.0-beta.20.tgz", 206 | "integrity": "sha512-RELEsiK4b4h+K4E2GzYqp3yEOR4OA+gUQi/PInIFtaDPKG4BYo0b28iqox+xrDILNce7NhmMC8Q/uqBGl33B4w==", 207 | "dev": true, 208 | "requires": { 209 | "allure-js-commons": "2.0.0-beta.20" 210 | } 211 | }, 212 | "axe-core": { 213 | "version": "4.6.3", 214 | "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.3.tgz", 215 | "integrity": "sha512-/BQzOX780JhsxDnPpH4ZiyrJAzcd8AfzFPkv+89veFSr1rcMjuq2JDCwypKaPeB6ljHp9KjXhPpjgCvQlWYuqg==", 216 | "dev": true 217 | }, 218 | "fsevents": { 219 | "version": "2.3.2", 220 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 221 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 222 | "dev": true, 223 | "optional": true 224 | }, 225 | "mkdirp": { 226 | "version": "1.0.4", 227 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 228 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 229 | "dev": true 230 | }, 231 | "playwright": { 232 | "version": "1.46.0", 233 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz", 234 | "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==", 235 | "dev": true, 236 | "requires": { 237 | "fsevents": "2.3.2", 238 | "playwright-core": "1.46.0" 239 | } 240 | }, 241 | "playwright-core": { 242 | "version": "1.46.0", 243 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz", 244 | "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==", 245 | "dev": true 246 | }, 247 | "properties": { 248 | "version": "1.2.1", 249 | "resolved": "https://registry.npmjs.org/properties/-/properties-1.2.1.tgz", 250 | "integrity": "sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ==", 251 | "dev": true 252 | }, 253 | "undici-types": { 254 | "version": "6.13.0", 255 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", 256 | "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", 257 | "dev": true 258 | }, 259 | "uuid": { 260 | "version": "8.3.2", 261 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 262 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 263 | "dev": true 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npx playwright test tests/fwkTesting.spec.ts ", 8 | "test:reporter": "npx playwright test tests/fwkTesting.spec.ts --headed --reporter=allure-playwright", 9 | "open:allure-report": "npx ./allure generate ./allure-results && allure open" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/jagota-s/playwright-typescript-pom" 14 | }, 15 | "keywords": [], 16 | "author": "", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "@axe-core/playwright": "4.6.0", 20 | "@playwright/test": "^1.46.0", 21 | "@types/node": "^22.2.0", 22 | "allure-playwright": "^2.0.0-beta.20" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pageObjects/CartPage/CartPage.ts: -------------------------------------------------------------------------------- 1 | import { expect, Page } from "@playwright/test"; 2 | import { CommonPage } from "../../base_fwk/common/CommonPage"; 3 | import { CommonScenario } from "../../base_fwk/common/CommonScenario"; 4 | import { locators } from "./CartPageLocators"; 5 | export class CartPage extends CommonPage { 6 | constructor(public page: Page, readonly scenario: CommonScenario) { 7 | super(page, scenario); 8 | } 9 | 10 | async verifyProductIsDisplayed(productName) { 11 | //await this.page.waitForTimeout(2000); 12 | const selectedProductElement = await this.page.getByRole('heading', { name: productName }); 13 | await selectedProductElement.waitFor({ state: "visible" }); 14 | expect(selectedProductElement.isVisible).toBeTruthy(); 15 | }; 16 | 17 | async clickCheckout() { 18 | await this.page.getByRole('button', { name: 'Checkout❯' }).click(); 19 | await this.takeScreenshot("checkout"); 20 | }; 21 | 22 | } -------------------------------------------------------------------------------- /pageObjects/CartPage/CartPageLocators.ts: -------------------------------------------------------------------------------- 1 | 2 | export const locators = { 3 | cartProducts: "div li", 4 | productsText: ".card-body b", 5 | cart: "[routerlink*='cart']", 6 | orders : "button[routerlink*='myorders']", 7 | checkout: "text=Checkout", 8 | } 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pageObjects/DashBoardPage/DashBoardLocators.ts: -------------------------------------------------------------------------------- 1 | 2 | export const locators = { 3 | products :".card-body", 4 | productsText :".card-body b", 5 | cart :"[routerlink*='cart']", 6 | orders :"button[routerlink*='myorders']", 7 | 8 | } 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /pageObjects/DashBoardPage/DashBoardPage.ts: -------------------------------------------------------------------------------- 1 | import { expect, Page } from "@playwright/test"; 2 | import { CommonPage } from "../../base_fwk/common/CommonPage"; 3 | import { CommonScenario } from "../../base_fwk/common/CommonScenario"; 4 | import { locators } from "./DashBoardLocators"; 5 | 6 | export class DashboardPage extends CommonPage { 7 | constructor(public page: Page, readonly scenario: CommonScenario) { 8 | super(page, scenario); 9 | } 10 | 11 | async searchProductAddCart(productName) { 12 | const product = await this.page.locator(locators.products, { hasText: productName }); 13 | await product.waitFor({ state: "visible" }); 14 | const addCartButton = await product.locator("button", { hasText: " Add To Cart" }); 15 | const cartButtonVisible = await addCartButton.isVisible(); 16 | expect(addCartButton, "Add cart button is visible").toBeTruthy(); 17 | if (cartButtonVisible) { 18 | await addCartButton.click(); 19 | } 20 | } 21 | 22 | async navigateToOrders() { 23 | await this.page.locator(locators.orders).click(); 24 | await this.page.waitForLoadState("networkidle"); 25 | } 26 | 27 | 28 | async navigateToCart() { 29 | await this.page.locator(locators.cart).click(); 30 | await this.page.waitForLoadState("networkidle"); 31 | await this.page.waitForLoadState("domcontentloaded"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pageObjects/LoginPage/LoginPage.ts: -------------------------------------------------------------------------------- 1 | import AxeBuilder from "@axe-core/playwright"; 2 | import { expect, Page, } from "@playwright/test"; 3 | import { CommonPage } from "../../base_fwk/common/CommonPage"; 4 | import { CommonScenario } from "../../base_fwk/common/CommonScenario"; 5 | import { testData } from "../../tests/testData"; 6 | import { locators } from "../LoginPage/LoginPageLocators"; 7 | 8 | export class LoginPage extends CommonPage { 9 | 10 | constructor(public page: Page, readonly scenario: CommonScenario) { 11 | super(page, scenario); 12 | } 13 | async goTo() { 14 | await this.page.goto(testData.qa); 15 | await this.page.waitForLoadState("domcontentloaded"); 16 | await this.scenario.a11yAnalysis(); 17 | } 18 | 19 | async validLogin(username, password) { 20 | await this.page.locator(locators.userName).type(username); 21 | await this.page.locator(locators.password).type(password); 22 | await this.page.locator(locators.signInbutton).click(); 23 | await this.page.waitForLoadState("networkidle"); 24 | } 25 | } -------------------------------------------------------------------------------- /pageObjects/LoginPage/LoginPageLocators.ts: -------------------------------------------------------------------------------- 1 | 2 | export const locators = { 3 | userName: "#userEmail", 4 | password: "#userPassword", 5 | signInbutton: "#login" 6 | 7 | } 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pageObjects/OrdersHistoryPage/OrdersHistoryPage.ts: -------------------------------------------------------------------------------- 1 | import { expect, Page } from "@playwright/test"; 2 | import { CommonPage } from "../../base_fwk/common/CommonPage"; 3 | import { CommonScenario } from "../../base_fwk/common/CommonScenario"; 4 | import { locators } from "./OrdersHistoryPageLocators"; 5 | 6 | export class OrdersHistoryPage extends CommonPage { 7 | constructor(public page: Page, public scenario: CommonScenario) { 8 | super(page, scenario); 9 | } 10 | 11 | async searchOrderAndSelect() { 12 | let orderFound = false; 13 | await this.page.waitForSelector('tbody'); 14 | for (const row of await this.page.locator(locators.rows).all()) { 15 | const matchrowOrderId = await row.locator("th").textContent(); 16 | if (this.getValue("orderId")!.includes(matchrowOrderId!)) { 17 | await row.locator(locators.BTN_View).click(); 18 | orderFound = true; 19 | break; 20 | } 21 | } 22 | expect(orderFound).toBeTruthy(); 23 | this.takeScreenshot("Orders page"); 24 | } 25 | 26 | async getOrderId() { 27 | return await this.page.locator(locators.orderdIdDetail).textContent(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /pageObjects/OrdersHistoryPage/OrdersHistoryPageLocators.ts: -------------------------------------------------------------------------------- 1 | 2 | export const locators = { 3 | ordersTable: "tbody", 4 | rows: "tbody tr", 5 | orderdIdDetail: ".col - text", 6 | BTN_View: ".btn-primary" 7 | } 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /pageObjects/OrdersReviewPage/OrdersReviewPage.ts: -------------------------------------------------------------------------------- 1 | import { expect, Page } from "@playwright/test"; 2 | import { CommonPage } from "../../base_fwk/common/CommonPage"; 3 | import { CommonScenario } from "../../base_fwk/common/CommonScenario"; 4 | import { locators } from "./OrdersReviewPageLocators"; 5 | 6 | export class OrdersReviewPage extends CommonPage { 7 | 8 | constructor(public page: Page, public scenario: CommonScenario) { 9 | super(page, scenario); 10 | } 11 | 12 | 13 | async searchCountryAndSelect(countryCode: string, countryName: string) { 14 | 15 | await this.page.locator(locators.country).type(countryCode, { delay: 100 }); 16 | await this.page.locator(locators.dropdown).waitFor(); 17 | const optionsCount = await this.page.locator(locators.dropdown).locator("button").count(); 18 | for (let i = 0; i < optionsCount; ++i) { 19 | const text = await this.page.locator(locators.dropdown).locator("button").nth(i).textContent(); 20 | if (text?.trim() === countryName) { 21 | await this.page.locator(locators.dropdown).locator("button").nth(i).click(); 22 | break; 23 | } 24 | } 25 | } 26 | 27 | async VerifyEmailId(username) { 28 | await expect(this.page.locator(locators.emailId)).toHaveText(username); 29 | } 30 | 31 | async SubmitAndGetOrderId() { 32 | await this.page.locator(locators.submit).click(); 33 | await expect(await this.page.locator(locators.orderConfirmationText)).toHaveText(" Thankyou for the order. "); 34 | const orderID = await this.page.locator(locators.orderId).textContent(); 35 | this.setValue("orderId", orderID!); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pageObjects/OrdersReviewPage/OrdersReviewPageLocators.ts: -------------------------------------------------------------------------------- 1 | 2 | export const locators = { 3 | country :"[placeholder*='Country']", 4 | dropdown :".ta-results", 5 | emailId :".user__name [type='text']", 6 | submit :".action__submit", 7 | orderConfirmationText :".hero-primary", 8 | orderId : ".em-spacer-1 .ng-star-inserted" 9 | } 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | /** 4 | * Read environment variables from file. 5 | * https://github.com/motdotla/dotenv 6 | */ 7 | // require('dotenv').config(); 8 | 9 | /** 10 | * See https://playwright.dev/docs/test-configuration. 11 | */ 12 | export default defineConfig({ 13 | testDir: './tests', 14 | /* Maximum time one test can run for. */ 15 | timeout: 20 * 1000, 16 | expect: { 17 | /** 18 | * Maximum time expect() should wait for the condition to be met. 19 | * For example in `await expect(locator).toHaveText();` 20 | */ 21 | timeout: 5000, 22 | }, 23 | 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 : 3, 32 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 33 | reporter: [ 34 | ['dot'], 35 | ['html'], 36 | //['allure-playwright', { outputFolder: 'allure-results' }] 37 | ], 38 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 39 | use: { 40 | viewport: null, 41 | // extraHTTPHeaders: { 42 | // "Authorization" : "Basic" 43 | // }, 44 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 45 | actionTimeout: 0, 46 | /* Base URL to use in actions like `await page.goto('/')`. */ 47 | // baseURL: 'http://localhost:3000', 48 | 49 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 50 | trace: 'on-first-retry', 51 | video: 'retry-with-video', 52 | launchOptions: { 53 | headless: true, 54 | args: ["--start-maximized"], 55 | }, 56 | // httpCredentials: { 57 | // username: 'bill', 58 | // password: 'pa55w0rd', 59 | // } 60 | }, 61 | 62 | /* Configure projects for major browsers */ 63 | projects: [ 64 | { 65 | name: 'chromium', 66 | use: { 67 | ...devices['Desktop Chrome'], 68 | screenshot: 'on', 69 | trace: 'on', 70 | }, 71 | }, 72 | 73 | // { 74 | // name: 'firefox', 75 | // use: { 76 | // ...devices['Desktop Firefox'], 77 | // }, 78 | // }, 79 | 80 | // { 81 | // name: 'webkit', 82 | // use: { 83 | // ...devices['Desktop Safari'], 84 | // }, 85 | // }, 86 | 87 | /* Test against mobile viewports. */ 88 | // { 89 | // name: 'Mobile Chrome', 90 | // use: { 91 | // ...devices['Pixel 5'], 92 | // }, 93 | // }, 94 | // { 95 | // name: 'Mobile Safari', 96 | // use: { 97 | // ...devices['iPhone 12'], 98 | // }, 99 | // }, 100 | 101 | /* Test against branded browsers. */ 102 | // { 103 | // name: 'Microsoft Edge', 104 | // use: { 105 | // channel: 'msedge', 106 | // }, 107 | // }, 108 | // { 109 | // name: 'Google Chrome', 110 | // use: { 111 | // channel: 'chrome', 112 | // }, 113 | // }, 114 | ], 115 | 116 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */ 117 | outputDir: 'test-results/', 118 | 119 | /* Run your local dev server before starting the tests */ 120 | // webServer: { 121 | // command: 'npm run start', 122 | // port: 3000, 123 | // }, 124 | }); 125 | 126 | -------------------------------------------------------------------------------- /tests/apiOperationTesting.spec.ts: -------------------------------------------------------------------------------- 1 | import { request } from "@playwright/test"; 2 | import test, { expect } from "../base_fwk/fixtures/baseTest" 3 | 4 | test.describe('API CRUD operations examples', () => { 5 | 6 | test("Post reqres=", async ({ request, page }) => { 7 | const createUser = await request.post("https://reqres.in/api/users", { 8 | data: { 9 | "name": "spiderman", 10 | "job": "QA" 11 | }, 12 | }); 13 | console.log(await createUser.json()); 14 | }); 15 | 16 | test("Get reqres=", async ({ request }) => { 17 | const getUser = await request.get("https://reqres.in/api/users/2",); 18 | console.log(await getUser.json()); 19 | }) 20 | 21 | test("Delete reqres=", async ({ request }) => { 22 | const deleteUser = await request.delete("https://reqres.in/api/users/2"); 23 | console.log(await deleteUser.status()); 24 | }) 25 | 26 | test("Update reqres", async ({ request }) => { 27 | const updateUser = await request.put("https://reqres.in/api/users/2", { 28 | data: { 29 | "name": "batman", 30 | "job": "gotham resident" 31 | } 32 | }) 33 | console.log(await updateUser.json()); 34 | }) 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /tests/demo-todo-app.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, type Page } from '@playwright/test'; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('https://demo.playwright.dev/todomvc'); 5 | }); 6 | 7 | const TODO_ITEMS = [ 8 | 'buy some cheese', 9 | 'feed the cat', 10 | 'book a doctors appointment' 11 | ]; 12 | 13 | test.describe('New Todo', () => { 14 | console.log("in new todo describe b") 15 | test('should allow me to add todo items', async ({ page }) => { 16 | // Create 1st todo. 17 | await page.locator('.new-todo').fill(TODO_ITEMS[0]); 18 | await page.locator('.new-todo').press('Enter'); 19 | 20 | // Make sure the list only has one todo item. 21 | await expect(page.locator('.view label')).toHaveText([ 22 | TODO_ITEMS[0] 23 | ]); 24 | 25 | // Create 2nd todo. 26 | await page.locator('.new-todo').fill(TODO_ITEMS[1]); 27 | await page.locator('.new-todo').press('Enter'); 28 | 29 | // Make sure the list now has two todo items. 30 | await expect(page.locator('.view label')).toHaveText([ 31 | TODO_ITEMS[0], 32 | TODO_ITEMS[1] 33 | ]); 34 | 35 | await checkNumberOfTodosInLocalStorage(page, 2); 36 | }); 37 | 38 | test('should clear text input field when an item is added', async ({ page }) => { 39 | // Create one todo item. 40 | await page.locator('.new-todo').fill(TODO_ITEMS[0]); 41 | await page.locator('.new-todo').press('Enter'); 42 | 43 | // Check that input is empty. 44 | await expect(page.locator('.new-todo')).toBeEmpty(); 45 | await checkNumberOfTodosInLocalStorage(page, 1); 46 | }); 47 | 48 | test('should append new items to the bottom of the list', async ({ page }) => { 49 | // Create 3 items. 50 | await createDefaultTodos(page); 51 | 52 | // Check test using different methods. 53 | await expect(page.locator('.todo-count')).toHaveText('3 items left'); 54 | await expect(page.locator('.todo-count')).toContainText('3'); 55 | await expect(page.locator('.todo-count')).toHaveText(/3/); 56 | 57 | // Check all items in one call. 58 | await expect(page.locator('.view label')).toHaveText(TODO_ITEMS); 59 | await checkNumberOfTodosInLocalStorage(page, 3); 60 | }); 61 | 62 | test('should show #main and #footer when items added', async ({ page }) => { 63 | await page.locator('.new-todo').fill(TODO_ITEMS[0]); 64 | await page.locator('.new-todo').press('Enter'); 65 | 66 | await expect(page.locator('.main')).toBeVisible(); 67 | await expect(page.locator('.footer')).toBeVisible(); 68 | await checkNumberOfTodosInLocalStorage(page, 1); 69 | }); 70 | }); 71 | 72 | test.describe('Mark all as completed', () => { 73 | test.beforeEach(async ({ page }) => { 74 | await createDefaultTodos(page); 75 | await checkNumberOfTodosInLocalStorage(page, 3); 76 | }); 77 | 78 | test.afterEach(async ({ page }) => { 79 | await checkNumberOfTodosInLocalStorage(page, 3); 80 | }); 81 | 82 | test('should allow me to mark all items as completed', async ({ page }) => { 83 | // Complete all todos. 84 | await page.locator('.toggle-all').check(); 85 | 86 | // Ensure all todos have 'completed' class. 87 | await expect(page.locator('.todo-list li')).toHaveClass(['completed', 'completed', 'completed']); 88 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 89 | }); 90 | 91 | test('should allow me to clear the complete state of all items', async ({ page }) => { 92 | // Check and then immediately uncheck. 93 | await page.locator('.toggle-all').check(); 94 | await page.locator('.toggle-all').uncheck(); 95 | 96 | // Should be no completed classes. 97 | await expect(page.locator('.todo-list li')).toHaveClass(['', '', '']); 98 | }); 99 | 100 | test('complete all checkbox should update state when items are completed / cleared', async ({ page }) => { 101 | const toggleAll = page.locator('.toggle-all'); 102 | await toggleAll.check(); 103 | await expect(toggleAll).toBeChecked(); 104 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 105 | 106 | // Uncheck first todo. 107 | const firstTodo = page.locator('.todo-list li').nth(0); 108 | await firstTodo.locator('.toggle').uncheck(); 109 | 110 | // Reuse toggleAll locator and make sure its not checked. 111 | await expect(toggleAll).not.toBeChecked(); 112 | 113 | await firstTodo.locator('.toggle').check(); 114 | await checkNumberOfCompletedTodosInLocalStorage(page, 3); 115 | 116 | // Assert the toggle all is checked again. 117 | await expect(toggleAll).toBeChecked(); 118 | }); 119 | }); 120 | 121 | test.describe('Item', () => { 122 | 123 | test('should allow me to mark items as complete', async ({ page }) => { 124 | // Create two items. 125 | for (const item of TODO_ITEMS.slice(0, 2)) { 126 | await page.locator('.new-todo').fill(item); 127 | await page.locator('.new-todo').press('Enter'); 128 | } 129 | 130 | // Check first item. 131 | const firstTodo = page.locator('.todo-list li').nth(0); 132 | await firstTodo.locator('.toggle').check(); 133 | await expect(firstTodo).toHaveClass('completed'); 134 | 135 | // Check second item. 136 | const secondTodo = page.locator('.todo-list li').nth(1); 137 | await expect(secondTodo).not.toHaveClass('completed'); 138 | await secondTodo.locator('.toggle').check(); 139 | 140 | // Assert completed class. 141 | await expect(firstTodo).toHaveClass('completed'); 142 | await expect(secondTodo).toHaveClass('completed'); 143 | }); 144 | 145 | test('should allow me to un-mark items as complete', async ({ page }) => { 146 | // Create two items. 147 | for (const item of TODO_ITEMS.slice(0, 2)) { 148 | await page.locator('.new-todo').fill(item); 149 | await page.locator('.new-todo').press('Enter'); 150 | } 151 | 152 | const firstTodo = page.locator('.todo-list li').nth(0); 153 | const secondTodo = page.locator('.todo-list li').nth(1); 154 | await firstTodo.locator('.toggle').check(); 155 | await expect(firstTodo).toHaveClass('completed'); 156 | await expect(secondTodo).not.toHaveClass('completed'); 157 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 158 | 159 | await firstTodo.locator('.toggle').uncheck(); 160 | await expect(firstTodo).not.toHaveClass('completed'); 161 | await expect(secondTodo).not.toHaveClass('completed'); 162 | await checkNumberOfCompletedTodosInLocalStorage(page, 0); 163 | }); 164 | 165 | test('should allow me to edit an item', async ({ page }) => { 166 | await createDefaultTodos(page); 167 | 168 | const todoItems = page.locator('.todo-list li'); 169 | const secondTodo = todoItems.nth(1); 170 | await secondTodo.dblclick(); 171 | await expect(secondTodo.locator('.edit')).toHaveValue(TODO_ITEMS[1]); 172 | await secondTodo.locator('.edit').fill('buy some sausages'); 173 | await secondTodo.locator('.edit').press('Enter'); 174 | 175 | // Explicitly assert the new text value. 176 | await expect(todoItems).toHaveText([ 177 | TODO_ITEMS[0], 178 | 'buy some sausages', 179 | TODO_ITEMS[2] 180 | ]); 181 | await checkTodosInLocalStorage(page, 'buy some sausages'); 182 | }); 183 | }); 184 | 185 | test.describe('Editing', () => { 186 | test.beforeEach(async ({ page }) => { 187 | await createDefaultTodos(page); 188 | await checkNumberOfTodosInLocalStorage(page, 3); 189 | }); 190 | 191 | test('should hide other controls when editing', async ({ page }) => { 192 | const todoItem = page.locator('.todo-list li').nth(1); 193 | await todoItem.dblclick(); 194 | await expect(todoItem.locator('.toggle')).not.toBeVisible(); 195 | await expect(todoItem.locator('label')).not.toBeVisible(); 196 | await checkNumberOfTodosInLocalStorage(page, 3); 197 | }); 198 | 199 | test('should save edits on blur', async ({ page }) => { 200 | const todoItems = page.locator('.todo-list li'); 201 | await todoItems.nth(1).dblclick(); 202 | await todoItems.nth(1).locator('.edit').fill('buy some sausages'); 203 | await todoItems.nth(1).locator('.edit').dispatchEvent('blur'); 204 | 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 | test('should trim entered text', async ({ page }) => { 214 | const todoItems = page.locator('.todo-list li'); 215 | await todoItems.nth(1).dblclick(); 216 | await todoItems.nth(1).locator('.edit').fill(' buy some sausages '); 217 | await todoItems.nth(1).locator('.edit').press('Enter'); 218 | 219 | await expect(todoItems).toHaveText([ 220 | TODO_ITEMS[0], 221 | 'buy some sausages', 222 | TODO_ITEMS[2], 223 | ]); 224 | await checkTodosInLocalStorage(page, 'buy some sausages'); 225 | }); 226 | 227 | test('should remove the item if an empty text string was entered', async ({ page }) => { 228 | const todoItems = page.locator('.todo-list li'); 229 | await todoItems.nth(1).dblclick(); 230 | await todoItems.nth(1).locator('.edit').fill(''); 231 | await todoItems.nth(1).locator('.edit').press('Enter'); 232 | 233 | await expect(todoItems).toHaveText([ 234 | TODO_ITEMS[0], 235 | TODO_ITEMS[2], 236 | ]); 237 | }); 238 | 239 | test('should cancel edits on escape', async ({ page }) => { 240 | const todoItems = page.locator('.todo-list li'); 241 | await todoItems.nth(1).dblclick(); 242 | await todoItems.nth(1).locator('.edit').press('Escape'); 243 | await expect(todoItems).toHaveText(TODO_ITEMS); 244 | }); 245 | }); 246 | 247 | test.describe('Counter', () => { 248 | test('should display the current number of todo items', async ({ page }) => { 249 | await page.locator('.new-todo').fill(TODO_ITEMS[0]); 250 | await page.locator('.new-todo').press('Enter'); 251 | await expect(page.locator('.todo-count')).toContainText('1'); 252 | 253 | await page.locator('.new-todo').fill(TODO_ITEMS[1]); 254 | await page.locator('.new-todo').press('Enter'); 255 | await expect(page.locator('.todo-count')).toContainText('2'); 256 | 257 | await checkNumberOfTodosInLocalStorage(page, 2); 258 | }); 259 | }); 260 | 261 | test.describe('Clear completed button', () => { 262 | test.beforeEach(async ({ page }) => { 263 | await createDefaultTodos(page); 264 | }); 265 | 266 | test('should display the correct text', async ({ page }) => { 267 | await page.locator('.todo-list li .toggle').first().check(); 268 | await expect(page.locator('.clear-completed')).toHaveText('Clear completed'); 269 | }); 270 | 271 | test('should remove completed items when clicked', async ({ page }) => { 272 | const todoItems = page.locator('.todo-list li'); 273 | await todoItems.nth(1).locator('.toggle').check(); 274 | await page.locator('.clear-completed').click(); 275 | await expect(todoItems).toHaveCount(2); 276 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 277 | }); 278 | 279 | test('should be hidden when there are no items that are completed', async ({ page }) => { 280 | await page.locator('.todo-list li .toggle').first().check(); 281 | await page.locator('.clear-completed').click(); 282 | await expect(page.locator('.clear-completed')).toBeHidden(); 283 | }); 284 | }); 285 | 286 | test.describe('Persistence', () => { 287 | test('should persist its data', async ({ page }) => { 288 | for (const item of TODO_ITEMS.slice(0, 2)) { 289 | await page.locator('.new-todo').fill(item); 290 | await page.locator('.new-todo').press('Enter'); 291 | } 292 | 293 | const todoItems = page.locator('.todo-list li'); 294 | await todoItems.nth(0).locator('.toggle').check(); 295 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 296 | await expect(todoItems).toHaveClass(['completed', '']); 297 | 298 | // Ensure there is 1 completed item. 299 | checkNumberOfCompletedTodosInLocalStorage(page, 1); 300 | 301 | // Now reload. 302 | await page.reload(); 303 | await expect(todoItems).toHaveText([TODO_ITEMS[0], TODO_ITEMS[1]]); 304 | await expect(todoItems).toHaveClass(['completed', '']); 305 | }); 306 | }); 307 | 308 | test.describe('Routing', () => { 309 | test.beforeEach(async ({ page }) => { 310 | await createDefaultTodos(page); 311 | // make sure the app had a chance to save updated todos in storage 312 | // before navigating to a new view, otherwise the items can get lost :( 313 | // in some frameworks like Durandal 314 | await checkTodosInLocalStorage(page, TODO_ITEMS[0]); 315 | }); 316 | 317 | test('should allow me to display active items', async ({ page }) => { 318 | await page.locator('.todo-list li .toggle').nth(1).check(); 319 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 320 | await page.locator('.filters >> text=Active').click(); 321 | await expect(page.locator('.todo-list li')).toHaveCount(2); 322 | await expect(page.locator('.todo-list li')).toHaveText([TODO_ITEMS[0], TODO_ITEMS[2]]); 323 | }); 324 | 325 | test('should respect the back button', async ({ page }) => { 326 | await page.locator('.todo-list li .toggle').nth(1).check(); 327 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 328 | 329 | await test.step('Showing all items', async () => { 330 | await page.locator('.filters >> text=All').click(); 331 | await expect(page.locator('.todo-list li')).toHaveCount(3); 332 | }); 333 | 334 | await test.step('Showing active items', async () => { 335 | await page.locator('.filters >> text=Active').click(); 336 | }); 337 | 338 | await test.step('Showing completed items', async () => { 339 | await page.locator('.filters >> text=Completed').click(); 340 | }); 341 | 342 | await expect(page.locator('.todo-list li')).toHaveCount(1); 343 | await page.goBack(); 344 | await expect(page.locator('.todo-list li')).toHaveCount(2); 345 | await page.goBack(); 346 | await expect(page.locator('.todo-list li')).toHaveCount(3); 347 | }); 348 | 349 | test('should allow me to display completed items', async ({ page }) => { 350 | await page.locator('.todo-list li .toggle').nth(1).check(); 351 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 352 | await page.locator('.filters >> text=Completed').click(); 353 | await expect(page.locator('.todo-list li')).toHaveCount(1); 354 | }); 355 | 356 | test('should allow me to display all items', async ({ page }) => { 357 | await page.locator('.todo-list li .toggle').nth(1).check(); 358 | await checkNumberOfCompletedTodosInLocalStorage(page, 1); 359 | await page.locator('.filters >> text=Active').click(); 360 | await page.locator('.filters >> text=Completed').click(); 361 | await page.locator('.filters >> text=All').click(); 362 | await expect(page.locator('.todo-list li')).toHaveCount(3); 363 | }); 364 | 365 | test('should highlight the currently applied filter', async ({ page }) => { 366 | await expect(page.locator('.filters >> text=All')).toHaveClass('selected'); 367 | await page.locator('.filters >> text=Active').click(); 368 | // Page change - active items. 369 | await expect(page.locator('.filters >> text=Active')).toHaveClass('selected'); 370 | await page.locator('.filters >> text=Completed').click(); 371 | // Page change - completed items. 372 | await expect(page.locator('.filters >> text=Completed')).toHaveClass('selected'); 373 | }); 374 | }); 375 | 376 | async function createDefaultTodos(page: Page) { 377 | for (const item of TODO_ITEMS) { 378 | await page.locator('.new-todo').fill(item); 379 | await page.locator('.new-todo').press('Enter'); 380 | } 381 | } 382 | 383 | async function checkNumberOfTodosInLocalStorage(page: Page, expected: number) { 384 | return await page.waitForFunction(e => { 385 | return JSON.parse(localStorage['react-todos']).length === e; 386 | }, expected); 387 | } 388 | 389 | async function checkNumberOfCompletedTodosInLocalStorage(page: Page, expected: number) { 390 | return await page.waitForFunction(e => { 391 | return JSON.parse(localStorage['reasct-todos']).filter((todo: any) => todo.completed).length === e; 392 | }, expected); 393 | } 394 | 395 | async function checkTodosInLocalStorage(page: Page, title: string) { 396 | return await page.waitForFunction(t => { 397 | return JSON.parse(localStorage['react-todos']).map((todo: any) => todo.title).includes(t); 398 | }, title); 399 | } 400 | -------------------------------------------------------------------------------- /tests/dialogHandling.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "../base_fwk/fixtures/baseTest" 2 | 3 | test.describe('Handle different types of dialogs', () => { 4 | 5 | test.beforeEach(async ({ page }) => { 6 | await page.goto("https://letcode.in/alert"); 7 | await page.waitForLoadState("domcontentloaded"); 8 | }); 9 | 10 | test("Simple alert", async ({ page }) => { 11 | await page.on('dialog', dialog => { 12 | console.log(dialog.message()); 13 | dialog.accept(); 14 | }); 15 | await page.locator("#accept", { 16 | hasText: "Simple Alert" 17 | }).click(); 18 | }) 19 | 20 | test("Confirm alert type", async ({ page }) => { 21 | await page.on('dialog', dialog => { 22 | console.log(dialog.message()); 23 | dialog.accept(); 24 | }); 25 | await page.locator("#confirm", { 26 | hasText: "Confirm Alert" 27 | }).click(); 28 | }) 29 | 30 | test("Prompt alert type", async ({ page }) => { 31 | await page.on('dialog', dialog => { 32 | console.log(dialog.message()); 33 | dialog.accept("i see you"); 34 | }); 35 | await page.locator("#prompt", { 36 | hasText: "Prompt Alert" 37 | }).click(); 38 | }) 39 | 40 | test("Modern alert type", async ({ page }) => { 41 | await page.on('dialog', dialog => { 42 | console.log(dialog.message()); 43 | dialog.dismiss(); 44 | }); 45 | await page.locator("#modern", { 46 | hasText: "Modern Alert" 47 | }).click(); 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/fwkTesting.spec.ts: -------------------------------------------------------------------------------- 1 | import AxeBuilder from "@axe-core/playwright"; 2 | import test, { expect } from "../base_fwk/fixtures/baseTest" 3 | import { OrdersReviewPage } from "../pageObjects/OrdersReviewPage/OrdersReviewPage"; 4 | //import { POManager } from "../pageObjects/POManager" 5 | import { testData } from "./testData"; 6 | //import { expect } from "@playwright/test" 7 | //import { test, expect, type Page } from '@playwright/test'; 8 | 9 | test.describe('two tests', () => { 10 | console.log("in describe from spec file"); 11 | test("first playwright test12", async ({ page, context, browser, loginPage, dashboardPage, cartPage, ordersReviewPage, ordersHistoryPage }) => { 12 | console.log("test start") 13 | await loginPage.goTo(); 14 | console.log("test ends") 15 | 16 | // await loginPage.validLogin(testConfig.username, testConfig.password); 17 | // //await dashboardPage. 18 | // await dashboardPage.testDashboard(); 19 | // await cartPage.testCartPage(); 20 | // await ordersHistoryPage.testOrdersHistory(); 21 | // await ordersReviewPage.testOrdersReviewPage(); 22 | // const value = await dashboardPage.getValue("sumit"); 23 | // console.log(value); 24 | }); 25 | 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /tests/networkMocking.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "../base_fwk/fixtures/baseTest" 2 | import { testData } from "./testData"; 3 | 4 | test.describe('Network mocking examples', () => { 5 | test("Modify API responses", async ({ page, request, loginPage }) => { 6 | await page.route("https://rahulshettyacademy.com/api/ecom/product/get-all-products", async (route) => { 7 | // route.fetch() is only available in version 1.29 onwards 8 | const response = await route.fetch(); 9 | const json = await response.json(); 10 | json.data[0].productName = "supermannnnnn"; 11 | json.data[1].productName = "batmann"; 12 | await route.fulfill({ response, json }); 13 | }) 14 | await loginPage.goTo(); 15 | await loginPage.validLogin(testData.username, testData.password); 16 | // await page.waitForResponse("https://rahulshettyacademy.com/api/ecom/product/get-all-products"); 17 | await page.close(); 18 | }) 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /tests/productCheckout.spec.ts: -------------------------------------------------------------------------------- 1 | import test, { expect } from "../base_fwk/fixtures/baseTest" 2 | import { testData } from "./testData"; 3 | 4 | test.describe('E2E test flows', () => { 5 | 6 | test("Product Checkout", async ({ page, loginPage, dashboardPage, cartPage, ordersReviewPage, ordersHistoryPage }, testinfo) => { 7 | await loginPage.goTo(); 8 | await loginPage.validLogin(testData.username, testData.password); 9 | 10 | await dashboardPage.searchProductAddCart("Zara Coat 3"); 11 | await dashboardPage.navigateToCart(); 12 | 13 | await cartPage.verifyProductIsDisplayed("Zara Coat 3"); 14 | await cartPage.clickCheckout(); 15 | 16 | await ordersReviewPage.searchCountryAndSelect("ind", "India"); 17 | await ordersReviewPage.SubmitAndGetOrderId(); 18 | 19 | await dashboardPage.navigateToOrders(); 20 | await ordersHistoryPage.searchOrderAndSelect(); 21 | }); 22 | }) 23 | -------------------------------------------------------------------------------- /tests/testData.ts: -------------------------------------------------------------------------------- 1 | export const testData = { 2 | qa: `https://rahulshettyacademy.com/client`, 3 | dev: ``, 4 | qaApi: `https://reqres.in`, 5 | devApi: ``, 6 | username: `practicetest@play.com`, 7 | password: `Test@1234`, 8 | waitForElement: 120000, 9 | dbUsername: ``, 10 | dbPassword: ``, 11 | dbServerName: ``, 12 | dbPort: ``, 13 | dbName: `` 14 | } --------------------------------------------------------------------------------