├── src
├── utils
│ ├── utils.ts
│ ├── constants.ts
│ ├── dbUtils.ts
│ ├── config
│ │ ├── env
│ │ │ ├── dev.env
│ │ │ ├── qa.env
│ │ │ └── local.env
│ │ ├── artillery
│ │ │ └── artillery-script.yml
│ │ └── azure
│ │ │ ├── azure-pipeline.yml
│ │ │ ├── playwright.service.config.ts
│ │ │ └── playwright.config.ts
│ ├── error
│ │ ├── ApiErrorHandler.ts
│ │ └── ErrorManager.ts
│ ├── dataStore.js
│ ├── report
│ │ ├── sendEmail.ts
│ │ ├── ReportAction.ts
│ │ ├── CustomReporterConfig.ts
│ │ └── Logger.ts
│ ├── reader
│ │ ├── config.json
│ │ └── jsonReader.ts
│ └── harAnalyser.ts
├── globals
│ ├── global-setup.ts
│ ├── global-teardown.ts
│ └── healthcheck.setup.ts
└── helper
│ ├── index.ts
│ ├── Helper.ts
│ ├── terminal
│ └── SSHHelper.ts
│ └── api
│ └── apiHelper.ts
├── environments
├── dev.env
├── local.env
└── qe.env
├── tests
├── api
│ └── example
│ │ ├── apiTestDemo.spec.ts
│ │ ├── user.spec.ts
│ │ ├── post.spec.ts
│ │ ├── api1.spec.ts
│ │ ├── api.spec.ts
│ │ └── dashboard.spec.ts
├── web
│ ├── example
│ │ ├── demo-fixture.spec.ts
│ │ ├── blockunnecessary.spec.ts
│ │ ├── runTestParallel.spect.ts
│ │ ├── screenshot.spec.ts
│ │ ├── runTestSerial.spec.ts
│ │ ├── launchChrome.spec.js
│ │ ├── generateHarFfile.spec.ts
│ │ ├── uploadfile.spec.ts
│ │ ├── slider.spec.ts
│ │ ├── runMultipleTestInSingleBrowserWindow.spec.js
│ │ ├── saveHarFile.spec.ts
│ │ ├── downloadfile.spec.ts
│ │ ├── frame.spec.ts
│ │ ├── UIBasics.spec.js
│ │ ├── multipleLocator.spec.js
│ │ ├── networkTest.spec.ts
│ │ ├── basicAuthentication.spec.ts
│ │ ├── localization.spec.ts
│ │ ├── verifybrokenlink.spec.js
│ │ ├── multipleTabs.spec.ts
│ │ ├── autoWait.spec.ts
│ │ ├── globalLogin.spec.ts
│ │ ├── multiplePages.spec.ts
│ │ ├── DragAndDrop.spec.ts
│ │ ├── webTable.spec.ts
│ │ ├── launchEdge.spec.js
│ │ ├── launchBrowserUsingConfig.spec.ts
│ │ ├── route.spec.ts
│ │ ├── authenticationUsingAuthJSON.spec.ts
│ │ ├── mockApi.spec.ts
│ │ ├── alert.spec.ts
│ │ ├── launchMultipleBrowserInSingle.spec.js
│ │ ├── ConsoleAndPageError.spec.ts
│ │ ├── locator.spec.ts
│ │ ├── inputFieldOperation.spec.ts
│ │ ├── accessibilitytest.spec.ts
│ │ ├── trackApiResponse.spec.js
│ │ └── assertion.spec.ts
│ ├── orangeHrm.spec.ts
│ └── IntercpetFailingAPI.js
├── e2e
│ └── moatazeldebsy
│ │ └── form.spec.ts
├── fixtures
│ ├── test-options.ts
│ └── customFixtures.ts
├── mobile
│ └── example
│ │ ├── mobiletest.spec.js
│ │ ├── healthcheckiOS.test.ts
│ │ └── healthcheckAndroid.test.ts
├── resources
│ └── uiMap
│ │ ├── moataeldebsy.json
│ │ └── orangeHRM.json
└── load
│ └── BrowerLoadTest.spec.js
├── .vscode
└── settings.json
├── .mocharc.js
├── middleware.js
├── .gitignore
├── data
├── routes.json
└── db.json
├── lint-staged.config.js
├── .env
├── testConfig.ts
├── .prettierrc
├── DockerFile
├── auth.json
├── .github
└── workflows
│ └── playwright.yml
├── tsconfig.json
├── eslint.config.mjs
├── config.ts
├── playwright.config.ts
├── package.json
├── logs
├── combined.log
└── rejections.log
├── Notes
├── .windsurf
└── rules
│ └── rules.md
└── README.md
/src/utils/utils.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/environments/dev.env:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/environments/local.env:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/environments/qe.env:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/dbUtils.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/globals/global-setup.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/config/env/dev.env:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/config/env/qa.env:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/globals/global-teardown.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/globals/healthcheck.setup.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/config/env/local.env:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/utils/error/ApiErrorHandler.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/api/example/apiTestDemo.spec.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/.mocharc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | timeout: 20000, // define a 20 seconds timeout for all tests
3 | };
4 |
--------------------------------------------------------------------------------
/middleware.js:
--------------------------------------------------------------------------------
1 | module.exports = (req, res, next) => {
2 | res.header('X-Hello', 'World')
3 | next()
4 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /test-results/
3 | /playwright-report/
4 | /blob-report/
5 | /playwright/.cache/
6 | logs/info.log
7 |
--------------------------------------------------------------------------------
/data/routes.json:
--------------------------------------------------------------------------------
1 | {
2 | "/api/*": "/$1",
3 | "/mobiles/:name": "/mobiles?name_like=:name",
4 | "/reviews/:id": "/reviews?id=:id"
5 | }
--------------------------------------------------------------------------------
/src/helper/index.ts:
--------------------------------------------------------------------------------
1 | export {ApiHelper} from "../helper/api/apiHelper"
2 | export {SshHelper} from "../helper/terminal/SSHHelper"
3 | export {WebHelper} from "../helper/web/webHelper"
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | // lint-staged.config.js
2 | module.exports = {
3 | '*.{ts,js}': [
4 | 'eslint --fix', // Run ESLint with auto-fix on TypeScript files
5 | 'prettier --write', // Run Prettier to format code
6 | ],
7 | };
--------------------------------------------------------------------------------
/tests/web/example/demo-fixture.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from "../../fixtures/customFixtures";
2 |
3 | test.describe('Demo website',()=>{
4 |
5 | test('Verify homepage', async({web})=>{
6 | await web.navigateToUrl("https://google.co.in");
7 | })
8 | })
--------------------------------------------------------------------------------
/tests/web/example/blockunnecessary.spec.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 |
3 | /*
4 | Blocking Unnecessary requests, can significantly speed up your tests
5 | */
6 | test("Block Unnecessary Requests", async ({ page }) => {
7 | await page.route("**/analytics/**", (route) => route.abort());
8 | });
9 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | #Browser variable
2 | HEADLESS_BROWSER=false
3 |
4 | #Paralleism, reties, timeouts
5 | WORKERS=10
6 | RETRY_FAILED=2
7 | MAX_TEST_RUNTIME=60000
8 |
9 | #Environment
10 | BASE_URL='https://moatazeldebsy.github.io/test-automation-practices'
11 |
12 | #Credentials
13 | USER_NAME='ABC'
14 | PASSWORd='ABC'
15 |
16 |
--------------------------------------------------------------------------------
/src/utils/dataStore.js:
--------------------------------------------------------------------------------
1 | /*
2 | This components acts as a in-memory data store. It is used to save data that needs to be shared between different test case*/
3 | let store = {};
4 |
5 | function saveData(key, data) {
6 | store[key] = data;
7 | }
8 |
9 | function getData(key) {
10 | return store[key];
11 | }
12 | module.exports = { saveData, getData };
13 |
--------------------------------------------------------------------------------
/tests/e2e/moatazeldebsy/form.spec.ts:
--------------------------------------------------------------------------------
1 | import {test} from "@tests/fixtures/customFixtures"
2 |
3 | test("Enter value in forms @form", async ({ page,web,api }) => {
4 | await page.goto("https://moatazeldebsy.github.io/test-automation-practices/#/forms");
5 |
6 | await web.changeValueOnUi({elementName:"Basic.Forms.UsernameText"})
7 | });
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/fixtures/test-options.ts:
--------------------------------------------------------------------------------
1 | //This file is used for merging all extended test fixtures
2 | import { mergeTests, test as base } from "@playwright/test";
3 | import {test as customFixture} from "./customFixtures";
4 | // import {test as harTest} from "./networkLogFixture";
5 |
6 | const test = mergeTests(customFixture);
7 |
8 | const expect = base.expect;
9 | export {test,expect};
--------------------------------------------------------------------------------
/testConfig.ts:
--------------------------------------------------------------------------------
1 | export const testConfig = {
2 | qa: `https://demoqa.com`,
3 | dev: ``,
4 | qaApi: `https://reqres.in`,
5 | devApi: ``,
6 | username: `demouat@gmail.com`,
7 | password: `U2FsdGVkX18/eMdsOJpvI4hJZ/w7hNgwSRFaDvAcZx4=`,
8 | waitForElement: 120000,
9 | dbUsername: ``,
10 | dbPassword: ``,
11 | dbServerName: ``,
12 | dbPort: ``,
13 | dbName: ``
14 | }
--------------------------------------------------------------------------------
/tests/web/example/runTestParallel.spect.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 |
3 | //1st way to set run tests in parallel
4 | test.describe.configure({ mode: "parallel" });
5 |
6 | //2nd way to set run tests in parallel
7 | test.describe.parallel("Run All Tests in Parallel", async () => {
8 | test("TestOne", async ({ page }) => {});
9 |
10 | test("TestTwo", async ({ page }) => {});
11 | });
12 |
--------------------------------------------------------------------------------
/tests/web/example/screenshot.spec.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 |
3 | test("Take Screenshot of Page", async ({ page }) => {
4 | //Take screenshot of full page
5 | await page.screenshot({ path: "screenshot.png" });
6 | });
7 |
8 | test("Take Screenshot of Element", async ({ page }) => {
9 | //Take screenshot of an element
10 | await page.goto("http://xxx.com");
11 | await page.getByTestId("todo-item").screenshot({ path: "screenshot.png" });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/web/example/runTestSerial.spec.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 |
3 | //1st way to define test suite to run tests in sequence
4 | test.describe.configure({ mode: "serial" });
5 |
6 | //2nd way to define test suite to run tests in sequence
7 | test.describe.serial("Run all test in serial", async () => {
8 | test("TestOne", async ({ page }) => {});
9 |
10 | test("TestTwo", async ({ page }) => {});
11 |
12 | test("TestThree", async ({ page }) => {});
13 | });
14 |
--------------------------------------------------------------------------------
/tests/web/example/launchChrome.spec.js:
--------------------------------------------------------------------------------
1 | import { chromium, test } from "@playwright/test";
2 |
3 | test('Launch Chrome', async () => {
4 | const browser = await chromium.launch({
5 | channel: 'chrome' //Use system installed chrome
6 | , headless: false //Show browser
7 | });
8 |
9 | const page = await browser.newPage();
10 | await page.goto('https://www.google.com');
11 | console.log(await page.title());
12 | await browser.close();
13 | })
14 |
15 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 4,
4 | "userTabs": false,
5 | "semi": true,
6 | "singleQuote": false,
7 | "trailingComma": "es5",
8 | "bracketSpacing": true,
9 | "jsxSingleQuote": false,
10 | "arrowParens": "always",
11 | "proseWrap": "preserve",
12 | "htmlWhitespaceSensitivity": "strict",
13 | "endOfLine": "lf",
14 | "overrides": [
15 | {
16 | "files": "*.json",
17 | "options": {
18 | "printWidth": 80,
19 | "singleQuote": false
20 | }
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/tests/web/example/generateHarFfile.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "../../fixtures/customFixtures"
2 |
3 | test.describe('Sample UI test', () => {
4 | test('Capture network logs', { tag: '@network1' }, async ({ web }) => {
5 | await web.navigateToUrl('https://practicesoftwaretesting.com/')
6 | const locator = await web.findElement("xpath","//*[@src='assets/img/products/pliers01.avif']");
7 | if (locator) {
8 | await locator.click();
9 | }
10 |
11 | //To Do : Add more steps to
12 | })
13 | })
--------------------------------------------------------------------------------
/src/utils/config/artillery/artillery-script.yml:
--------------------------------------------------------------------------------
1 | config:
2 | target: https://opensource-demo.orangehrmlive.com/web/index.php/auth/login
3 | phases:
4 | - duration: 100
5 | arrivalRate: 1
6 | maxVusers: 1
7 | engines:
8 | playwright:
9 | showAllPageMetrics: true,
10 | extendedMetrics: true
11 | aggregateByName: true
12 | launchOptions:
13 | headless: false
14 | processor: ./tests/load/BrowerLoadTest.spec.js
15 | scenarios:
16 | - name: "Basic Load"
17 | engine: playwright
18 | flowFunction: "browserloadTest"
19 |
--------------------------------------------------------------------------------
/DockerFile:
--------------------------------------------------------------------------------
1 | # Use the official Playwright Docker image
2 | FROM mcr.microsoft.com/playwright:focal
3 |
4 | # Set the working directory inside the container
5 | WORKDIR /app
6 |
7 | # Copy the package.json and package-lock.json (if available)
8 | COPY package*.json ./
9 |
10 | # Install dependencies
11 | RUN npm install
12 |
13 | # Copy the entire project to the working directory
14 | COPY . .
15 |
16 | # Install Playwright browsers (Chromium, Firefox, WebKit)
17 | RUN npx playwright install --with-deps
18 |
19 | # Command to run the Playwright tests
20 | CMD ["npx", "playwright", "test"]
--------------------------------------------------------------------------------
/src/utils/report/sendEmail.ts:
--------------------------------------------------------------------------------
1 | var nodeoutlook = require("nodejs-nodemailer-outlook");
2 |
3 | function sendEmailReport() {
4 | nodeoutlook.sendEmail({
5 | auth: {
6 | user: "xxxxx",
7 | pass: "password",
8 | },
9 | from: "emailid",
10 | to: "emailid",
11 | subject: "subject",
12 | html: "Pls open attached HTML file ",
13 | replyTo: "abhaybharti@gmail.com",
14 | attachments: [
15 | { filename: "index.html", path: "../../../playwright-report/index.html" },
16 | ],
17 | onError: (e: any) => console.log(e),
18 | onSuccess: (i: any) => console.log(i),
19 | });
20 | }
21 | sendEmailReport();
22 |
--------------------------------------------------------------------------------
/tests/web/example/uploadfile.spec.ts:
--------------------------------------------------------------------------------
1 | import test, { expect } from "@playwright/test";
2 | import { WebHelper } from "../../../helper/web/webHelper";
3 | import path from "path";
4 |
5 | test("File Upload Test ", async ({ page, browser }) => {
6 | const context = await browser.newContext();
7 | const webHelper = new WebHelper(page, context);
8 |
9 | //Arrange
10 | const fileName = "uploadFile.txt";
11 | const filePath = path.resolve(__dirname, `../test-data/${fileName}`);
12 |
13 | //Action
14 | await webHelper.uploadFile(fileName, filePath, "locatorOfUploadLink");
15 |
16 | //Assert
17 | expect(await webHelper.elementHasText("locatorOfUploadLink", fileName));
18 | });
19 |
--------------------------------------------------------------------------------
/auth.json:
--------------------------------------------------------------------------------
1 | {
2 | "cookies": [
3 | {
4 | "name": "session-username",
5 | "value": "standard_user",
6 | "domain": "www.saucedemo.com",
7 | "path": "/",
8 | "expires": 1758732605,
9 | "httpOnly": false,
10 | "secure": false,
11 | "sameSite": "Lax"
12 | }
13 | ],
14 | "origins": [
15 | {
16 | "origin": "https://www.saucedemo.com",
17 | "localStorage": [
18 | {
19 | "name": "backtrace-guid",
20 | "value": "6d346665-28b8-40b7-84bb-574bc9aed4c6"
21 | },
22 | {
23 | "name": "backtrace-last-active",
24 | "value": "1758731628459"
25 | }
26 | ]
27 | }
28 | ]
29 | }
--------------------------------------------------------------------------------
/tests/web/example/slider.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from "@tests/fixtures/customFixtures";
2 | import {expect} from "@playwright/test";
3 |
4 |
5 | test('Test Slider function', async ({web}) => {
6 |
7 | await web.navigateToUrl('https://the-internet.herokuapp.com/horizontal_slider')
8 |
9 | const slider = await web.findElement('xpath', "//input[@type='range']");
10 |
11 | await slider?.evaluate(node=>{(node as HTMLInputElement).value = '3'
12 | node.dispatchEvent(new Event('change'))
13 | })
14 |
15 | const result = await web.findElement('xpath', "//span[@id='range']")
16 |
17 | const resultText = await web.getText(result)
18 |
19 | await expect(resultText).toBe('3')
20 | })
--------------------------------------------------------------------------------
/src/utils/reader/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "John Doe",
3 | "age": 30,
4 | "email": "johndoe@example.com",
5 | "address": {
6 | "street": "123 Main St",
7 | "city": "New York",
8 | "state": "NY",
9 | "zip": "10001"
10 | },
11 | "phone_numbers": [
12 | {
13 | "type": "home",
14 | "number": "555-1234"
15 | },
16 | {
17 | "type": "work",
18 | "number": "555-5678"
19 | }
20 | ],
21 | "is_active": true,
22 | "roles": ["admin", "editor"],
23 | "preferences": {
24 | "newsletter": true,
25 | "notifications": {
26 | "email": true,
27 | "sms": false
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/web/example/runMultipleTestInSingleBrowserWindow.spec.js:
--------------------------------------------------------------------------------
1 | test.describe('1 page multiple tests', () => {
2 | let page;
3 | test.beforeAll(async ({ browser }) => {
4 | const context = await browser.newContext();
5 | page = await context.newPage();
6 | await page.goto('https://example.com');
7 | });
8 |
9 | test.afterAll(async ({ browser }) => {
10 | browser.close;
11 | });
12 |
13 | test('nav test', async () => {
14 | const name = await page.innerText('h1');
15 | expect(name).toContain('Example');
16 | });
17 |
18 | test('header test', async () => {
19 | const name = await page.innerText('h1');
20 | expect(name).toContain('Domain');
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/web/example/saveHarFile.spec.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 |
3 | test(`Generate HAR file`, async ({ page: Page }, testInfo) => {
4 | // To record HAR file, use "update:true", below code will create a directory named har and
5 | //store all the har related files in it
6 | await Page.routeFromHAR("har/example.har", { update: true });
7 |
8 | /* The `await testInfo.attach(`HAR FILE`, { path: `../../../../har/example.har` });` line is
9 | attaching the generated HAR file to the test report. It allows the HAR file to be easily
10 | accessible and viewable alongside the test results. The `path` parameter specifies the location of
11 | the HAR file. */
12 | await testInfo.attach(`HAR FILE`, { path: `../../../../har/example.har` });
13 | });
14 |
--------------------------------------------------------------------------------
/.github/workflows/playwright.yml:
--------------------------------------------------------------------------------
1 | name: Playwright Tests
2 | on:
3 | push:
4 | branches: [ main, master ]
5 | pull_request:
6 | branches: [ main, master ]
7 | jobs:
8 | test:
9 | timeout-minutes: 60
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - uses: actions/setup-node@v3
14 | with:
15 | node-version: 18
16 | - name: Install dependencies
17 | run: npm ci
18 | - name: Install Playwright Browsers
19 | run: npx playwright install --with-deps
20 | - name: Run Playwright tests
21 | run: npx playwright test
22 | - uses: actions/upload-artifact@v3
23 | if: always()
24 | with:
25 | name: playwright-report
26 | path: playwright-report/
27 | retention-days: 30
28 |
--------------------------------------------------------------------------------
/tests/web/example/downloadfile.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "../../fixtures/customFixtures"
2 | import fs from "fs";
3 | import path from "path";
4 |
5 | test("File Download Test ", async ({ web }) => {
6 | //Arrange
7 | const expectedFileName = "fileToDownload.txt";
8 | const downloadFolderPath = path.resolve(__dirname, "../test-data"); // path to the folder where the downloaded file will be saved
9 | const savePath = path.join(downloadFolderPath, expectedFileName);
10 |
11 | //Action
12 | await web.downLoadFile(
13 | expectedFileName,
14 | downloadFolderPath,
15 | "locatorOfDownloadLink"
16 | );
17 |
18 | //Assert
19 | expect(fs.existsSync(savePath)).toBeTruthy();
20 |
21 | //Clean up : remove downloaded file
22 | fs.unlinkSync(savePath);
23 | });
24 |
--------------------------------------------------------------------------------
/tests/web/example/frame.spec.ts:
--------------------------------------------------------------------------------
1 |
2 | import { test, expect } from "../../fixtures/customFixtures"
3 |
4 | test("iframe", async ({ page }) => {
5 | await page.goto("http://rahulshettyacademy.com/AutomationPractice/");
6 | const framesPage = await page.frameLocator("#courses-iframe");
7 | framesPage.locator("li a[href*='lifetime-access]:visible").click();
8 | const textCheck = await framesPage.locator(".text h2").textContent();
9 | console.log(textCheck);
10 | });
11 |
12 | test("Test 2 : Operation on frame", async ({ web, browser }) => {
13 | const context = await browser.newContext();
14 | const page = await context.newPage();
15 | await page.goto("http://rahulshettyacademy.com/AutomationPractice/");
16 | const frameOne = await web.getFrame("iframe[name='courses-iframe']");
17 | });
18 |
--------------------------------------------------------------------------------
/tests/web/example/UIBasics.spec.js:
--------------------------------------------------------------------------------
1 | import { test } from "@playwright/test";
2 |
3 | //In general terminology, fixture are global variable which are available across your project.
4 |
5 | //What is context
6 | //
7 | test('Browser Context Playwright Test', async ({ browser, page }) => {
8 | // write test step here
9 |
10 | //Opens a new & fresh instance of browser
11 | const context = await browser.newContext()
12 | const pageLocal = await context.newPage();
13 | await pageLocal.goto("https://sso.teachable.com/secure/9521/identity/login/otp")
14 | await pageLocal.close()
15 | })
16 |
17 | test('Page Playwrigth Test', async function ({ page }) {
18 | //write test step here using page fixtures
19 | await page.goto("https://google.co.in")
20 | await page.close()
21 | })
22 |
--------------------------------------------------------------------------------
/tests/web/example/multipleLocator.spec.js:
--------------------------------------------------------------------------------
1 | import test from '@playwright/test';
2 | import {logInfo, logError, logWarn} from "../../../src/utils/report/Logger"
3 |
4 | test('find multiple elements', async ({ page }) => {
5 | await page.goto('https://timesofindia.indiatimes.com/');
6 | const links = await page.locator('a');
7 | const links2 = await page.locator('a').all();
8 | console.log(links);
9 | console.log(links2);
10 |
11 | // 2. Get the number of links, waiting for them to appear
12 | const count = await links.count();
13 |
14 | // 3. Loop through them using nth()
15 | for (let i = 0; i < count; i++) {
16 | // For each link, Playwright finds it again and waits if necessary
17 | const linkText = await links.nth(i).textContent();
18 | logInfo(linkText);
19 |
20 | }
21 | })
22 |
--------------------------------------------------------------------------------
/tests/mobile/example/mobiletest.spec.js:
--------------------------------------------------------------------------------
1 | import { remote } from "webdriverio";
2 |
3 | const capabilities = {
4 | platformName: "Android",
5 | "appium:automationName": "UiAutomator2",
6 | "appium:deviceName": "Android",
7 | "appium:appPackage": "com.android.settings",
8 | "appium:appActivity": ".Settings",
9 | };
10 |
11 | const wdOpts = {
12 | hostname: process.env.APPIUM_HOST || "localhost",
13 | port: parseInt(process.env.APPIUM_PORT, 10) || 4723,
14 | logLevel: "info",
15 | capabilities,
16 | };
17 |
18 | // async function runTest() {
19 | // const driver = await remote(wdOpts);
20 | // try {
21 | // const batteryItem = await driver.$('//*[@text="Battery"]');
22 | // await batteryItem.click();
23 | // } finally {
24 | // await driver.pause(1000);
25 | // await driver.deleteSession();
26 | // }
27 | // }
28 |
29 | // runTest().catch(console.error);
30 |
--------------------------------------------------------------------------------
/tests/web/example/networkTest.spec.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 | import { ApiHelper } from "../../../helper/api/apiHelper";
3 |
4 | let token: string;
5 |
6 | test.beforeAll(async ({ browser }) => {
7 | const context = await browser.newContext();
8 | const apiContent = await context.newPage();
9 | const apiHelper = new ApiHelper(apiContent);
10 |
11 | //save token value returned from getToken() function in token variable
12 | token = await apiHelper.getToken();
13 | });
14 |
15 | test("Network Test -> Inject token generated through API into browser", async ({
16 | page,
17 | }) => {
18 | //executed JavaScript to inject token into browser
19 | page.addInitScript((value) => {
20 | window.localStorage.setItem("token", value);
21 | }, token);
22 |
23 | //when script hits URL, browser will open as logged in using above generated token
24 | await page.goto("https://www.xxxx.com");
25 | });
26 |
--------------------------------------------------------------------------------
/tests/web/example/basicAuthentication.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@tests/fixtures/test-options";
2 |
3 | test.use({
4 | baseURL: 'https://the-internet.herokuapp.com',
5 | httpCredentials: {
6 | username: 'admin',
7 | password: 'admin',
8 | }
9 | })
10 |
11 |
12 | test('Basic Authetication', async ({ page }) => {
13 | await page.goto('/')
14 | await page.getByRole('link', { name: 'Basic Auth', exact: true }).click();
15 | await expect(page.getByRole('heading', { name: 'Basic Auth' })).toBeVisible()
16 | })
17 |
18 | test.only('Basic Authetication with Extra HTTP headers', async ({ page }) => {
19 | await page.goto('/')
20 | await page.setExtraHTTPHeaders({
21 | Authorization: 'Basic '+btoa('admin:admin')
22 | })
23 | await page.getByRole('link', { name: 'Basic Auth', exact: true }).click();
24 | await expect(page.getByRole('heading', { name: 'Basic Auth' })).toBeVisible()
25 | })
--------------------------------------------------------------------------------
/tests/web/example/localization.spec.ts:
--------------------------------------------------------------------------------
1 | import { logInfo } from "@src/utils/report/Logger";
2 | import { test } from "@tests/fixtures/test-options";
3 |
4 | test.describe('Local testing', () => {
5 |
6 | test.use({ locale: 'ru-RU', timezoneId: 'Asia/Tokyo', baseURL:'https://www.google.com' })
7 |
8 | test('Launch the Google website in Russian local', async ({ page }) => {
9 | await page.goto('/')
10 | const dateRes = await page.evaluate(() => {
11 | return {
12 | date: Date(),
13 | localization: navigator.language,
14 | timezoneId : Intl.DateTimeFormat().resolvedOptions().timeZone
15 | }
16 | });
17 | logInfo('dateRes : ',dateRes)
18 |
19 | await page.goto('https://www.typescriptlang.org/')
20 |
21 | await page.goto('/')
22 |
23 |
24 | await page.goto('https://yahoo.com')
25 |
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/utils/report/ReportAction.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 |
3 | export function step(_stepName?: string) {
4 | return function (target: any, context: ClassMethodDecoratorContext) {
5 | return function (...args: any) {
6 | // get the class and method details
7 | const methodDetails = this.constructor.name + '.' + (context.name as string)
8 | // Get the step details and add the parameter value with steps
9 | const name = _stepName ? `${_replacePlaceholders(_stepName, args)} - ${methodDetails} ` : methodDetails
10 | return test.step(name, async () => {
11 | return await target.call(this, ...args)
12 | })
13 | }
14 | }
15 | }
16 |
17 | function _replacePlaceholders(template: string, values: any[]): string {
18 | values.forEach(value => {
19 | if (typeof value === 'string' || typeof value === 'number') {
20 | // add support for other data types if needed
21 | template = template.replace(/{(.*?)}/, value as any)
22 | }
23 | })
24 | return template
25 | }
--------------------------------------------------------------------------------
/tests/web/example/verifybrokenlink.spec.js:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test'
2 |
3 | const html = `
4 | Example
5 | yc
6 | google
7 | yahoo
8 | `;
9 |
10 | test("All links are valid", async ({ page, request }) => {
11 | // await page.setContent(html);
12 | const links = await page.locator("a").evaluateAll(els => els.map(el => el.href));
13 |
14 |
15 | const chunkSize = 3;
16 |
17 |
18 | for (let i = 0; i < links.length; i += chunkSize) {
19 | const chunkLinks = links.slice(i, i + chunkSize);
20 | console.log('chunkLinks : ',chunkLinks);
21 | const responses = await Promise.all(chunkLinks.map(url => request.get(url)));
22 | responses.forEach((response, index) => {
23 | const url = chunkLinks[index];
24 | expect(response.ok(), `${url} is broken with status ${response.status()}`).toBe(true);
25 | });
26 | }
27 | })
--------------------------------------------------------------------------------
/tests/web/example/multipleTabs.spec.ts:
--------------------------------------------------------------------------------
1 | import { chromium,test } from "@playwright/test"
2 |
3 | test('Multiple Tabs',async()=>{
4 | //Launch Browser
5 | const browser = await chromium.launch({headless:false,args:["--start-maximized"]})
6 |
7 | //Create New browser Context
8 | const context = await browser.newContext();
9 |
10 |
11 | //Open the first tab (page)
12 | const tabOne = await context.newPage()
13 | await tabOne.goto('https://www.google.com')
14 | await tabOne.waitForTimeout(2000)
15 | console.log(await tabOne.url())
16 |
17 |
18 | //Open the second tab (page) in same context
19 | const tabTwo = await context.newPage()
20 | await tabTwo.goto('https://timesofindia.indiatimes.com/')
21 | await tabTwo.waitForTimeout(2000)
22 | console.log(await tabTwo.url())
23 |
24 | //Perform action on first tab, focus on first tab
25 | await tabOne.bringToFront();
26 |
27 | //Perform action on second tab, focus on second tab
28 | await tabTwo.bringToFront();
29 |
30 | //Close the browser
31 | await browser.close()
32 | })
33 |
34 |
35 |
--------------------------------------------------------------------------------
/tests/web/example/autoWait.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect, Request, Response } from "@playwright/test";
2 |
3 | //Different wait available in Playwright
4 | // 1. waitForTimeout -- Thread.sleep()
5 |
6 | // 2. waitForRequest
7 | test("test waitForRequest demo", async ({ page }) => {
8 | const request: Request = await page.waitForRequest(/ohrm_logo.png/);
9 | console.log(request.url());
10 | });
11 | // 3. waitForResponse
12 | test("test waitForResponse demo", async ({ page }) => {
13 | const response: Response = await page.waitForResponse(/ohrm_logo.png/);
14 | console.log(response.request().url());
15 | });
16 |
17 | // 4. waitForUrl
18 | // 5. waitForLoadState
19 | // 6. waitForSelector
20 |
21 | test("test waitForSelector demo", async ({ page }) => {
22 | //use below approach than using waitForSelector
23 | await expect(page.getByAltText("OrangeHRM")).toBeVisible({ timeout: 3_000 });
24 | });
25 | // 7. waitForFunction
26 | test("test waitForFunction demo", async ({ page }) => {
27 | await page.waitForFunction(() => {
28 | window.scrollBy(0, 600);
29 | });
30 | });
31 |
32 | // 8. waitForEvent
33 |
34 | test("test waitForEvent demo", async ({ page }) => {
35 | await page.waitForEvent("domcontentloaded");
36 | });
37 |
--------------------------------------------------------------------------------
/src/utils/error/ErrorManager.ts:
--------------------------------------------------------------------------------
1 | import { logError } from '../report/Logger';
2 |
3 | // Define an interface for the expected error object
4 | interface ApiErrorResponse {
5 | response?: {
6 | status?: number;
7 | data?: any; // Replace 'any' with a more specific type if known
8 | };
9 | }
10 |
11 | export class ApiError extends Error {
12 | constructor(
13 | message: string,
14 | public statusCode?: number,
15 | public response?: any
16 | ) {
17 | super(message);
18 | this.name = 'ApiError';
19 | }
20 | }
21 |
22 | export const handleError = (error: any): never => {
23 | if (error instanceof ApiError) {
24 | logError(`API Error: ${error.message}`, {
25 | statusCode: error.statusCode,
26 | response: error.response
27 | });
28 | } else {
29 | logError('Unexpected error occurred', error);
30 | }
31 | throw error;
32 | };
33 |
34 | export const wrapAsync = async (
35 | fn: () => Promise,
36 | errorMessage: string
37 | ): Promise => {
38 | try {
39 | return await fn();
40 | } catch (error) {
41 | const apiError = error as ApiErrorResponse; // Cast error to the expected type
42 | throw new ApiError(
43 | errorMessage,
44 | apiError?.response?.status,
45 | apiError?.response?.data
46 | );
47 | }
48 | };
--------------------------------------------------------------------------------
/src/helper/Helper.ts:
--------------------------------------------------------------------------------
1 |
2 | import { step } from "../utils/report/ReportAction";
3 |
4 | export class Helper {
5 | // Internal storage for locators
6 | const locatorStorage = new Map();
7 |
8 | /**
9 | * The `delay` function is an asynchronous function that waits for a specified amount of time before
10 | * resolving.
11 | * @param {number} time - The `time` parameter is a number that represents the duration of the delay
12 | * in seconds.
13 | * @returns a Promise that resolves to void.
14 | */
15 | @step('delay')
16 | async delay(time: number): Promise {
17 | return new Promise(function (resolve) {
18 | setTimeout(resolve, time * 1000);
19 | });
20 | }
21 |
22 | /**
23 | * Validates whether the provided string is a valid date parsable by `Date.parse()`.
24 | * @param {string} date - The date string to validate.
25 | * @returns {Promise} Resolves to true if the date string is valid; otherwise false.
26 | */
27 | @step('isValidDate')
28 | async isValidDate(date: string): Promise {
29 | if (Date.parse(date)) {
30 | return true;
31 | } else {
32 | return false;
33 | }
34 | }
35 |
36 | /**
37 | * Clears all stored locators
38 | */
39 | clearStoredLocators() {
40 | this.locatorStorage.clear();
41 | }
42 |
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/utils/config/azure/azure-pipeline.yml:
--------------------------------------------------------------------------------
1 | # Starter pipeline
2 | # Start with a minimal pipeline that you can customize to build and deploy your code.
3 | # Add steps that build, run tests, deploy, and more:
4 | # https://aka.ms/yaml
5 |
6 | trigger:
7 | - none
8 |
9 | pool:
10 | vmImage: ubuntu-latest
11 |
12 | variables:
13 | "system.debug": false
14 |
15 | steps:
16 | - task: NodeTool@0
17 | inputs:
18 | versionSource: "spec"
19 | versionSpec: ">16.x"
20 |
21 | - task: PowerShell@2
22 | enabled: true
23 | displayName: "Install dependencies"
24 | inputs:
25 | targetType: "inline"
26 | script: "npm ci"
27 | workingDirectory: "$(System.DefaultWorkingDirectory)/samples/get-started"
28 |
29 | - task: PowerShell@2
30 | enabled: true
31 | displayName: "Run Playwright tests"
32 | env:
33 | PLAYWRIGHT_SERVICE_ACCESS_TOKEN: $(Secret)
34 | inputs:
35 | targetType: "inline"
36 | script: "npx playwright test -c playwright.service.config.ts --workers=20"
37 | workingDirectory: "$(System.DefaultWorkingDirectory)/samples/get-started"
38 |
39 | - task: PublishPipelineArtifact@1
40 | inputs:
41 | targetPath: "$(System.DefaultWorkingDirectory)/samples/get-started/playwright-report/"
42 | artifact: "Playwright tests"
43 | publishLocation: "pipeline"
44 |
--------------------------------------------------------------------------------
/tests/web/example/globalLogin.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, BrowserContext } from "@playwright/test";
2 |
3 |
4 | let webContext: any;
5 |
6 | /* The `test.beforeAll()` function is a hook provided by the Playwright test framework. It is used to
7 | run a setup function before all the tests in a test file. In this hook, we will login into application and save login details in state.json file*/
8 | test.beforeAll(
9 | "Login into web app through browser and save login detail in JSON",
10 | async ({ browser }) => {
11 | const browserContext = await browser.newContext();
12 | const webPage = await browserContext.newPage();
13 | const webHelper = new WebHelper(webPage, browserContext);
14 | webHelper.navigateToUrl("www.gmail.com");
15 |
16 | //write code to login in gmail
17 |
18 | await browserContext.storageState({ path: "state.json" });
19 | webContext = await browserContext.newContext({
20 | storageState: "state.json",
21 | });
22 | await webPage.close();
23 | }
24 | );
25 | /* The code you provided is a test case that logs into a web application using the saved login state. */
26 | test("Login into web app using saved login state", async () => {
27 | const webPage = await webContext.newPage();
28 |
29 | const webHelper = new WebHelper(webPage, webContext);
30 | webHelper.navigateToUrl("www.gmail.com"); // Browser will open page using login details saved in test.beforeAll() step
31 | });
32 |
--------------------------------------------------------------------------------
/tests/web/example/multiplePages.spec.ts:
--------------------------------------------------------------------------------
1 |
2 | import { chromium, test } from "@playwright/test";
3 |
4 | // Define a test case named 'Multiple Browser Window'
5 | test('Multiple Browser Window', async () => {
6 |
7 | // Launch the first browser instance Chromium
8 | const browserOne = await chromium.launch({ headless: false, args: ["--start-maximized"] });
9 |
10 | // Create a new browser context for the first browser
11 | const contextOne = await browserOne.newContext();
12 |
13 | // Open a new page (tab) in the first browser context
14 | const pageOne = await contextOne.newPage();
15 |
16 | // Navigate the first page to Google
17 | await pageOne.goto('https://www.google.com');
18 |
19 | // Launch the second browser instance Chromium
20 | const browserTwo = await chromium.launch({ headless: false, args: ["--start-maximized"] });
21 |
22 | // Create a new browser context for the second browser
23 | const contextTwo = await browserTwo.newContext();
24 |
25 | // Open a new page (tab) in the second browser context
26 | const pageTwo = await contextTwo.newPage();
27 |
28 | // Navigate the second page to DEV.to
29 | await pageTwo.goto('https://dev.to/');
30 |
31 | // Bring the first browser window to the focus
32 | await pageOne.bringToFront();
33 |
34 | // Bring the second browser window to the focus
35 | await pageTwo.bringToFront();
36 |
37 | // Close the first browser
38 | await browserOne.close();
39 |
40 | // Close the second browser
41 | await browserTwo.close();
42 | });
--------------------------------------------------------------------------------
/src/utils/report/CustomReporterConfig.ts:
--------------------------------------------------------------------------------
1 | import {
2 | FullConfig,
3 | FullResult,
4 | Reporter,
5 | Suite,
6 | TestCase,
7 | TestError,
8 | TestResult,
9 | TestStep,
10 | } from "@playwright/test/reporter";
11 | import {logInfo} from './Logger'
12 |
13 | export default class CustomReporterConfig implements Reporter {
14 | constructor(options: { customOption?: string } = {}) {
15 | logInfo(`playwright-framework-template ${options.customOption}`);
16 | }
17 |
18 | onBegin(config: FullConfig, suite: Suite): void {
19 | logInfo(`info`,
20 | `Test Suite Started : ${suite.title} , ${suite.allTests().length} tests`
21 | );
22 | }
23 | onTestBegin(test: TestCase): void {
24 | logInfo(`info`,`Test Case Started : ${test.title}`);
25 | }
26 |
27 | onTestEnd(test: TestCase, result: TestResult): void {
28 | logInfo(`info`,`Test Case Completed : ${test.title} Status : ${result.status}`);
29 | }
30 |
31 | onStepBegin(test: TestCase, result: TestResult, step: TestStep): void {
32 | if (step.category === `test.step`) {
33 | logInfo(`info`,`Executing Step : ${step.title}`);
34 | }
35 | }
36 |
37 | onError(error: TestError): void {
38 | logInfo(`error`,`TestError : ${error.message}`);
39 | }
40 |
41 | onEnd(
42 | result: FullResult
43 | ): void | Promise<
44 | | void
45 | | { status?: "passed" | "failed" | "timedout" | "interrupted" | undefined }
46 | | undefined
47 | > {
48 | console.log(`Test Suite Completed : ${result.status}`);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/tests/web/example/DragAndDrop.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from "@tests/fixtures/test-options";
2 |
3 | test('Drag and Drop', async ({ web }) => { // Define a test case named 'Drag and Drop' using the 'web' fixture.
4 | await web.navigateToUrl('https://the-internet.herokuapp.com/drag_and_drop') // Navigate the browser to the specified URL.
5 |
6 | const A = await web.findElement('xpath', "//div[@id='column-a']") // Find the source element (Column A) using XPath.
7 | const B = await web.findElement('xpath', "//div[@id='column-b']") // Find the target element (Column B) using XPath.
8 |
9 | await A!.dragTo(B!); // Perform the drag-and-drop action, moving element A onto element B.
10 | })
11 |
12 | test('Drag and Drop Using Mouse Hover', async ({ web }) => {
13 | await web.navigateToUrl('https://the-internet.herokuapp.com/drag_and_drop') // Navigate to the drag and drop test page.
14 |
15 | const A = await web.findElement('xpath', "//div[@id='column-a']") // Locate the source element (Column A).
16 | const B = await web.findElement('xpath', "//div[@id='column-b']") // Locate the target element (Column B).
17 |
18 | await A!.hover() // Move the mouse pointer over the source element (Column A).
19 | await web.webPage.mouse.down(); // Press and hold the left mouse button to start the drag operation.
20 | await B!.hover(); // Move the mouse pointer to the target element (Column B) while still holding the button.
21 | await web.webPage.mouse.up(); // Release the left mouse button to complete the drop operation.
22 | })
23 |
24 |
--------------------------------------------------------------------------------
/tests/web/example/webTable.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test';
2 | import { logInfo } from '@src/utils/report/Logger';
3 |
4 | test('Read all data from table', async ({ page }) => {
5 | //navigate to URL
6 | //Locate Table
7 | //Find number of rows in table
8 | //Traverse rows
9 | //Find number of column in each row
10 | //Traverse columns & print values
11 |
12 | await page.goto('https://the-internet.herokuapp.com/tables')
13 | const rows = await page.locator("//*[@id='table1']").locator("tbody").locator("tr");
14 | const columnHeader = await page.locator("//*[@id='table1']").locator("thead").locator("tr").locator("th");
15 | const columnHeaderCount = await columnHeader.count();
16 | const headerText = [];
17 | for (let i = 0; i < columnHeaderCount; i++) {
18 | headerText.push(await columnHeader.nth(i).textContent());
19 | }
20 | logInfo(`Header Text: ${headerText.join('|')}`);
21 |
22 |
23 | const count = await rows.count();
24 | logInfo(`Number of rows in table: ${count}`);
25 | for (let i = 0; i < count; i++) {
26 | const columns = rows.nth(i).locator("td");
27 | const columnCount = await columns.count();
28 | logInfo(`Number of columns in row ${i + 1}: ${columnCount}`);
29 | const rowData = [];
30 | for (let j = 0; j < columnCount; j++) {
31 | const cellText = await columns.nth(j).textContent();
32 | rowData.push(cellText);
33 | }
34 | logInfo(`Row ${i + 1}: ${rowData.join('|')}`);
35 | }
36 | })
--------------------------------------------------------------------------------
/tests/web/example/launchEdge.spec.js:
--------------------------------------------------------------------------------
1 | import { chromium, test, firefox } from "@playwright/test";
2 |
3 | test('Launch Edge', async () => {
4 | const browser = await chromium.launch({
5 | channel: 'msedge' //Use system installed chrome
6 | , headless: false //Show browser
7 | });
8 |
9 | const page = await browser.newPage();
10 | await page.goto('https://www.google.com');
11 | console.log(await page.title());
12 | await browser.close();
13 | });
14 |
15 |
16 | test('Launch Firefox', async () => {
17 | // Launch the Firefox browser. 'headless: false' means we will see the browser UI.
18 | const firefoxBrowser = await firefox.launch({
19 | headless: false
20 | });
21 |
22 | // Create a new page (tab) in the Firefox browser
23 | const firefoxPage = await firefoxBrowser.newPage();
24 |
25 | // Navigate the page to a specific URL
26 | await firefoxPage.goto('https://playwright.dev/');
27 | console.log('Firefox launched successfully and navigated to playwright.dev');
28 |
29 | try {
30 | // Launch the WebKit browser.
31 | const webkitBrowser = await webkit.launch({
32 | headless: false
33 | });
34 |
35 | // Create a new page in the WebKit browser
36 | const webkitPage = await webkitBrowser.newPage();
37 |
38 | // Navigate the page to a specific URL
39 | await webkitPage.goto('https://webkit.org/');
40 | console.log('WebKit launched successfully and navigated to webkit.org');
41 |
42 | // To automatically close it, you would use: await webkitBrowser.close();
43 |
44 | } catch (error) {
45 | console.error('Failed to launch WebKit:', error);
46 | }
47 | });
--------------------------------------------------------------------------------
/tests/resources/uiMap/moataeldebsy.json:
--------------------------------------------------------------------------------
1 | {
2 | "Basic": {
3 | "Forms": {
4 | "Username": {
5 | "objType": "Label",
6 | "locatorType": "xpath",
7 | "locatorValue": "//label[@for='username']",
8 | "label": "Username"
9 | },
10 | "UsernameText": {
11 | "objType": "textbox",
12 | "locatorType": "xpath",
13 | "locatorValue": "//input[@data-test='username-input']",
14 | "saveTestData": "A|B",
15 | "invalidTestData": "",
16 | "validTestData": ""
17 | },
18 | "Email": {
19 | "objType": "Label",
20 | "locatorType": "xpath",
21 | "locatorValue": "//label[@for='email']",
22 | "label": "Email"
23 | },
24 | "EmailText": {
25 | "objType": "textbox",
26 | "locatorType": "xpath",
27 | "locatorValue": "//input[@data-test='email-input']",
28 | "saveTestData": "",
29 | "invalidTestData": "",
30 | "validTestData": ""
31 | },
32 | "Password": {
33 | "objType": "Label",
34 | "locatorType": "xpath",
35 | "locatorValue": "//label[@for='password']",
36 | "label": "Password"
37 | },
38 | "PasswordText": {
39 | "objType": "textbox",
40 | "locatorType": "xpath",
41 | "locatorValue": "//input[@data-test='password-input']",
42 | "saveTestData": "",
43 | "invalidTestData": "",
44 | "validTestData": ""
45 | },
46 | "SignIn": {
47 | "objType": "button",
48 | "locatorType": "xpath",
49 | "locatorValue": "//button[@data-test='submit-button']",
50 | "label": "Sign In"
51 | }
52 | },
53 | "Table": {}
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/tests/api/example/user.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test';
2 | import { ApiHelper } from '@src/helper/api/apiHelper';
3 |
4 |
5 | test.describe('Users API', () => {
6 | let apiHelper: ApiHelper;
7 |
8 | test.beforeEach(async ({ request,page }) => {
9 | apiHelper = new ApiHelper(page,request);
10 | });
11 |
12 | test('should get all users', async () => {
13 | const response = await apiHelper.get('/users');
14 | expect(response.status).toBe(200);
15 | expect(Array.isArray(response.data)).toBeTruthy();
16 | expect(response.data.length).toBeGreaterThan(0);
17 | });
18 |
19 | test('should get a single user', async () => {
20 | const response = await apiHelper.get('/users/1');
21 | expect(response.status).toBe(200);
22 | expect(response.data.id).toBe(1);
23 | expect(response.data.name).toBeTruthy();
24 | expect(response.data.email).toBeTruthy();
25 | });
26 |
27 | test('should create a new user', async () => {
28 | const userData = {
29 | name: 'John Doe',
30 | email: 'john@example.com',
31 | username: 'johndoe',
32 | website: 'example.com'
33 | };
34 | const response = await apiHelper.post('/users', userData);
35 | expect(response.status).toBe(201);
36 | expect(response.data.name).toBe(userData.name);
37 | expect(response.data.email).toBe(userData.email);
38 | expect(response.data.id).toBeTruthy();
39 | });
40 |
41 | test('should update a user', async () => {
42 | const updateData = {
43 | name: 'Jane Doe',
44 | email: 'jane@example.com'
45 | };
46 | const response = await apiHelper.patch('/users/1', updateData);
47 | expect(response.status).toBe(200);
48 | expect(response.data.name).toBe(updateData.name);
49 | expect(response.data.email).toBe(updateData.email);
50 | });
51 | });
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "Nodenext" /* Specify what module code is generated. */,
5 | "lib": [
6 | "ESNext",
7 | "DOM"
8 | ],
9 | "moduleResolution": "Nodenext" /* Specify what module code is generated. */,
10 | "baseUrl": "./" /* Specify the base directory to resolve non-relative module names. */,
11 | "paths": {
12 | "@src/*": [
13 | "src/*"
14 | ],
15 | "@tests/*": [
16 | "tests/*"
17 | ],
18 | "@pages/*": [
19 | "tests/pages/*"
20 | ]
21 | },
22 | "experimentalDecorators": true,
23 | "emitDecoratorMetadata": true,
24 | "rootDir": ".",
25 | "allowJs": true,
26 | "checkJs": false,
27 | "outDir": "./dist" /* Specify an output folder for all emitted files. */,
28 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
29 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
30 | "strict": true /* Enable all strict type-checking options. */,
31 | "skipLibCheck": true /* Skip type checking all .d.ts files. */,
32 | "noEmit": true,
33 | "noImplicitAny": true,
34 | "resolveJsonModule": true,
35 | "types": [
36 | "node",
37 | "playwright"
38 | ]
39 | },
40 | "include": [
41 | "src",
42 | "eslint.config.mjs",
43 | "./playwright.config.ts",
44 | "tests",
45 | "*.js",
46 | "*.ts",
47 | "*.mts",
48 | "tests/**/*.ts",
49 | "fixtures/**/*.ts",
50 | "pages/**/*.ts",
51 | "helpers/**/*.ts",
52 | "enums/**/*.ts"
53 | ],
54 | "exclude": [
55 | "node_modules",
56 | "dist",
57 | "playwright-report",
58 | "test-results"
59 | ]
60 | }
--------------------------------------------------------------------------------
/tests/web/example/launchBrowserUsingConfig.spec.ts:
--------------------------------------------------------------------------------
1 | // Import all browser types from the Playwright library
2 | import { chromium, firefox, webkit, test } from "@playwright/test";
3 |
4 | let browserType = 'msedge'; // <-- CHANGE THIS VALUE
5 |
6 | test("Launch browser using config", async () => {
7 | let browser;
8 | // --- Browser Launch Logic ---
9 | // Use a switch statement to select and launch the correct browser
10 | switch (browserType) {
11 | case 'chrome':
12 | browser = await chromium.launch({
13 | headless: false
14 | });
15 | break;
16 |
17 | case 'msedge':
18 | browser = await chromium.launch({
19 | channel: 'msedge', // This is specific to chromium
20 | headless: false
21 | });
22 | break;
23 | case 'firefox':
24 | browser = await firefox.launch({
25 | headless: false
26 | });
27 | break;
28 | case 'webkit':
29 | browser = await webkit.launch({
30 | headless: false
31 | });
32 | break;
33 |
34 | default:
35 | // If the browserType is not recognized, throw an error
36 | throw new Error(`Invalid browser type specified: ${browserType}. Use 'chrome', 'msedge', 'firefox', or 'webkit'.`);
37 | }
38 |
39 | // The rest of the code works for any browser
40 | const context = await browser.newContext();
41 | const page = await context.newPage();
42 |
43 | // Navigate to a test page
44 | await page.goto('https://playwright.dev/');
45 | console.log(`${browserType} launched successfully and navigated to playwright.dev`);
46 |
47 | // Add a delay of 5 seconds so you can see the browser before it closes
48 | await page.waitForTimeout(5000);
49 | })
50 |
51 |
52 |
--------------------------------------------------------------------------------
/tests/mobile/example/healthcheckiOS.test.ts:
--------------------------------------------------------------------------------
1 | import { AppiumHelper } from "../../../helper/mobile/appiumHelper";
2 |
3 | const iOSVersion = "16.4";
4 |
5 | const capabilities = {
6 | platformName: "iOS",
7 | "appium:platformVersion": iOSVersion,
8 | "appium:deviceName": "iPhone 13 Pro Max",
9 | "appium:automationName": "XCUITest",
10 | "appium:app": "com.apple.Preferences",
11 | "appium:locale": "US",
12 | "appium:language": "en",
13 | };
14 |
15 | // describe("Healthcheck iOS Appium connection", function () {
16 | // let app: AppiumHelper;
17 |
18 | // before(async () => {
19 | // app = new AppiumHelper();
20 | // await app.init(capabilities);
21 | // });
22 |
23 | // after(async () => {
24 | // await app.quit();
25 | // });
26 |
27 | // it("checks iOS version number on Settings App", async () => {
28 | // // Go the the "General" section
29 | // const generalElement = await app.findElement(
30 | // '//XCUIElementTypeCell[@name="General"]'
31 | // );
32 | // await generalElement.click();
33 |
34 | // // Go the the "About" section
35 | // const aboutElement = await app.findElement(
36 | // '//XCUIElementTypeCell[@name="About"]'
37 | // );
38 | // await aboutElement.click();
39 |
40 | // // Go the the "iOS Version" section
41 | // const versionElement = await app.findElement(
42 | // '//XCUIElementTypeCell[@name="iOS Version"]'
43 | // );
44 | // await versionElement.click();
45 |
46 | // // Check the version is the on expected
47 | // const iosVersionElement = await app.findElement(
48 | // `//XCUIElementTypeButton[contains(@name, "iOS ${iOSVersion}")]`
49 | // );
50 | // const isDisplayed = await iosVersionElement.isDisplayed();
51 |
52 | // if (!isDisplayed) {
53 | // throw new Error(
54 | // `Could not find iOS version label ${iOSVersion} on the device`
55 | // );
56 | // }
57 | // });
58 | // });
59 |
--------------------------------------------------------------------------------
/tests/web/example/route.spec.ts:
--------------------------------------------------------------------------------
1 | import test from "@playwright/test";
2 | import { Console, log } from "console";
3 |
4 | let fakePayloadOrders = { data: [], message: "No Users" };
5 |
6 | test("Intercept Network call and Fake/Mock API response", async ({ page }) => {
7 | await page.goto("http://xxxx.com");
8 | await page.route("http://xxxx.com/abc/ird", async (route) => {
9 | //go the response
10 | const response = await page.request.fetch(route.request());
11 | let body = JSON.stringify(fakePayloadOrders);
12 |
13 | //send response to browser and override respond body
14 | route.fulfill({
15 | status: 200,
16 | body: body,
17 | });
18 | });
19 | });
20 |
21 | test("Intercept Network request", async ({ page }) => {
22 | await page.goto("http://xxxx.com");
23 |
24 | //intercept request send to server & respond by url value passed in route.continue
25 | await page.route("http://xxxx.com/abc/ird", async (route) => {
26 | route.continue({
27 | url: "http://xxxx.com/abc/123455",
28 | });
29 | });
30 | });
31 |
32 | test.only("Capture Request and Response", async ({ page }) => {
33 | page.on("request", (request) => {
34 | console.log(request.url(), request.method());
35 | });
36 | page.on("requestfinished", (data) => {
37 | console.log(data);
38 | });
39 |
40 | page.on("websocket", (data) => {
41 | console.log(data.url());
42 | });
43 |
44 | page.on("console", async (msg) => {
45 | const values = [];
46 | for (const arg of msg.args()) values.push(await arg.jsonValue());
47 | console.log(...values);
48 | });
49 |
50 | page.on("response", (response) => {
51 | // console.log(response?.status(), response?.body());
52 | console.log(response?.status());
53 | });
54 |
55 | page.on("requestfailed", (request) => {
56 | console.log(request.url() + " " + request.failure()?.errorText);
57 | });
58 | await page.goto("https://www.flipkart.com");
59 | });
60 |
--------------------------------------------------------------------------------
/tests/mobile/example/healthcheckAndroid.test.ts:
--------------------------------------------------------------------------------
1 | import { AppiumHelper } from "../../../helper/mobile/appiumHelper";
2 |
3 | const androidCapabilities = {
4 | platformName: "Android",
5 | "appium:automationName": "UiAutomator2",
6 | "appium:deviceName": "Android",
7 | "appium:appPackage": "com.android.settings",
8 | "appium:appActivity": ".Settings",
9 | "appium:locale": "US",
10 | "appium:language": "en",
11 | };
12 |
13 | // describe("Healthcheck Android Appium connection", function () {
14 | // let app: AppiumHelper;
15 |
16 | // before(async () => {
17 | // app = new AppiumHelper();
18 | // await app.init(androidCapabilities);
19 | // });
20 |
21 | // after(async () => {
22 | // await app.quit();
23 | // });
24 |
25 | // it("checks battery level on Settings App", async () => {
26 | // const imageButton = await app.findElement("android.widget.ImageButton");
27 | // await imageButton.click();
28 | // const editText = await app.findElement(
29 | // "android=new UiSelector().className(android.widget.EditText)"
30 | // );
31 | // await editText.click();
32 | // await editText.setValue("bat");
33 | // const linearLayout = await app.findElement(
34 | // "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout[2]/android.widget.ScrollView/android.widget.LinearLayout/android.widget.ScrollView/android.widget.LinearLayout/android.widget.FrameLayout/android.support.v7.widget.RecyclerView/android.widget.LinearLayout[3]/android.widget.LinearLayout"
35 | // );
36 | // await linearLayout.click();
37 | // const progressBar = await app.findElement(
38 | // "android=new UiSelector().className(android.widget.ProgressBar)"
39 | // );
40 | // await progressBar.isDisplayed();
41 | // });
42 | // });
43 |
--------------------------------------------------------------------------------
/tests/api/example/post.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test';
2 | import { ApiHelper } from '../../../helper/api/apiHelper';
3 |
4 | test.describe('Posts API', () => {
5 | let apiHelper: ApiHelper;
6 |
7 | test.beforeEach(async ({ request }) => {
8 | apiHelper = new ApiHelper(request);
9 | });
10 |
11 | test('should get all posts', async () => {
12 | const response = await apiHelper.get('/posts');
13 | expect(response.status).toBe(200);
14 | expect(Array.isArray(response.data)).toBeTruthy();
15 | expect(response.data.length).toBeGreaterThan(0);
16 | });
17 |
18 | test('should get a single post', async () => {
19 | const response = await apiHelper.get('/posts/1');
20 | expect(response.status).toBe(200);
21 | expect(response.data.id).toBe(1);
22 | expect(response.data.title).toBeTruthy();
23 | expect(response.data.body).toBeTruthy();
24 | });
25 |
26 | test('should create a new post', async () => {
27 | const postData = {
28 | title: 'Test Post',
29 | body: 'This is a test post',
30 | userId: 1
31 | };
32 | const response = await apiHelper.post('/posts', postData);
33 | expect(response.status).toBe(201);
34 | expect(response.data.title).toBe(postData.title);
35 | expect(response.data.body).toBe(postData.body);
36 | expect(response.data.userId).toBe(postData.userId);
37 | expect(response.data.id).toBeTruthy();
38 | });
39 |
40 | test('should update a post', async () => {
41 | const updateData = {
42 | title: 'Updated Post',
43 | body: 'This post has been updated',
44 | userId: 1
45 | };
46 | const response = await apiHelper.put('/posts/1', updateData);
47 | expect(response.status).toBe(200);
48 | expect(response.data.title).toBe(updateData.title);
49 | expect(response.data.body).toBe(updateData.body);
50 | });
51 |
52 | test('should delete a post', async () => {
53 | const response = await apiHelper.delete('/posts/1');
54 | expect(response.status).toBe(200);
55 | });
56 | });
--------------------------------------------------------------------------------
/tests/load/BrowerLoadTest.spec.js:
--------------------------------------------------------------------------------
1 | const withTransactionTimer = async (transactionName, events, userActions) => {
2 | const startedTime = Date.now();
3 | await userActions();
4 | const difference = Date.now() - startedTime;
5 | events.emit("histogram", transactionName, difference);
6 | };
7 |
8 | async function browserloadTest(page, userContext, events) {
9 | await withTransactionTimer("login", events, async () => {
10 | await page.goto(
11 | "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"
12 | );
13 | await page.getByPlaceholder("Username").click();
14 | await page.getByPlaceholder("Username").fill("Admin");
15 | await page.getByPlaceholder("Password").fill("admin123");
16 | await page.getByRole("button", { name: "Login" }).click();
17 | await page.waitForLoadState("domcontentloaded");
18 | });
19 |
20 | await withTransactionTimer("OpenAdminPage", events, async () => {
21 | await page.getByRole("link", { name: "Admin" }).click();
22 | await page.waitForLoadState("domcontentloaded");
23 | });
24 |
25 | await withTransactionTimer("OpenPimPage", events, async () => {
26 | await page.getByRole("link", { name: "PIM" }).click();
27 | await page.waitForLoadState("domcontentloaded");
28 | });
29 |
30 | await withTransactionTimer("OpenTimePage", events, async () => {
31 | await page.getByRole("link", { name: "Time" }).click();
32 | await page.waitForLoadState("domcontentloaded");
33 | });
34 |
35 | await withTransactionTimer("OpenRecruitmentPage", events, async () => {
36 | await page.getByRole("link", { name: "Recruitment" }).click();
37 | await page.waitForLoadState("domcontentloaded");
38 | });
39 |
40 | await withTransactionTimer("OpenMyInfoPage", events, async () => {
41 | await page.getByRole("link", { name: "My Info" }).click();
42 | await page.waitForLoadState("domcontentloaded");
43 | });
44 |
45 | await withTransactionTimer("OpenPerformancePage", events, async () => {
46 | await page.getByRole("link", { name: "Performance" }).click();
47 | await page.waitForLoadState("domcontentloaded");
48 | });
49 | }
50 |
51 | module.exports = { browserloadTest };
52 |
--------------------------------------------------------------------------------
/tests/web/example/authenticationUsingAuthJSON.spec.ts:
--------------------------------------------------------------------------------
1 | import { test } from '@playwright/test';
2 |
3 | // Run tests in this describe block sequentially (serial). If one test fails,
4 | // the remaining tests in the block are skipped.
5 | test.describe.serial('Auth Flow', () => {
6 | // First test logs in and saves the authenticated browser context state
7 | // to a file (auth.json) for reuse in subsequent tests.
8 | test('Authenticate and save state', async ({ page }) => {
9 | // Navigate to the login page
10 | await page.goto('https://www.saucedemo.com/');
11 |
12 | // (Optional UI interaction) Focus/double-click area showing login hints
13 | await page.locator('[data-test="login-credentials"]').dblclick();
14 |
15 | // Enter username and password
16 | await page.locator('[data-test="username"]').click();
17 | await page.locator('[data-test="username"]').fill('standard_user');
18 | await page.locator('[data-test="username"]').press('Tab');
19 | await page.locator('[data-test="password"]').fill('secret_sauce');
20 |
21 | // Submit the form and wait for a post-login UI element to be visible
22 | await page.locator('[data-test="login-button"]').click();
23 | await page.locator("//*[@id='shopping_cart_container']").waitFor({ state: 'visible' });
24 |
25 | // Persist the authenticated state to a file so it can be reused
26 | // by other tests without logging in again.
27 | await page.context().storageState({ path: 'auth.json' });
28 |
29 | // Close the page
30 | await page.close();
31 | });
32 |
33 | // Apply the saved storage state to tests defined below this line
34 | test.use({ storageState: 'auth.json' });
35 |
36 | // Second test reuses the saved auth state to directly access an authenticated page
37 | test('Use saved State and login into saucedemo website', async ({ page }) => {
38 | // With storageState applied, user should already be logged in
39 | await page.goto('https://www.saucedemo.com/inventory.html');
40 |
41 | // Verify a logged-in UI element is present and visible
42 | await page.locator("//*[@id='shopping_cart_container']").waitFor({ state: 'visible' });
43 | });
44 | })
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import tseslint from '@typescript-eslint/eslint-plugin';
2 | import tsParser from '@typescript-eslint/parser';
3 | import prettierPlugin from 'eslint-plugin-prettier';
4 | import playwright from 'eslint-plugin-playwright';
5 | import { fileURLToPath } from 'url';
6 | import { dirname } from 'path';
7 |
8 | const prettierConfig = {
9 | semi: true,
10 | tabWidth: 4,
11 | useTabs: false,
12 | printWidth: 80,
13 | singleQuote: true,
14 | trailingComma: 'es5',
15 | bracketSpacing: true,
16 | arrowParens: 'always',
17 | proseWrap: 'preserve',
18 | };
19 |
20 | const __dirname = dirname(fileURLToPath(import.meta.url));
21 |
22 | const config = [
23 | {
24 | ignores: ['node_modules', 'dist', 'playwright-report', 'test-results'],
25 | },
26 | {
27 | files: ['**/*.ts', '**/*.tsx'],
28 | languageOptions: {
29 | parser: tsParser,
30 | parserOptions: {
31 | ecmaVersion: 2020,
32 | sourceType: 'module',
33 | project: ['./tsconfig.json'],
34 | tsconfigRootDir: __dirname,
35 | },
36 | },
37 | plugins: {
38 | '@typescript-eslint': tseslint,
39 | prettier: prettierPlugin,
40 | playwright,
41 | },
42 | rules: {
43 | ...(tseslint.configs.recommended?.rules ?? {}),
44 | ...(playwright.configs['flat/recommended']?.rules ?? {}),
45 | 'prettier/prettier': ['error', prettierConfig],
46 | '@typescript-eslint/explicit-function-return-type': 'error',
47 | '@typescript-eslint/no-explicit-any': 'error',
48 | '@typescript-eslint/no-unused-vars': [
49 | 'error',
50 | { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
51 | ],
52 | 'no-console': 'error',
53 | 'prefer-const': 'error',
54 | '@typescript-eslint/no-inferrable-types': 'error',
55 | '@typescript-eslint/no-empty-function': 'error',
56 | '@typescript-eslint/no-floating-promises': 'error',
57 | 'playwright/missing-playwright-await': 'error',
58 | 'playwright/no-page-pause': 'error',
59 | 'playwright/no-useless-await': 'error',
60 | 'playwright/no-skipped-test': 'error',
61 | },
62 | },
63 | ];
64 |
65 | export default config;
66 |
--------------------------------------------------------------------------------
/tests/web/example/mockApi.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@tests/fixtures/test-options"
2 |
3 | test.use({ baseURL: 'https://practicesoftwaretesting.com' })
4 | test.describe('Mock the brand category', () => {
5 |
6 | test('Validate category with subcategoty', async ({ page, request }) => {
7 | // 1. Set up the Route: Intercept calls to the specific API endpoint
8 | await page.route('https://api.practicesoftwaretesting.com/categories/tree', async (route) => {
9 | const response = await route.fetch();
10 | const mockres = await request.get('http://localhost:3001/brands')
11 | const mockJson = await mockres.json()
12 | // 2. Fulfill the Route: Provide a fake JSON response
13 | await route.fulfill({ response: response, json: mockJson });
14 | })
15 |
16 | // 3. Navigate to the page that makes the API call
17 | await page.goto('/')
18 | await page.waitForTimeout(2000)
19 | // 4. Run the Test: Assert that the UI displays the mocked data
20 | await expect(page.getByRole('textbox', { name: 'Search' })).toBeVisible()
21 | await expect(page.getByRole('checkbox', { name: 'Hand Tools' })).not.toBeVisible()
22 | })
23 |
24 | test('Fetch Fake Product Price', async ({ page, request }) => {
25 | await page.route('**/api/products', async route => {
26 | // 1. Fetch the real response
27 | const response = await route.fetch();
28 | // 2. Get the JSON body
29 | const json = await response.json();
30 |
31 | // 3. Modify the data (e.g., add a new item or change a field)
32 | json.push({ id: 999, name: 'Fake Special Offer', price: 0.00 });
33 |
34 | // 4. Fulfill the route with the modified data
35 | await route.fulfill({
36 | // Use the original response object to keep status, headers, etc.
37 | response,
38 | // Override the body with the modified JSON
39 | json,
40 | });
41 | });
42 |
43 | await page.goto('/products');
44 | await expect(page.getByText('Fake Special Offer')).toBeVisible();
45 | })
46 | })
47 |
48 |
--------------------------------------------------------------------------------
/tests/web/orangeHrm.spec.ts:
--------------------------------------------------------------------------------
1 | //test 1 -> Login successful - Verify welcome message on Dashboard
2 | //test 2 - Message Invalid credentials
3 | //test 3 -> Error message password can not be empty
4 | //test 4 -> Error message username can not be empty
5 | //test 5 -> Verify Access to HR Administration Panel
6 | //Navigate to HR Administration panel (login as HR admin)
7 | //test 6 -> Verify Employee addition (login as HR admin)
8 | // Navigate to Employee Management section
9 | // Add a new Employee with required details
10 | // Save the employee record
11 | //test 7 -> Verify Employee deletion (login as HR admin)
12 | // Navigate to Employee Management section
13 | //select an existing employee and initiate the deletion process
14 | // confirm the deletion action
15 | //test 8 -> Verify Leave Application Submission (login as Employee)
16 | // Navigate to the leave section
17 | // Apply for leave by providing required details
18 | // submit the leave application
19 | //test 9 -> Verify Leave Approval(login as HR admin)
20 | // Navigate to leave Approval section
21 | // Review pending leave application
22 | // Approve or reject the leave
23 | //test 10 -> Verify Time Tracking Record Creation
24 | //Navigate to Time Tracking module
25 | // Click on Add Time Record button
26 | // Fill the details & Save the record
27 | //test 11 -> Verify Time Tracking Record Deletion
28 | //Navigate to Time Tracking module
29 | // find an existing time tracking record
30 | // Click on delete button
31 | //confirm the deletion
32 | //test 12 -> Validate attendance marking for an employee
33 | //Navigate to attendance module
34 | //Select an employee from the employee list
35 | //Mark attendance for that employee for the current date
36 | //test 13 -> Verify Job posting Creation (login as HR admin)
37 | //Navigate to Recruitment section
38 | //Create a new job posting
39 | //test 14 -> Verify Application Submission(login as job applicant)
40 | //Navigate to career section
41 | //Apply for specific job posting by submitting the application
42 |
43 | //test 15 -> Verify New Employee Onboarding(login as HR admin)
44 | //Navigate to Onboarding section
45 | //Initiate the onboarding process for a newly added employee
46 | //test 16 -> Verify Training Program Creation (login as HR admin)
47 | //Navigate to training section
48 | //create a new training program with all details
49 |
--------------------------------------------------------------------------------
/src/utils/config/azure/playwright.service.config.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * This file enables Playwright client to connect to remote browsers.
3 | * It should be placed in the same directory as playwright.config.ts.
4 | */
5 |
6 | import { defineConfig } from "@playwright/test";
7 | import config from "./playwright.config";
8 | import dotenv from "dotenv";
9 |
10 | // Define environment on the dev box in .env file:
11 | // .env:
12 | // PLAYWRIGHT_SERVICE_ACCESS_TOKEN=XXX
13 | // PLAYWRIGHT_SERVICE_URL=XXX
14 |
15 | // Define environment in your GitHub workflow spec.
16 | // env:
17 | // PLAYWRIGHT_SERVICE_ACCESS_TOKEN: ${{ secrets.PLAYWRIGHT_SERVICE_ACCESS_TOKEN }}
18 | // PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }}
19 | // PLAYWRIGHT_SERVICE_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}-${{ github.sha }}
20 |
21 | dotenv.config();
22 |
23 | // Name the test run if it's not named yet.
24 | process.env.PLAYWRIGHT_SERVICE_RUN_ID = process.env.PLAYWRIGHT_SERVICE_RUN_ID || new Date().toISOString();
25 |
26 | // Can be 'linux' or 'windows'.
27 | const os = process.env.PLAYWRIGHT_SERVICE_OS || "linux";
28 |
29 | export default defineConfig(config, {
30 | // Define more generous timeout for the service operation if necessary.
31 | // timeout: 60000,
32 | // expect: {
33 | // timeout: 10000,
34 | // },
35 | workers: 20,
36 |
37 | // Enable screenshot testing and configure directory with expectations.
38 | // https://learn.microsoft.com/azure/playwright-testing/how-to-configure-visual-comparisons
39 | ignoreSnapshots: false,
40 | snapshotPathTemplate: `{testDir}/__screenshots__/{testFilePath}/${os}/{arg}{ext}`,
41 |
42 | use: {
43 | // Specify the service endpoint.
44 | connectOptions: {
45 | wsEndpoint: `${process.env.PLAYWRIGHT_SERVICE_URL}?cap=${JSON.stringify({
46 | // Can be 'linux' or 'windows'.
47 | os,
48 | runId: process.env.PLAYWRIGHT_SERVICE_RUN_ID,
49 | })}`,
50 | timeout: 30000,
51 | headers: {
52 | "x-mpt-access-key": process.env.PLAYWRIGHT_SERVICE_ACCESS_TOKEN!,
53 | },
54 | // Allow service to access the localhost.
55 | exposeNetwork: "",
56 | },
57 | },
58 | // Tenmp workaround for config merge bug in OSS https://github.com/microsoft/playwright/pull/28224
59 | projects: config.projects ? config.projects : [{}],
60 | });
61 |
--------------------------------------------------------------------------------
/config.ts:
--------------------------------------------------------------------------------
1 | import * as dotenv from "dotenv"
2 | import Joi from "joi"
3 | import {z} from "zod"
4 |
5 | //Load enviornment variables from .env
6 | dotenv.config()
7 |
8 |
9 | //Define the schema for validation
10 | // const envSchema = Joi.object({
11 | // //Enironment
12 | // BASE_URL : Joi.string().uri().required(),
13 | // USER_NAME: Joi.string().required(),
14 | // PASSWORD: Joi.string().required(),
15 |
16 | // //Playwright settings
17 | // WORKERS : Joi.number().integer().min(1).required(),
18 | // RETRY_FAILED : Joi.number().integer().min(0).required(),
19 | // MAX_TEST_RUNTIME : Joi.number().integer().min(1000).required(),
20 | // HEADLESS_BROWSER : Joi.boolean().required()
21 | // }).unknown(true) //Allow other unknow enviornment variables
22 |
23 | // Define the schema for validation using Zod
24 | // const envSchema = z.object({
25 | // // Environment
26 | // BASE_URL: z.string().url().nonempty(),
27 | // USER_NAME: z.string().nonempty(),
28 | // PASSWORD: z.string().nonempty(),
29 |
30 | // // Playwright settings
31 | // WORKERS: z.number().int().min(1),
32 | // RETRY_FAILED: z.number().int().min(0),
33 | // MAX_TEST_RUNTIME: z.number().int().min(1000),
34 | // HEADLESS_BROWSER: z.boolean()
35 | // }).passthrough(); //Allow unknonn environment variables
36 |
37 | //.strict() //Allow only known environment variables
38 |
39 | //To Do - Convert above validation schema in zod
40 |
41 |
42 | //Validate environment variables
43 | // const envVars = envSchema.validate(process.env,{allowUnknown:true,abortEarly:false})
44 | // const envVars = envSchema.safeParse(process.env)
45 |
46 | // if (envVars.error){
47 | // throw new Error(`Enviornment validation error : ${envVars.error.message}`)
48 | // }
49 |
50 | //Config class with validate enviornment variables
51 | export class Config{
52 |
53 | static readonly BASE_URL :string = process.env.BASE_URL || '';
54 | static readonly USER_NAME :string = process.env.USER_NAME || '';
55 | static readonly PASSWORD :string = process.env.PASSWORD || '';
56 |
57 | static readonly WORKERS :number = parseInt(process.env.WORKERS || '1');
58 | static readonly RETRY_FAILED :number = parseInt(process.env.RETRY_FAILED || '0');
59 | static readonly MAX_TEST_RUNTIME :number = parseInt(process.env.MAX_TEST_RUNTIME || '1000');
60 | static readonly HEADLESS_BROWSER :boolean = process.env.HEADLESS_BROWSER === 'true';
61 | }
62 |
63 | export const MCP_CONFIG = {
64 | mcpServers: {
65 | playwright: {
66 | url: "http://localhost:8931/sse"
67 | }
68 | }
69 | };
--------------------------------------------------------------------------------
/tests/web/example/alert.spec.ts:
--------------------------------------------------------------------------------
1 | import { logInfo } from "@src/utils/report/Logger";
2 | import { expect, test } from "@tests/fixtures/test-options";
3 |
4 | const baseURL = 'https://test-edzotech.web.app/index.html';
5 |
6 |
7 | test("Test 0 : Subscribe on dialog and call dismiss by clicking on Ok button", async ({
8 | web,page
9 | }) => {
10 |
11 | page.on('dialog', dialog => {
12 | logInfo('Script Alert ' + dialog.message());
13 | dialog.accept();
14 | })
15 |
16 | await web.navigateToUrl(baseURL)
17 |
18 | const element = await web.findElement('xpath', "//*[@id='show-alert-btn']");
19 |
20 | expect(element).not.toBeNull();
21 |
22 | await element!.click();
23 | });
24 |
25 | test("Test 1 : Subscribe on dialog and call dismiss by clicking on Ok button", async ({
26 | web
27 | }) => {
28 |
29 | await web.navigateToUrl(baseURL)
30 |
31 | const element = await web.findElement('xpath', "//*[@id='show-alert-btn']");
32 |
33 | expect(element).not.toBeNull();
34 |
35 | await Promise.all([
36 | web.waitAndHandleDialog('accept'), // or 'dismiss', with optional prompt text
37 | await element!.click(), // this triggers the dialog
38 | ]);
39 | });
40 |
41 | test("Test 2 : Subscribe on dialog and call accept by clicking on Ok button", async ({
42 | web, page
43 | }) => {
44 |
45 | page.on('dialog', dialog => {
46 | logInfo('Script Alert ' + dialog.message());
47 | dialog.accept();
48 | })
49 |
50 | await web.navigateToUrl(baseURL)
51 |
52 | const element = await web.findElement('xpath', "//*[@id='show-confirm-btn']");
53 |
54 | await element!.click();
55 |
56 | });
57 |
58 | test("Test 3 : Subscribe on dialog and dismiss by clicking on Cancel button", async ({
59 | web, page
60 | }) => {
61 |
62 | page.on('dialog', dialog => {
63 | logInfo('Script Alert ' + dialog.message());
64 | dialog.dismiss();
65 | })
66 |
67 | await web.navigateToUrl(baseURL)
68 |
69 | const element = await web.findElement('xpath', "//*[@id='show-confirm-btn']");
70 |
71 | await element!.click();
72 |
73 | });
74 |
75 | test("Test 4 : Subscribe on Prompt, enter text in input box and call accept by clicking on Ok button", async ({
76 | web, page
77 | }) => {
78 |
79 | let textVal = "";
80 | page.on('dialog', dialog => {
81 | logInfo('Script Alert ' + dialog.message());
82 | dialog.accept(textVal);
83 | })
84 |
85 | await web.navigateToUrl(baseURL)
86 |
87 | const element = await web.findElement('xpath', "//*[@id='show-prompt-btn']");
88 |
89 | await element?.click();
90 |
91 |
92 | textVal = "Enter value in Prompt"
93 | });
94 |
--------------------------------------------------------------------------------
/tests/api/example/api1.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from '@playwright/test';
2 | import { api } from './utils/apiHelper';
3 | import { handleError } from './utils/errorHandler';
4 |
5 | interface Post {
6 | userId: number;
7 | id: number;
8 | title: string;
9 | body: string;
10 | }
11 |
12 | interface User {
13 | id: number;
14 | name: string;
15 | username: string;
16 | email: string;
17 | }
18 |
19 | test.describe('JSONPlaceholder API Tests', () => {
20 | test('should get all posts', async () => {
21 | try {
22 | const response = await api.get('/posts');
23 | expect(response.status).toBe(200);
24 | expect(Array.isArray(response.data)).toBeTruthy();
25 | expect(response.data.length).toBeGreaterThan(0);
26 |
27 | const firstPost = response.data[0];
28 | expect(firstPost).toHaveProperty('id');
29 | expect(firstPost).toHaveProperty('title');
30 | expect(firstPost).toHaveProperty('body');
31 | expect(firstPost).toHaveProperty('userId');
32 | } catch (error) {
33 | handleError(error);
34 | }
35 | });
36 |
37 | test('should create a new post', async () => {
38 | try {
39 | const newPost = {
40 | title: 'Test Post',
41 | body: 'This is a test post',
42 | userId: 1
43 | };
44 |
45 | const response = await api.post('/posts', newPost);
46 | expect(response.status).toBe(201);
47 | expect(response.data).toHaveProperty('id');
48 | expect(response.data.title).toBe(newPost.title);
49 | expect(response.data.body).toBe(newPost.body);
50 | } catch (error) {
51 | handleError(error);
52 | }
53 | });
54 |
55 | test('should update a post', async () => {
56 | try {
57 | const updatedPost = {
58 | title: 'Updated Post',
59 | body: 'This post has been updated',
60 | userId: 1
61 | };
62 |
63 | const response = await api.put('/posts/1', updatedPost);
64 | expect(response.status).toBe(200);
65 | expect(response.data.title).toBe(updatedPost.title);
66 | expect(response.data.body).toBe(updatedPost.body);
67 | } catch (error) {
68 | handleError(error);
69 | }
70 | });
71 |
72 | test('should delete a post', async () => {
73 | try {
74 | const response = await api.delete<{}>('/posts/1');
75 | expect(response.status).toBe(200);
76 | } catch (error) {
77 | handleError(error);
78 | }
79 | });
80 |
81 | test('should handle 404 error gracefully', async () => {
82 | try {
83 | await api.get('/nonexistent-endpoint');
84 | } catch (error) {
85 | expect(error.statusCode).toBe(404);
86 | }
87 | });
88 | });
--------------------------------------------------------------------------------
/tests/web/example/launchMultipleBrowserInSingle.spec.js:
--------------------------------------------------------------------------------
1 | import { chromium, firefox, test } from '@playwright/test'
2 |
3 | test('Launch Multiple Browser In Single Spec', async () => {
4 | const browser1 = await chromium.launch({
5 | channel: 'chrome' //Use system installed chrome
6 | , headless: false //Show browser
7 | });
8 |
9 | const browser2 = await firefox.launch({
10 | channel: 'firefox' //Use system installed chrome
11 | , headless: false //Show browser
12 | });
13 |
14 | const browser3 = await chromium.launch({
15 | channel: 'msedge' //Use system installed chrome
16 | , headless: false //Show browser
17 | });
18 |
19 |
20 | const page1 = browser1.newPage();
21 | const page2 = browser2.newPage();
22 | const page3 = browser3.newPage();
23 |
24 | await page1.goto('https://www.google.com');
25 | await page2.goto('https://www.google.com');
26 | await page3.goto('https://www.google.com');
27 |
28 | console.log(`Page 1 Title: await ${await page1.title()}`);
29 | console.log(`Page 2 Title: await ${await page2.title()}`);
30 | console.log(`Page 3 Title: await ${await page3.title()}`);
31 |
32 | await browser1.close();
33 | await browser2.close();
34 | await browser3.close();
35 | });
36 |
37 |
38 | test('Run Different Navigate Method', async () => {
39 | const browser = await chromium.launch({
40 | channel: 'chrome' //Use system installed chrome
41 | , headless: false //Show browser
42 | });
43 |
44 | const page = await browser.newPage();
45 |
46 |
47 | // 1. Navigate to a URL using page.goto()
48 | console.log('Navigating to first page: example.com');
49 | await page.goto('https://example.com');
50 | await page.waitForTimeout(2000); // Wait for 2 seconds to see the page
51 |
52 | // 2. Navigate to a second URL
53 | console.log('Navigating to second page: playwright.dev');
54 | await page.goto('https://playwright.dev');
55 | await page.waitForTimeout(2000);
56 |
57 | // 3. Go back in browser history using page.goBack()
58 | console.log('Going back to example.com');
59 | await page.goBack();
60 | await page.waitForTimeout(2000);
61 |
62 | // 4. Go forward in browser history using page.goForward()
63 | console.log('Going forward to playwright.dev');
64 | await page.goForward();
65 | await page.waitForTimeout(2000);
66 |
67 | // 5. Reload the current page using page.reload()
68 | console.log('Reloading the current page');
69 | await page.reload();
70 | await page.waitForTimeout(2000);
71 |
72 | // Close the browser
73 | await browser.close();
74 | console.log('Browser closed.');
75 | });
--------------------------------------------------------------------------------
/src/utils/config/azure/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, devices } from "@playwright/test";
2 | import * as dotenv from "dotenv";
3 |
4 | switch (process.env.NODE_ENV) {
5 | case "local":
6 | dotenv.config({ path: "./environments/local.env" });
7 | break;
8 | case "dev":
9 | dotenv.config({ path: "./environments/local.env" });
10 | break;
11 | case "qa":
12 | dotenv.config({ path: "./environments/qa.env" });
13 | break;
14 | case "prod":
15 | dotenv.config({ path: "./environments/prod.env" });
16 |
17 | break;
18 |
19 | default:
20 | break;
21 | }
22 | export default defineConfig({
23 | testDir: "./src",
24 | /* Run tests in files in parallel */
25 | fullyParallel: true,
26 | /* Fail the build on CI if you accidentally left test.only in the source code. */
27 | forbidOnly: !!process.env.CI,
28 | /* Retry on CI only */
29 | retries: process.env.CI ? 2 : 0,
30 | /* Opt out of parallel tests on CI. */
31 | workers: process.env.CI ? 1 : undefined,
32 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
33 | reporter: "html",
34 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
35 | use: {
36 | /* Base URL to use in actions like `await page.goto('/')`. */
37 | // baseURL: 'http://127.0.0.1:3000',
38 |
39 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
40 | trace: "on-first-retry",
41 | },
42 |
43 | /* Configure projects for major browsers */
44 | projects: [
45 | // {
46 | // name: "chromium",
47 | // use: { ...devices["Desktop Chrome"] },
48 | // },
49 |
50 | // {
51 | // name: "firefox",
52 | // use: { ...devices["Desktop Firefox"] },
53 | // },
54 |
55 | // {
56 | // name: "webkit",
57 | // use: { ...devices["Desktop Safari"] },
58 | // },
59 |
60 | /* Test against mobile viewports. */
61 | // {
62 | // name: 'Mobile Chrome',
63 | // use: { ...devices['Pixel 5'] },
64 | // },
65 | // {
66 | // name: 'Mobile Safari',
67 | // use: { ...devices['iPhone 12'] },
68 | // },
69 |
70 | /* Test against branded browsers. */
71 | {
72 | name: "Microsoft Edge",
73 | use: { ...devices["Desktop Edge"], channel: "msedge" },
74 | },
75 | // {
76 | // name: 'Google Chrome',
77 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' },
78 | // },
79 | ],
80 |
81 | /* Run your local dev server before starting the tests */
82 | // webServer: {
83 | // command: 'npm run start',
84 | // url: 'http://127.0.0.1:3000',
85 | // reuseExistingServer: !process.env.CI,
86 | // },
87 | });
88 |
--------------------------------------------------------------------------------
/tests/web/IntercpetFailingAPI.js:
--------------------------------------------------------------------------------
1 | //Create a setup.ts File (Global Hook)
2 | //Create a new file named setup.ts inside your tests/ folder (or any preferred directory). This file will extend Playwright’s test configuration.
3 |
4 | import { test as base } from '@playwright/test';
5 |
6 | // Extend Playwright's test object to include API monitoring in all tests
7 | export const test = base.extend<{ page: any }>({
8 | page: async ({ browser }, use) => {
9 | const context = await browser.newContext();
10 | const page = await context.newPage();
11 |
12 | // Listen to all network requests
13 | page.on('request', request => {
14 | console.log(`➡️ Request: ${request.method()} ${request.url()}`);
15 | });
16 |
17 | // Intercept API responses and check for failures
18 | page.on('response', async response => {
19 | if (!response.ok()) {
20 | console.error(`❌ Failed API Call: ${response.url()}`);
21 | console.error(`Status Code: ${response.status()}`);
22 |
23 | const request = response.request();
24 | const requestData = request.postData();
25 | const responseBody = await response.text();
26 |
27 | console.error(`📌 Request Data: ${requestData || 'No Body'}`);
28 | console.error(`📌 Response Body: ${responseBody || 'No Response Body'}`);
29 | }
30 | });
31 |
32 | await use(page); // Makes `page` available for all tests
33 | await page.close();
34 | }
35 | });
36 |
37 | export { expect } from '@playwright/test';
38 |
39 |
40 | // Use the Extended Test in Your Spec Files
41 | // Modify all your spec files (*.spec.ts) to import the extended test object from setup.ts instead of Playwright's default test.
42 |
43 | // Example: tests/example.spec.ts
44 |
45 | import { expect } from '../setup'; // Import from setup.ts
46 |
47 | test.describe('UI Test Suite with API Monitoring', () => {
48 | test('Test Case 1 - Navigate to Homepage', async ({ page }) => {
49 | await page.goto('https://example.com');
50 | await page.click('button#submit');
51 | await page.waitForTimeout(2000);
52 | });
53 |
54 | test('Test Case 2 - Login Flow', async ({ page }) => {
55 | await page.goto('https://example.com/login');
56 | await page.fill('input[name="username"]', 'testUser');
57 | await page.fill('input[name="password"]', 'password123');
58 | await page.click('button#login');
59 | await page.waitForTimeout(2000);
60 | });
61 | });
62 |
63 |
64 | //Prompt
65 | //Write playwright script using typescript which should log all API requests failures across test files when UI test is running.
--------------------------------------------------------------------------------
/tests/web/example/ConsoleAndPageError.spec.ts:
--------------------------------------------------------------------------------
1 | import { test} from "@tests/fixtures/test-options";
2 | import { expect } from "@playwright/test";
3 |
4 | // @ts-check // Enables type checking in JavaScript files, good practice for Playwright tests.
5 | test
6 |
7 | // Array to store console log messages of type 'error'.
8 | const logs: {
9 | message: string;
10 | type: string;
11 | }[] = []
12 |
13 | // Array to store unhandled page errors (exceptions).
14 | const errorMsg: {
15 | name: string,
16 | message: string
17 | }[] = []
18 |
19 | // Group tests related to error and console log checks.
20 | test.describe('error and console log check', () => {
21 |
22 | // This block runs before each test in this describe block.
23 | test.beforeEach(async ({ page }) => {
24 |
25 | // --- Console Log Listener ---
26 | // Attaches an event listener for 'console' events on the page.
27 | page.on('console', (msg) => {
28 | // Filter and store only console messages of type 'error'.
29 | if (msg.type() == 'error') {
30 | logs.push({ message: msg.text(), type: msg.type() })
31 | }
32 | })
33 |
34 | // --- Page Error Listener ---
35 | // Attaches an event listener for unhandled 'pageerror' exceptions in the page's execution context.
36 | page.on('pageerror', (error) => {
37 | // Store the name and message of the unhandled error.
38 | errorMsg.push({ name: error.name, message: error.message })
39 | })
40 |
41 | })
42 |
43 |
44 | test('Check the exception and logs in console log', async ({ page }, testInfo) => {
45 |
46 | // Navigate to the target URL. This action will trigger the listeners
47 | // for any console errors or unhandled exceptions that occur during load.
48 | await page.goto('https://timesofindia.indiatimes.com/')
49 |
50 |
51 | // Format the collected console errors into a single string for attachment.
52 | const errorLogs = [...logs?.map(e => e.message)].join('\n')
53 |
54 | // If console errors were found, attach them to the test report for debugging.
55 | if (logs?.length > 0) {
56 | await testInfo.attach('console logs', {
57 | body: errorLogs.toString(),
58 | contentType: 'text/plain'
59 | })
60 | }
61 |
62 | // --- Assertions ---
63 |
64 | // Soft assertion: Checks if no console errors were logged.
65 | // The test continues if this fails, but it's marked as a failure.
66 | expect.soft(logs.length).toBe(0)
67 |
68 | // Hard assertion: Checks if no unhandled page errors/exceptions occurred.
69 | // The test fails immediately if this condition is not met.
70 | expect(errorMsg.length).toBe(0)
71 |
72 | })
73 |
74 | })
--------------------------------------------------------------------------------
/tests/web/example/locator.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "@playwright/test";
2 |
3 | /* playwright built in locators
4 | example code is run on website -> https://opensource-demo.orangehrmlive.com/web/index.php/auth/login
5 | In case you want to read more about role refer -> https://www.w3.org/TR/wai-aria-1.2/#role_definitions*/
6 |
7 | test("test locator getByRole() @locator", async ({ page: page }) => {
8 | /*supported role in playwright:
9 | "alert"|"alertdialog"|"application"|"article"|"banner"|"blockquote"|"button"|
10 | "caption"|"cell"|"checkbox"|"code"|"columnheader"|"combobox"|"complementary"|"contentinfo"|"definition"|
11 | "deletion"|"dialog"|"directory"|"document"|"emphasis"|"feed"|"figure"|"form"|"generic"|"grid"|"gridcell"|
12 | "group"|"heading"|"img"|"insertion"|"link"|"list"|"listbox"|"listitem"|"log"|"main"|"marquee"|"math"|"meter"|
13 | "menu"|"menubar"|"menuitem"|"menuitemcheckbox"|"menuitemradio"|"navigation"|"none"|"note"|"option"|"paragraph"|
14 | "presentation"|"progressbar"|"radio"|"radiogroup"|"region"|"row"|"rowgroup"|"rowheader"|"scrollbar"|"search"|"
15 | "searchbox"|"separator"|"slider"|"spinbutton"|"status"|"strong"|"subscript"|"superscript"|"switch"|"tab"|"table"|
16 | "tablist"|"tabpanel"|"term"|"textbox"|"time"|"timer"|"toolbar"|"tooltip"|"tree"|"treegrid"|"treeitem"
17 | */
18 |
19 | await page.goto(
20 | "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"
21 | );
22 | await page.getByRole("textbox", { name: "username" }).fill("Admin");
23 | await page.getByRole("textbox", { name: "password" }).fill("admin123");
24 | await page.getByRole("button", { name: "Login" }).click({
25 | button: "left",
26 | });
27 | });
28 |
29 | test("test locator getByPlaceholder() @locator", async ({ page }) => {
30 | await page.goto(
31 | "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"
32 | );
33 | await page.getByPlaceholder("Username").fill("Adminplaceholder");
34 | await page.getByPlaceholder("Password").fill("admin123");
35 |
36 | await page.getByPlaceholder(/Username/).fill("Adminregexp");
37 | await page.getByPlaceholder(/Password/).fill("admin123");
38 |
39 | await page.getByPlaceholder(/username/i).fill("Admin_reg_ex_ignorecase");
40 | await page.getByPlaceholder(/Password/i).fill("admin123");
41 |
42 | await page.getByRole("button", { name: "Login" }).click({
43 | button: "left",
44 | });
45 |
46 | await page.getByText("Invalid credentials").click();
47 | expect(await page.getByText("Invalid credentials").count()).toBe(1);
48 | });
49 |
50 | test("test locator getByText() @locator", async ({ page }) => {});
51 | test("test locator getByLabel() @locator", async ({ page }) => {});
52 |
53 | test("test locator getByAltText() @locator", async ({ page }) => {});
54 | test("test locator getByTitle() @locator", async ({ page }) => {});
55 | test("test locator getByTestId() @locator", async ({ page }) => {});
56 |
--------------------------------------------------------------------------------
/src/utils/reader/jsonReader.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import { logInfo } from '../report/Logger';
4 |
5 | export class JsonReader {
6 | private filePath: string;
7 | public jsonData: unknown;
8 |
9 | constructor(filePath: string) {
10 | // Resolve the path relative to the project root
11 | // Adjust the path resolution based on where the file is located
12 | this.filePath = path.resolve(__dirname, '..', '..', '..', 'tests', 'resources', 'uiMap', filePath);
13 | logInfo('Reading JSON from:', this.filePath);
14 | this.readJsonFile();
15 | }
16 |
17 | /**
18 | * Reads a JSON file from the given path and returns its content as an object.
19 | * @returns Parsed JSON object or null if an error occurs.
20 | */
21 | readJsonFile(): unknown {
22 | try {
23 | if (!fs.existsSync(this.filePath)) {
24 | console.error(`File not found: ${this.filePath}`);
25 | return false;
26 | }
27 |
28 | const fileContent = fs.readFileSync(this.filePath, 'utf-8');
29 | this.jsonData = JSON.parse(fileContent);
30 | logInfo(this.jsonData);
31 | } catch (error) {
32 | console.error("Error reading or parsing JSON file:", error);
33 | this.jsonData = null;
34 | return false;
35 | }
36 | }
37 |
38 | /**
39 | * Retrieves a value from a nested JSON object using a JSON path hierarchy.
40 | * @param jsonObj - The JSON object.
41 | * @param path - The dot-separated JSON path (e.g., "user.address.city").
42 | * @param key - The specific key to retrieve from the parent object (e.g., "city").
43 | * @returns The value at the specified key in the parent object or undefined if not found.
44 | */
45 | getJsonValue(path: string, key: string): string | undefined {
46 | if (!this.jsonData) {
47 | logInfo("No Json Data loaded")
48 | return undefined;
49 | }
50 |
51 | // First navigate to the parent object using the path
52 | const parentObject = path.split('.').reduce((acc, part) => {
53 | if (acc && acc[part] !== undefined) {
54 | return acc[part];
55 | }
56 | return undefined;
57 | }, this.jsonData);
58 |
59 | // If we found the parent object, return the value for the specific key
60 | if (parentObject && parentObject[key] !== undefined) {
61 | return parentObject[key];
62 | }
63 | logInfo(`Path or key not found: ${path}.${key}`);
64 |
65 | return undefined;
66 | }
67 | }
68 |
69 |
70 | // Example usage
71 |
72 |
73 | // function readData() {
74 | // const jsonReader = new JsonReader('./orangeHRM.json'); // Update with your actual JSON file path
75 | // // const jsonData = jsonReader.readJsonFile();
76 | // if (jsonReader.jsonData) {
77 | // const value = jsonReader.getJsonValue("App.Login.OrangeHRMLogo", 'locatorValue'); // Example: Reading "city" from "user.address.city"
78 | // logInfo("Extracted Value:", value);
79 | // }
80 | // }
81 |
82 | // readData();
--------------------------------------------------------------------------------
/tests/web/example/inputFieldOperation.spec.ts:
--------------------------------------------------------------------------------
1 | import { test, expect } from "../../fixtures/customFixtures"
2 | import type { TestInfo } from "../../fixtures/customFixtures"
3 | import { logInfo } from '../../../src/utils/report/Logger';
4 |
5 | test("Perform operation on Input field using fill()", async ({ web }, testInfo: TestInfo) => {
6 | //Navigate to URL
7 | await web.navigateToUrl("https://test-edzotech.web.app/index.html");
8 |
9 | //Find locator
10 | const nameInputField = await web.webPage.locator("//input[@id='name']");
11 |
12 | //Check if enabled
13 | const isInputFieldEnabled = await nameInputField.isEnabled();
14 | logInfo("Is input field enabled?", isInputFieldEnabled); //returns true
15 |
16 | //Check if visible
17 | const isInputFieldVisible = await nameInputField.isVisible();
18 | logInfo("Is input field visible?", isInputFieldVisible); //returns true
19 |
20 | //Fill data
21 | await nameInputField.fill("ABCD123");
22 |
23 | //Read value using textContent()
24 | const textOne = await nameInputField.textContent();
25 |
26 | //Read value using innerText()
27 | const textTwo = await nameInputField.innerText();
28 |
29 | //Read value using innerHTML()
30 | const textThree = await nameInputField.innerHTML();
31 |
32 | //Read value using inputValue()
33 | let textFour = await nameInputField.inputValue();
34 |
35 | //Log value using textContent()
36 | logInfo("Read value using textContent()", textOne); //returns blank
37 |
38 | //Log value using innerText()
39 | logInfo("Read value using innerText()", textTwo); //returns blank
40 |
41 | //Log value using innerHTML()
42 | logInfo("Read value using innerHTML()", textThree); //returns blank
43 |
44 | //Log value using inputValue()
45 | logInfo("Read value using inputValue()", textFour); //returns ABCD123
46 |
47 | // Assertion to verify the value was set correctly
48 | await expect(nameInputField).toHaveValue('ABCD123');
49 |
50 | //Clear input field
51 | await nameInputField.clear();
52 |
53 | //Read value using inputValue() after clear()
54 | textFour = await nameInputField.inputValue();
55 |
56 | //Log value using inputValue() after clear()
57 | logInfo("Read value using inputValue() after clear()", textFour); //returns ''
58 | });
59 |
60 |
61 | test("Perform operation on Input field using pressSequentially()", async ({ web }, testInfo: TestInfo) => {
62 | //Navigate to URL
63 | await web.navigateToUrl("https://test-edzotech.web.app/index.html");
64 |
65 | //Find locator
66 | const nameInputField = await web.webPage.locator("//input[@id='name']");
67 |
68 | //Enter text on interval of 1 second
69 | await nameInputField.pressSequentially("ABCD123",{delay:1000});
70 |
71 | //Assertion to verify the value was set correctly
72 | expect(nameInputField).toHaveValue('ABCD123');
73 | })
74 |
75 |
76 | test.only('Perofrm operation on Input field using press',async({web},testInfo:TestInfo)=>{
77 | //Navigate to URL
78 | await web.navigateToUrl("https://test-edzotech.web.app/index.html");
79 |
80 | //Find locator
81 | const nameInputField = await web.webPage.locator("//input[@id='name']");
82 |
83 | //Press the Enter key
84 | await nameInputField.press("Enter");
85 | })
--------------------------------------------------------------------------------
/tests/web/example/accessibilitytest.spec.ts:
--------------------------------------------------------------------------------
1 | import AxeBuilder from "@axe-core/playwright";
2 | import fs from 'fs'
3 | import { createHtmlReport } from 'axe-html-reporter'
4 |
5 | import { test,expect } from "@tests/fixtures/test-options";
6 |
7 | //the following WCAG are the default from lighthouse
8 | const lightHouseTags:string[] =[ 'wcag2a','wcag2aa']
9 |
10 | //define rules for each page, you can disable specific rule by using option in AxeBuilder
11 | const rules:string[] =["accesskeys","aria-allowed-attr","aria-allowed-role","aria-command-name",
12 | "aria-conditional-attr","aria-deprecated-role","aria-dialog-name","aria-hidden-body",
13 | "aria-hidden-focus","aria-input-field-name","aria-meter-name","aria-progressbar-name",
14 | "aria-prohibited-attr","aria-required-attr","aria-required-children","aria-required-parent",
15 | "aria-roles","aria-text","aria-toggle-field-name","aria-tooltip-name","aria-treeitem-name",
16 | "aria-valid-attr-value","aria-valid-attr","button-name","bypass","color-contrast",
17 | "definition-list","dlitem","document-title","duplicate-id-aria","form-field-multiple-labels",
18 | "frame-title","heading-order","html-has-lang","html-lang-valid","html-xml-lang-mismatch",
19 | "image-alt","image-redundant-alt","input-button-name","input-image-alt","label",
20 | "link-in-text-block","link-name","list","listitem","meta-refresh","meta-viewport",
21 | "object-alt","select-name","skip-link","tabindex","table-duplicate-name","target-size",
22 | "td-headers-attr","th-has-data-cells","valid-lang","video-caption",
23 | //"focusable-controls",
24 | "interactive-element-affordance","logical-tab-order","visual-order-follows-dom",
25 | "focus-traps","managed-focus","use-landmarks","offscreen-content-hidden",
26 | "custom-controls-labels","custom-controls-roles","empty-heading","identical-links-same-purpose",
27 | "landmark-one-main","label-content-name-mismatch","table-fake-caption",
28 | "td-has-header"
29 | ]
30 |
31 | test('product listing page', async ({ web, page }, testInfo) => {
32 | await web.navigateToUrl('https://www.saucedemo.com/')
33 | await web.webPage.getByRole('textbox', { name: 'Username'}).fill('standard_user')
34 | await web.webPage.getByRole('textbox', { name: 'Password'}).fill('secret_sauce')
35 | await web.webPage.getByRole('button', { name: 'Login'}).click()
36 | await expect(web.webPage.getByText('Swag Labs')).toBeVisible()
37 |
38 | await test.step('Check the accessablity test', async () => {
39 | const report = await new AxeBuilder({page})
40 | .withTags(lightHouseTags)
41 | .withRules(rules)
42 | .analyze()
43 |
44 | //attach the violation output to each test results
45 | testInfo.attach(testInfo.title,{
46 | body: JSON.stringify(report.violations, null, 2),
47 | contentType: 'application/json'
48 | })
49 |
50 | //create the axe-core html report
51 | const htmlReport = createHtmlReport({
52 | results: report,
53 | options:{
54 | projectKey: testInfo.title,
55 | doNotCreateReportFile: true
56 | }
57 | });
58 |
59 | //write the html report for each page
60 | write_accesability_output(`${testInfo.title.replaceAll(' ','_')}`, htmlReport)
61 |
62 | expect(report.violations).toHaveLength(0)
63 | })
64 |
65 | })
66 |
67 | function write_accesability_output(path:string, htmlReport: string){
68 |
69 | if (!fs.existsSync('test-results/reports')) {
70 | fs.mkdirSync('test-results/reports', {
71 | recursive: true,
72 | });
73 | }
74 | fs.writeFileSync(`test-results/reports/${path}.html`, htmlReport);
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/tests/web/example/trackApiResponse.spec.js:
--------------------------------------------------------------------------------
1 | // example.spec.ts
2 | // We import our custom 'test' object from the fixtures file.
3 | // This is the key step to use the extended 'page' fixture.
4 | import { test } from './fixtures';
5 | import { expect } from '@playwright/test';
6 | import { chromium, test } from "@playwright/test";
7 |
8 | test('test', async () => {
9 | const browser = await chromium.launch({ headless: false }); // Adjust as needed
10 | const page = await browser.newPage();
11 |
12 | // Array to store failed API details
13 | const failedApis = [];
14 |
15 | // Add the response listener
16 | page.on('response', (response) => {
17 | const request = response.request();
18 | // Filter for API calls (XHR or fetch requests)
19 | if (['xhr', 'fetch'].includes(request.resourceType()) && response.status() !== 200) {
20 | failedApis.push({
21 | url: request.url(),
22 | method: request.method(),
23 | status: response.status(),
24 | // Optional: Add more details if needed, e.g., response.body() for content (but it's async)
25 | });
26 | }
27 | });
28 |
29 | // Your existing UI navigation and operations go here
30 | await page.goto('https://example.com'); // Example navigation
31 | await page.click('button#some-button'); // Example interaction
32 | // ... more actions ...
33 |
34 | // At the end of your script, handle the results (e.g., log or integrate into a report)
35 | if (failedApis.length > 0) {
36 | console.log('Failed APIs detected:', failedApis);
37 | // Optionally: Throw an error, write to a file, or add to a test result object
38 | // e.g., result.failedApis = failedApis;
39 | } else {
40 | console.log('All APIs succeeded (status 200).');
41 | }
42 |
43 | await browser.close();
44 | });
45 |
46 |
47 |
48 | // Use 'test' just like you would with the default Playwright test runner.
49 | // The custom 'page' fixture is automatically provided.
50 | test('should successfully load a page and report no API failures', async ({ page }) => {
51 | console.log('--- Test 1: Successful API calls ---');
52 | // Navigate to a test page. Any API calls will be monitored by the fixture.
53 | await page.goto('https://playwright.dev');
54 |
55 | // Perform some actions that might trigger API calls.
56 | // The fixture will silently monitor for non-200 responses in the background.
57 | await expect(page.locator('text=Docs')).toBeVisible();
58 |
59 | // At the end of the test, the logic in 'fixtures.ts' will run,
60 | // and since all API calls (on this page) were successful,
61 | // it will log 'All API calls for this test succeeded.'.
62 | });
63 |
64 | // A second test to demonstrate how the fixture captures a failed API call.
65 | test('should capture and report a failed API response', async ({ page }) => {
66 | console.log('--- Test 2: Failed API calls ---');
67 |
68 | // Intercept and mock a specific API route to return a 404 status.
69 | // This simulates a failed backend call.
70 | await page.route('**/api/users/123', (route) => {
71 | route.fulfill({
72 | status: 404,
73 | contentType: 'application/json',
74 | body: JSON.stringify({ error: 'User not found' }),
75 | });
76 | });
77 |
78 | // Navigate to a page or perform an action that triggers the mocked API call.
79 | await page.goto('https://example.com');
80 |
81 | // Trigger a fetch request that will be intercepted and fail.
82 | await page.evaluate(async () => {
83 | await fetch('/api/users/123');
84 | });
85 |
86 | // There is no explicit assertion here because the custom fixture
87 | // handles the reporting and potential assertion for us at the end of the test.
88 | // When this test finishes, the logic in 'fixtures.ts' will log the 404 error.
89 | });
90 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, devices } from "@playwright/test";
2 | import * as dotenv from "dotenv";
3 | import { Config } from "./config";
4 | import { on } from "events";
5 | //import OrtoniReport from "ortoni-report";
6 |
7 | switch (process.env.NODE_ENV) {
8 | case "local":
9 | dotenv.config({ path: "./environments/local.env" });
10 | break;
11 | case "dev":
12 | dotenv.config({ path: "./environments/local.env" });
13 | break;
14 | case "qa":
15 | dotenv.config({ path: "./environments/qa.env" });
16 | break;
17 | case "prod":
18 | dotenv.config({ path: "./environments/prod.env" });
19 | break;
20 | default:
21 | break;
22 | }
23 | export default defineConfig({
24 | testDir: "./tests",
25 | testMatch: ["**/*.ts", "**/*.js"],
26 | timeout: 180 * 1000,
27 | expect: { timeout: 180 * 1000 },
28 | /* Run tests in files in parallel */
29 | fullyParallel: true,
30 | /* Fail the build on CI if you accidentally left test.only in the source code. */
31 | forbidOnly: !!process.env.CI,
32 | /* Retry on CI only */
33 | retries: process.env.CI ? 1 : Config.RETRY_FAILED,
34 | /* Opt out of parallel tests on CI. */
35 | workers: process.env.CI ? 1 : Config.WORKERS,
36 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
37 | reporter: [
38 | [`./src/utils/report/CustomReporterConfig.ts`],
39 | ["html", { open: "always", host: "127.0.0.1", port: 5723 }],
40 | //["OrtoniReport", { outputFolder: "reports" }],
41 | ],
42 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
43 | use: {
44 | /* Base URL to use in actions like `await page.goto('/')`. */
45 | // baseURL: 'http://127.0.0.1:3000',
46 |
47 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
48 | trace: "retain-on-failure",
49 | headless: Config.HEADLESS_BROWSER,
50 | baseURL: Config.BASE_URL,
51 | screenshot: "on",
52 | video: "on",
53 | storageState: "auth.json",
54 | },
55 |
56 | /* Configure projects for major browsers */
57 | projects: [
58 | // {
59 | // name: "chromium",
60 | // use: { ...devices["Desktop Chrome"] },
61 | // metadata:{
62 | // video:"on"
63 | // }
64 | // },
65 |
66 | // {
67 | // name: "firefox",
68 | // use: { ...devices["Desktop Firefox"] },
69 | // },
70 |
71 | // {
72 | // name: "webkit",
73 | // use: { ...devices["Desktop Safari"] },
74 | // },
75 |
76 | /* Test against mobile viewports. */
77 | // {
78 | // name: 'Mobile Chrome',
79 | // use: { ...devices['Pixel 5'] },
80 | // },
81 | // {
82 | // name: 'Mobile Safari',
83 | // use: { ...devices['iPhone 12'] },
84 | // },
85 |
86 | /* Test against branded browsers. */
87 | {
88 | name: "Microsoft Edge",
89 | use: {
90 | ...devices["Desktop Edge"],
91 | channel: "msedge",
92 | //viewport: { width: 1920, height: 1080 },
93 | viewport: devices["Desktop Edge"].viewport, //set viewport dynamically
94 | // baseURL:'https://restful-booker.herokuapp.com',
95 | ignoreHTTPSErrors: true,
96 | acceptDownloads: true,
97 | },
98 | },
99 | // {viewport: devices["Desktop Edge"].viewport,
100 | // name: "Chrome",
101 | // use: {
102 | // ...devices["Desktop Chrome"],
103 | // channel: "chrome",
104 | // screenshot: "on",
105 | // trace: "on",
106 | // video: "on",
107 | // headless: false,
108 | // viewport: { width: 1920, height: 1080 },
109 | // },
110 | // },
111 | ],
112 |
113 |
114 | /* Run your local dev server before starting the tests */
115 | // webServer: {
116 | // command: 'npm run mock-data',
117 | // url: 'http://localhost:3001',
118 | // reuseExistingServer: !process.env.CI,
119 | // },
120 | });
121 |
--------------------------------------------------------------------------------
/tests/api/example/api.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, APIRequestContext } from "@playwright/test";
2 | import { ApiHelper } from "@src/helper/api/apiHelper";
3 | import { pwApi } from "pw-api-plugin";
4 |
5 |
6 | let token: string;
7 | let bookingId: string;
8 |
9 | test.beforeAll(async ({ request }) => {
10 | //1. Hit /Auth Api and provide username/password as body
11 | //2. fetch token value from JSON response
12 | //3. save in token variable
13 |
14 | const apiHelper = await new ApiHelper(page,pwApi);
15 | const responseMsg = await apiHelper.invokePostApi("/auth", {
16 | username: "admin",
17 | password: "password123",
18 | });
19 |
20 | expect(responseMsg.token);
21 |
22 | token = responseMsg.token;
23 | console.log(token);
24 | });
25 |
26 | test("Get booking list and verify response -- No Authentication Required ", async ({
27 | request,
28 | }) => {
29 | /* Test Flow
30 | 1. Hit API endpoint
31 | 2. Verify API status code
32 | 3. Verify JSON Response
33 | 4. Verify JSON Schema
34 | */
35 | test.info().annotations.push({
36 | type: "purpose",
37 | description:
38 | "This will make GET call to https://restful-booker.herokuapp.com/booking with no authentication",
39 | });
40 | const apiHelper = await new ApiHelper(request); //
41 | const responseMsg = await apiHelper.invokeGetApi("/booking");
42 | console.log(JSON.stringify(responseMsg));
43 | for (let index = 0; index < responseMsg.length; index++) {
44 | test.info().annotations.push({
45 | type: "value",
46 | description: `BookingId : ${responseMsg[index].bookingid}`,
47 | });
48 | expect(responseMsg[index].bookingid).not.toBeNull();
49 | }
50 | });
51 |
52 | test("Get Booking Details using BookingID --> 1914", async ({ request }) => {
53 | /* Test Flow
54 | 1. Hit API endpoint
55 | 2. Verify API status code
56 | 3. Verify JSON Response
57 | 4. Verify JSON Schema
58 | */
59 | test.info().annotations.push({
60 | type: "purpose",
61 | description:
62 | "This will make GET call to https://restful-booker.herokuapp.com/booking/:id and verify keys/values in response",
63 | });
64 |
65 | const apiHelper = await new ApiHelper(request); //
66 | const bookingDetails = await apiHelper.invokeGetApi("/booking/1914");
67 | console.log(JSON.stringify(bookingDetails));
68 |
69 | expect(bookingDetails.firstname).toBe("John");
70 | expect(bookingDetails.lastname).toBe("Allen");
71 | expect(bookingDetails.totalprice).toBe(111);
72 | expect(bookingDetails.depositpaid).toBeTruthy();
73 | expect(apiHelper.isValidDate(bookingDetails.bookingdates.checkin)).toBe(true);
74 | expect(apiHelper.isValidDate(bookingDetails.bookingdates.checkout)).toBe(
75 | true
76 | );
77 | expect(bookingDetails.additionalneeds).toBe("super bowls");
78 | });
79 |
80 | test("Get booking list, pass to booking/:id API and verify response -- No Authentication Required ", async ({
81 | request,
82 | }) => {
83 | /* Test Flow
84 | 1. Hit API endpoint
85 | 2. Verify API status code
86 | 3. Verify JSON Response
87 | 4. Verify JSON Schema
88 | */
89 | test.info().annotations.push({
90 | type: "purpose",
91 | description:
92 | "This will make GET call to https://restful-booker.herokuapp.com/booking with no authentication",
93 | });
94 | const apiHelper = await new ApiHelper(request); //
95 | const responseMsg = await apiHelper.invokeGetApi("/booking");
96 | console.log(JSON.stringify(responseMsg));
97 | for (let index = 0; index < responseMsg.length; index++) {
98 | test.info().annotations.push({
99 | type: "value",
100 | description: `BookingId : ${responseMsg[index].bookingid}`,
101 | });
102 | expect(responseMsg[index].bookingid).not.toBeNull();
103 | let bookingDetail = await apiHelper.invokeGetApi(
104 | `/booking/${responseMsg[index].bookingid}`
105 | );
106 | console.log(JSON.stringify(bookingDetail));
107 | }
108 | });
109 | //API used for writing test code - https://restful-booker.herokuapp.com/apidoc/index.html
110 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playwright-framework-template",
3 | "version": "1.0.0",
4 | "description": "Playwright Framework Template : Unified automation framework for Web, Mobile, API & Load testing",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo %NODE_ENV% && npx playwright test --headed --config=playwright.config.ts",
8 | "test:local": "cross-env NODE_ENV='local' npm run test",
9 | "test:dev": "cross-env NODE_ENV='dev' npm run test",
10 | "test:sit": "cross-env NODE_ENV='sit' npm run test",
11 | "test:headed": "npx playwright test --headed",
12 | "test:chromium": "npx playwright test --project=chromium",
13 | "test:firefox": "npx playwright test --project=firefox",
14 | "test:webkit": "npx playwright test --project=webkit",
15 | "test:mobile": "npx playwright test --project=mobile-chrome",
16 | "test:api": "npx playwright test tests/api",
17 | "test:debug": "npx playwright test --debug",
18 | "test:ui-mode": "npx playwright test --ui",
19 | "test:report": "npx playwright show-report",
20 | "install:deps": "npx playwright install --with-deps",
21 | "install:browsers": "npm install -D @playwright/test@latest --force",
22 | "codegen": "npx playwright codegen",
23 | "mcp": "ts-node mcp-server.ts",
24 | "test:mcp": "playwright test",
25 | "ci": "npx playwright test --project=chromium --workers=1",
26 | "flaky": "npx playwright test --project=chromium --repeat-each=20",
27 | "sanity": "npx playwright test --grep @Sanity --workers=1",
28 | "smoke": "npx playwright test --grep @Smoke --workers=1",
29 | "api": "npx playwright test --grep @Api --workers=1",
30 | "regresion": "npx playwright test --grep @Regressoin --workers=1",
31 | "lint": "eslint . --ext .js,.ts",
32 | "lint:fix": "npx eslint . --ext .js --fix",
33 | "check": "npm run lint && npm run build",
34 | "format": "prettier --write \"**/*.{js,ts,tsx}\"",
35 | "format:check": "prettier --check \"**/*.{js,ts,tsx}\"",
36 | "pretest": "tsc --noEmit && npx eslint tests/**/*.ts && npx prettier tests/**/*.ts --write",
37 | "prepare": "husky"
38 | },
39 | "lint-staged": {
40 | "*.{ts,tsx,js,jsx}": [
41 | "npx eslint --fix",
42 | "npx prettier --write"
43 | ]
44 | },
45 | "keywords": [
46 | "playwright",
47 | "typescript",
48 | "testing",
49 | "automation",
50 | "e2e",
51 | "cross-browser",
52 | "page-object-model"
53 | ],
54 | "author": "Abhay Bharti",
55 | "license": "MIT",
56 | "devDependencies": {
57 | "@commitlint/cli": "^19.8.1",
58 | "@commitlint/config-conventional": "^19.8.1",
59 | "@eslint/js": "^9.34.0",
60 | "@playwright/test": "^1.56.0",
61 | "@types/eslint__js": "^8.42.3",
62 | "@types/node": "^20.17.24",
63 | "@types/ssh2": "^1.15.4",
64 | "@typescript-eslint/eslint-plugin": "^8.46.2",
65 | "@typescript-eslint/parser": "^8.46.2",
66 | "eslint": "^9.38.0",
67 | "eslint-config-airbnb-base": "^15.0.0",
68 | "eslint-config-prettier": "^10.1.8",
69 | "eslint-define-config": "^2.1.0",
70 | "eslint-plugin-import": "^2.25.2",
71 | "eslint-plugin-jsdoc": "^51.3.1",
72 | "eslint-plugin-playwright": "^2.2.2",
73 | "eslint-plugin-prettier": "^5.5.4",
74 | "eslint-plugin-sonarjs": "^3.0.4",
75 | "eslint-plugin-unicorn": "^59.0.1",
76 | "jiti": "^2.6.1",
77 | "json-server": "^0.17.4",
78 | "lint-staged": "^16.2.6",
79 | "playwright-ajv-schema-validator": "^1.0.2",
80 | "prettier": "3.6.2",
81 | "pw-api-plugin": "^2.0.2",
82 | "ts-node": "^10.9.2",
83 | "typedoc": "^0.28.7",
84 | "typescript": "^5.9.2",
85 | "typescript-eslint": "^8.42.0"
86 | },
87 | "dependencies": {
88 | "@axe-core/playwright": "^4.10.2",
89 | "@types/mocha": "^10.0.6",
90 | "@types/webdriverio": "^5.0.0",
91 | "ajv": "^8.12.0",
92 | "appium": "^2.4.1",
93 | "axe-html-reporter": "^2.2.11",
94 | "dotenv": "^16.5.0",
95 | "joi": "^17.13.3",
96 | "mocha": "^10.2.0",
97 | "moment": "^2.30.1",
98 | "nodejs-nodemailer-outlook": "^1.2.4",
99 | "ssh2": "^1.16.0",
100 | "webdriverio": "^8.28.0",
101 | "winston": "^3.11.0",
102 | "zod": "^3.24.2"
103 | }
104 | }
--------------------------------------------------------------------------------
/src/helper/terminal/SSHHelper.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This module mimics SSHLibrary keywords using Node.js + SSH2 library
3 | * Install dependency: npm install ssh2
4 | */
5 |
6 | import { Client, ConnectConfig } from 'ssh2';
7 | import { step } from "@src/utils/report/ReportAction";
8 |
9 | export class SshHelper {
10 | private conn: Client;
11 | private isConnected: boolean = false;
12 | private stdout: string = '';
13 | private stderr: string = '';
14 |
15 | constructor() {
16 | this.conn = new Client();
17 | }
18 |
19 | /**
20 | * Establish SSH Connection
21 | * @param config - SSH connection details
22 | */
23 | @step('openConnection')
24 | public async openConnection(config: ConnectConfig): Promise {
25 | return new Promise((resolve, reject) => {
26 | this.conn
27 | .on('ready', () => {
28 | console.log('SSH Connection Established');
29 | this.isConnected = true;
30 | resolve();
31 | })
32 | .on('error', (err) => {
33 | console.error('SSH Connection Error:', err);
34 | reject(err);
35 | })
36 | .connect(config);
37 | });
38 | }
39 |
40 | /**
41 | * Run Command on Remote Linux Machine
42 | * @param command - Command to execute
43 | * @returns Command output as a string
44 | */
45 | @step('runCommand')
46 | public async runCommand(command: string): Promise {
47 | return new Promise((resolve, reject) => {
48 | if (!this.isConnected) {
49 | return reject(new Error('SSH session is not connected'));
50 | }
51 |
52 | this.conn.exec(command, (err, stream) => {
53 | if (err) return reject(err);
54 |
55 | let output = '';
56 | stream
57 | .on('close', (code: string, signal: string) => {
58 | console.log(`Command execution completed. Exit code: ${code}, Signal: ${signal}`);
59 | resolve(output);
60 | })
61 | .on('data', (data: string) => {
62 | output += data.toString();
63 | })
64 | .stderr.on('data', (data) => {
65 | console.error('Error:', data.toString());
66 | });
67 | });
68 | });
69 | }
70 |
71 | /**
72 | * Close the SSH Connection
73 | */
74 | @step('closeConnection')
75 | public closeConnection(): void {
76 | if (this.conn && this.isConnected) {
77 | this.conn.end();
78 | this.isConnected = false;
79 | console.log('SSH Connection Closed');
80 | }
81 | }
82 |
83 | /**
84 | * Get last command output
85 | */
86 | @step('getStdout')
87 | getStdout(): string {
88 | return this.stdout;
89 | }
90 |
91 | /**
92 | * Verify output contains string
93 | */
94 | @step('verifyOutputContains')
95 | verifyOutputContains(text: string): boolean {
96 | return this.stdout.includes(text);
97 | }
98 |
99 | }
100 |
101 | //Example code
102 |
103 | // import { SSHHelper } from './SSHHelper';
104 |
105 | // async function main() {
106 | // const sshHelper = new SSHHelper();
107 |
108 | // const config = {
109 | // host: '192.168.1.100', // Replace with your Linux device's IP
110 | // port: 22, // Default SSH port
111 | // username: 'your-username',
112 | // password: 'your-password', // Use SSH keys for better security
113 | // };
114 |
115 | // try {
116 | // // Step 1: Establish SSH Connection
117 | // await sshHelper.setUpSshSession(config);
118 |
119 | // // Step 2: Run Commands
120 | // const output = await sshHelper.runCommand('ls -l');
121 | // console.log('Command Output:', output);
122 |
123 | // } catch (error) {
124 | // console.error('Error:', error);
125 | // } finally {
126 | // // Step 3: Close Connection
127 | // sshHelper.closeConnection();
128 | // }
129 | // }
130 |
131 | // main();
132 |
--------------------------------------------------------------------------------
/tests/fixtures/customFixtures.ts:
--------------------------------------------------------------------------------
1 | import { test as base, BrowserContext, Browser, Page, TestInfo, VideoMode, expect, request } from '@playwright/test'
2 | import { ApiHelper, SshHelper, WebHelper } from "../../src/helper";
3 | import { pwApi } from 'pw-api-plugin';
4 | import { analyzeHAR } from '@src/utils/harAnalyser';
5 | import { JsonReader } from '@src/utils/reader/jsonReader';
6 |
7 | type MyMixtures = {
8 | context: BrowserContext
9 | }
10 |
11 | // Initialize browser context with HAR recording
12 | const initHarRecording = async (browser: Browser, testInfo: TestInfo) => {
13 | const fileName = testInfo.title.replace(/[^a-zA-Z0-9]/g, '-')
14 | const harFilePath = `./test-results/${fileName}.har`
15 | const newContext = await browser.newContext({
16 | recordHar: {
17 | path: harFilePath,
18 | mode: 'full',
19 | // urlFilter: /api.practicesoftwaretesting.com/,
20 | urlFilter: /.*/,
21 | },
22 | ...(testInfo.project.metadata?.video ? {
23 | recordVideo: {
24 | dir: testInfo.outputPath('videos'),
25 | },
26 | } : {}),
27 | })
28 | return newContext
29 | }
30 |
31 | // Teardown and save HAR file
32 | const teardownHarRecording = async (page: Page, testInfo: TestInfo, videoMode: 'on' | 'off') => {
33 | const fileName = testInfo.title.replace(/[^a-zA-Z0-9]/g, '-')
34 | if (videoMode === 'on') {
35 | const videoPath = testInfo.outputPath('my-video.webm')
36 | await Promise.all([page.video()?.saveAs(videoPath), page.close()])
37 | testInfo.attachments.push({
38 | name: 'video',
39 | path: videoPath,
40 | contentType: 'video/webm',
41 | })
42 | }
43 | await page.context().close({ reason: 'Saving HAR file' })
44 | await analyzeHAR(`./test-results/${fileName}.har`, `./test-results/${fileName}.html`)
45 | await testInfo.attach('Network Console', { path: `./test-results/${fileName}.html`, contentType: 'text/html' })
46 | }
47 |
48 | export const test = base.extend<{
49 | api: ApiHelper;
50 | web: WebHelper;
51 | ssh: SshHelper;
52 | MyMixtures: any;
53 | json: JsonReader;
54 | config: { jsonPath: string };
55 | }>({
56 | config: async ({ }, use) => {
57 | // Get JSON path from environment variable or use default
58 | const jsonPath = process.env.JSON_PATH || 'moataeldebsy.json';
59 | await use({ jsonPath });
60 | },
61 | api: async ({ request }, use) => {
62 | await use(new ApiHelper(request))
63 | },
64 |
65 | web: async ({ page, browser, config }, use) => {
66 | console.log(config.jsonPath)
67 | const context = await browser.newContext();
68 | await use(new WebHelper(page, context, config.jsonPath));
69 | },
70 | ssh: async ({ }, use) => {
71 | await use(new SshHelper())
72 | },
73 | json: async ({ config }, use) => {
74 | console.log('Initializing JSON reader with:', config.jsonPath);
75 | await use(new JsonReader(config.jsonPath))
76 |
77 | // npx playwright test --config:jsonPath=./alternative-config.json -- command to pass json filename
78 |
79 | //npx playwright test --config:jsonPath=./moataeldebsy.json --debug ./tests/e2e/moatazeldebsy/form.spec.ts
80 | },
81 | page: async ({ browser }, use, testInfo) => {
82 | // Initialize HAR recording
83 | const context = await initHarRecording(browser, testInfo);
84 | const page = await context.newPage();
85 |
86 | // Array to store failed API details for this test run
87 | const failedApis: any = [];
88 |
89 | // Add the response listener to the page
90 | page.on('response', (response) => {
91 | const request = response.request();
92 | // Filter for API calls (XHR or fetch requests) and non-200 status codes
93 | if (['xhr', 'fetch'].includes(request.resourceType()) && response.status() !== 200) {
94 | failedApis.push({
95 | url: request.url(),
96 | method: request.method(),
97 | status: response.status(),
98 | });
99 | }
100 | });
101 |
102 | // Proceed with the actual test execution
103 | await use(page);
104 |
105 | // Log any failed APIs
106 | if (failedApis.length > 0) {
107 | console.log('\n--- Failed APIs Detected ---');
108 | console.log(failedApis);
109 | console.log('----------------------------\n');
110 | } else {
111 | console.log('All API calls for this test succeeded.');
112 | }
113 |
114 | // Teardown HAR recording
115 | await teardownHarRecording(page, testInfo, 'on');
116 | },
117 |
118 | })
119 |
120 | export type { TestInfo } from "@playwright/test"
121 | export { expect } from "@playwright/test"
122 |
123 |
--------------------------------------------------------------------------------
/tests/web/example/assertion.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect,test } from "@tests/fixtures/test-options";
2 |
3 |
4 | /*
5 | These assertions will retry until the assertion passes, or the assertion timeout is reached.
6 | */
7 | test("Using playwright Auto-Retrying Assertion", async ({ web,api }) => {
8 | await web.navigateToUrl("https://www.google.com");
9 | // await expect(web.toHaveTitle("Google"));
10 | await expect(web.webPage.getByTestId("status")).toHaveText("PASS");
11 |
12 | const response = api.invokeGetApi("url");
13 |
14 | await expect(locator).toBeAttached(); //Element is attached
15 | await expect(locator).toBeChecked(); //Checkbox is checked
16 | await expect(locator).toBeDisabled(); //Element is disabled
17 | await expect(locator).toBeEditable(); //Element is editable
18 | await expect(locator).toBeEmpty(); //Container is empty
19 | await expect(locator).toBeEnabled(); //Element is enabled
20 | await expect(locator).toBeFocused(); //Element is focused
21 | await expect(locator).toBeHidden(); //Element is not visible
22 | await expect(locator).toBeInViewport(); //Element intersects viewport
23 | await expect(locator).toBeVisible(); //Element is visible
24 | await expect(locator).toContainText("xyz"); //Element contains text
25 | await expect(locator).toHaveAttribute("class"); //Element has a DOM attribute
26 | await expect(locator).toHaveClass("icon"); //Element has a class property
27 | await expect(locator).toHaveCount(1); //List has exact number of children
28 | // await expect(locator).toHaveCSS() //Element has CSS property
29 | await expect(locator).toHaveId("id"); //Element has an ID
30 | // await expect(locator).toHaveJSProperty() //Element has a JavaScript property
31 | await expect(locator).toHaveScreenshot(); //Element has a screenshot
32 | await expect(locator).toHaveText("ABC"); //Element matches text
33 | await expect(locator).toHaveValue("ABC"); //Input has a value
34 | //await expect(locator).toHaveValues("ABC") //Select has options selected
35 | await expect(page).toHaveScreenshot(); //Page has a screenshot
36 | await expect(page).toHaveTitle("ABC"); //Page has a title
37 | await expect(page).toHaveURL("ABC"); //Page has a URL
38 | //await expect(response).toBeOK() //Response has an OK status
39 | });
40 |
41 | /*
42 | These assertions will test any condition but do not auto-retry. Using these assertions can lead to a flaky test
43 |
44 | */
45 | test("Using playwright Non-Retrying Assertion", async ({ page }) => {
46 | await page.goto("https://www.google.com");
47 | await expect(page).toHaveTitle("Google");
48 | const value = "";
49 | expect(value).toBe("ABC"); //Value is the same
50 | expect(value).toBeCloseTo(100); // Number is approximately equal
51 | expect(value).toBeDefined(); //Value is not undefined
52 | expect(value).toBeFalsy(); //Value is falsy, e.g. false, 0, null, etc.
53 | expect(value).toBeGreaterThan(5); // Number is more than
54 | expect(value).toBeGreaterThanOrEqual(5); // Number is more than or equal
55 | expect(value).toBeInstanceOf(Object); //Object is an instance of a class
56 | expect(value).toBeLessThan(19); //Number is less than
57 | expect(value).toBeLessThanOrEqual(19); // Number is less than or equal
58 | expect(value).toBeNaN(); //Value is NaN
59 | expect(value).toBeNull(); //Value is null
60 | expect(value).toBeTruthy(); // Value is truthy, i.e. not false, 0, null, etc.
61 | expect(value).toBeUndefined(); // Value is undefined
62 | expect(value).toContain("ABC"); //String contains a substring
63 | expect(value).toContain("ABC"); //Array or set contains an element
64 | expect(value).toContainEqual("ABC"); //Array or set contains a similar element
65 | expect(value).toEqual(5); //Value is similar - deep equality and pattern matching
66 | expect(value).toHaveLength(10); //Array or string has length
67 | expect(value).toHaveProperty("name"); // Object has a property
68 | expect(value).toMatch(/name/i); //String matches a regular expression
69 | // expect(value).toMatchObject(); // Object contains specified properties
70 | // expect(value).toStrictEqual(); //Value is similar, including property types
71 | expect(value).toThrow(); //Function throws an error
72 | // expect(value).any(); //Matches any instance of a class/primitive
73 | // expect(value).anything(); //Matches anything
74 | // expect(value).arrayContaining(); //Array contains specific elements
75 | // expect(value).closeTo(); //Number is approximately equal
76 | // expect(value).objectContaining(); // Object contains specific properties
77 | // expect(value).stringContaining(); //String contains a substring
78 | // expect(value).stringMatching(); //String matches a regular expression
79 | });
80 |
--------------------------------------------------------------------------------
/src/utils/report/Logger.ts:
--------------------------------------------------------------------------------
1 | import winston, {
2 | Logger as WinstonLogger,
3 | LoggerOptions,
4 | format,
5 | } from 'winston';
6 | import { TransformableInfo } from 'logform';
7 | import { inspect } from 'util';
8 | import fs from 'fs';
9 | // Types
10 | type LogLevel =
11 | | 'error'
12 | | 'warn'
13 | | 'info'
14 | | 'http'
15 | | 'verbose'
16 | | 'debug'
17 | | 'silly';
18 | type LogMethod = (message: string, meta?: unknown) => void;
19 |
20 | // Constants
21 | const LOGS_DIRECTORY = 'logs';
22 | const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
23 | const MAX_FILES = 5;
24 |
25 | // Custom formatter for console output
26 | const consoleFormat = format.combine(
27 | format.timestamp({ format: 'DD-MM-YYYY HH:mm:ss' }),
28 | format.errors({ stack: true }),
29 | format.colorize({ all: true }),
30 | format.printf((info: TransformableInfo) => {
31 | const { timestamp, level, message, stack, ...meta } = info;
32 | let log = `${timestamp} [${level}]: ${message}`;
33 |
34 | if (Object.keys(meta).length > 0) {
35 | log += `\n${inspect(meta, { depth: null, colors: true })}`;
36 | }
37 |
38 | return stack ? `${log}\n${stack}` : log;
39 | })
40 | );
41 |
42 | // File format (JSON)
43 | const fileFormat = format.combine(
44 | format.timestamp(),
45 | format.json(),
46 | format.errors({ stack: true })
47 | );
48 |
49 | // Create logs directory if it doesn't exist
50 |
51 | if (!fs.existsSync(LOGS_DIRECTORY)) {
52 | fs.mkdirSync(LOGS_DIRECTORY);
53 | }
54 |
55 | // Logger configuration
56 | const loggerConfig: LoggerOptions = {
57 | level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
58 | format: fileFormat,
59 | defaultMeta: { service: 'job-posting-service' },
60 | transports: [
61 | // Error logs
62 | new winston.transports.File({
63 | filename: `${LOGS_DIRECTORY}/error.log`,
64 | level: 'error',
65 | maxsize: MAX_FILE_SIZE,
66 | maxFiles: MAX_FILES,
67 | }),
68 | // Combined logs
69 | new winston.transports.File({
70 | filename: `${LOGS_DIRECTORY}/combined.log`,
71 | maxsize: MAX_FILE_SIZE,
72 | maxFiles: MAX_FILES,
73 | }),
74 | // Console (only in development)
75 | ...(process.env.NODE_ENV !== 'production'
76 | ? [
77 | new winston.transports.Console({
78 | format: consoleFormat,
79 | }),
80 | ]
81 | : []),
82 | ],
83 | // Handle uncaught exceptions
84 | exceptionHandlers: [
85 | new winston.transports.File({
86 | filename: `${LOGS_DIRECTORY}/exceptions.log`,
87 | maxsize: MAX_FILE_SIZE,
88 | maxFiles: MAX_FILES,
89 | }),
90 | ],
91 | // Handle unhandled promise rejections
92 | rejectionHandlers: [
93 | new winston.transports.File({
94 | filename: `${LOGS_DIRECTORY}/rejections.log`,
95 | maxsize: MAX_FILE_SIZE,
96 | maxFiles: MAX_FILES,
97 | }),
98 | ],
99 | // Exit on error, set to false if using with express
100 | exitOnError: false,
101 | };
102 |
103 | // Create logger instance
104 | const logger: WinstonLogger = winston.createLogger(loggerConfig);
105 |
106 | // Add console transport in production (without colors)
107 | if (process.env.NODE_ENV === 'production') {
108 | logger.add(
109 | new winston.transports.Console({
110 | format: format.combine(format.timestamp(), format.json()),
111 | })
112 | );
113 | }
114 |
115 | // Log method with type safety
116 | const log = (level: LogLevel, message: string, meta?: unknown): void => {
117 | logger.log(level, message, {
118 | ...(meta && typeof meta === 'object' ? meta : { meta }),
119 | });
120 | };
121 |
122 | // Public API
123 | export const logInfo: LogMethod = (message, meta) => log('info', message, meta);
124 | export const logError: LogMethod = (message, error) =>
125 | log('error', message, error);
126 | export const logWarn: LogMethod = (message, meta) => log('warn', message, meta);
127 | export const logDebug: LogMethod = (message, meta) =>
128 | log('debug', message, meta);
129 | export const logHttp: LogMethod = (message, meta) => log('http', message, meta);
130 |
131 | export const logFunctionCall = (
132 | funcName: string,
133 | args: Record
134 | ): void => {
135 | logInfo(`Function: ${funcName} called`, {
136 | parameters: args,
137 | timestamp: new Date().toISOString(),
138 | });
139 | };
140 |
141 | // Export the logger instance
142 | export default logger;
143 |
144 | //Example code to use logger
145 | // import { logInfo, logError } from './logger';
146 |
147 | // Basic usage
148 | // logInfo('Application started');
149 | // logError('Something went wrong', error);
150 |
151 | // With metadata
152 | // logInfo('User logged in', { userId: 123, ip: '192.168.1.1' });
153 |
154 | // // Function call logging
155 | // logFunctionCall('processUser', { userId: 123, action: 'login' });
156 |
--------------------------------------------------------------------------------
/logs/combined.log:
--------------------------------------------------------------------------------
1 | {"level":"info","message":"info","meta":"Test Case Completed : Read all data from table Status : interrupted","service":"job-posting-service","timestamp":"2025-11-08T04:26:33.094Z"}
2 | {"level":"info","message":"playwright-framework-template undefined","service":"job-posting-service","timestamp":"2025-11-08T04:26:43.594Z"}
3 | {"level":"info","message":"info","meta":"Test Suite Started : , 1 tests","service":"job-posting-service","timestamp":"2025-11-08T04:26:43.704Z"}
4 | {"level":"info","message":"info","meta":"Test Case Started : Read all data from table","service":"job-posting-service","timestamp":"2025-11-08T04:26:48.470Z"}
5 | {"level":"info","message":"Column 1: Last Name","service":"job-posting-service","timestamp":"2025-11-08T04:27:24.813Z"}
6 | {"level":"info","message":"info","meta":"Test Case Completed : Read all data from table Status : interrupted","service":"job-posting-service","timestamp":"2025-11-08T04:27:58.705Z"}
7 | {"level":"info","message":"playwright-framework-template undefined","service":"job-posting-service","timestamp":"2025-11-08T04:28:29.217Z"}
8 | {"level":"info","message":"info","meta":"Test Suite Started : , 1 tests","service":"job-posting-service","timestamp":"2025-11-08T04:28:29.322Z"}
9 | {"level":"info","message":"info","meta":"Test Case Started : Read all data from table","service":"job-posting-service","timestamp":"2025-11-08T04:28:33.383Z"}
10 | {"level":"info","message":"Column 1: Last Name","service":"job-posting-service","timestamp":"2025-11-08T04:28:45.729Z"}
11 | {"level":"info","message":"Column 2: First Name","service":"job-posting-service","timestamp":"2025-11-08T04:28:48.188Z"}
12 | {"level":"info","message":"Column 3: Email","service":"job-posting-service","timestamp":"2025-11-08T04:28:54.058Z"}
13 | {"level":"info","message":"Column 4: Due","service":"job-posting-service","timestamp":"2025-11-08T04:28:56.196Z"}
14 | {"level":"info","message":"Column 5: Web Site","service":"job-posting-service","timestamp":"2025-11-08T04:28:56.226Z"}
15 | {"level":"info","message":"Column 6: Action","service":"job-posting-service","timestamp":"2025-11-08T04:28:56.255Z"}
16 | {"level":"info","message":"Number of rows in table: 4","service":"job-posting-service","timestamp":"2025-11-08T04:28:56.285Z"}
17 | {"level":"info","message":"Number of columns in row 1: 6","service":"job-posting-service","timestamp":"2025-11-08T04:28:58.933Z"}
18 | {"level":"info","message":"Row 1: Smith,John,jsmith@gmail.com,$50.00,http://www.jsmith.com,\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:28:59.240Z"}
19 | {"level":"info","message":"Number of columns in row 2: 6","service":"job-posting-service","timestamp":"2025-11-08T04:28:59.291Z"}
20 | {"level":"info","message":"Row 2: Bach,Frank,fbach@yahoo.com,$51.00,http://www.frank.com,\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:28:59.578Z"}
21 | {"level":"info","message":"Number of columns in row 3: 6","service":"job-posting-service","timestamp":"2025-11-08T04:28:59.666Z"}
22 | {"level":"info","message":"Row 3: Doe,Jason,jdoe@hotmail.com,$100.00,http://www.jdoe.com,\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:29:00.019Z"}
23 | {"level":"info","message":"Number of columns in row 4: 6","service":"job-posting-service","timestamp":"2025-11-08T04:29:00.067Z"}
24 | {"level":"info","message":"Row 4: Conway,Tim,tconway@earthlink.net,$50.00,http://www.timconway.com,\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:29:00.507Z"}
25 | {"level":"info","message":"info","meta":"Test Case Completed : Read all data from table Status : passed","service":"job-posting-service","timestamp":"2025-11-08T04:29:03.904Z"}
26 | {"level":"info","message":"playwright-framework-template undefined","service":"job-posting-service","timestamp":"2025-11-08T04:31:22.554Z"}
27 | {"level":"info","message":"info","meta":"Test Suite Started : , 1 tests","service":"job-posting-service","timestamp":"2025-11-08T04:31:22.674Z"}
28 | {"level":"info","message":"info","meta":"Test Case Started : Read all data from table","service":"job-posting-service","timestamp":"2025-11-08T04:31:29.080Z"}
29 | {"level":"info","message":"Header Text: Last Name|First Name|Email|Due|Web Site|Action","service":"job-posting-service","timestamp":"2025-11-08T04:32:02.776Z"}
30 | {"level":"info","message":"Number of rows in table: 4","service":"job-posting-service","timestamp":"2025-11-08T04:32:02.811Z"}
31 | {"level":"info","message":"Number of columns in row 1: 6","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.177Z"}
32 | {"level":"info","message":"Row 1: Smith|John|jsmith@gmail.com|$50.00|http://www.jsmith.com|\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.323Z"}
33 | {"level":"info","message":"Number of columns in row 2: 6","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.346Z"}
34 | {"level":"info","message":"Row 2: Bach|Frank|fbach@yahoo.com|$51.00|http://www.frank.com|\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.512Z"}
35 | {"level":"info","message":"Number of columns in row 3: 6","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.538Z"}
36 | {"level":"info","message":"Row 3: Doe|Jason|jdoe@hotmail.com|$100.00|http://www.jdoe.com|\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.674Z"}
37 | {"level":"info","message":"Number of columns in row 4: 6","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.697Z"}
38 | {"level":"info","message":"Row 4: Conway|Tim|tconway@earthlink.net|$50.00|http://www.timconway.com|\n edit\n delete\n ","service":"job-posting-service","timestamp":"2025-11-08T04:32:17.836Z"}
39 | {"level":"info","message":"info","meta":"Test Case Completed : Read all data from table Status : passed","service":"job-posting-service","timestamp":"2025-11-08T04:32:22.991Z"}
40 |
--------------------------------------------------------------------------------
/tests/api/example/dashboard.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test, APIRequestContext } from "@playwright/test";
2 | import moment from "moment";
3 | import { ApiHelper } from "../../../helper/api/apiHelper";
4 |
5 | let token: string;
6 | let bookingId: string;
7 |
8 | test.beforeAll(async ({ request }) => {
9 | //1. Hit /Auth Api and provide username/password as body
10 | //2. fetch token value from JSON response
11 | //3. save in token variable
12 |
13 | const apiHelper = await new ApiHelper(request);
14 | });
15 |
16 | const testSuiteTestScriptObject = {
17 | Product_Buy_Flow:
18 | "Verify_that_on_the_product_page,_the_user_can_select_the_desired_attribute_of_the_product_eg_size_color_etc,Verify_that_the_user_can_add_to_the_cart_one_or_more_products,Verify_that_users_can_add_products_to_the_wishlist,Verify_that_the_user_can_see_the_previously_added_products_on_the_cart_page_after_signing_in_to_the_application,Verify_that_the_user_can_successfully_buy_more_than_one_products_that_were_added_to_his/her_cart,Verify_that_the_user_cannot_add_more_than_the_available_inventory_of_the_product,Verify_that_the_limit_to_the_number_of_products_a_user_can_buy_is_working_correctly_Also_an_error_message_gets_displayed_preventing_the_user_from_buying_more_than_the_limit,Verify_that_the_delivery_can_be_declined_during_checkout_for_the_places_where_shipping_is_not_available,Verify_that_the_Cash_on_Delivery_option_of_payment_is_working_fine,Verify_that_the_different_prepaid_methods_of_payments_are_working_fine,Verify_that_product_return_functionality_works_correctly",
19 | User_Registration:
20 | "Verify_that_all_the_required_fields__username,_email,_password,_confirm_password,_etc_are_present_on_the_registration_page,Verify_that_on_passing_valid_values,_a_user_should_get_registered_and_the_same_should_be_allowed_to_log_in_to_the_application,Verify_that_if_a_user_tries_to_register_an_existing_username_then_an_error_message_should_get_displayed,Verify_that_the_required/mandatory_fields_are_marked_with_the_‘*’_symbol,Verify_that_for_a_better_user_interface__dropdowns,_radio_buttons,_checkboxes,_etc_fields_are_displayed_wherever_possible_instead_of_just_text_boxes,Verify_the_page_has_both_submit_and_cancel/reset_buttons_at_the_end,Verify_that_clicking_submits_button_after_entering_all_the_required_fields,_submits_the_data_to_the_server,Verify_that_clicking_the_cancel/reset_button_after_entering_all_the_required_fields,_cancels_the_submit_request,_and_reset_all_the_fields,Verify_that_if_no_value_is_passed_to_the_mandatory_fields_and_submit_button_is_clicked_then_it_leads_to_a_validation_error,Verify_that_the_user_can_leave_the_optional_fields_blank_and_on_clicking_the_submit_button_no_validation_error_appears,Verify_that_whenever_possible_validation_should_take_place_on_the_client_side_For_example,_if_a_user_presses_submit_button_without_entering_a_username_and_password_then_this_validation_should_take_place_on_the_client_side_instead_of_sending_blank_entries_to_the_server,Check_the_upper_limit_of_the_different_textbox_fields,Verify_validation_on_the_date_and_email_fields_Only_valid_dates_and_valid_email_Ids_should_be_allowed,Check_validation_on_numeric_fields_by_entering_alphabets_and_special_characters,Check_that_leading_and_trailing_spaces_are_trimmed_ie_in_case,_the_user_appends_space_before_and_after_a_field,_then_the_same_should_get_trimmed_before_getting_stored_on_the_server",
21 | Portal_Validation:
22 | "Verify_that_the_company_logo_and_name_are_clearly_visible,Verify_that_the_user_is_able_to_navigate_through_all_the_products_across_different_categories,Verify_that_all_the_links_and_banners_are_redirecting_to_the_correct_product/category_pages_and_none_of_the_links_are_broken,Verify_that_all_the_information_displayed__product_name,_category_name,_price,_and_product_description_is_clearly_visible,Verify_that_all_the_images__product_and_banner_are_clearly_visible,Verify_that_category_pages_have_a_relevant_product_listed,_specific_to_the_category,Verify_that_the_correct_count_of_total_products_is_displayed_on_the_category_pages,Search__Verify_that_on_searching,_all_the_products_satisfying_the_search_criteria_are_visible_on_the_search_result_page,Search__Verify_that_on_searching,_products_get_displayed_on_the_basis_of_their_relevancy,Search__Verify_that_the_count_of_products_is_correctly_displayed_on_the_search_result_page_for_a_particular_search_term,Filtering__Verify_that_filtering_functionality_correctly_filters_products_based_on_the_filter_applied,Filtering__Verify_that_filtering_works_correctly_on_category_pages,Filtering__Verify_that_filtering_works_correctly_on_the_search_result_page,Filtering__Verify_that_the_correct_count_of_total_products_is_displayed_after_a_filter_is_applied,Filtering__Verify_that_the_correct_count_and_products_get_displayed_on_applying_multiple_filters,Sorting__Verify_that_all_the_sort_options_work_correctly_On_sorting_the_products_based_on_the_sort_option_chosen,Sorting__Verify_that_sorting_works_correctly_on_the_category_pages,Sorting__Verify_that_sorting_works_correctly_on_the_search_result_page,Sorting__Verify_that_sorting_works_correctly_on_the_pages_containing_the_filtered_result_after_applying_filters,Sorting__Verify_that_the_product_count_remains_the_same_irrespective_of_the_sorting_option_applied",
23 | };
24 |
25 | // for (const suiteName in testSuiteTestScriptObject) {
26 | // let testCaseList = testSuiteTestScriptObject[suiteName].split(",");
27 | // for (let iLoop = 0; testCaseList[iLoop]; iLoop++) {
28 | // //console.log(testCaseList[iLoop]);
29 | // test(`${testCaseList[iLoop]}`, async ({ request }) => {
30 | // const apiHelper = await new ApiHelper(request); //
31 | // let todayDate = new Date();
32 | // let env = "SIT";
33 | // let runid = `ecom_run_${moment().format("YYYY-MM-DD")}`; //ecom_run_02_17_2024
34 | // let payLoad = {
35 | // runid: runid,
36 | // suite: suiteName,
37 | // testcasename: testCaseList[iLoop],
38 | // status: "PASS",
39 | // env: "SIT",
40 | // failurereason: "",
41 | // duration: 10,
42 | // browser: null,
43 | // timestamp: moment().format("YYYY-MM-DD HH:mm:ss"), //"2024-03-17 16:40:48",
44 | // reportpath: "abc.html",
45 | // subscriptionkey: "clttt86dt0000mxs8a9thes2h",
46 | // testid: `${runid}|${suiteName}|${testCaseList[iLoop]}|${env}`,
47 | // executiondate: `${moment().format("YYYY-MM-DD")}`,
48 | // };
49 | // console.log("payload : ", payLoad);
50 | // const responseMsg = await apiHelper.invokePostApi(
51 | // "/api/testcase/updateTestCaseExecution",
52 | // payLoad
53 | // );
54 | // });
55 | // }
56 | // }
57 |
--------------------------------------------------------------------------------
/data/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "brands": [
3 | {
4 | "id": "01J47796Q8XWWJ66Z4MX764YKS",
5 | "name": "Hand Tools",
6 | "slug": "hand-tools",
7 | "parent_id": null,
8 | "sub_categories": [
9 | {
10 | "id": "01J47796QCFV3FS7SEKFF51VD1",
11 | "name": "Hammer",
12 | "slug": "hammer",
13 | "parent_id": "01J47796Q8XWWJ66Z4MX764YKS",
14 | "sub_categories": []
15 | },
16 | {
17 | "id": "01J47796QCFV3FS7SEKFF51VD2",
18 | "name": "Hand Saw",
19 | "slug": "hand-saw",
20 | "parent_id": "01J47796Q8XWWJ66Z4MX764YKS",
21 | "sub_categories": []
22 | },
23 | {
24 | "id": "01J47796QCFV3FS7SEKFF51VD3",
25 | "name": "Wrench",
26 | "slug": "wrench",
27 | "parent_id": "01J47796Q8XWWJ66Z4MX764YKS",
28 | "sub_categories": []
29 | },
30 | {
31 | "id": "01J47796QCFV3FS7SEKFF51VD4",
32 | "name": "Screwdriver",
33 | "slug": "screwdriver",
34 | "parent_id": "01J47796Q8XWWJ66Z4MX764YKS",
35 | "sub_categories": []
36 | },
37 | {
38 | "id": "01J47796QCFV3FS7SEKFF51VD5",
39 | "name": "Pliers",
40 | "slug": "pliers",
41 | "parent_id": "01J47796Q8XWWJ66Z4MX764YKS",
42 | "sub_categories": []
43 | },
44 | {
45 | "id": "01J47796QCFV3FS7SEKFF51VD6",
46 | "name": "Chisels",
47 | "slug": "chisels",
48 | "parent_id": "01J47796Q8XWWJ66Z4MX764YKS",
49 | "sub_categories": []
50 | },
51 | {
52 | "id": "01J47796QCFV3FS7SEKFF51VD7",
53 | "name": "Glass",
54 | "slug": "Glass",
55 | "parent_id": "01J47796Q8XWWJ66Z4MX764YKS",
56 | "sub_categories": []
57 | }
58 | ]
59 | }
60 | ],
61 | "mobiles": [
62 | {
63 | "id": 1,
64 | "name": "Google Pixel 6 Pro",
65 | "data": {
66 | "color": "Cloudy White",
67 | "capacity": "128 GB"
68 | },
69 | "reviewId": 1
70 | },
71 | {
72 | "id": 2,
73 | "name": "Apple iPhone 12 Mini, 256GB, Blue",
74 | "data": null,
75 | "reviewId": 2
76 | },
77 | {
78 | "id": 3,
79 | "name": "Apple iPhone 12 Pro Max",
80 | "data": {
81 | "color": "Cloudy White",
82 | "capacity GB": 512
83 | },
84 | "reviewId": 3
85 | },
86 | {
87 | "id": 4,
88 | "name": "Apple iPhone 11, 64GB",
89 | "data": {
90 | "price": 389.99,
91 | "color": "Purple"
92 | }
93 | },
94 | {
95 | "id": 5,
96 | "name": "Samsung Galaxy Z Fold2",
97 | "data": {
98 | "price": 689.99,
99 | "color": "Brown"
100 | }
101 | },
102 | {
103 | "id": 6,
104 | "name": "Apple AirPods",
105 | "data": {
106 | "generation": "3rd",
107 | "price": 120
108 | }
109 | },
110 | {
111 | "id": 7,
112 | "name": "Apple MacBook Pro 16",
113 | "data": {
114 | "year": 2019,
115 | "price": 1849.99,
116 | "CPU model": "Intel Core i9",
117 | "Hard disk size": "1 TB"
118 | }
119 | },
120 | {
121 | "id": 8,
122 | "name": "Apple Watch Series 8",
123 | "data": {
124 | "Strap Colour": "Elderberry",
125 | "Case Size": "41mm"
126 | }
127 | },
128 | {
129 | "id": 9,
130 | "name": "Beats Studio3 Wireless",
131 | "data": {
132 | "Color": "Red",
133 | "Description": "High-performance wireless noise cancelling headphones"
134 | }
135 | },
136 | {
137 | "id": 10,
138 | "name": "Apple iPad Mini 5th Gen",
139 | "data": {
140 | "Capacity": "64 GB",
141 | "Screen size": 7.9
142 | }
143 | },
144 | {
145 | "id": 11,
146 | "name": "Apple iPad Mini 5th Gen",
147 | "data": {
148 | "Capacity": "254 GB",
149 | "Screen size": 7.9
150 | }
151 | },
152 | {
153 | "id": 12,
154 | "name": "Apple iPad Air",
155 | "data": {
156 | "Generation": "4th",
157 | "Price": "419.99",
158 | "Capacity": "64 GB"
159 | }
160 | },
161 | {
162 | "id": 13,
163 | "name": "Apple iPad Air",
164 | "data": {
165 | "Generation": "4th",
166 | "Price": "519.99",
167 | "Capacity": "256 GB"
168 | }
169 | },
170 | {
171 | "data": {
172 | "color": "Cloudy White",
173 | "capacity": "128 GB",
174 | "price": "500"
175 | },
176 | "reviewId": 5,
177 | "id": 20
178 | }
179 | ],
180 | "reviews": [
181 | {
182 | "id": 1,
183 | "rating": 3,
184 | "comment": "Mobile camera have good resolution",
185 | "mobileId": 1
186 | },
187 | {
188 | "id": 2,
189 | "rating": 4,
190 | "comment": "Battery life is good",
191 | "mobileId": 1
192 | },
193 | {
194 | "id": 3,
195 | "rating": 3,
196 | "comment": "Price is too high",
197 | "mobileId": 2
198 | },
199 | {
200 | "id": 4,
201 | "rating": 5,
202 | "comment": "Lot of new apps....",
203 | "mobileId": 3
204 | }
205 | ]
206 | }
--------------------------------------------------------------------------------
/tests/resources/uiMap/orangeHRM.json:
--------------------------------------------------------------------------------
1 | {
2 | "App": {
3 | "Login": {
4 | "objType": "page",
5 | "OrangeHRMLogo": {
6 | "objType": "img",
7 | "locatorType": "xpath",
8 | "locatorValue": "//img[@alt='company-branding']"
9 | },
10 | "LoginInfo": {},
11 | "UsernameText": {
12 | "objType": "text",
13 | "locatorType": "xpath",
14 | "locatorValue": "//label[normalize-space()='Username']",
15 | "label": "Username"
16 | },
17 | "Username": {
18 | "objType": "input",
19 | "locatorType": "xpath",
20 | "locatorValue": "//input[@placeholder='Username']",
21 | "label": "",
22 | "validTestData": "a,abcde",
23 | "invalidTestData": ",abcdef,%%%%,$$$$",
24 | "saveTestData": "admin,test",
25 | "fieldErrorMessage": "Required,invalid error"
26 | },
27 | "PasswordText": {
28 | "objType": "text",
29 | "locatorType": "xpath",
30 | "locatorValue": "//label[normalize-space()='Password']",
31 | "label": "Password"
32 | },
33 | "Password": {
34 | "objType": "input",
35 | "locatorType": "xpath",
36 | "locatorValue": "//input[@placeholder='Password']",
37 | "label": "",
38 | "validTestData": "",
39 | "invalidTestData": "",
40 | "saveTestData": "",
41 | "fieldErrorMessage": ""
42 | },
43 | "Login": {
44 | "elementType": "button",
45 | "locatorType": "xpath",
46 | "locatorValue": "//button[normalize-space()='Login']",
47 | "labelName": "Login"
48 | },
49 | "Forgotyourpassword": {
50 | "elementType": "link",
51 | "locatorType": "xpath",
52 | "locatorValue": "//p[normalize-space()='Forgot your password?']",
53 | "labelName": "Forgot your password?"
54 | }
55 | },
56 | "Admin": {
57 | "objType": "sidebar",
58 | "locatorType": "xpath",
59 | "locatorValue": "//a[@class='oxd-main-menu-item active']//span[1]",
60 | "label": "Admin",
61 | "UserManagement": {
62 | "objType": "tab",
63 | "locatorType": "xpath",
64 | "locatorValue": "//span[normalize-space()='User Management']",
65 | "label": "User Management",
66 | "Users": {
67 | "objType": "subtab",
68 | "locatorType": "xpath",
69 | "locatorValue": "//a[text()='Users']",
70 | "SystemUsers": {
71 | "objType": "section",
72 | "locatorType": "xpath",
73 | "locatorValue": "//*[text()='System Users']",
74 | "UsernameText": {
75 | "objType": "text",
76 | "locatorType": "xpath",
77 | "locatorValue": "//label[text()='Username']",
78 | "label": "Username",
79 | "Username": {
80 | "objType": "input",
81 | "locatorType": "xpath",
82 | "locatorValue": "//input[@class='oxd-input oxd-input--active']",
83 | "label": "",
84 | "validTestData": "",
85 | "invalidTestData": "",
86 | "saveTestData": "",
87 | "fieldErrorMessage": "",
88 | "UserRoleText": {
89 | "objType": "text",
90 | "locatorType": "xpath",
91 | "locatorValue": "//label[text()='User Role']",
92 | "label": "User Role",
93 | "UserRole": {
94 | "objType": "input",
95 | "locatorType": "xpath",
96 | "locatorValue": "//div[@class='oxd-select-text-input']",
97 | "label": "",
98 | "validTestData": "",
99 | "invalidTestData": "",
100 | "saveTestData": "",
101 | "fieldErrorMessage": "",
102 | "EmployeeNameText": {
103 | "objType": "text",
104 | "locatorType": "xpath",
105 | "locatorValue": "//label[text()='Employee Name']",
106 | "label": "Employee Name",
107 | "EmployeeName": {
108 | "objType": "input",
109 | "locatorType": "xpath",
110 | "locatorValue": "//input[@placeholder='Type for hints...']",
111 | "label": "",
112 | "validTestData": "",
113 | "invalidTestData": "",
114 | "saveTestData": "",
115 | "fieldErrorMessage": "",
116 | "StatusText": {
117 | "objType": "text",
118 | "locatorType": "xpath",
119 | "locatorValue": "//label[text()='Status']",
120 | "label": "User Role",
121 | "Status": {
122 | "objType": "input",
123 | "locatorType": "xpath",
124 | "locatorValue": "//div[@class='oxd-select-text-input']",
125 | "label": "",
126 | "validTestData": "",
127 | "invalidTestData": "",
128 | "saveTestData": "",
129 | "fieldErrorMessage": "",
130 | "Reset": {
131 | "elementType": "button",
132 | "locatorType": "xpath",
133 | "locatorValue": "//button[text()=' Reset ']",
134 | "labelName": "Reset",
135 | "Search": {
136 | "elementType": "button",
137 | "locatorType": "xpath",
138 | "locatorValue": "//button[text()=' Search ']",
139 | "labelName": "Search",
140 | "Add": {
141 | "elementType": "button",
142 | "locatorType": "xpath",
143 | "locatorValue": "//button[text()=' Add ']",
144 | "labelName": "Add",
145 | "RecordsTable": {
146 | "objType": "table/grid",
147 | "locatorType": "xpath",
148 | "locatorValue": "//div[@role='table']"
149 | }
150 | }
151 | }
152 | }
153 | }
154 | }
155 | }
156 | }
157 | }
158 | }
159 | }
160 | }
161 | }
162 | }
163 | }
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/utils/harAnalyser.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync } from 'fs'
2 | // Types for findings
3 | type SlowRequest = Record<'url' | 'method' | 'timestamp', string> & Record<'waitTime', number>
4 | type LargePayload = Record<'url' | 'method' | 'totalSize' | 'timestamp', string>
5 | type ErrorResponse = Record<'url' | 'method' | 'status' | 'errorMessage' | 'timestamp', string>
6 |
7 | // Initialize findings
8 | const findings = {
9 | slowRequests: [] as SlowRequest[],
10 | largePayloads: [] as LargePayload[],
11 | errorResponses: [] as ErrorResponse[],
12 | };
13 |
14 | // Analyze HAR entries
15 | export const analyzeHAR = async (harFilePath: string, htmlOututPath: string) => {
16 | const harData = JSON.parse(readFileSync(harFilePath, 'utf-8'))
17 | harData.log.entries.forEach((entry: { request: any; response: any; timings: any; startedDateTime: any }) => {
18 | const { request, response, timings, startedDateTime } = entry
19 | const { method, url } = request
20 | const { status, statusText, headersSize, bodySize } = response
21 |
22 | // Identify slow requests (e.g., > 1 second)
23 | if (timings.wait > 1000) {
24 | findings.slowRequests.push({
25 | url,
26 | method,
27 | waitTime: timings.wait,
28 | timestamp: startedDateTime,
29 | })
30 | }
31 |
32 | // Identify large payloads (e.g., > 1 MB)
33 | const totalSize = headersSize + bodySize
34 | if (totalSize > 1024 * 1024) {
35 | findings.largePayloads.push({
36 | url,
37 | method,
38 | totalSize: `${(totalSize / 1024 / 1024).toFixed(2)} MB`,
39 | timestamp: startedDateTime,
40 | })
41 | }
42 |
43 | // Identify error responses (status codes 4xx and 5xx)
44 | if (status !== 200) {
45 | findings.errorResponses.push({
46 | url,
47 | method,
48 | status,
49 | errorMessage: statusText || 'N/A',
50 | timestamp: startedDateTime,
51 | })
52 | }
53 | })
54 |
55 | // Generate HTML report
56 | interface GenerateHTMLData {
57 | slowRequests: SlowRequest[]
58 | largePayloads: LargePayload[]
59 | errorResponses: ErrorResponse[]
60 | }
61 |
62 | interface GenerateHTML {
63 | (data: GenerateHTMLData): string
64 | }
65 |
66 | const generateHTML: GenerateHTML = (data) => `
67 |
68 |
69 |
70 |
71 |
72 | Data Analysis Report
73 |
74 |
75 |
76 |
77 | Comprehensive API Data Analysis Report
78 |
79 |
80 |
81 | Summary
82 |
83 | This report provides a detailed analysis of the provided HAR file, highlighting slow requests, large payloads, and error responses.
84 |
85 |
86 |
87 | Slow Requests
88 |
89 |
90 |
91 |
92 | URL
93 | Method
94 | Wait Time (ms)
95 | Timestamp
96 |
97 |
98 |
99 | ${data.slowRequests
100 | .map(
101 | (req: SlowRequest) => `
102 |
103 | ${req.url}
104 | ${req.method}
105 | ${req.waitTime}
106 | ${req.timestamp}
107 |
108 | `,
109 | )
110 | .join('')}
111 |
112 |
113 |
114 |
115 |
116 | Large Payloads
117 |
118 |
119 |
120 |
121 | URL
122 | Method
123 | Size
124 | Timestamp
125 |
126 |
127 |
128 | ${data.largePayloads
129 | .map(
130 | (payload: LargePayload) => `
131 |
132 | ${payload.url}
133 | ${payload.method}
134 | ${payload.totalSize}
135 | ${payload.timestamp}
136 |
137 | `,
138 | )
139 | .join('')}
140 |
141 |
142 |
143 |
144 |
145 | Error Responses
146 |
147 |
148 |
149 |
150 | URL
151 | Method
152 | Status
153 | Error Message
154 | Timestamp
155 |
156 |
157 |
158 | ${data.errorResponses
159 | .map(
160 | (error: ErrorResponse) => `
161 |
162 | ${error.url}
163 | ${error.method}
164 | ${error.status}
165 | ${error.errorMessage}
166 | ${error.timestamp}
167 |
168 | `,
169 | )
170 | .join('')}
171 |
172 |
173 |
174 |
175 |
176 |
177 | © 2025 Comprehensive Data Analysis
178 |
179 |
180 |
181 |
182 | `
183 |
184 | // Write the HTML report to a file
185 | const htmlContent = generateHTML(findings)
186 | writeFileSync(htmlOututPath, htmlContent, 'utf-8')
187 | console.log('Data analysis report generated successfully')
188 | }
--------------------------------------------------------------------------------
/src/helper/api/apiHelper.ts:
--------------------------------------------------------------------------------
1 | import { Helper } from "@src/helper/Helper";
2 | import type { APIRequestContext, APIResponse } from "@playwright/test";
3 | import { ApiError } from "@src/utils/error/ErrorManager";
4 | import { validateSchema } from 'playwright-ajv-schema-validator';
5 | import { step } from "@src/utils/report/ReportAction";
6 | import { logError, logInfo } from "@src/utils/report/Logger";
7 |
8 | enum methodType {
9 | GET = "get",
10 | POST = "post",
11 | DELETE = "delete",
12 | PUT = "put",
13 | PATCH = "patch",
14 | HEAD = "head",
15 | }
16 |
17 | const BASE_URL = "https://restful-booker.herokuapp.com";
18 |
19 | export class ApiHelper extends Helper {
20 | private readonly apiRequest: APIRequestContext;
21 | private readonly retries: number;
22 | private readonly timeout: number;
23 |
24 |
25 | /**
26 | * The constructor function initializes a new context for the API.
27 | * @param {any} apiContext - The `apiContext` parameter is an object that represents the context of an
28 | * API. It is used to store and manage information related to the API, such as authentication
29 | * credentials, request headers, and other configuration settings.
30 | */
31 | constructor(apiRequest: APIRequestContext, config?: { timeout?: number; retries?: number }) {
32 | super();
33 | this.apiRequest = apiRequest;
34 | this.timeout = config?.timeout || 30000;
35 | this.retries = config?.retries || 3;
36 | }
37 |
38 |
39 | /**
40 | * Simplified helper for making API requests and returning the status and JSON body.
41 | * This helper automatically performs the request based on the provided method, URL, body, and headers.
42 | *
43 | * @param {string} params.method - The HTTP method to use (POST, GET, PUT, DELETE).
44 | * @param {string} params.url - The URL to send the request to.
45 | * @param {string} [params.baseUrl] - The base URL to prepend to the request URL.
46 | * @param {Record | null} [params.body=null] - The body to send with the request (for POST and PUT requests).
47 | * @param {Record | undefined} [params.headers=undefined] - The headers to include with the request.
48 | * @returns {Promise<{ status: number; body: unknown }>} - An object containing the status code and the parsed response body.
49 | * - `status`: The HTTP status code returned by the server.
50 | * - `body`: The parsed JSON response body from the server.
51 | */
52 | @step('hitApiEndPoint')
53 | async hitApiEndPoint({
54 | method,
55 | endPoint,
56 | body = null,
57 | headers,
58 | statusCode
59 | }: { method: methodType, endPoint: string, body?: string | null, headers?: string, statusCode: number }): Promise<{ status: number, body: unknown }> {
60 |
61 | let response: APIResponse | null = null;
62 |
63 | const options: {
64 | data?: Record | null;
65 | headers?: Record;
66 | } = {};
67 |
68 | if (body) { options.data = body; }
69 |
70 | if (headers) {
71 | options.headers = {
72 | Authorization: `Token ${headers}`,
73 | "Content-Type": "application/json",
74 | };
75 | } else {
76 | options.headers = {
77 | "Content-Type": "application/json",
78 | };
79 | }
80 |
81 | switch (method.toLowerCase()) {
82 | case methodType.GET:
83 | response = await this.invokeGetApi(endPoint, options);
84 | break;
85 | case methodType.POST:
86 | response = await this.invokePostApi(endPoint, body, options);
87 | break;
88 | case methodType.DELETE:
89 | response = await this.invokeDeleteApi(endPoint, options);
90 | break;
91 | case methodType.PUT:
92 | response = await this.invokePutApi(endPoint, body, options);
93 | break;
94 | default:
95 | logError(`Unsupported operation type: ${method}`);
96 | }
97 |
98 | if (!response) {
99 | throw new ApiError(`No response received for method ${method} on ${endPoint}`);
100 | }
101 |
102 | if (!response.ok()) {
103 | const text = await response.text();
104 | new ApiError(
105 | `POST ${endPoint} failed: ${response.status()} ${response.statusText()} | body: ${text}`
106 | );
107 | }
108 | const status = response.status();
109 |
110 | let bodyData: unknown = null;
111 | const contentType = response.headers()["content-type"] || "";
112 |
113 | try {
114 | if (contentType.includes("application/json")) {
115 | bodyData = await response.json();
116 | } else if (contentType.includes("text/")) {
117 | bodyData = await response.text();
118 | }
119 | } catch (err) {
120 | console.warn(`Failed to parse response body for status ${status}: ${err}`);
121 | }
122 |
123 | return { status, body: bodyData };
124 | }
125 |
126 | @step('invokeGetApi')
127 | async invokeGetApi(endPoint: string, options: unknown): Promise {
128 | try {
129 | logInfo(`Making GET request to endPoint: ${BASE_URL}${endPoint} `);
130 | const response = await this.apiRequest.get(endPoint, options);
131 | return await response;
132 | } catch (error) {
133 | console.log(error);
134 | throw new ApiError("Get request failed");
135 | }
136 | }
137 |
138 | @step('invokeDeleteApi')
139 | async invokeDeleteApi(endPoint: string, options: unknown): Promise {
140 | let response;
141 | try {
142 | logInfo(
143 | `Making DELETE request to endPoint: ${BASE_URL}${endPoint} `
144 | );
145 | response = await this.apiRequest.delete(endPoint, options);
146 |
147 | return await response;
148 | } catch (error) {
149 | throw new ApiError("Delete request failed");
150 | }
151 | }
152 |
153 | /**
154 | * The function `invokePostApi` is an asynchronous function that sends a POST request to an API
155 | * endpoint with a payload and returns the response data.
156 | * @param {string} endPoint - The `endPoint` parameter is a string that represents the URL or endpoint
157 | * of the API you want to call.
158 | * @param {object} payload - The `payload` parameter is an object that contains the data to be sent in
159 | * the request body. It is typically used to send data to the server for processing or to update a
160 | * resource.
161 | * @returns the response data as a JSON object if the response status is 200. If there is an error, it
162 | * will return the error object.
163 | */
164 | @step('invokePostApi')
165 | async invokePostApi(
166 | endPoint: string,
167 | body: unknown,
168 | options: unknown,
169 | ): Promise {
170 | let response;
171 | try {
172 | logInfo(
173 | `Making POST request to endPoint: ${BASE_URL}${endPoint} `
174 | );
175 | const reqOptions: any = { ...(options as any) };
176 | if (body !== undefined && body !== null) {
177 | reqOptions.data = body;
178 | }
179 | response = await this.apiRequest.post(endPoint, reqOptions);
180 | return await response;
181 | } catch (error) {
182 | throw new ApiError("Post request failed");
183 | }
184 | }
185 |
186 | @step('invokePutApi')
187 | async invokePutApi(
188 | endPoint: string,
189 | payload: unknown,
190 | options: unknown,
191 | ): Promise {
192 | let response;
193 | try {
194 | logInfo(
195 | `Making PUT request to endPoint: ${BASE_URL}${endPoint} `
196 | );
197 | const reqOptions: any = { ...(options as any) };
198 | if (payload !== undefined && payload !== null) {
199 | reqOptions.data = payload;
200 | }
201 | response = await this.apiRequest.put(endPoint, reqOptions);
202 | return await response;
203 | } catch (error) {
204 | throw new ApiError("Post request failed");
205 | }
206 | }
207 |
208 | getResponseHeader(response:APIResponse){
209 | return response.headers()
210 | }
211 |
212 | validateSchema(response:APIResponse,schema:object){
213 |
214 | }
215 | }
216 |
217 |
218 |
--------------------------------------------------------------------------------
/logs/rejections.log:
--------------------------------------------------------------------------------
1 | {"date":"Sat Nov 08 2025 09:19:40 GMT+0530 (India Standard Time)","error":{},"level":"error","message":"unhandledRejection: columnHeaderText.textextContent is not a function\nTypeError: columnHeaderText.textextContent is not a function\n at eval (eval-31c3f682.repl:2:33)\n at textContent (eval-31c3f682.repl:3:3)\n at C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts:18:62\n at C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:310:9\n at C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\testInfo.js:305:11\n at TimeoutManager.withRunnable (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\timeoutManager.js:67:14)\n at TestInfoImpl._runWithTimeout (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\testInfo.js:303:7)\n at C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:308:7\n at WorkerMain._runTest (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:283:5)\n at WorkerMain.runTestGroup (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:198:11)\n at process. (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\common\\process.js:65:22)","os":{"loadavg":[0,0,0],"uptime":782977.14},"process":{"argv":["C:\\Program Files\\nodejs\\node.exe","C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\common\\process.js"],"cwd":"C:\\fastcode\\playwright-framework-template","execPath":"C:\\Program Files\\nodejs\\node.exe","gid":null,"memoryUsage":{"arrayBuffers":1296009,"external":5094883,"heapTotal":70443008,"heapUsed":42648656,"rss":140890112},"pid":21108,"uid":null,"version":"v22.18.0"},"rejection":true,"service":"job-posting-service","stack":"TypeError: columnHeaderText.textextContent is not a function\n at eval (eval-31c3f682.repl:2:33)\n at textContent (eval-31c3f682.repl:3:3)\n at C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts:18:62\n at C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:310:9\n at C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\testInfo.js:305:11\n at TimeoutManager.withRunnable (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\timeoutManager.js:67:14)\n at TestInfoImpl._runWithTimeout (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\testInfo.js:303:7)\n at C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:308:7\n at WorkerMain._runTest (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:283:5)\n at WorkerMain.runTestGroup (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js:198:11)\n at process. (C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\common\\process.js:65:22)","timestamp":"2025-11-08T03:49:40.835Z","trace":[{"column":33,"file":"eval-31c3f682.repl","function":"eval","line":2,"method":null,"native":false},{"column":3,"file":"eval-31c3f682.repl","function":"textContent","line":3,"method":null,"native":false},{"column":62,"file":"C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts","function":null,"line":18,"method":null,"native":false},{"column":9,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js","function":null,"line":310,"method":null,"native":false},{"column":11,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\testInfo.js","function":null,"line":305,"method":null,"native":false},{"column":14,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\timeoutManager.js","function":"TimeoutManager.withRunnable","line":67,"method":"withRunnable","native":false},{"column":7,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\testInfo.js","function":"TestInfoImpl._runWithTimeout","line":303,"method":"_runWithTimeout","native":false},{"column":7,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js","function":null,"line":308,"method":null,"native":false},{"column":5,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js","function":"WorkerMain._runTest","line":283,"method":"_runTest","native":false},{"column":11,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\worker\\workerMain.js","function":"WorkerMain.runTestGroup","line":198,"method":"runTestGroup","native":false},{"column":22,"file":"C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\common\\process.js","function":null,"line":65,"method":null,"native":false}]}
2 | {"date":"Sat Nov 08 2025 09:22:59 GMT+0530 (India Standard Time)","error":{"name":"Error"},"level":"error","message":"unhandledRejection: locator.textContent: Error: strict mode violation: locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span') resolved to 6 elements:\n 1) Last Name aka locator('#table1').getByText('Last Name')\n 2) First Name aka locator('#table1').getByText('First Name')\n 3) Email aka locator('#table1').getByText('Email')\n 4) Due aka locator('#table1').getByText('Due')\n 5) Web Site aka locator('#table1').getByText('Web Site')\n 6) Action aka locator('#table1').getByText('Action')\n\nCall log:\n\u001b[2m - waiting for locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span')\u001b[22m\n\nlocator.textContent: Error: strict mode violation: locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span') resolved to 6 elements:\n 1) Last Name aka locator('#table1').getByText('Last Name')\n 2) First Name aka locator('#table1').getByText('First Name')\n 3) Email aka locator('#table1').getByText('Email')\n 4) Due aka locator('#table1').getByText('Due')\n 5) Web Site aka locator('#table1').getByText('Web Site')\n 6) Action aka locator('#table1').getByText('Action')\n\nCall log:\n\u001b[2m - waiting for locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span')\u001b[22m\n\n at eval (eval-b5eed3dc.repl:2:66)\n at nth (eval-b5eed3dc.repl:3:3)\n at C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts:17:47","os":{"loadavg":[0,0,0],"uptime":783175.89},"process":{"argv":["C:\\Program Files\\nodejs\\node.exe","C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\common\\process.js"],"cwd":"C:\\fastcode\\playwright-framework-template","execPath":"C:\\Program Files\\nodejs\\node.exe","gid":null,"memoryUsage":{"arrayBuffers":1313693,"external":5068800,"heapTotal":34476032,"heapUsed":32383224,"rss":107753472},"pid":8916,"uid":null,"version":"v22.18.0"},"rejection":true,"service":"job-posting-service","stack":"locator.textContent: Error: strict mode violation: locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span') resolved to 6 elements:\n 1) Last Name aka locator('#table1').getByText('Last Name')\n 2) First Name aka locator('#table1').getByText('First Name')\n 3) Email aka locator('#table1').getByText('Email')\n 4) Due aka locator('#table1').getByText('Due')\n 5) Web Site aka locator('#table1').getByText('Web Site')\n 6) Action aka locator('#table1').getByText('Action')\n\nCall log:\n\u001b[2m - waiting for locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span')\u001b[22m\n\n at eval (eval-b5eed3dc.repl:2:66)\n at nth (eval-b5eed3dc.repl:3:3)\n at C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts:17:47","timestamp":"2025-11-08T03:52:59.577Z","trace":[{"column":66,"file":"eval-b5eed3dc.repl","function":"eval","line":2,"method":null,"native":false},{"column":3,"file":"eval-b5eed3dc.repl","function":"nth","line":3,"method":null,"native":false},{"column":47,"file":"C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts","function":null,"line":17,"method":null,"native":false}]}
3 | {"date":"Sat Nov 08 2025 09:23:12 GMT+0530 (India Standard Time)","error":{},"level":"error","message":"unhandledRejection: locator.textContent: Test ended.\nCall log:\n\u001b[2m - waiting for locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span')\u001b[22m\n\nlocator.textContent: Test ended.\nCall log:\n\u001b[2m - waiting for locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span')\u001b[22m\n\n at C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts:17:84","os":{"loadavg":[0,0,0],"uptime":783188.687},"process":{"argv":["C:\\Program Files\\nodejs\\node.exe","C:\\fastcode\\playwright-framework-template\\node_modules\\playwright\\lib\\common\\process.js"],"cwd":"C:\\fastcode\\playwright-framework-template","execPath":"C:\\Program Files\\nodejs\\node.exe","gid":null,"memoryUsage":{"arrayBuffers":2182349,"external":5944843,"heapTotal":69562368,"heapUsed":38869160,"rss":139264000},"pid":17000,"uid":null,"version":"v22.18.0"},"rejection":true,"service":"job-posting-service","stack":"locator.textContent: Test ended.\nCall log:\n\u001b[2m - waiting for locator('//*[@id=\\'table1\\']').locator('thead').locator('tr').first().locator('th').locator('span')\u001b[22m\n\n at C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts:17:84","timestamp":"2025-11-08T03:53:12.381Z","trace":[{"column":84,"file":"C:\\fastcode\\playwright-framework-template\\tests\\web\\example\\webTable.spec.ts","function":null,"line":17,"method":null,"native":false}]}
4 |
--------------------------------------------------------------------------------
/Notes:
--------------------------------------------------------------------------------
1 | import { linkSync } from 'fs';
2 |
3 | /**
4 | * Extends Playwright locator with a helper function to find, store, and manage elements using different locator strategies
5 | * @param {import('@playwright/test').Page} page - Playwright page instance
6 | * @returns {Object} Helper object with locator methods
7 | */
8 | function createLocatorHelper(page) {
9 | // Internal storage for locators
10 | const locatorStorage = new Map();
11 |
12 | return {
13 | /**
14 | * Finds a single element using the specified locator strategy
15 | * Checks if element is displayed and enabled
16 | * Returns null if element is not found
17 | * @param {string} type - Locator type ('css', 'xpath', 'text', 'testid', 'role', 'label')
18 | * @param {string} value - Locator value
19 | * @param {Object} [options] - Optional locator options
20 | * @returns {Promise<{locator: import('@playwright/test').Locator, isDisplayed: boolean, isEnabled: boolean} | null>} Playwright locator with status or null
21 | */
22 | async findElement(type, value, options = {}) {
23 | try {
24 | let locator;
25 | switch (type.toLowerCase()) {
26 | case 'css':
27 | locator = page.locator(value, options);
28 | break;
29 | case 'xpath':
30 | locator = page.locator(`xpath=${value}`, options);
31 | break;
32 | case 'text':
33 | locator = page.getByText(value, options);
34 | break;
35 | case 'testid':
36 | locator = page.getByTestId(value, options);
37 | break;
38 | case 'role':
39 | locator = page.getByRole(value, options);
40 | break;
41 | case 'label':
42 | locator = page.getByLabel(value, options);
43 | break;
44 | default:
45 | console.warn(`Unsupported locator type: ${type}`);
46 | return null;
47 | }
48 |
49 | // Check if element exists
50 | const count = await locator.count();
51 | if (count === 0) {
52 | console.warn(`Element not found with ${type} locator: ${value}`);
53 | return null;
54 | }
55 |
56 | // Check visibility and enabled status
57 | const isDisplayed = await locator.isVisible();
58 | const isEnabled = await locator.isEnabled();
59 |
60 | return {
61 | locator,
62 | isDisplayed,
63 | isEnabled
64 | };
65 | } catch (error) {
66 | console.warn(`Error while finding element with ${type} locator: ${value}. Error: ${error.message}`);
67 | return null;
68 | }
69 | }
70 |
71 |
72 |
73 | /**
74 | * Waits for an element to be visible using the specified locator strategy
75 | * @param {string} type - Locator type
76 | * @param {string} value - Locator value
77 | * @param {Object} [options] - Optional locator options with timeout
78 | * @returns {Promise} Playwright locator
79 | */
80 | async waitForElement(type, value, options = { timeout: 5000 }) {
81 | try {
82 | const result = await this.findElement(type, value);
83 | if (!result) {
84 | throw new Error(`Element not found with ${type} locator: ${value}`);
85 | }
86 | await result.locator.waitFor({ state: 'visible', ...options });
87 | return result.locator;
88 | } catch (error) {
89 | throw new Error(`Failed to wait for element with ${type} locator: ${value}. Error: ${error.message}`);
90 | }
91 | }
92 |
93 | };
94 | }
95 |
96 | // Example usage:
97 | /*
98 | const { test, expect } = require('@playwright/test');
99 | const { createLocatorHelper } = require('./playwright_locator_helper');
100 |
101 | test('example test with enhanced findElement', async ({ page }) => {
102 | const locatorHelper = createLocatorHelper(page);
103 | await page.goto('https://example.com');
104 |
105 | // Example with CSS locator
106 | const buttonResult = await locatorHelper.findElement('css', 'button.submit');
107 | if (buttonResult) {
108 | console.log(`Button is displayed: ${buttonResult.isDisplayed}`);
109 | console.log(`Button is enabled: ${buttonResult.isEnabled}`);
110 | if (buttonResult.isDisplayed && buttonResult.isEnabled) {
111 | await buttonResult.locator.click();
112 | }
113 | } else {
114 | console.log('Button not found');
115 | }
116 | });
117 | */
118 |
119 | module.exports = { createLocatorHelper };
120 |
121 | // Example usage:
122 | /*
123 | const { test, expect } = require('@playwright/test');
124 | const { createLocatorHelper } = require('./playwright_locator_helper');
125 |
126 | test('example test with stored locators', async ({ page }) => {
127 | const locatorHelper = createLocatorHelper(page);
128 |
129 | await page.goto('https://example.com');
130 |
131 | // Store locators
132 | await locatorHelper.storeLocator('submitButton', 'css', 'button[type="submit"]');
133 | await locatorHelper.storeLocator('usernameInput', 'css', '#username');
134 |
135 | // Use stored locators
136 | const usernameInput = await locatorHelper.getStoredLocator('usernameInput');
137 | await usernameInput.fill('testuser');
138 |
139 | const submitButton = await locatorHelper.getStoredLocator('submitButton');
140 | await submitButton.click();
141 |
142 | // Clean up
143 | locatorHelper.clearStoredLocators();
144 | });
145 | */
146 |
147 | module.exports = { createLocatorHelper };
148 |
149 | Usage Example
150 |
151 | /**
152 | * Example usage of findElement function for different locator types
153 | * Assumes a webpage with common elements (e.g., a login form)
154 | */
155 | const { test, expect } = require('@playwright/test');
156 | const { createLocatorHelper } = require('./playwright_locator_helper');
157 |
158 | test.describe('Locator Examples', () => {
159 | test.beforeEach(async ({ page }) => {
160 | // Navigate to a sample page (replace with your test URL)
161 | await page.goto('https://example.com/login');
162 | });
163 |
164 | test('Use CSS locator', async ({ page }) => {
165 | const locatorHelper = createLocatorHelper(page);
166 |
167 | // Find an input field by CSS selector
168 | const usernameInput = await locatorHelper.findElement('css', '#username');
169 | await usernameInput.fill('testuser');
170 |
171 | // Verify the input value
172 | await expect(usernameInput).toHaveValue('testuser');
173 | });
174 |
175 | test('Use XPath locator', async ({ page }) => {
176 | const locatorHelper = createLocatorHelper(page);
177 |
178 | // Find a submit button using XPath
179 | const submitButton = await locatorHelper.findElement('xpath', '//button[@type="submit"]');
180 | await submitButton.click();
181 |
182 | // Verify button is clickable (enabled)
183 | await expect(submitButton).toBeEnabled();
184 | });
185 |
186 | test('Use Text locator', async ({ page }) => {
187 | const locatorHelper = createLocatorHelper(page);
188 |
189 | // Find a link by its text content
190 | const forgotPasswordLink = await locatorHelper.findElement('text', 'Forgot Password');
191 | await forgotPasswordLink.click();
192 |
193 | // Verify navigation or element visibility
194 | await expect(page).toHaveURL(/forgot-password/);
195 | });
196 |
197 | test('Use TestID locator', async ({ page }) => {
198 | const locatorHelper = createLocatorHelper(page);
199 |
200 | // Find an element by data-testid attribute
201 | const loginForm = await locatorHelper.findElement('testid', 'login-form');
202 |
203 | // Verify the element is visible
204 | await expect(loginForm).toBeVisible();
205 | });
206 |
207 | test('Use Role locator', async ({ page }) => {
208 | const locatorHelper = createLocatorHelper(page);
209 |
210 | // Find a button by its role and name
211 | const loginButton = await locatorHelper.findElement('role', 'button', { name: 'Log In' });
212 | await loginButton.click();
213 |
214 | // Verify button interaction
215 | await expect(loginButton).toHaveAttribute('aria-pressed', 'true');
216 | });
217 |
218 | test('Use Label locator', async ({ page }) => {
219 | const locatorHelper = createLocatorHelper(page);
220 |
221 | // Find an input by its associated label
222 | const passwordInput = await locatorHelper.findElement('label', 'Password');
223 | await passwordInput.fill('secure123');
224 |
225 | // Verify input value
226 | await expect(passwordInput).toHaveValue('secure123');
227 | });
228 | });
229 |
230 | ## phaatak package
231 | https://www.npmjs.com/package/phaatak Try this npm package for
232 | - managing multiple tabs
233 | - handling new windows
234 | - Saving and loading profiles
235 | - Stop the browser
236 | - close All tabs
237 |
238 |
239 | Example code - https://github.com/ghostinlinux/phaatak-playwright-demo/
240 |
241 | ## Visual testing
242 | https://medium.com/@adam_pajda/playwright-visual-tests-with-git-lfs-and-docker-d537ddd6e86a
243 |
244 | Verify all broken links
245 | https://medium.com/@thananjayan1988/automatic-broken-link-detection-with-playwright-a241a6f41973
246 |
247 | How to link existing chrome browser in playwright run
248 | https://dev.to/mxschmitt/running-playwright-codegen-with-existing-chromium-profiles-5g7k
249 |
250 | Mobile App testing
251 | https://medium.com/@testerstalk/mobile-app-testing-with-playwright-a-step-by-step-guide-in-detail-cabf5def30a9
252 |
253 | Excel data comparision using Playwright
254 | https://medium.com/@testerstalk/how-to-download-validate-excel-file-in-playwright-b8acbb19a4e8
255 |
256 | https://idavidov.eu/never-commit-broken-code-again-a-guide-to-eslint-and-husky-in-playwright?source=more_series_bottom_blogs
257 |
258 | App to run UI test -
259 | https://moatazeldebsy.github.io/test-automation-practices/#/
260 |
261 | https://github.com/AtulKrSharma/PlaywrightInjectTagsWithCaching/blob/main/playwright.config.ts
262 |
263 | https://www.saucedemo.com/
264 | https://the-internet.herokuapp.com/
265 | https://selectorshub.com/xpath-practice-page/
266 | https://test-edzotech.web.app/doms.html
267 | https://github.com/gauravkhuraana/PlaywrightTypeScriptWithAgenticAI
268 | https://idavidov.eu/my-first-live-session-developing-playwright-framework-for-rest-api-testin
269 |
270 | Try this prompt
271 | https://gauravkhurana.in/docs/AI/prompt-library/#playwright-typescript-framework-creation
272 |
273 | A must follow:
274 | https://github.com/seontechnologies/playwright-utils
--------------------------------------------------------------------------------
/.windsurf/rules/rules.md:
--------------------------------------------------------------------------------
1 | ---
2 | trigger: always_on
3 | ---
4 |
5 | ## Primary Goal for AI Agent
6 |
7 | Your primary goal is to assist in the development and maintenance of a robust and maintainable automated testing framework. This includes generating high-quality TypeScript code for Playwright tests (UI, API, E2E), page objects, fixtures, and utility functions, as well as providing expert advice on testing strategies and best practices, all while adhering to the instructions provided in specific prompts.
8 |
9 | ## Tech Stack
10 |
11 | - TypeScript (Strict Mode, ESNext)
12 | - Playwright (latest stable version)
13 | - Zod (for schema validation)
14 |
15 | ## AI Persona & Role
16 |
17 | You are an expert Senior Test Automation Engineer with deep specialization in:
18 | - TypeScript for test automation.
19 | - Playwright for UI, API, and end-to-end testing.
20 | - Designing and implementing scalable and maintainable Page Object Models (POM).
21 | - API testing best practices, including request/response validation using libraries like Zod.
22 | - Frontend and Backend testing considerations relevant to a complex platforms.
23 | - Adherence to strict coding standards and best practices.
24 |
25 | You are expected to:
26 | - Write concise, idiomatic, and technically accurate TypeScript code.
27 | - Provide type-safe examples and ensure all generated code includes correct type annotations.
28 | - Proactively identify potential issues and suggest improvements in test design and implementation *when asked or if it directly relates to the prompt`s request*.
29 | - Explain complex concepts clearly and provide rationale for your suggestions when asked.
30 | - Perform tasks like refactoring, debugging, suggesting improvements, or generating test cases *only when explicitly instructed to do so in a prompt*.
31 |
32 | ## Project Structure & Contextroot/
33 | |
34 | ├── env/ # Environment configuration files
35 | │ ├── .env.dev
36 | │ └── .env.example
37 | │
38 | ├── fixture/ # Playwright test fixtures
39 | │ ├── api/ # API specific fixtures
40 | │ │ ├── api-request-fixture.ts # Base fixture for API requests
41 | │ │ ├── plain-function.ts # Utility functions for API interactions
42 | │ │ ├── schemas.ts # Zod schemas for API validation
43 | │ │ └── types-guards.ts # Type guards for API responses
44 | │ └── pom/ # Page Object Model fixtures
45 | │ ├── page-object-fixtures.ts # Fixtures for instantiating page objects
46 | │ └── test-options.ts # Merges various test fixtures (e.g., page object and API fixtures)
47 | |
48 | ├── pages/ # Page Object Model classes
49 | │ ├── adminPanel/ # Page objects for the Admin Panel
50 | │ └── clientSite/ # Page objects for the Client Site
51 | |
52 | ├── tests-data/ # Test data (e.g., JSON, CSV files) - To be populated later.
53 | |
54 | ├── tests/ # Test scripts
55 | │ ├── auth.setup.ts # Global authentication setup
56 | │ ├── AdminPanel/ # E2E tests for the Admin Panel
57 | │ ├── API/ # API tests
58 | │ └── ClientSite/ # E2E tests for the Client Site
59 | |
60 | ├── .gitignore
61 | ├── .prettierrc # Prettier configuration for code formatting
62 | ├── package-lock.json
63 | ├── package.json
64 | ├── playwright.config.ts # Main Playwright configuration
65 | ├── README.md
66 | └── TEST-PLAN.md # Overall test plan document
67 |
68 | **Key Files & Their Purpose:**
69 | - `playwright.config.ts`: Defines global settings, projects, reporters (standard HTML reporter is sufficient), and base URLs.
70 | - `fixture/pom/test-options.ts`: Responsible for merging different sets of fixtures.
71 | - `fixture/api/schemas.ts`: Contains Zod schemas used for validating API request payloads and response bodies.
72 | - `pages/**/*.ts`: Contain Page Object classes, structured as per the guidelines below.
73 | - Any new utility functions or classes should be created in appropriate files within this structure.
74 |
75 | ## Code Style & Best Practices
76 |
77 | **General:**
78 | - Adhere strictly to the settings defined in `.prettierrc`.
79 | - All code must be type-safe. Leverage TypeScript`s features to ensure this.
80 | - Avoid `any` type unless absolutely necessary and provide a justification.
81 | - Use modern JavaScript features (ESNext) where appropriate (e.g., optional chaining, nullish coalescing).
82 | - No commented-out code in the final output. Explanatory comments are acceptable if they clarify complex logic.
83 |
84 | **Playwright Specific:**
85 | 1. **Page Object Model (POM) (`pages/**/*.ts`):**
86 | * Page classes should encapsulate locators (as getters) and methods representing user interactions or assertions for that page/component.
87 | * **Constructor:** Must accept `page: Page` (from Playwright) as its first argument, using the `private` shorthand.
88 | ```
89 |
90 | typescript
91 | constructor(private page: Page) {}
92 |
93 |
94 | ```
95 | * **Locators:**
96 | * Define all locators as public `get` accessors.
97 | * **Prioritize user-facing locators (Playwright`s recommendations):**
98 | 1. `this.page.getByRole()`
99 | 2. `this.page.getByText()`
100 | 3. `this.page.getByLabel()`
101 | 4. `this.page.getByPlaceholder()`
102 | 5. `this.page.getByAltText()`
103 | 6. `this.page.getByTitle()`
104 | * Use `this.page.frameLocator()` for elements within iframes.
105 | * Only use `this.page.locator()` (CSS or XPath) as a last resort when semantic locators are not feasible or stable. If using `this.page.locator()`, prefer `data-testid` attributes for robustness (e.g., `this.page.locator('[data-testid="error-message"]')`).
106 | * Ensure locators are specific enough to avoid ambiguity (e.g., using `.first()` if multiple elements match and it`s intended).
107 | * **Methods:**
108 | * Methods should represent complete user actions or flows (e.g., `publishArticle(...)`, `navigateToEditArticlePage()`) or retrieve page state (e.g., `getPublishErrorMessageText()`).
109 | * Methods performing actions **must include validation** to confirm the action`s success. This can involve:
110 | * Waiting for network responses: `await this.page.waitForResponse(response => ...)`
111 | * Asserting element visibility/state: `await expect(this.page.getByRole('heading', { name: title })).toBeVisible();`
112 | * Checking URL changes or other relevant side effects.
113 | * Provide comprehensive JSDoc comments for all public methods, including:
114 | * A clear description of what the method does.
115 | * `@param` for each parameter with its type and description.
116 | * `@returns {Promise}` for action methods or `Promise` for methods returning data.
117 | * Mention any important pre/post-conditions or potential errors if applicable.
118 | * Avoid methods that perform only a single Playwright action (e.g., a method solely for `await someButton.click()`). Incorporate such actions into larger, more meaningful user flow methods.
119 |
120 | * **Example Page Object (`ArticlePage`):**
121 | ```
122 |
123 | 1. **Tests (`tests/**/*.spec.ts`):**
124 | * Use the merged fixtures from `fixture/pom/test-options.ts` (e.g., `test('should display user dashboard', async ({ articlePage, otherPage }) => { ... })`).
125 | * Tests should be independent and focused on a single piece of functionality or user story.
126 | * Employ web-first assertions (`expect(locator).toBeVisible()`, `expect(page).toHaveURL()`).
127 | * Use built-in configuration objects like `devices` from `playwright.config.ts` when appropriate (e.g., for responsive testing).
128 | * Avoid hardcoded timeouts. Rely on Playwright`s auto-waiting mechanisms and web-first assertions. If a specific timeout is needed for an assertion, pass it as an option: `expect(locator).toBeVisible({ timeout: 5000 });`.
129 | * Group related tests using `test.describe()`.
130 | * Use `test.beforeEach` and `test.afterEach` for setup and teardown logic specific to a group of tests.
131 | * For global setup (like authentication), use the `auth.setup.ts` pattern.
132 |
133 | 2. **API Tests (`tests/API/**/*.spec.ts`):**
134 | * Utilize `apiRequest` context (from fixtures) for making API calls.
135 | * Use Zod schemas from `fixture/api/schemas.ts` to validate request payloads and response structures.
136 | * Verify status codes, headers, and response bodies thoroughly.
137 |
138 | 3. **Fixtures (`fixture/**/*.ts`):**
139 | * Clearly define the purpose and usage of each fixture with JSDoc.
140 | * Ensure fixtures are scoped correctly (worker vs. test) based on their purpose.
141 | * `fixture/pom/page-object-fixture.ts` should define how page objects are instantiated and provided to tests.
142 | * `fixture/api/api-request-fixture.ts` should configure the `APIRequestContext` (e.g., base URL from environment variables).
143 |
144 | ## Output Format & Expectations
145 |
146 | - **Code Generation:** Provide complete, runnable TypeScript code blocks that strictly follow these rules.
147 | - **Explanations:** When asked for explanations or advice, be clear, concise, and provide actionable recommendations aligned with these guidelines.
148 | - **Error Handling in Generated Code:** Generated code should include robust error handling where appropriate (e.g., try-catch for API calls if not handled by Playwright`s `toPass` or similar, checks for element states if complex interactions are involved). Standard Playwright assertions usually handle waits and implicit erroring.
149 | - **Clarity on Ambiguity:** If a user`s prompt is ambiguous or lacks necessary detail to follow these rules, ask clarifying questions before generating code.
150 |
151 | ## Things to Avoid
152 |
153 | - Generating overly complex or monolithic functions/classes. Prefer smaller, focused units that adhere to Single Responsibility Principle.
154 | - Using `page.waitForTimeout()` for arbitrary waits. Rely on web-first assertions and Playwright`s actionability checks.
155 | - Directly manipulating the DOM (e.g., `page.evaluate()` to change styles or content) unless it`s for a specific, justified testing scenario (like mocking complex browser APIs not supported by Playwright).
156 | - Hardcoding sensitive data (credentials, API keys) or environment-specific URLs directly in tests or page objects. Use environment variables (`process.env`) accessed via `playwright.config.ts` or fixtures.
157 | - Suggesting outdated practices or libraries not listed in the Tech Stack.
158 | - Including commented-out code in the final output, unless it`s a JSDoc or a brief, necessary clarification for exceptionally complex logic that cannot be made self-evident through code structure.
159 | - Deviating from the specified POM structure and locator strategies.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Playwright-Framework-Template
2 |
3 | ## Introduction
4 |
5 | Playwright-Framework-Template - This project is based on Microsoft Playwright, Appium, Artillery which enables reliable end-to-end testing, Web testing, API testing, Mobile testing, load testing. It is a single unified for your entire testing suite : UI, API, Mobile, Load testing.
6 |
7 | _☝ If you liked the project, please give a ⭐ on [GitHub](https://github.com/abhaybharti/playwright-framework-template). It will motivate me to add more such project._
8 |
9 | \_☝ If you want new feature to be added or you believe you've encountered a bug [Open an issue] (https://github.com/abhaybharti/playwright-framework-template/issues) .
10 |
11 | ## Core capabilites
12 | - **Cross-Browser Testing**: Chromium, Firefox,WebKit, Edge
13 | - **Multipel Test Types**: UI, API, Mobile, Load Testing, Visual Testing
14 | - **Page Object Model**: Scalable & maintainable test architecture
15 | - **JSON Config**: Build script
16 | - **Dependency Injection**: Custom Fixtures, TypeScript Decorators for clean test & function code
17 | - **Comprehenisve Reporting**: HTML, Log file, Video, Trace, Screenshot,
18 | - **Capture API**: HTML, Log file, Video, Trace, Screenshot,
19 |
20 |
21 |
22 | ## Features
23 |
24 | - Easy to Configure
25 | - Auto wait for all elements & checks
26 | - Generate HTML report
27 | - Generate detailed trace file which helps to debug
28 | - Generate snapshot for each step
29 | - Record video for test case execution
30 | - Support Web automation with support for chrome, Edge, Firefox and Safari
31 | - Support Mobile automation with support for Android native, web and hybrid apps automation
32 | - Support iOS native, web and hybrid apps automation
33 | - Support execution of Web tests on Microsoft Azure (can scale on demand)
34 | - Support API testing (GET, POST, PUT, DELETE HTTP methods)
35 | - Dynamic data handling using external JSON files
36 | - Support taking screenshots
37 | - Run functional test for load testing using Artillery
38 | - Support Serial and Parallel execution
39 | - Environment configuration using .env files
40 |
41 | ## Tech Stack/Libraries Used
42 |
43 | - [PlayWright](https://playwright.dev/) - for web/API automation/Mobile Device Emulation Testing
44 | - [Artillery](https://www.artillery.io/) - for load testing
45 | - [Appium](https://appium.io/docs/en/2.4/) - for mobile app automation
46 | - [ESLint](https://eslint.org/) - pinpoint issues and guide you in rectifying potential problems in both JavaScript and TypeScript.
47 | - [Prettier](https://prettier.io/) - for formatting code & maintain consistent style throughout codebase
48 | - [Dotenv](https://www.dotenv.org/) - to load environment variables from .env file
49 | - [Secrets](https://github.com/du5rte/secrets) - to load secrets from AWS Secrets Manager
50 | - [TypeScript](https://www.typescriptlang.org/) - for type safety
51 | - [Joi](https://github.com/sideway/joi) - for data validation
52 | - [Moment](https://momentjs.com/) - for date & time handling
53 | - [Winston](https://github.com/winstonjs/winston) - for logging
54 | - [Mocha](https://mochajs.org/) - for test runner
55 | - [Chai](https://www.chaijs.com/) - for assertion
56 |
57 |
58 |
59 | ## Getting Started
60 |
61 | ## Project Structure
62 | ```text
63 | playwright-framework-template/
64 | ├── .github/workflows/ # GitHub Actions CI/CD
65 | ├── src/
66 | | └── helper/ # Helpers
67 | | | ├── api/apiHelper.ts # API Helper
68 | | | ├── load/loadHelper.ts # Load testing Helper
69 | | | ├── mobile/mobileHelper.ts # Mobile action Helper
70 | | | └── web/webHelper.ts # Web action Helper
71 | | └── utils/ # Utilities
72 | | | ├── config/ # config files
73 | | | ├── error/ # Centralized error manager
74 | | | ├── reader/ # JSON/Excel reader Helper
75 | | | ├── report/ # Custom report manager
76 | | | └── other/ # Har Analyzer
77 | | └── globals/ # Global setup/teardown/healthcheck scripts
78 | ├── tests/
79 | | ├── fixtures/ # Custom test fixtures
80 | | ├── api/ # API Integration tests
81 | | | └── example/ # example api tests
82 | | ├── e2e/ # E2E tets
83 | | | └── example/ # example e2e tests
84 | | ├── load/ # Load testing
85 | | | └── example/ # example load tests
86 | | ├── resources/ # resource files
87 | | ├── web/ # browser tests
88 | | | └── example/ # example web tests
89 | | └── mobile/ # Mobile tests
90 | | └── example/ # example mobile tests
91 | ├── Dockerfile # Dockerfile
92 | ├── docker-compose.yml # docker-compose.yml
93 | ├── .env # environment variables
94 | ├── .env.example # environment variables example
95 | ├── .gitignore # git ignore
96 | ├── .prettierrc # prettier config
97 | ├── .eslintrc.json # eslint config
98 | ├── package.json # package.json
99 | ├── playwright.config.ts # playwright config
100 | ├── README.md # README.md
101 | └── tsconfig.json # tsconfig.json
102 |
103 |
104 | ### Prerequisite
105 |
106 | - `nodejs`: Download and install Node JS from
107 | > `https://nodejs.org/en/download`
108 | - `Visual Studio Code/WindSurf/Cursor`: Download and install code editor
109 | - `Git`
110 |
111 | ### Installation
112 |
113 | - clone the repo using below URL
114 |
115 | > `https://github.com/abhaybharti/playwright-framework-template.git`
116 |
117 | - Navigate to folder and install dependencies
118 |
119 | > `npm install`
120 |
121 | - For first time installation use below command to download & install required browsers:
122 |
123 | > `npx playwright install`
124 |
125 | - In case you want to do fresh setup of playwright
126 | - Create a folder & run command `npm init playwright@latest`
127 | - select `TypeScript` & select default for other options
128 |
129 |
130 | ## Usage
131 |
132 | - For browser configuration, change required parameters in playwright.config.ts
133 | - To run your suite on MS Azure, copy the below code in `azure-pipeline.yml` and `playwright.service.config.ts` to root folder, update following key & run your suite
134 | - PLAYWRIGHT_SERVICE_ACCESS_TOKEN
135 | - PLAYWRIGHT_SERVICE_URL=XXX
136 |
137 | ## How to generate Playwright code (Playwright Test Generator)
138 |
139 | - run command `npx playwright codegen`
140 | - Browser gets opened & navigate to web app & perform test actions
141 |
142 | Playwright test generator generates tests and pick locator for you. It uses role,text and test ID locators.
143 |
144 | To pick a locator, run the `codegen` command followed by URL, `npx playwright codegen https://opensource-demo.orangehrmlive.com/web/index.php/auth/login`
145 |
146 | ## Writing Tests
147 |
148 | - Create test files in `src/tests` folder
149 |
150 | ## Sample Test
151 |
152 | ### Unit/Integration Testing
153 |
154 | ### Sample Web Test
155 |
156 | > Note: Refer to [sample-web-test](https://github.com/abhaybharti/playwright-framework-template/tree/master/src/tests/web/example)
157 |
158 | Pls go through different `\*.ts` file, which has tests example for different purpose.
159 |
160 | #### Locator Example
161 |
162 | > Note: Refer to [sample-web-test](https://github.com/abhaybharti/playwright-framework-template/tree/master/src/tests/web/example/locator.spec.ts)
163 |
164 | ### Sample Web Load Test
165 |
166 | ### Sample Api Test
167 |
168 | > Note: Refer to [sample-api-test](https://github.com/abhaybharti/playwright-framework-template/tree/master/src/tests/api/example)
169 |
170 | Pls go through different `\*.ts` files, which has tests example for different api tests.
171 |
172 | ### Sample API Load Test
173 |
174 | ### Sample Mobile Test
175 |
176 | ## Run Test
177 |
178 | ### To Run Web Test
179 |
180 | - `npx playwright test (name-of-file.spec.ts) --headed` to run test in ui mode
181 | - `npx playwright test (name-of-file.spec.ts) --headed --config=playwright.config.chrome.ts` to run test in ui mode on chrome browser
182 | - `npx playwright test (name-of-file.spec.ts) --headed --config=playwright.config.firefox.ts` to run test in ui mode on firefox browser
183 | - `npx playwright test (name-of-file.spec.ts) --headed --config=playwright.config.edge.ts` to run test in ui mode on edge browser
184 |
185 | ### To Run Api Test
186 |
187 | ### To Run Mobile Test
188 |
189 | ### To Run Test Multiple Times in Parallel
190 |
191 | `npx playwright test --workers=5 --headed --repeat-each=5`
192 |
193 | - This will run test 5 times, at a time 5 instance will run. `--workers=5` will run 5 instances
194 |
195 | ### To Run Test Multiple Times in Sequence
196 |
197 | `npx playwright test --workers=1 --headed --repeat-each=5`
198 |
199 | - This will run test 5 times, at a time single instance will run, `--repeat-each=5` will run 5 times
200 |
201 | ### To Run Load Test using Artillery & PlayWright Suite
202 |
203 | `artillery run artillery-script.yml --output report.json`
204 |
205 | ### Grouping and Organizing Test Suite in PlayWright
206 |
207 | `npx playwright test --grep @smoke` This will run only test tagged as @smoke
208 |
209 | ## Debug And Analyze
210 |
211 | ### View Trace Result of PlayWright Execution
212 |
213 | - Open `https://trace.playwright.dev`
214 | - Upload `trace.zip` file to above site, it will show trace details
215 |
216 | ### Run test in debug mode
217 |
218 | `npx playwright test UIBasictest.spec.js --debug`
219 |
220 | This will start running script in debug mode & open PlayWright inspector
221 |
222 | ### How to generate load test report using artillery + Playwright
223 |
224 | `artillery report report.json --output report.html`
225 |
226 | #### Run & Generate Report
227 |
228 | ### Best Practices in Test Authoring:
229 |
230 | - `Create isolated test cases`: Each test case should be independent.
231 | - `Write Meaningful Test Case Titles`: Make your test case titles descriptive and meaningful.
232 | - `Follow the AAA (Arrange-Act-Assert) Pattern`: Align your Test-Driven Development (TDD) approach with the clarity of Arrange, Act, and Assert.
233 | - `Maintain Cleanliness`: Separate additional logic from tests for a tidy and focused codebase.
234 |
235 | ## GitHub Actions - created workflow to run test
236 |
237 | ## Contributing
238 |
239 | We love our contributors! Here's how you can contribute:
240 |
241 | - [Open an issue](https://github.com/abhaybharti/playwright-framework-template/issues) if you believe you've encountered a bug.
242 | - Make a [pull request](https://github.com/abhaybharti/playwright-framework-template/pulls) to add new features/make quality-of-life improvements/fix bugs.
243 |
244 |
245 |
246 |
247 |
248 |
249 | g
--------------------------------------------------------------------------------