diff --git a/.gitignore b/.gitignore index b758612..91aa611 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ # Node modules node_modules/ +#custom json report +global/test-run-context.json + + # Output directories dist/ build/ @@ -34,7 +38,6 @@ lcov-report/ *.tsbuildinfo # IDE settings -.vscode/ .idea/ # Mac system file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..61a3106 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + + { + "type": "node", + "request": "launch", + "name": "Debug Playwright Test", + "program": "${workspaceFolder}/node_modules/@playwright/test/cli.js", + "args": [ + "test", + "--headed", + "--project=chromium" + ], + "env": { + "TAG": "@smoke", + "BROWSER": "chromium", + "WORKERS": "1", + "RETRIES": "1", + "TEST_ENV": "qa1" + }, + "console": "integratedTerminal", + "internalConsoleOptions": "openOnSessionStart" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index e4bff6f..6a3c859 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,8 @@ This project uses **GitHub Actions** to automate test execution and reporting. --- +TEST_ENV=qa1 npx playwright test + ## 📄 License diff --git a/package.json b/package.json index f41a177..1d25edf 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "test:allure": "npm run test:params && npm run allure:generate", "test:params": "sh ./run-with-params.sh", "allure:generate": "allure generate ./allure-results --clean -o ./allure-report", - "allure:open": "allure open ./allure-report" + "allure:open": "allure open ./allure-report", + "test:env": "TAG='@smoke' BROWSER='chromium' WORKERS=2 RETRIES=1 TEST_ENV='qa1' npm run test:allure", + "test:env:debug":"TAG='@smoke' BROWSER='chromium' WORKERS=2 RETRIES=1 TEST_ENV='qa1' npx playwright test $@" }, "keywords": [], "author": "", diff --git a/pages/BasePage.ts b/pages/BasePage.ts index 863332d..26ba831 100644 --- a/pages/BasePage.ts +++ b/pages/BasePage.ts @@ -1,75 +1,94 @@ -import { Locator } from '@playwright/test'; -import { logger } from '../utils/logger'; // adjust path based on your project -import { expect } from '@playwright/test'; +import { Locator } from "@playwright/test"; +import { logger } from "../utils/logger"; export class BasePage { - /** - * Waits for an element with retry and timeout support. - */ - async waitForElement( - locator: Locator, - timeout: number = 5000, - maxRetries: number = 3, - delayBetweenRetries: number = 1000 - ): Promise { - let attempt = 0; - while (attempt < maxRetries) { - try { - logger.info(`Attempt ${attempt + 1}: Waiting for element → ${locator.toString()}`); - await locator.waitFor({ timeout }); - logger.info(`Element is ready: ${locator.toString()}`); - return; - } catch (error) { - logger.warn(`Attempt ${attempt + 1} failed: ${(error as Error).message}`); - attempt++; - if (attempt < maxRetries) { - await new Promise(res => setTimeout(res, delayBetweenRetries)); - } else { - logger.error(`Element not found after ${maxRetries} retries: ${locator.toString()}`); - throw new Error(`Failed to locate element after ${maxRetries} retries: ${locator.toString()}`); - } - } - } - } + async clickElement(locator: Locator, wait: boolean = true): Promise { + if (wait) await this.waitForElement(locator); + await locator.click(); + logger.info(`Clicked on element: ${locator.toString()}`); + } - async clickElement(locator: Locator, wait: boolean = true): Promise { - if (wait) await this.waitForElement(locator); - await locator.click(); - logger.info(`Clicked on element: ${locator.toString()}`); - } + async enterText( + locator: Locator, + value: string, + wait: boolean = true + ): Promise { + if (wait) await this.waitForElement(locator); + const response = await locator.fill(value); - async enterText(locator: Locator, value: string, wait: boolean = true): Promise { - if (wait) await this.waitForElement(locator); - const response = await locator.fill(value); - - logger.info(`Filled input ${locator.toString()} with value: ${value}`); - } + logger.info(`Filled input ${locator.toString()} with value: ${value}`); + } - async typeText(locator: Locator, value: string, wait: boolean = true): Promise { - if (wait) await this.waitForElement(locator); - await locator.type(value); - logger.info(`Typed into ${locator.toString()} with value: ${value}`); - } + async typeText( + locator: Locator, + value: string, + wait: boolean = true + ): Promise { + if (wait) await this.waitForElement(locator); + await locator.type(value); + logger.info(`Typed into ${locator.toString()} with value: ${value}`); + } - async selectDropdown(locator: Locator, value: string, wait: boolean = true): Promise { - if (wait) await this.waitForElement(locator); - await locator.selectOption(value); - logger.info(`Selected dropdown value '${value}' on ${locator.toString()}`); - } + async selectDropdown( + locator: Locator, + value: string, + wait: boolean = true + ): Promise { + if (wait) await this.waitForElement(locator); + await locator.selectOption(value); + logger.info(`Selected dropdown value '${value}' on ${locator.toString()}`); + } + + async getElementText( + locator: Locator, + wait: boolean = true + ): Promise { + if (wait) await this.waitForElement(locator); + const text = await locator.textContent(); + logger.info(`Extracted text from ${locator.toString()}: ${text}`); + return text || ""; + } - async getElementText(locator: Locator, wait: boolean = true): Promise { - if (wait) await this.waitForElement(locator); - const text = await locator.textContent(); - logger.info(`Extracted text from ${locator.toString()}: ${text}`); - return text || ''; + async isElementVisible(locator: Locator): Promise { + try { + logger.info(`Verifying the visibility of locator: ${locator.toString()}`); + return await locator.isVisible(); + } catch { + return false; } + } - async isElementVisible(locator: Locator): Promise { - try { - logger.info(`Verifying the visibility of locator: ${locator.toString()}`); - return await locator.isVisible(); - } catch { - return false; + async waitForElement( + locator: Locator, + timeout: number = 5000, + maxRetries: number = 3, + delayBetweenRetries: number = 1000 + ): Promise { + let attempt = 0; + while (attempt < maxRetries) { + try { + logger.info( + `Attempt ${attempt + 1}: Waiting for element → ${locator.toString()}` + ); + await locator.waitFor({ timeout }); + logger.info(`Element is ready: ${locator.toString()}`); + return; + } catch (error) { + logger.warn( + `Attempt ${attempt + 1} failed: ${(error as Error).message}` + ); + attempt++; + if (attempt < maxRetries) { + await new Promise((res) => setTimeout(res, delayBetweenRetries)); + } else { + logger.error( + `Element not found after ${maxRetries} retries: ${locator.toString()}` + ); + throw new Error( + `Failed to locate element after ${maxRetries} retries: ${locator.toString()}` + ); } + } } + } } diff --git a/pages/LoginPage.ts b/pages/LoginPage.ts index 1244988..6f9ebbf 100644 --- a/pages/LoginPage.ts +++ b/pages/LoginPage.ts @@ -1,6 +1,5 @@ import {Page, Locator} from '@playwright/test' import { BasePage } from '../pages/BasePage' -import { AllureHelper } from '../utils/allureHelper'; import { config } from '../utils/config'; export class LoginPage extends BasePage { @@ -30,7 +29,7 @@ export class LoginPage extends BasePage { } async login(userName: string, password: string) { - const response = await this.enterText(this.txtUserName, userName); + await this.enterText(this.txtUserName, userName); await this.enterText(this.txtPassword, password); await this.clickElement(this.btnLogin); } diff --git a/playwright.config.ts b/playwright.config.ts index 938f107..18ba576 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -20,7 +20,7 @@ export default defineConfig({ ], use: { baseURL: process.env.BASE_URL || 'https://example.com', - headless: true, + headless: false, screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'on-first-retry',