A comprehensive end-to-end test automation framework for web applications built with Microsoft Playwright.
- Features
- Quick Start
- Prerequisites
- Installation
- Configuration
- Running Tests
- Test Development
- CI/CD Integration
- Docker Support
- Project Structure
- Best Practices
- Troubleshooting
- Contributing
- Cross-browser Testing: Chromium, Firefox, and WebKit support
- Multi-environment Support: Development, Staging, and UAT environments
- Session Management: Persistent authentication across test runs
- Parallel Execution: Optimized test performance with configurable workers
- Rich Reporting: HTML reports with screenshots, videos, and traces
- Docker Support: Containerized test execution for CI/CD pipelines
- TypeScript Support: Full type safety and IntelliSense
- Page Object Model: Maintainable and scalable test architecture
- Auto-retry Logic: Configurable retry mechanisms for flaky tests
This repository demonstrates a real-world migration from Cypress to Playwright, showcasing the evolution of our testing strategy:
- Legacy Framework: Cypress (JavaScript/TypeScript)
- Modern Framework: Playwright (TypeScript)
- Migration Strategy: Gradual transition with parallel implementation
- Timeline: Progressive migration allowing for comparison and validation
- Performance: Playwright's faster execution and better parallelization
- Cross-browser Support: Native multi-browser testing capabilities
- Modern Architecture: Better TypeScript support and API design
- Maintenance: Reduced flakiness and improved reliability
- Developer Experience: Enhanced debugging and reporting features
βββ playwright/ # Modern Playwright implementation
β βββ ClientPortal/ # Main application tests
β βββ utils/ # Shared utilities and services
β βββ config/ # Playwright configuration
βββ cypress/ # Legacy Cypress implementation
β βββ e2e/ # End-to-end tests
β βββ support/ # Custom commands and utilities
β βββ fixtures/ # Test data and fixtures
βββ docs/ # Documentation and guides
- Side-by-side Comparison: Both frameworks available for evaluation
- Knowledge Transfer: Team can learn from both implementations
- Risk Mitigation: Gradual transition reduces deployment risks
- Best Practices: Demonstrates framework selection criteria
- Real-world Experience: Shows actual migration challenges and solutions
# Run Playwright tests (primary)
npm run test:playwright
# Run Cypress tests (legacy)
npm run test:cypress
# Run both for comparison
npm run test:all# Clone and setup
git clone <repository-url>
cd web-automation-framework
# Install dependencies
npm install
# Install Playwright browsers
npx playwright install
# Configure environment
cp playwright/.env.example playwright/.env
# Edit playwright/.env with your credentials
# Run tests
npm run test:playwright- Node.js: >= 18.0.0
- npm: >= 8.0.0
- Operating System: Windows 10+, macOS 10.15+, or Ubuntu 18.04+
- Memory: Minimum 4GB RAM (8GB recommended)
- Application Client Portal credentials
- Application Back Office access
- GitHub Personal Access Token (for private packages)
npm install# Install all browsers
npx playwright install
# Install specific browser only
npx playwright install chromium# Check Playwright version
npx playwright --version
# List available tests
npm run test:playwright -- --listCreate playwright/.env file with your configuration:
# Client Portal Configuration
CP_BASE_URL={CLIENT_PORTAL_URL}
CP_USERNAME=your.email@company.com
CP_PASSWORD=your_secure_password
CP_OTPSECRET=your_otp_secret_key
# Back Office Configuration
BO_BASE_URL={BACK_OFFICE_URL}
BO_USERNAME=your.backoffice@company.com
BO_PASSWORD=your_bo_password
BO_OTPSECRET=your_bo_otp_secret
# Back Office API Configuration
BOAPI_CLIENT_ID=your_client_id
BOAPI_CLIENT_SECRET=your_client_secret
BOAPI_SCOPE=your_api_scope
# GitHub Token (required for private packages)
GITHUB_TOKEN=ghp_your_github_token_hereThe project requires a GitHub Personal Access Token to access private packages. Follow these steps to set it up:
- Navigate to GitHub Settings > Personal Access Tokens
- Click "Generate new token" β "Generate new token (classic)"
- Provide a descriptive name (e.g., "Web Automation Framework")
- Set expiration (recommended: 90 days or custom)
Select the following scopes for your token:
- β
read:packages- Required to download private npm packages - β
repo- Required if accessing private repositories β οΈ Additional scopes - Add others as needed for your specific use case
After creating the token:
- Enable SAML SSO: Click "Enable SSO" next to the organization
- Authorize the token: Complete the SAML authentication flow
- Verify access: Ensure the token shows "Authorized" for the organization
Update your playwright/.env file:
# Replace the placeholder with your actual token
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxFor npm to access the token via .npmrc, you must set it as a permanent system environment variable:
# Using setx command (simple)
setx GITHUB_TOKEN "ghp_your_actual_token_here"
# Using .NET Environment class (advanced)
[System.Environment]::SetEnvironmentVariable('GITHUB_TOKEN', 'ghp_your_actual_token_here', [System.EnvironmentVariableTarget]::User)Verification:
# Restart PowerShell, then check if variable is accessible
$env:GITHUB_TOKEN# Add to ~/.bashrc, ~/.zshrc, or ~/.profile
echo 'export GITHUB_TOKEN=ghp_your_actual_token_here' >> ~/.bashrc
# Reload shell configuration
source ~/.bashrcTest your token configuration:
# Test npm authentication with GitHub packages
npm config set @your-org:registry https://npm.pkg.github.com
npm config set //npm.pkg.github.com/:_authToken ${GITHUB_TOKEN}
# Verify access to private packages
npm view @your-org/your-package version- π Never commit tokens to version control
- π Rotate tokens regularly (every 90 days recommended)
- π Use descriptive names to track token usage
- π« Revoke unused tokens immediately
- π₯ Use organization secrets in CI/CD pipelines
Authentication Failed:
# Check if token is properly set
echo $GITHUB_TOKEN
# Verify token permissions
curl -H "Authorization: token $GITHUB_TOKEN" https://api.github.com/userPackage Access Denied:
- Ensure SAML SSO is enabled for the token
- Verify
read:packagesscope is selected - Check if you're a member of the organization
Token Expired:
- Create a new token following the same steps
- Update the
GITHUB_TOKENvalue in your.envfile - Consider setting a longer expiration period
The main configuration is in playwright/playwright.config.ts:
export default defineConfig({
testDir: './ClientPortal/tests',
timeout: 90_000,
retries: 1,
workers: 1, // Adjust based on your system
projects: [
{
name: 'ClientPortal',
testDir: './ClientPortal/tests',
use: { baseURL: CP_BASE_URL }
},
{
name: 'BackOffice',
testDir: './BackOffice/tests',
use: { baseURL: BO_BASE_URL }
}
]
});# Run all tests
npm run test:playwright
# Run tests in UI mode (interactive)
npm run playwright:ui
# Run specific test file
npx playwright test ClientPortal/tests/fiat-transfer-in.spec.ts
# Run tests with specific browser
npx playwright test --project=ClientPortal
# Run tests in headed mode (visible browser)
npx playwright test --headed
# Run tests with debug mode
npx playwright test --debug# Development environment
ENVIRONMENT=dev npm run test:playwright
# Staging environment (default)
npm run test:playwright
# UAT environment
ENVIRONMENT=uat npm run test:playwright# Run tests by tag
npx playwright test --grep "@smoke"
# Run tests by title pattern
npx playwright test --grep "Transfer-In"
# Exclude specific tests
npx playwright test --grep-invert "@flaky"import { test, expect } from '@playwright/test';
import { ClientPortalPage } from '../pages/ClientPortalPage';
test.describe('Asset Transfer', () => {
test('should complete transfer-in instruction @smoke', async ({ page }) => {
const clientPortal = new ClientPortalPage(page);
await clientPortal.navigateToTransfers();
await clientPortal.createTransferIn({
amount: '1000',
sourceOfFunds: 'Investment proceeds'
});
await expect(page.locator('[data-testid="success-message"]'))
.toBeVisible();
});
});export class ClientPortalPage {
constructor(private page: Page) {}
async navigateToTransfers() {
await this.page.click('[data-testid="transfers-menu"]');
await this.page.waitForURL('**/transfers');
}
async createTransferIn(options: TransferOptions) {
await this.page.fill('[data-testid="amount-input"]', options.amount);
await this.page.selectOption('[data-testid="source-select"]', options.sourceOfFunds);
await this.page.click('[data-testid="submit-button"]');
}
}// fixtures/test-data.ts
export const TestData = {
validUser: {
username: 'test@example.com',
password: 'SecurePass123!'
},
transferAmounts: {
small: '100',
medium: '1000',
large: '10000'
}
};name: Playwright Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npm run test:playwright
env:
CP_USERNAME: ${{ secrets.CP_USERNAME }}
CP_PASSWORD: ${{ secrets.CP_PASSWORD }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright/reports/trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18'
- script: |
npm ci
npx playwright install --with-deps
displayName: 'Install dependencies'
- script: npm run test:playwright
displayName: 'Run Playwright tests'
env:
CP_USERNAME: $(CP_USERNAME)
CP_PASSWORD: $(CP_PASSWORD)
GITHUB_TOKEN: $(GITHUB_TOKEN)
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'playwright/reports/junit/results.xml'# Build Docker image
docker build -t playwright-tests .
# Run tests in container
docker run --rm \
--env-file playwright/.env \
-e ENVIRONMENT=staging \
playwright-tests
# Run with volume mounts for reports
docker run --rm \
--env-file playwright/.env \
-v $(pwd)/reports:/app/playwright/reports \
playwright-testsversion: '3.8'
services:
playwright-tests:
build: .
environment:
- ENVIRONMENT=staging
env_file:
- playwright/.env
volumes:
- ./reports:/app/playwright/reportsWeb_Automation_UI/
βββ playwright/ # π Modern Playwright Framework
β βββ ClientPortal/
β β βββ tests/ # Client Portal test files
β β βββ pages/ # Page Object Models
β β βββ fixtures/ # Test data and session files
β β βββ utils/ # Utility functions
β βββ BackOffice/
β β βββ tests/ # Back Office test files
β β βββ pages/ # Page Object Models
β β βββ utils/ # Utility functions
β βββ reports/ # Test execution reports
β βββ artifacts/ # Screenshots, videos, traces
β βββ global-setup.ts # Global test setup
β βββ playwright.config.ts # Main configuration
β βββ .env # Environment variables
βββ cypress/ # π Legacy Cypress Framework
β βββ e2e/ # End-to-end test files
β βββ support/ # Custom commands and utilities
β βββ fixtures/ # Test data and fixtures
β βββ cypress.config.*.ts # Cypress configurations
βββ docs/
β βββ README.md # This file
β βββ DOCKER.md # Docker usage guide
β βββ TESTING_WRAPPERS.md # Component testing guide
βββ package.json # Dependencies and scripts
βββ Dockerfile # Container configuration
βββ .dockerignore # Docker build optimization
π Migration Note: This repository contains both Playwright (modern) and Cypress (legacy) implementations, demonstrating a real-world framework migration. See the Framework Migration section for details.
- Group related tests using
test.describe() - Use descriptive test names that explain the expected behavior
- Tag tests appropriately (@smoke, @regression, @api)
- Keep tests independent - avoid dependencies between tests
// Prefer data-testid attributes
await page.click('[data-testid="submit-button"]');
// Use role-based selectors
await page.click('button:has-text("Submit")');
// Avoid fragile CSS selectors
// β await page.click('.btn-primary.large');
// β
await page.click('[data-testid="primary-submit-btn"]');// Use specific assertions
await expect(page.locator('[data-testid="message"]'))
.toHaveText('Success');
// Wait for conditions
await expect(page.locator('[data-testid="loader"]'))
.toBeHidden();
// Soft assertions for multiple checks
await expect.soft(page.locator('#name')).toBeVisible();
await expect.soft(page.locator('#email')).toBeVisible();test('should handle network failures gracefully', async ({ page }) => {
// Simulate network conditions
await page.route('**/api/**', route => route.abort());
await page.goto('/transfers');
await expect(page.locator('[data-testid="error-message"]'))
.toBeVisible();
});# Clear browser cache and reinstall
npx playwright install --force
# Install system dependencies (Linux)
npx playwright install-deps# Verify environment loading
node -e "require('dotenv').config({path: './playwright/.env'}); console.log(process.env.CP_BASE_URL);"// Increase timeout for specific test
test('slow operation', async ({ page }) => {
test.setTimeout(120_000); // 2 minutes
// ... test code
});# Run single test in debug mode
npx playwright test --debug transfer-in.spec.ts
# Generate trace files
npx playwright test --trace on- Use
page.waitForLoadState()for reliable page loads - Implement proper wait strategies instead of fixed delays
- Optimize test data setup using global setup
- Use parallel execution where appropriate
- Fork the repository
- Create a feature branch:
git checkout -b feature/new-test-suite - Write tests following conventions
- Run test suite:
npm run test:playwright - Submit pull request
- TypeScript: Use strict type checking
- ESLint: Follow configured linting rules
- Prettier: Use for code formatting
- Naming: Use descriptive names for tests and functions
- Tests pass locally
- New tests added for new functionality
- Documentation updated if needed
- No sensitive data in commits
- Code follows existing patterns
- Issues: Report bugs via GitHub Issues
- Documentation: Check
/docsfolder for additional guides - Playwright Docs: Official Playwright Documentation
Built with β€οΈ using Microsoft Playwright