diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 5908e4a..6c94711 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -42,6 +42,14 @@ jobs: - name: Checkout repo uses: actions/checkout@v3 + - name: Cache node_modules + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- + - name: Build Docker Image run: docker build -t my-playwright-runner -f Dockerfile.playwright . @@ -79,4 +87,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./allure-report - publish_branch: gh-pages + publish_branch: gh-pages \ No newline at end of file diff --git a/Dockerfile.playwright b/Dockerfile.playwright index 4c5b9f1..e670669 100644 --- a/Dockerfile.playwright +++ b/Dockerfile.playwright @@ -1,30 +1,48 @@ -FROM mcr.microsoft.com/playwright:v1.53.1-jammy +# ---- Build Stage ---- +FROM mcr.microsoft.com/playwright:v1.53.1-jammy AS build -# Install Java 11 (required for Allure) +# Install Java 11 (for Allure) RUN apt-get update && apt-get install -y wget gnupg2 && \ wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /usr/share/keyrings/adoptium.gpg && \ echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb focal main" | tee /etc/apt/sources.list.d/adoptium.list && \ apt-get update && apt-get install -y temurin-11-jdk && \ rm -rf /var/lib/apt/lists/* -# Install Allure CLI globally -RUN npm install -g allure-commandline --force - -# Set JAVA_HOME for Allure -ENV JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64 -ENV PATH=$JAVA_HOME/bin:$PATH - -# Set working directory WORKDIR /app -# Copy only package files first to cache layer +# Copy package files first to leverage Docker caching COPY package*.json ./ # Install Node dependencies RUN npm ci -# Copy the rest of the application +# Copy environment files +COPY env ./env + +# Copy the full application COPY . . -# Default CMD is overridden by GitHub Action's docker run step +# ---- Final Stage ---- +FROM mcr.microsoft.com/playwright:v1.53.1-jammy + +# Install Java 11 and Allure CLI +RUN apt-get update && apt-get install -y wget gnupg2 && \ + wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor -o /usr/share/keyrings/adoptium.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/adoptium.gpg] https://packages.adoptium.net/artifactory/deb focal main" | tee /etc/apt/sources.list.d/adoptium.list && \ + apt-get update && apt-get install -y temurin-11-jdk && \ + npm install -g allure-commandline --force && \ + rm -rf /var/lib/apt/lists/* + +ENV JAVA_HOME=/usr/lib/jvm/temurin-11-jdk-amd64 +ENV PATH=$JAVA_HOME/bin:$PATH + +WORKDIR /app + +# Copy built assets and node_modules from the build stage +COPY --from=build /app/node_modules ./node_modules +COPY --from=build /app/env ./env +COPY --from=build /app/. . +COPY --from=build /app/package*.json ./ + +# Default CMD β€” runs Playwright tests and generates Allure report CMD ["npm", "run", "test:allure"] diff --git a/README.md b/README.md index 1fc6dc4..e4bff6f 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Supports cross-browser testing, rich reporting with **Allure**, Dockerized execu ## πŸ“š Documentation +- [ARCHITECTURE](docs/ARCHITECTURE.md) - [BasePage](docs/BasePage.md) - [LoginPage](docs/LoginPage.md) - [HomePage](docs/HomePage.md) @@ -96,7 +97,20 @@ e2e-playwright-framework/ β”œβ”€β”€ DockerFile.playwright # Dockerfile for CI/CD β”œβ”€β”€ .github/workflows/ # GitHub Actions workflows ``` +## 🌐 Overriding baseURL +- By default, `baseURL` is loaded from your environment file (e.g., `env/.env.dev1`). +- To override for a specific run, set the `BASE_URL` variable: + +```sh +BASE_URL=https://another-url.com npm test +``` + +- Or use a different environment: + +```sh +TEST_ENV=dev1 npm test +``` --- ## βœ… GitHub Actions CI/CD @@ -148,6 +162,8 @@ This project uses **GitHub Actions** to automate test execution and reporting. --- + + ## πŸ“„ License MIT Β© 2025 Ramakrishna Jangatisetty \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..4b7926d --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,141 @@ +# πŸ“˜ Playwright E2E Framework - Architecture Documentation + +## πŸ“ Project Structure Overview + +``` +β”œβ”€β”€ .github/workflows/ +β”‚ └── playwright.yml # GitHub Actions workflow with dynamic test config +β”œβ”€β”€ env/ +β”‚ └── .env.qa1 # Environment-specific config +β”œβ”€β”€ global/ +β”‚ β”œβ”€β”€ setup.ts # Global setup for context and metadata +β”‚ β”œβ”€β”€ teardown.ts # Global teardown to enrich test results +β”œβ”€β”€ pages/ +β”‚ β”œβ”€β”€ BasePage.ts # Base class with reusable Playwright actions +β”‚ └── LoginPage.ts # Page Object Model (POM) for Login page +β”œβ”€β”€ playwright-report/ # Allure and JSON report outputs +β”œβ”€β”€ tests/ +β”‚ β”œβ”€β”€ testsetup.ts # Custom test hooks with logger integration +β”‚ └── ui/ +β”‚ └── login.spec.ts # Sample test for login validation +β”œβ”€β”€ utils/ +β”‚ β”œβ”€β”€ allureHelper.ts # Helper for tagging tests in Allure +β”‚ β”œβ”€β”€ config.ts # Typed environment config reader +β”‚ β”œβ”€β”€ loadEnv.ts # Dynamic .env loader by TEST_ENV +β”‚ └── logger.ts # Winston logger +β”œβ”€β”€ Dockerfile.playwright # Multi-stage Dockerfile with Java + Allure setup +β”œβ”€β”€ package.json +β”œβ”€β”€ playwright.config.ts # Main test runner config +└── run-with-params.sh # Shell script for running tests with dynamic env +``` + +--- + +## πŸ”§ Core Technologies + +* **Playwright** for browser automation +* **TypeScript** for type safety and maintainability +* **Allure Reporter** for rich test visualization +* **Docker** to encapsulate execution +* **GitHub Actions** for CI/CD with test param flexibility + +--- + +## πŸ—οΈ Key Components Explained + +### βœ… playwright.config.ts + +* Configures base test settings, retries, reporters, and project runners. +* Includes: + + * Global hooks (`globalSetup`, `globalTeardown`) + * `json` reporter for enriching data in teardown + * Allure and HTML reports + +### βœ… global/setup.ts + +* Loads environment from `.env.{env}` using `loadEnv.ts` +* Initializes metadata like testRunId, startTime +* Writes `test-run-context.json` + +### βœ… global/teardown.ts + +* Parses `test-results.json` +* Enhances each test case with: + + * Duration, tags, retry count + * Failure message + stack trace if applicable + * History stub fields (e.g., flaky count, last failed) +* Merges data into `enriched-test-results.json` + +### βœ… testsetup.ts + +* Adds `beforeEach` and `afterEach` logging using Winston +* Replaces Playwright's default test + +### βœ… Dockerfile.playwright + +* Multi-stage: + + * Stage 1 installs deps and caches builds + * Stage 2 copies node\_modules, env, and source +* Includes Java and Allure CLI for reporting + +### βœ… GitHub Actions Workflow + +* Supports inputs: `tag`, `browser`, `workers`, `retries`, `test_env` +* Uses `run-with-params.sh` inside container +* Publishes Allure report to GitHub Pages + +### βœ… env/.env.qa1 + +* Customizes: + + * `URL=https://saucedemo.com` + * `USERNAME`, `PASSWORD` + +### βœ… utils/config.ts + +* Loads env vars with type safety +* Validates missing values early + +### βœ… utils/logger.ts + +* Uses Winston logger with ISO timestamps + +### βœ… run-with-params.sh + +* Dynamically runs Playwright with flags like: + +```bash +npx playwright test \ + --project="$BROWSER" \ + --workers="$WORKERS" \ + --retries="$RETRIES" \ + $TAG +``` + +--- + +## πŸ“ˆ Future-Ready Features (Planned or Extendable) + +* πŸ” Store enriched JSON to DynamoDB/Postgres +* πŸ“Š Build custom dashboards from enriched test result metadata +* πŸ“€ Slack/GitHub notification integration +* πŸ€– Integrate GPT-based RCA suggestions per failure +* πŸ”„ Automatic test rerun for flaky failures + +--- + +## πŸ™Œ Usage Summary + +* `TEST_ENV=qa1 npx playwright test` (local) +* `docker build -t my-playwright-runner .` +* `docker run --rm -e TEST_ENV=qa1 my-playwright-runner` +* CI: Trigger via GitHub Actions with tag and env + +--- + +## βœ… Author + +Maintained by [@ramjangatisetty](https://github.com/ramjangatisetty) with ❀️ for the testing community. diff --git a/env/.env.dev1 b/env/.env.dev1 index db053b5..43fa645 100644 --- a/env/.env.dev1 +++ b/env/.env.dev1 @@ -1,3 +1,3 @@ -URL=https://saucedemo.com +BASE_URL=https://saucedemo.com USERNAME=standard_user PASSWORD=secret_sauce \ No newline at end of file diff --git a/env/.env.dev2 b/env/.env.dev2 index db053b5..43fa645 100644 --- a/env/.env.dev2 +++ b/env/.env.dev2 @@ -1,3 +1,3 @@ -URL=https://saucedemo.com +BASE_URL=https://saucedemo.com USERNAME=standard_user PASSWORD=secret_sauce \ No newline at end of file diff --git a/env/.env.qa1 b/env/.env.qa1 index db053b5..43fa645 100644 --- a/env/.env.qa1 +++ b/env/.env.qa1 @@ -1,3 +1,3 @@ -URL=https://saucedemo.com +BASE_URL=https://saucedemo.com USERNAME=standard_user PASSWORD=secret_sauce \ No newline at end of file diff --git a/env/.env.qa2 b/env/.env.qa2 index db053b5..43fa645 100644 --- a/env/.env.qa2 +++ b/env/.env.qa2 @@ -1,3 +1,3 @@ -URL=https://saucedemo.com +BASE_URL=https://saucedemo.com USERNAME=standard_user PASSWORD=secret_sauce \ No newline at end of file diff --git a/global/test-run-context.json b/global/test-run-context.json index d9b57e2..b8aed53 100644 --- a/global/test-run-context.json +++ b/global/test-run-context.json @@ -1,6 +1,6 @@ { - "testRunId": "run_20250628182021939", - "startTime": "2025-06-28T18:20:21.939Z", + "testRunId": "run_20250628215003653", + "startTime": "2025-06-28T21:50:03.653Z", "environment": "qa1", "project": "e2e-playwright-typescript-framework", "status": "in-progress" diff --git a/pages/LoginPage.ts b/pages/LoginPage.ts index c1690f6..1244988 100644 --- a/pages/LoginPage.ts +++ b/pages/LoginPage.ts @@ -21,7 +21,7 @@ export class LoginPage extends BasePage { } async goto() { - await this.page.goto(config.url) + await this.page.goto(config.baseUrl) } constructor(page: Page) { diff --git a/playwright.config.ts b/playwright.config.ts index daf66b0..938f107 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,6 +1,13 @@ import { defineConfig } from '@playwright/test'; +import * as dotenvFlow from 'dotenv-flow'; import path from 'path'; +// Load env variables before config is defined +dotenvFlow.config({ + path: path.resolve(__dirname, 'env'), + node_env: process.env.TEST_ENV || 'local' +}); + export default defineConfig({ testDir: './tests', workers: 2, @@ -19,9 +26,11 @@ export default defineConfig({ trace: 'on-first-retry', }, projects: [ - { name: 'Chromium', use: { browserName: 'chromium' } } + { name: 'Chromium', use: { browserName: 'chromium' } }, + { name: 'Firefox', use: { browserName: 'firefox' } }, + { name: 'WebKit', use: { browserName: 'webkit' } } ], globalSetup: require.resolve('./global/setup.ts'), globalTeardown: require.resolve('./global/teardown.ts'), -}); +}); \ No newline at end of file diff --git a/tests/setup.ts b/tests/setup.ts index b7827c3..e229634 100644 --- a/tests/setup.ts +++ b/tests/setup.ts @@ -1,11 +1,26 @@ -import { test as base, expect, type Page, type TestInfo } from '@playwright/test'; -import { logger } from '../utils/logger'; +/** + * Custom Playwright test setup. + * + * This file exports a customized `test` and `expect` for use in all test files. + * It wraps Playwright's base test to add common hooks, logging, and shared fixtures. + * + * Usage: + * Import `test` and `expect` from this file in your test specs using a relative path: + * import { test, expect } from '../setup'; + * + * Why not use a path alias? + * Playwright and Node.js do not resolve TypeScript path aliases at runtime by default. + * Using relative imports ensures compatibility in all environments. + * + * Contributors: + * - Add shared hooks, fixtures, or logging here to apply them across all tests. + * - Do not import `@playwright/test` directly in your test filesβ€”use this setup instead. + */ -base.beforeEach(async ({ page }, testInfo) => { - logger.info(`πŸš€ Starting test: ${testInfo.title}`); - logger.info(`πŸ”– Tags: ${testInfo.annotations.map(a => a.type).join(', ') || 'None'}`); -}); +import { test as base, expect } from '@playwright/test'; +import { logger } from '../utils/logger'; +// Example: Add a global afterEach hook for logging base.afterEach(async ({ page }, testInfo) => { const status = testInfo.status?.toUpperCase(); const duration = `${testInfo.duration}ms`; @@ -20,4 +35,4 @@ base.afterEach(async ({ page }, testInfo) => { }); export const test = base; -export { expect }; +export { expect }; \ No newline at end of file diff --git a/utils/config.ts b/utils/config.ts index f9616e9..3c5d174 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -1,16 +1,16 @@ -import * as dotenv from 'dotenv'; -import * as path from 'path'; +import * as dotenvFlow from 'dotenv-flow'; -// Determine the environment (default to 'local' if not provided) const envName = process.env.TEST_ENV || 'local'; console.log("Environment Name is: " + envName); -// Load the corresponding .env file from /env folder -console.log(`../../env/.env.${envName}`); -dotenv.config({ path: path.resolve(__dirname, `../env/.env.${envName}`) }); +// Load .env files using dotenv-flow +dotenvFlow.config({ + node_env: envName, // This will load .env.qa1, .env.dev, etc. + path: `${__dirname}/../env` +}); interface TestConfig { - url: string; + baseUrl: string; username: string; password: string; } @@ -23,7 +23,7 @@ function assertEnvVar(name: string, value: string | undefined): string { } export const config: TestConfig = { - url: assertEnvVar('URL', process.env.URL), + baseUrl: assertEnvVar('BASE_URL', process.env.BASE_URL), username: assertEnvVar('USERNAME', process.env.USERNAME), password: assertEnvVar('PASSWORD', process.env.PASSWORD), }; \ No newline at end of file