Skip to main content
Glama
test-automation-2025.md6.76 kB
# Test Automation 2025: Playwright, Cypress, CI/CD **Updated**: 2025-11-23 | **Stack**: Playwright, Cypress, Pytest, K6 --- ## Framework Comparison | Feature | Playwright | Cypress | Selenium | |---------|-----------|---------|----------| | Speed | ⚡⚡⚡ | ⚡⚡ | ⚡ | | Multi-browser | ✅ Chrome, Firefox, Safari, Edge | ✅ Chrome, Firefox, Edge | ✅ All | | Auto-wait | ✅ | ✅ | ❌ | | Network interception | ✅ | ✅ | ❌ | | Parallelization | ✅ Built-in | ✅ Paid | ⚠️ Grid | | Learning curve | Medium | Easy | Hard | **Recommendation**: Playwright for new projects (2025 standard) --- ## Playwright Best Practices ### Page Object Model ```typescript // pages/LoginPage.ts import { Page, Locator } from '@playwright/test'; export class LoginPage { readonly page: Page; readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; constructor(page: Page) { this.page = page; this.emailInput = page.getByLabel('Email'); this.passwordInput = page.getByLabel('Password'); this.submitButton = page.getByRole('button', { name: 'Sign in' }); } async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); } } // tests/login.spec.ts import { test, expect } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; test('successful login', async ({ page }) => { const loginPage = new LoginPage(page); await page.goto('/login'); await loginPage.login('user@example.com', 'password123'); await expect(page).toHaveURL('/dashboard'); }); ``` ### Fixtures & Hooks ```typescript // fixtures/auth.ts import { test as base } from '@playwright/test'; export const test = base.extend({ authenticatedPage: async ({ page }, use) => { // Login before each test await page.goto('/login'); await page.fill('[name=email]', process.env.TEST_EMAIL!); await page.fill('[name=password]', process.env.TEST_PASSWORD!); await page.click('button[type=submit]'); await page.waitForURL('/dashboard'); await use(page); } }); // tests/dashboard.spec.ts test('view dashboard', async ({ authenticatedPage }) => { // Already logged in! await expect(authenticatedPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); }); ``` ### Network Interception ```typescript test('mock API response', async ({ page }) => { await page.route('**/api/users', async route => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: 1, name: 'Test User' } ]) }); }); await page.goto('/users'); await expect(page.getByText('Test User')).toBeVisible(); }); test('wait for API call', async ({ page }) => { const responsePromise = page.waitForResponse('**/api/users'); await page.click('button'); const response = await responsePromise; expect(response.status()).toBe(200); }); ``` --- ## Cypress Patterns ### Custom Commands ```typescript // cypress/support/commands.ts Cypress.Commands.add('login', (email: string, password: string) => { cy.session([email, password], () => { cy.visit('/login'); cy.get('[name=email]').type(email); cy.get('[name=password]').type(password); cy.get('button[type=submit]').click(); cy.url().should('include', '/dashboard'); }); }); // cypress/e2e/dashboard.cy.ts describe('Dashboard', () => { beforeEach(() => { cy.login('user@example.com', 'password123'); }); it('displays user name', () => { cy.visit('/dashboard'); cy.contains('Welcome, Test User').should('be.visible'); }); }); ``` --- ## API Testing (Pytest + Requests) ```python import pytest import requests BASE_URL = "https://api.example.com" @pytest.fixture def auth_headers(): response = requests.post(f"{BASE_URL}/auth/login", json={ "email": "test@example.com", "password": "password123" }) token = response.json()["token"] return {"Authorization": f"Bearer {token}"} def test_get_users(auth_headers): response = requests.get(f"{BASE_URL}/users", headers=auth_headers) assert response.status_code == 200 assert len(response.json()) > 0 assert "name" in response.json()[0] def test_create_user(auth_headers): payload = { "name": "New User", "email": "new@example.com" } response = requests.post(f"{BASE_URL}/users", json=payload, headers=auth_headers) assert response.status_code == 201 assert response.json()["name"] == "New User" ``` --- ## Performance Testing (K6) ```javascript // load-test.js import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { stages: [ { duration: '30s', target: 10 }, // Ramp up { duration: '1m', target: 50 }, // Stay at 50 users { duration: '30s', target: 0 }, // Ramp down ], thresholds: { http_req_duration: ['p(95)<500'], // 95% < 500ms http_req_failed: ['rate<0.01'], // <1% failures }, }; export default function () { const res = http.get('https://api.example.com/users'); check(res, { 'status is 200': (r) => r.status === 200, 'response time < 500ms': (r) => r.timings.duration < 500, }); sleep(1); } ``` --- ## CI/CD Integration ### GitHub Actions ```yaml # .github/workflows/e2e.yml name: E2E Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install dependencies run: npm ci - name: Install Playwright browsers run: npx playwright install --with-deps - name: Run tests run: npx playwright test - name: Upload test results if: always() uses: actions/upload-artifact@v4 with: name: playwright-report path: playwright-report/ ``` --- ## Test Strategy Pyramid ``` /\ / \ E2E (10%) /────\ / \ Integration (30%) /────────\ / \ Unit (60%) /____________\ E2E: Critical user flows (login, checkout) Integration: API contracts, component interactions Unit: Business logic, utilities ``` --- ## Key Metrics ```markdown Test Coverage: >80% (unit), >60% (E2E) Flakiness Rate: <2% Execution Time: <10 min (CI) Parallel Execution: 4-8 workers Test-to-Code Ratio: 1:3 ``` --- ## References - Playwright Documentation - Cypress Best Practices - "The Art of Software Testing" - Glenford Myers **Related**: `ci-cd.md`, `test-driven-development.md`

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/seanshin0214/persona-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server