├── .github └── workflows │ └── playwright.yml ├── .gitignore ├── README.md ├── fixtures ├── demo.png ├── demo2.png ├── demo3.jpg └── input-data.json ├── package-lock.json ├── package.json ├── pages └── budget-app │ └── User.js ├── playwright.config.js └── tests ├── budget-tracker.spec.js ├── changeable-iframe.spec.js ├── context-menu.spec.js ├── download.spec.js ├── dynamic-table.spec.js ├── fetch.spec.js ├── geolocation.spec.js ├── hidden.spec.js ├── iframe.spec.js ├── links.spec.js ├── mouseover.spec.js ├── multi-level-dropdown.spec.js ├── onboarding.spec.js ├── popup.spec.js ├── qr-code.spec.js ├── qr-code.spec.js-snapshots ├── QR-Code-Generator-Should-visually-validate-generated-image-1-darwin.png └── QR-Code-Generator-Should-visually-validate-generated-image-1-linux.png ├── rating.spec.js ├── redirect.spec.js ├── shadow-dom.spec.js ├── sortable-list.spec.js ├── tags.spec.js ├── upload.spec.js └── verify-account.spec.js /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: QA Playground Playwright Tests 2 | on: 3 | push: 4 | branches: [main, master] 5 | pull_request: 6 | branches: [main, master] 7 | jobs: 8 | tests: 9 | timeout-minutes: 60 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v2 14 | with: 15 | node-version: "18.x" 16 | - name: Install dependencies 17 | run: npm ci 18 | - name: Install Playwright 19 | run: npx playwright install --with-deps 20 | - name: Run Playwright tests 21 | run: npx playwright test 22 | - uses: actions/upload-artifact@v2 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 | userStorageState.json 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # End-to-end tests for the [QAPlayground.dev](https://qaplayground.dev) mini web apps 2 | 3 | With the help of a great [Playwright](https://playwright.dev), I managed to solve all the test automation challenges. 😉 4 | 5 | ## Installing dependencies 6 | 7 | How to install dependencies: 8 | 9 | ``` 10 | npm i 11 | ``` 12 | 13 | ## Running the project 14 | 15 | How to run the tests: 16 | 17 | ``` 18 | npx playwright test 19 | ``` 20 | 21 | How to run the tests with open browser: 22 | 23 | ``` 24 | npx playwright test --headed 25 | ``` 26 | 27 | Or you can run a pre-defined script with the command 28 | 29 | ``` 30 | npm run test 31 | ``` 32 | 33 | or 34 | 35 | ``` 36 | npm run headed 37 | ``` 38 | -------------------------------------------------------------------------------- /fixtures/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marko-simic/qa-playground-tests/206fb804293589ffc756b3fb9e756634ff5fb0fe/fixtures/demo.png -------------------------------------------------------------------------------- /fixtures/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marko-simic/qa-playground-tests/206fb804293589ffc756b3fb9e756634ff5fb0fe/fixtures/demo2.png -------------------------------------------------------------------------------- /fixtures/demo3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marko-simic/qa-playground-tests/206fb804293589ffc756b3fb9e756634ff5fb0fe/fixtures/demo3.jpg -------------------------------------------------------------------------------- /fixtures/input-data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "date": "2023-07-01", 4 | "description": "Salary", 5 | "type": "income", 6 | "amount": 5000 7 | }, 8 | { 9 | "date": "2023-06-08", 10 | "description": "Dividends", 11 | "type": "income", 12 | "amount": 500 13 | }, 14 | { 15 | "date": "2023-08-20", 16 | "description": "Mortgage", 17 | "type": "expense", 18 | "amount": 1500 19 | }, 20 | { 21 | "date": "2023-09-19", 22 | "description": "Car", 23 | "type": "expense", 24 | "amount": 500 25 | }, 26 | { 27 | "date": "2023-07-15", 28 | "description": "Phone", 29 | "type": "expense", 30 | "amount": 100 31 | }, 32 | { 33 | "date": "2023-11-17", 34 | "description": "Health Insurance", 35 | "type": "expense", 36 | "amount": 1000 37 | }, 38 | { 39 | "date": "2023-10-26", 40 | "description": "Food", 41 | "type": "expense", 42 | "amount": 2000 43 | }, 44 | { 45 | "date": "2023-08-11", 46 | "description": "Entertainment", 47 | "type": "expense", 48 | "amount": 100 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qa-playground-tests", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "qa-playground-tests", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "playwright": "^1.33.0" 13 | }, 14 | "devDependencies": { 15 | "@playwright/test": "^1.33.0" 16 | } 17 | }, 18 | "node_modules/@playwright/test": { 19 | "version": "1.33.0", 20 | "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.33.0.tgz", 21 | "integrity": "sha512-YunBa2mE7Hq4CfPkGzQRK916a4tuZoVx/EpLjeWlTVOnD4S2+fdaQZE0LJkbfhN5FTSKNLdcl7MoT5XB37bTkg==", 22 | "dev": true, 23 | "dependencies": { 24 | "@types/node": "*", 25 | "playwright-core": "1.33.0" 26 | }, 27 | "bin": { 28 | "playwright": "cli.js" 29 | }, 30 | "engines": { 31 | "node": ">=14" 32 | }, 33 | "optionalDependencies": { 34 | "fsevents": "2.3.2" 35 | } 36 | }, 37 | "node_modules/@types/node": { 38 | "version": "18.6.1", 39 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", 40 | "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==", 41 | "dev": true 42 | }, 43 | "node_modules/fsevents": { 44 | "version": "2.3.2", 45 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 46 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 47 | "dev": true, 48 | "hasInstallScript": true, 49 | "optional": true, 50 | "os": [ 51 | "darwin" 52 | ], 53 | "engines": { 54 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 55 | } 56 | }, 57 | "node_modules/playwright": { 58 | "version": "1.33.0", 59 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.33.0.tgz", 60 | "integrity": "sha512-+zzU3V2TslRX2ETBRgQKsKytYBkJeLZ2xzUj4JohnZnxQnivoUvOvNbRBYWSYykQTO0Y4zb8NwZTYFUO+EpPBQ==", 61 | "hasInstallScript": true, 62 | "dependencies": { 63 | "playwright-core": "1.33.0" 64 | }, 65 | "bin": { 66 | "playwright": "cli.js" 67 | }, 68 | "engines": { 69 | "node": ">=14" 70 | } 71 | }, 72 | "node_modules/playwright-core": { 73 | "version": "1.33.0", 74 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.33.0.tgz", 75 | "integrity": "sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw==", 76 | "bin": { 77 | "playwright": "cli.js" 78 | }, 79 | "engines": { 80 | "node": ">=14" 81 | } 82 | } 83 | }, 84 | "dependencies": { 85 | "@playwright/test": { 86 | "version": "1.33.0", 87 | "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.33.0.tgz", 88 | "integrity": "sha512-YunBa2mE7Hq4CfPkGzQRK916a4tuZoVx/EpLjeWlTVOnD4S2+fdaQZE0LJkbfhN5FTSKNLdcl7MoT5XB37bTkg==", 89 | "dev": true, 90 | "requires": { 91 | "@types/node": "*", 92 | "fsevents": "2.3.2", 93 | "playwright-core": "1.33.0" 94 | } 95 | }, 96 | "@types/node": { 97 | "version": "18.6.1", 98 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.1.tgz", 99 | "integrity": "sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==", 100 | "dev": true 101 | }, 102 | "fsevents": { 103 | "version": "2.3.2", 104 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", 105 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 106 | "dev": true, 107 | "optional": true 108 | }, 109 | "playwright": { 110 | "version": "1.33.0", 111 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.33.0.tgz", 112 | "integrity": "sha512-+zzU3V2TslRX2ETBRgQKsKytYBkJeLZ2xzUj4JohnZnxQnivoUvOvNbRBYWSYykQTO0Y4zb8NwZTYFUO+EpPBQ==", 113 | "requires": { 114 | "playwright-core": "1.33.0" 115 | } 116 | }, 117 | "playwright-core": { 118 | "version": "1.33.0", 119 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.33.0.tgz", 120 | "integrity": "sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw==" 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qa-playground-tests", 3 | "version": "1.0.0", 4 | "description": "Playwright end-to-end tests for the QAPlayground.dev mini web apps", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npx playwright test", 8 | "headed": "npx playwright test --headed" 9 | }, 10 | "keywords": [], 11 | "author": "Marko Simic", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@playwright/test": "^1.33.0" 15 | }, 16 | "dependencies": { 17 | "playwright": "^1.33.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pages/budget-app/User.js: -------------------------------------------------------------------------------- 1 | exports.UserPage = class UserPage { 2 | /** 3 | * @param {import('playwright').Page} page 4 | */ 5 | 6 | constructor(page) { 7 | this.page = page 8 | } 9 | 10 | async openApp() { 11 | await this.page.goto("apps/budget-tracker/") 12 | await this.page.locator(".budget-tracker").waitFor({ state: "visible" }) 13 | } 14 | 15 | async addEntry(record) { 16 | const previousRecords = await this.page.locator(".entries >> tr").count() 17 | 18 | await this.page.locator('text="New Entry"').click() 19 | 20 | const currentRow = this.page.locator(".entries >> tr").nth(previousRecords) 21 | const date = currentRow.locator('[type="date"]') 22 | const description = currentRow.locator(".input-description") 23 | const type = currentRow.locator(".input-type") 24 | const amount = currentRow.locator(".input-amount") 25 | const total = this.page.locator(".total") 26 | 27 | // Setting date by executing JavaScript code in the page 28 | await date.evaluate( 29 | (datepicker, date) => (datepicker.value = date), 30 | record.date 31 | ) 32 | 33 | await description.type(record.description) 34 | await type.selectOption(record.type) 35 | await amount.clear() 36 | await amount.type(record.amount.toString()) 37 | await amount.press("Enter") 38 | 39 | return { 40 | date: date, 41 | description: description, 42 | type: type, 43 | amount: amount, 44 | total: total, 45 | } 46 | } 47 | 48 | async editRow(row, record) { 49 | const currentRow = this.page.locator(".entries >> tr").nth(row - 1) 50 | const date = currentRow.locator('[type="date"]') 51 | const description = currentRow.locator(".input-description") 52 | const type = currentRow.locator(".input-type") 53 | const amount = currentRow.locator(".input-amount") 54 | const total = this.page.locator(".total") 55 | 56 | await date.type(record.date) 57 | await description.clear() 58 | await description.type(record.description) 59 | await type.selectOption(record.type) 60 | await amount.clear() 61 | await amount.type(record.amount.toString()) 62 | await amount.press("Enter") 63 | 64 | return { 65 | date: date, 66 | description: description, 67 | type: type, 68 | amount: amount, 69 | total: total, 70 | } 71 | } 72 | 73 | async removeAllRecords() { 74 | const rows = this.page.locator(".entries >> tr") 75 | for (let index = await rows.count(); index > 0; index--) { 76 | await rows 77 | .nth(index - 1) 78 | .locator(".delete-entry") 79 | .click({ delay: 300 }) 80 | } 81 | 82 | return rows 83 | } 84 | 85 | convertDate(date) { 86 | return new Date(date).toLocaleString("en-US") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /playwright.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | const { devices } = require("@playwright/test") 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // require('dotenv').config(); 9 | 10 | /** 11 | * @see https://playwright.dev/docs/test-configuration 12 | * @type {import('@playwright/test').PlaywrightTestConfig} 13 | */ 14 | const config = { 15 | testDir: "./tests", 16 | /* Maximum time one test can run for. */ 17 | timeout: 30 * 1000, 18 | expect: { 19 | /** 20 | * Maximum time expect() should wait for the condition to be met. 21 | * For example in `await expect(locator).toHaveText();` 22 | */ 23 | timeout: 5000, 24 | }, 25 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 26 | forbidOnly: !!process.env.CI, 27 | /* Retry on CI only */ 28 | retries: process.env.CI ? 2 : 0, 29 | /* Opt out of parallel tests on CI. */ 30 | workers: process.env.CI ? 1 : undefined, 31 | 32 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 33 | reporter: [["list"], ["html", { open: "never" }]], 34 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 35 | use: { 36 | // Emulates the user locale. 37 | locale: "en-US", 38 | 39 | channel: "chrome", // or 'msedge', 'chrome-beta', 'msedge-beta', 'msedge-dev', etc. 40 | 41 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 42 | actionTimeout: 0, 43 | /* Base URL to use in actions like `await page.goto('/')`. */ 44 | baseURL: "https://qaplayground.dev", 45 | 46 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 47 | trace: "retain-on-failure", 48 | video: "on", 49 | screenshot: "on", 50 | 51 | // launchOptions: { 52 | // slowMo: 500, 53 | // }, 54 | 55 | viewport: { width: 1366, height: 768 }, 56 | }, 57 | 58 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */ 59 | // outputDir: 'test-results/', 60 | 61 | /* Run your local dev server before starting the tests */ 62 | // webServer: { 63 | // command: 'npm run start', 64 | // port: 3000, 65 | // }, 66 | } 67 | 68 | module.exports = config 69 | -------------------------------------------------------------------------------- /tests/budget-tracker.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test") 2 | 3 | const data = require("../fixtures/input-data.json") 4 | 5 | const { UserPage } = require("../pages/budget-app/User") 6 | 7 | test.describe.serial("Budget Tracker", () => { 8 | // Create one shared browser context for all tests 9 | /** @type {import('@playwright/test').Page} */ 10 | let page 11 | 12 | test.beforeAll(async ({ browser }) => { 13 | page = await browser.newPage() 14 | }) 15 | // \\ Create one shared browser context for all tests 16 | 17 | test("Should add all incomes and expenses", async () => { 18 | const user = new UserPage(page) 19 | 20 | await user.openApp() 21 | 22 | let calculatedTotal = 0 23 | for (let index = 0; index < data.length; index++) { 24 | const record = await user.addEntry(data[index]) 25 | 26 | await page.reload() // Reload the page to verify persistent of data 27 | 28 | const expectedDate = user.convertDate(data[index].date) 29 | const receivedDate = user.convertDate(await record.date.inputValue()) 30 | expect(receivedDate).toMatch(expectedDate) 31 | 32 | await expect(record.description).toHaveValue(data[index].description) 33 | await expect(record.type).toHaveValue(data[index].type) 34 | await expect(record.amount).toHaveValue(data[index].amount.toString()) 35 | 36 | // Calculate the expected amount after entry 37 | if (data[index].type == "income") { 38 | calculatedTotal += Number(data[index].amount) 39 | } else if (data[index].type == "expense") { 40 | calculatedTotal -= Number(data[index].amount) 41 | } 42 | 43 | expect(record.total).toHaveText( 44 | // Converts a number to U.S. currency format string 45 | calculatedTotal.toLocaleString("en-US", { 46 | style: "currency", 47 | currency: "USD", 48 | minimumFractionDigits: 2, 49 | maximumFractionDigits: 2, 50 | }) 51 | ) 52 | } 53 | }) 54 | 55 | test("Should modify first record", async () => { 56 | const user = new UserPage(page) 57 | 58 | await user.openApp() 59 | 60 | const modifiedEntry = { 61 | date: "01/01/2021", 62 | expected_date: "2021-01-01", 63 | description: "Inheritance", 64 | type: "income", 65 | amount: 50000, 66 | } 67 | 68 | const expectedTotal = "$45,300.00" 69 | 70 | const record = await user.editRow(1, modifiedEntry) 71 | 72 | await expect(record.date).toHaveValue(modifiedEntry.expected_date) 73 | await expect(record.description).toHaveValue(modifiedEntry.description) 74 | await expect(record.type).toHaveValue(modifiedEntry.type) 75 | await expect(record.amount).toHaveValue(modifiedEntry.amount.toString()) 76 | 77 | expect(record.total).toHaveText(expectedTotal) 78 | }) 79 | 80 | test("Should remove all the records", async () => { 81 | const user = new UserPage(page) 82 | 83 | await user.openApp() 84 | 85 | const records = await user.removeAllRecords() 86 | 87 | await expect(records).toHaveCount(0) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /tests/changeable-iframe.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Changeable Iframe", () => { 4 | test("Should verify 53 seconds as remaining time and message at the end", async ({ page }) => { 5 | await page.goto("apps/changing-iframe/"); 6 | await expect(page.locator("#frame1")).toBeVisible(); 7 | 8 | // Get timer locator 9 | const time = page.frameLocator("#frame1").locator("#time"); 10 | // Get info message locator 11 | const info = page.frameLocator("#frame1").locator("#msg"); 12 | 13 | await expect(time).toHaveText("00:53", { timeout: 10000 }); 14 | 15 | await expect(info).toHaveText("This is the end of the journey"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/context-menu.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Context Menu", () => { 4 | test("Should click on each menu and sub-menu item", async ({ page }) => { 5 | await page.goto("apps/context-menu/"); 6 | await expect(page.locator('text="Open Right-Click Context Menu"')).toBeVisible(); 7 | 8 | const menuItems = ["Preview", "Get Link", "Rename", "Delete", "Settings"]; 9 | const subMenuItems = ["Twitter", "Instagram", "Dribble", "Telegram"]; 10 | 11 | const message = page.locator("#msg"); // html paragraph 12 | 13 | // Validate menu items 14 | for (let index = 0; index < menuItems.length; index++) { 15 | await page.click("body", { button: "right", delay: 300 }); // right click on the page 16 | await page.locator("text=" + menuItems[index]).click(); 17 | await expect(message).toContainText(menuItems[index]); 18 | } 19 | 20 | // Validate sub-menu items 21 | for (let index = 0; index < subMenuItems.length; index++) { 22 | await page.click("body", { button: "right", delay: 300 }); // right click on the page 23 | await page.locator('text="Share"').hover(); // Opens Share sub-menu 24 | await page.locator(".share >> text=" + subMenuItems[index]).click(); 25 | await expect(message).toContainText(subMenuItems[index]); 26 | } 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/download.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | const fs = require("fs"); 3 | 4 | test.describe("Download", () => { 5 | test("Should download a PDF file", async ({ page }) => { 6 | await page.goto("apps/download/"); 7 | await expect(page.locator("text=Click to download PDF file")).toBeVisible(); 8 | 9 | const downButton = page.locator("#file"); 10 | 11 | // Note that Promise.all prevents a race condition 12 | // between clicking and waiting for the download. 13 | const [download] = await Promise.all([ 14 | // It is important to call waitForEvent before click to set up waiting. 15 | page.waitForEvent("download"), 16 | // Triggers the download. 17 | downButton.click(), 18 | ]); 19 | 20 | const fileName = download.suggestedFilename(); // Returns the file name from the server 21 | expect(fileName).toMatch("sample.pdf"); // Asserts file name 22 | 23 | // Save downloaded file 24 | await download.saveAs(fileName); 25 | 26 | const fileSizeInBytes = fs.statSync(fileName).size; // Get saved file size 27 | expect(fileSizeInBytes.toString()).toMatch("1042157"); // Asserts file size 28 | 29 | // Delete downloaded file 30 | fs.unlink(fileName, (err) => { 31 | if (err) throw err; 32 | // if no error, file has been deleted successfully 33 | // console.log("File deleted!"); 34 | }); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/dynamic-table.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Dynamic Table", () => { 4 | test("Should verify Spider-Man's real name", async ({ page }) => { 5 | await page.goto("apps/dynamic-table/"); 6 | await expect(page.locator("text=SUPERHERO")).toBeVisible(); 7 | 8 | const realName = "Peter Parker"; 9 | const row = page.locator('text="Spider-Man" >> xpath=../../../..'); 10 | const realNameCell = row.locator("td").nth(2); 11 | 12 | await expect(realNameCell).toHaveText(realName); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/fetch.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Fetching Data", () => { 4 | test("Should load and display all posts", async ({ page }) => { 5 | await page.goto("apps/fetch/", { waitUntil: "commit" }); 6 | 7 | await page.waitForResponse((resp) => resp.url().includes("/posts")); // Wait for JSON 8 | await page.waitForResponse("https://images.unsplash.com/**"); // Wait for image 9 | 10 | expect(await page.locator(".icard").count()).toBeGreaterThan(90); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/geolocation.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test") 2 | 3 | test.describe("Geolocation", () => { 4 | test("Should change browser geolocation", async ({ page, context }) => { 5 | await context.grantPermissions(["geolocation"]) 6 | await context.setGeolocation({ longitude: -122.03118, latitude: 37.33182 }) 7 | 8 | await page.goto("apps/geolocation/") 9 | await expect(page.locator("#get-location")).toBeVisible() 10 | 11 | const currentLocation = page.locator("#location-info") 12 | 13 | page.locator("#get-location").click() 14 | 15 | await expect(currentLocation).toContainText("Cupertino, United States") 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /tests/hidden.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Hidden Elements", () => { 4 | test("Should click on the hidden button", async ({ page }) => { 5 | await page.goto("apps/covered/"); 6 | await expect(page.locator("text=Click the button below")).toBeVisible(); 7 | 8 | const infoMsg = page.locator("#info"); // Select paragraph element 9 | 10 | await page.locator("#fugitive").click(); // Click on the button 11 | 12 | await expect(infoMsg).toHaveText("Mission accomplished"); // Assert changed message 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/iframe.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Nested Iframe", () => { 4 | test("Should click on the button that is in two-level deep iframe", async ({ page }) => { 5 | await page.goto("apps/iframe/"); 6 | await expect(page.locator("#frame1")).toBeVisible(); 7 | 8 | // Get button locator 9 | const button = page.frameLocator("#frame1").frameLocator("#frame2").locator("text=Click Me"); 10 | // Get info message locator 11 | const info = page.frameLocator("#frame1").frameLocator("#frame2").locator("#msg"); 12 | 13 | // Click on the button 14 | await button.click(); 15 | 16 | await expect(info).toBeVisible(); // Assert that the button clicked 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/links.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Links", () => { 4 | test("Should open each menu link in the new tab", async ({ page, context }) => { 5 | await page.goto("apps/links/"); 6 | await expect(page.locator("#nav")).toBeVisible(); 7 | 8 | const links = page.locator("#nav > a"); 9 | 10 | await expect(links).toHaveCount(5); 11 | 12 | // Create a new tab page 13 | const newPage = await context.newPage(); 14 | 15 | for (let index = 1; index < (await links.count()); index++) { 16 | const menuItemText = await links.nth(index).textContent(); 17 | const nemuItemLink = await links.nth(index).getAttribute("href"); 18 | 19 | await newPage.goto("/apps/links/" + nemuItemLink); // Open a link 20 | 21 | await expect(newPage.locator("#title")).toContainText(menuItemText); 22 | } 23 | 24 | newPage.close(); // Close the newly opened page 25 | }); 26 | 27 | test("Should open each menu link by clicking on it", async ({ page }) => { 28 | await page.goto("apps/links/"); 29 | await expect(page.locator("#nav")).toBeVisible(); 30 | 31 | const links = page.locator("#nav > a"); 32 | 33 | await expect(links).toHaveCount(5); 34 | 35 | for (let index = 1; index < (await links.count()); index++) { 36 | const menuItemText = await links.nth(index).textContent(); 37 | 38 | await links.nth(index).click({ delay: 300 }); // Click on the menu item 39 | 40 | await expect(page.locator("#title")).toContainText(menuItemText); 41 | 42 | await page.goBack(); // Navigate back to the home page 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /tests/mouseover.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Mouse Over", () => { 4 | test("Should put the mouse pointer on the image", async ({ page }) => { 5 | await page.goto("apps/mouse-hover/"); 6 | await expect(page.locator(".poster-container >> img")).toBeVisible(); 7 | 8 | const expectedPrice = "$24.96"; 9 | const currentPrice = page.locator(".current-price"); 10 | 11 | await page.locator(".poster").hover(); 12 | 13 | await expect(currentPrice).toHaveText(expectedPrice); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/multi-level-dropdown.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Multi Level Dropdown", () => { 4 | test("Should validate Setting sub-menu items", async ({ page }) => { 5 | await page.goto("apps/multi-level-dropdown/"); 6 | await expect(page.locator(".navbar-nav")).toBeVisible(); 7 | 8 | await page.locator(".icon-button").last().click(); // Opens main menu 9 | await page.locator("text=Settings").click(); // Opens Settings sub-menu 10 | 11 | await expect(page.locator(".menu-secondary-enter-done >> .menu-item")).toHaveText([ 12 | "My Tutorial", 13 | "HTML", 14 | "CSS", 15 | "JavaScript", 16 | "Awesome!", 17 | ]); 18 | 19 | const links = page.locator(".menu-secondary-enter-done >> a:visible"); 20 | const linksCount = await links.count(); 21 | 22 | await expect(links).not.toHaveCount(0); 23 | 24 | const expectedLinks = ["#main", "#!HTML", "#!CSS", "#!JavaScript", "#!Awesome"]; 25 | for (let i = 0; i < linksCount; i++) { 26 | await expect(links.nth(i)).toHaveAttribute("href", expectedLinks[i]); 27 | } 28 | }); 29 | 30 | test("Should validate Animals sub-menu items", async ({ page }) => { 31 | await page.goto("apps/multi-level-dropdown/"); 32 | await expect(page.locator(".navbar-nav")).toBeVisible(); 33 | 34 | await page.locator(".icon-button").last().click(); // Opens main menu 35 | await page.locator("text=Animals").click(); // Opens Animals sub-menu 36 | 37 | await expect(page.locator(".menu-secondary-enter-done >> .menu-item")).toHaveText([ 38 | "Animals", 39 | "🦘Kangaroo", 40 | "🐸Frog", 41 | "🦋Horse", 42 | "🦔Hedgehog", 43 | ]); 44 | 45 | const links = page.locator(".menu-secondary-enter-done >> a:visible"); 46 | const linksCount = await links.count(); 47 | 48 | await expect(links).not.toHaveCount(0); 49 | 50 | const expectedLinks = ["#main", "#!Kangaroo", "#!Frog", "#!Horse", "#!Hedgehog"]; 51 | for (let i = 0; i < linksCount; i++) { 52 | await expect(links.nth(i)).toHaveAttribute("href", expectedLinks[i]); 53 | } 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /tests/onboarding.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Onboarding", () => { 4 | test("Should close onboarding modal if it exists", async ({ page }) => { 5 | await page.goto("apps/onboarding-modal/"); 6 | await expect(page.locator(".title")).toBeVisible(); 7 | 8 | const title = page.locator(".title"); 9 | const modal = await page.locator("#active").isChecked(); 10 | const closeButton = page.locator('[for="active"]'); 11 | 12 | // Check if modal is displayed 13 | if (modal) { 14 | await closeButton.click(); 15 | await expect(title).toHaveText("Welcome Peter Parker! 🕷🎉"); 16 | } else { 17 | await expect(title).toHaveText("Application successfully launched! 🚀"); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/popup.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test") 2 | 3 | test.describe("Pop-Up", () => { 4 | test("Should open a pop-up and click on the button in it", async ({ 5 | page, 6 | }) => { 7 | await page.goto("apps/popup/") 8 | await expect(page.locator('text="Click to open pop-up"')).toBeVisible() 9 | 10 | // Open the popup and wait to catch the window handle 11 | const [popup] = await Promise.all([ 12 | page.waitForEvent("popup"), 13 | page.locator("#login").click(), 14 | ]) 15 | 16 | await expect(popup).toHaveURL(/popup/) // Asser pop-up's URL 17 | 18 | await expect(popup.locator("button")).toBeInViewport() 19 | await popup.locator("button").click() // Click on the button inside the pop-up 20 | 21 | // At the main window, assert that the button clicked 22 | await expect(page.locator("#info")).toHaveText("Button Clicked") 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /tests/qr-code.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test") 2 | 3 | test.describe("QR Code Generator", () => { 4 | test("Should visually validate generated image", async ({ page }) => { 5 | await page.goto("apps/qr-code-generator/") 6 | await expect(page.locator("text=QR Code Generator")).toBeVisible() 7 | 8 | await page.locator('[placeholder="Enter text or URL"]').fill("Playwright") 9 | await page.locator("button >> text=Generate QR Code").click() 10 | 11 | const imageLocator = page.locator(".qr-code >> img") 12 | 13 | await expect(imageLocator).toBeVisible() 14 | await expect(imageLocator).toHaveScreenshot() 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /tests/qr-code.spec.js-snapshots/QR-Code-Generator-Should-visually-validate-generated-image-1-darwin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marko-simic/qa-playground-tests/206fb804293589ffc756b3fb9e756634ff5fb0fe/tests/qr-code.spec.js-snapshots/QR-Code-Generator-Should-visually-validate-generated-image-1-darwin.png -------------------------------------------------------------------------------- /tests/qr-code.spec.js-snapshots/QR-Code-Generator-Should-visually-validate-generated-image-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marko-simic/qa-playground-tests/206fb804293589ffc756b3fb9e756634ff5fb0fe/tests/qr-code.spec.js-snapshots/QR-Code-Generator-Should-visually-validate-generated-image-1-linux.png -------------------------------------------------------------------------------- /tests/rating.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Rating", () => { 4 | test("Should rate with one star", async ({ page }) => { 5 | await page.goto("apps/rating/"); 6 | await expect(page.locator(".stars")).toBeVisible(); 7 | 8 | const emoji = page.locator("#star-1"); // Select first emoji 9 | const stars = page.locator(".stars >> label"); 10 | 11 | await stars.nth(0).click(); // Click on the first star 12 | 13 | // Assert selected stars 14 | await expect(stars.nth(0)).toHaveCSS("color", "rgb(255, 221, 68)"); 15 | 16 | // Assert that the related emoji is displayed 17 | await expect(emoji).toBeChecked(); 18 | 19 | // Assert message text for one star 20 | let msg = await page.$eval(".footer >> .text", (elem) => { 21 | // Get CSS computed content property 22 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 23 | }); 24 | expect(msg).toMatch("I just hate it"); 25 | 26 | // Assert numerically represented rate 27 | let rate = await page.$eval(".footer >> .numb", (elem) => { 28 | // Get CSS computed content property 29 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 30 | }); 31 | expect(rate).toMatch("1 out of 5"); 32 | }); 33 | 34 | test("Should rate with two stars", async ({ page }) => { 35 | await page.goto("apps/rating/"); 36 | await expect(page.locator(".stars")).toBeVisible(); 37 | 38 | const emoji = page.locator("#star-2"); // Select second emoji 39 | const emojiPosition = page.locator(".slideImg"); 40 | const stars = page.locator(".stars >> label"); 41 | 42 | await stars.nth(1).click(); // Click on the second star 43 | 44 | // Assert selected stars 45 | await expect(stars.nth(0)).toHaveCSS("color", "rgb(255, 221, 68)"); 46 | await expect(stars.nth(1)).toHaveCSS("color", "rgb(255, 221, 68)"); 47 | 48 | // Assert that the related emoji is displayed 49 | expect(emoji).toBeChecked; 50 | await expect(emojiPosition).toHaveCSS("margin-top", "-135px"); 51 | 52 | // Assert message text for one star 53 | let msg = await page.$eval(".footer >> .text", (elem) => { 54 | // Get CSS computed content property 55 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 56 | }); 57 | expect(msg).toMatch("I don't like it"); 58 | 59 | // Assert numerically represented rate 60 | let rate = await page.$eval(".footer >> .numb", (elem) => { 61 | // Get CSS computed content property 62 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 63 | }); 64 | expect(rate).toMatch("2 out of 5"); 65 | }); 66 | 67 | test("Should rate with three stars", async ({ page }) => { 68 | await page.goto("apps/rating/"); 69 | await expect(page.locator(".stars")).toBeVisible(); 70 | 71 | const emoji = page.locator("#star-3"); // Select third emoji 72 | const emojiPosition = page.locator(".slideImg"); 73 | const stars = page.locator(".stars >> label"); 74 | 75 | await stars.nth(2).click(); // Click on the third star 76 | 77 | // Assert selected stars 78 | await expect(stars.nth(0)).toHaveCSS("color", "rgb(255, 221, 68)"); 79 | await expect(stars.nth(1)).toHaveCSS("color", "rgb(255, 221, 68)"); 80 | await expect(stars.nth(2)).toHaveCSS("color", "rgb(255, 221, 68)"); 81 | 82 | // Assert that the related emoji is displayed 83 | expect(emoji).toBeChecked; 84 | await expect(emojiPosition).toHaveCSS("margin-top", "-270px"); 85 | 86 | // Assert message text for one star 87 | let msg = await page.$eval(".footer >> .text", (elem) => { 88 | // Get CSS computed content property 89 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 90 | }); 91 | expect(msg).toMatch("This is awesome"); 92 | 93 | // Assert numerically represented rate 94 | let rate = await page.$eval(".footer >> .numb", (elem) => { 95 | // Get CSS computed content property 96 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 97 | }); 98 | expect(rate).toMatch("3 out of 5"); 99 | }); 100 | 101 | test("Should rate with four stars", async ({ page }) => { 102 | await page.goto("apps/rating/"); 103 | await expect(page.locator(".stars")).toBeVisible(); 104 | 105 | const emoji = page.locator("#star-4"); // Select fourth emoji 106 | const emojiPosition = page.locator(".slideImg"); 107 | const stars = page.locator(".stars >> label"); 108 | 109 | await stars.nth(3).click(); // Click on the fourth star 110 | 111 | // Assert selected stars 112 | await expect(stars.nth(0)).toHaveCSS("color", "rgb(255, 221, 68)"); 113 | await expect(stars.nth(1)).toHaveCSS("color", "rgb(255, 221, 68)"); 114 | await expect(stars.nth(2)).toHaveCSS("color", "rgb(255, 221, 68)"); 115 | await expect(stars.nth(3)).toHaveCSS("color", "rgb(255, 221, 68)"); 116 | 117 | // Assert that the related emoji is displayed 118 | expect(emoji).toBeChecked; 119 | await expect(emojiPosition).toHaveCSS("margin-top", "-405px"); 120 | 121 | // Assert message text for one star 122 | let msg = await page.$eval(".footer >> .text", (elem) => { 123 | // Get CSS computed content property 124 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 125 | }); 126 | expect(msg).toMatch("I just like it"); 127 | 128 | // Assert numerically represented rate 129 | let rate = await page.$eval(".footer >> .numb", (elem) => { 130 | // Get CSS computed content property 131 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 132 | }); 133 | expect(rate).toMatch("4 out of 5"); 134 | }); 135 | 136 | test("Should rate with five stars", async ({ page }) => { 137 | await page.goto("apps/rating/"); 138 | await expect(page.locator(".stars")).toBeVisible(); 139 | 140 | const emoji = page.locator("#star-5"); // Select fifth emoji 141 | const emojiPosition = page.locator(".slideImg"); 142 | const stars = page.locator(".stars >> label"); 143 | 144 | await stars.nth(4).click(); // Click on the fifth star 145 | 146 | // Assert selected stars 147 | await expect(stars.nth(0)).toHaveCSS("color", "rgb(255, 221, 68)"); 148 | await expect(stars.nth(1)).toHaveCSS("color", "rgb(255, 221, 68)"); 149 | await expect(stars.nth(2)).toHaveCSS("color", "rgb(255, 221, 68)"); 150 | await expect(stars.nth(3)).toHaveCSS("color", "rgb(255, 221, 68)"); 151 | await expect(stars.nth(4)).toHaveCSS("color", "rgb(255, 221, 68)"); 152 | 153 | // Assert that the related emoji is displayed 154 | expect(emoji).toBeChecked; 155 | await expect(emojiPosition).toHaveCSS("margin-top", "-540px"); 156 | 157 | // Assert message text for one star 158 | let msg = await page.$eval(".footer >> .text", (elem) => { 159 | // Get CSS computed content property 160 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 161 | }); 162 | expect(msg).toMatch("I just love it"); 163 | 164 | // Assert numerically represented rate 165 | let rate = await page.$eval(".footer >> .numb", (elem) => { 166 | // Get CSS computed content property 167 | return window.getComputedStyle(elem, ":before").getPropertyValue("content"); 168 | }); 169 | expect(rate).toMatch("5 out of 5"); 170 | }); 171 | }); 172 | -------------------------------------------------------------------------------- /tests/redirect.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test") 2 | 3 | test.describe("Redirection", () => { 4 | test("Should verify each redirection", async ({ page }) => { 5 | await page.goto("apps/redirect/") 6 | await expect(page.locator("text=Start Redirection chain")).toBeVisible() 7 | 8 | // 1st redirection 9 | const firstRedirectPromise = page.waitForResponse((response) => 10 | response.url().includes("second") 11 | ) 12 | // 2nd redirection 13 | const secondRedirectPromise = page.waitForResponse((response) => 14 | response.url().includes("third") 15 | ) 16 | // 3rd redirection 17 | const thirdRedirectPromise = page.waitForResponse((response) => 18 | response.url().includes("fourth") 19 | ) 20 | // 4th redirection 21 | const fourthRedirectPromise = page.waitForResponse((response) => 22 | response.url().includes("fifth") 23 | ) 24 | // 5th redirection 25 | const fifthRedirectPromise = page.waitForResponse((response) => 26 | response.url().includes("sixth") 27 | ) 28 | // 6th redirection 29 | const lastRedirectPromise = page.waitForResponse((response) => 30 | response.url().includes("last") 31 | ) 32 | 33 | await page.locator("#redirect").click() // Start redirects 34 | 35 | // Wait for the redirects to complete 36 | await firstRedirectPromise 37 | await secondRedirectPromise 38 | await thirdRedirectPromise 39 | await fourthRedirectPromise 40 | await fifthRedirectPromise 41 | await lastRedirectPromise 42 | 43 | // Verifies that the landing page is open 44 | await expect(page.locator("#info")).toHaveText("Welcome to the Last Page") 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /tests/shadow-dom.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Shadow DOM", () => { 4 | test("Should boost progress bar by clicking on the button", async ({ page }) => { 5 | await page.goto("apps/shadow-dom/"); 6 | await expect(page.locator("text=Boost 🚀")).toBeVisible(); 7 | 8 | await page.locator("button").click(); 9 | 10 | const progressBar = page.locator("progress-bar"); 11 | 12 | await expect(progressBar).toHaveAttribute("percent", "95", { timeout: 9000 }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /tests/sortable-list.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Sortable List", () => { 4 | test("Should reorder the list to match the correct order", async ({ page }) => { 5 | await page.goto("apps/sortable-list"); 6 | await expect(page.locator('text="10 Richest People"')).toBeVisible(); 7 | 8 | const items = page.locator(".person-name"); // Mark all list items 9 | 10 | // Expected order of names 11 | const topList = [ 12 | { position: 1, name: "Jeff Bezos" }, 13 | { position: 2, name: "Bill Gates" }, 14 | { position: 3, name: "Warren Buffett" }, 15 | { position: 4, name: "Bernard Arnault" }, 16 | { position: 5, name: "Carlos Slim Helu" }, 17 | { position: 6, name: "Amancio Ortega" }, 18 | { position: 7, name: "Larry Ellison" }, 19 | { position: 8, name: "Mark Zuckerberg" }, 20 | { position: 9, name: "Michael Bloomberg" }, 21 | ]; 22 | 23 | // Iterate through the list and move items to the correct position 24 | let currentPosition, distance; 25 | for (let index = 0; index < topList.length; index++) { 26 | // Determine the current position of the name 27 | currentPosition = await page 28 | .locator("text='" + topList[index].name + "' >> .. >> .. >> .number") 29 | .textContent(); 30 | 31 | // Calculate the distance from the correct position 32 | distance = currentPosition - topList[index].position; 33 | 34 | // Continue with the loop because the item is in the correct place 35 | if (distance == 0) continue; 36 | 37 | // The element is close enough to the target and can be moved immediately 38 | if (distance < 5) { 39 | await page.locator("text='" + topList[index].name + "'").scrollIntoViewIfNeeded(); 40 | await page.dragAndDrop( 41 | "text='" + topList[index].name + "'", 42 | "text='" + topList[index].position + "'", 43 | { 44 | force: true, 45 | } 46 | ); 47 | } 48 | 49 | // Move item step by step since the target position is not in the viewport 50 | for (let pos = currentPosition; pos > topList[index].position - 1; pos--) { 51 | // If the target is very close, do not scroll 52 | if (pos - topList[index].position > 1) { 53 | await page.locator("text='" + (pos - 1) + "'").scrollIntoViewIfNeeded(); 54 | } 55 | 56 | await page.dragAndDrop("text='" + topList[index].name + "'", "text='" + pos + "'", {}); 57 | 58 | // If failed to move an item, try again 59 | if ((await items.nth(pos - 1).textContent()) != topList[index].name) { 60 | await page.dragAndDrop("text='" + topList[index].name + "'", "text='" + pos + "'", {}); 61 | } 62 | // Assert that the item is in the correct temporary position 63 | await expect(items.nth(pos - 1)).toHaveText(topList[index].name); 64 | } 65 | 66 | // Assert that the item is in the correct position 67 | await expect(items.nth(index)).toHaveText(topList[index].name); 68 | } 69 | 70 | await page.locator("#check").click(); // Initiates order validation 71 | 72 | // Assert that all elements are in place 73 | for (let i = 0; i < (await items.count()); i++) { 74 | await expect(items.nth(i)).toHaveCSS("color", "rgb(58, 227, 116)"); 75 | } 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /tests/tags.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Tags Input Box", () => { 4 | test("Should remove the last tag", async ({ page }) => { 5 | await page.goto("apps/tags-input-box/"); 6 | await expect(page.locator('text="Tags"')).toBeVisible(); 7 | 8 | const tags = page.locator(".content >> li"); // Get locators of all tags 9 | const tagsCount = await tags.count(); // Save the current tags count 10 | 11 | // Remove the last tag 12 | await tags.last().locator("i").click(); 13 | 14 | await expect(tags).toHaveCount(tagsCount - 1); 15 | }); 16 | 17 | test("Should add max number of tags", async ({ page }) => { 18 | await page.goto("apps/tags-input-box/"); 19 | await expect(page.locator('text="Tags"')).toBeVisible(); 20 | 21 | const newTags = [ 22 | "vue", 23 | "python", 24 | "go", 25 | "react", 26 | "svelte", 27 | "tailwind", 28 | "linux", 29 | "ios", 30 | "android", 31 | "docker", 32 | ]; 33 | 34 | const tags = page.locator(".content >> li"); // Get locators of all tags 35 | const inputField = page.locator(".content >> input"); 36 | const tagsCount = await tags.count(); // Save the current tags count 37 | const currentCount = page.locator(".details >> p >> span"); 38 | let remainingSlots = 10 - tagsCount; 39 | 40 | for (let index = 0; index < 10 - tagsCount; index++) { 41 | await inputField.type(newTags[index]); 42 | await inputField.press("Enter", { delay: 200 }); 43 | remainingSlots--; 44 | await expect(tags.locator("text=" + newTags[index])).toBeVisible(); 45 | await expect(currentCount).toHaveText(remainingSlots.toString()); 46 | } 47 | 48 | await expect(currentCount).toHaveText("0"); 49 | }); 50 | 51 | test("Should remove all tags", async ({ page }) => { 52 | await page.goto("apps/tags-input-box/"); 53 | await expect(page.locator('text="Tags"')).toBeVisible(); 54 | 55 | await page.locator(".details >> button").click(); // "Remove All" button 56 | 57 | const currentCount = page.locator(".details >> p >> span"); 58 | const tags = page.locator(".content >> li"); // Get locators of all tags 59 | 60 | await expect(currentCount).toHaveText("10"); 61 | await expect(tags).toHaveCount(0); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /tests/upload.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Upload", () => { 4 | test("Should upload an image file", async ({ page }) => { 5 | await page.goto("apps/upload/"); 6 | await expect(page.locator("text=No File Selected")).toBeVisible(); 7 | 8 | const file = process.cwd() + "/fixtures/demo.png"; 9 | 10 | // Select one file to upload 11 | await page.setInputFiles("#file-input", file); 12 | 13 | const firstImageCaption = page.locator("#images >> figure >> nth=0 >> figcaption"); 14 | 15 | await expect(firstImageCaption).toContainText("demo.png"); 16 | }); 17 | 18 | test("Should upload multiple image files", async ({ page }) => { 19 | await page.goto("apps/upload/"); 20 | await expect(page.locator("text=No File Selected")).toBeVisible(); 21 | 22 | const imgDir = process.cwd() + "/fixtures/"; 23 | const images = ["demo.png", "demo2.png", "demo3.jpg"]; 24 | 25 | // Select multiple files to upload 26 | await page.setInputFiles("#file-input", [ 27 | imgDir + images[0], 28 | imgDir + images[1], 29 | imgDir + images[2], 30 | ]); 31 | 32 | const addedImages = page.locator("#images >> figure"); 33 | 34 | // Validate that the images are displayed 35 | for (let index = 0; index < (await addedImages.count()); index++) { 36 | await expect(addedImages.nth(index)).toContainText(images[index]); 37 | } 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/verify-account.spec.js: -------------------------------------------------------------------------------- 1 | const { test, expect } = require("@playwright/test"); 2 | 3 | test.describe("Verify Account", () => { 4 | test("Should solve verification code by typing numbers", async ({ page }) => { 5 | await page.goto("apps/verify-account"); 6 | await expect(page.locator('text="Verify Your Account"')).toBeVisible(); 7 | 8 | const codeFields = page.locator(".code"); // Get locators of all input fields 9 | 10 | for (let index = 0; index < (await codeFields.count()); index++) { 11 | codeFields.nth(index).type("9"); 12 | await expect(codeFields.nth(index)).toHaveValue("9"); 13 | } 14 | 15 | await expect(page.locator('text="Success"')).toBeVisible(); 16 | }); 17 | 18 | test("Should solve verification code by pressing the key-up button", async ({ page }) => { 19 | await page.goto("apps/verify-account"); 20 | await expect(page.locator('text="Verify Your Account"')).toBeVisible(); 21 | 22 | let codeValue = "0"; 23 | const codeFields = page.locator(".code"); // Get locators of all input fields 24 | for (let index = 0; index < (await codeFields.count()); index++) { 25 | while (codeValue != "9") { 26 | await codeFields.nth(index).press("ArrowUp", { delay: 100 }); 27 | codeValue = await codeFields.nth(index).inputValue(); 28 | } 29 | await expect(codeFields.nth(index)).toHaveValue("9"); 30 | codeValue = "0"; 31 | } 32 | 33 | await expect(page.locator('text="Success"')).toBeVisible(); 34 | }); 35 | }); 36 | --------------------------------------------------------------------------------