End-to-End (E2E) testing pipeline The article provides a guide on building an End-to-End (E2E) testing pipeline using Playwright and GitHub Actions, explaining how to test applications from a user's perspective rather than testing individual functions. It covers setting up the project structure, writing test scripts for user interactions like login and navigation, and configuring the pipeline to run automatically on code pushes or pull requests. The guide also includes advanced features such as auto-capturing screenshots and videos on test failures, running tests against production URLs, and best practices for writing reliable tests. Let’s build a real End-to-End E2E testing pipeline like teams use in production using Playwright recommended and GitHub Actions. I’ll show you: E2E End-to-End testing means: β€œTest your app like a real user would use it.” Instead of testing functions, you test: πŸ‘‰ We’ll use Playwright industry standard in 2025 bash id="pw1" npm init playwright@latest When prompted choose: JavaScript or TypeScript TS recommended Tests folder: tests GitHub Actions: YES --- πŸ“ 2. Project structure plaintext id="pw2" my-app/ β”œβ”€β”€ tests/ β”‚ β”œβ”€β”€ example.spec.ts β”œβ”€β”€ playwright.config.ts β”œβ”€β”€ package.json ts id="test1" import { test, expect } from '@playwright/test'; test 'user can login successfully', async { page } = { await page.goto 'http://localhost:3000/login' ; await page.fill 'input name="email" ', 'test@example.com' ; await page.fill 'input name="password" ', 'password123' ; await page.click 'button type="submit" ' ; await expect page .toHaveURL /dashboard/ ; } ; --- πŸš€ 4. Run tests locally bash id="run1" npx playwright test Open UI mode very useful : bash id="ui1" npx playwright test --ui --- πŸ“Έ 5. Auto screenshots on failure Playwright automatically captures: screenshots videos traces Enable in config: ts id="cfg1" use: { screenshot: 'only-on-failure', video: 'retain-on-failure', trace: 'on-first-retry' } .github/workflows/e2e.yml yaml id="ci1" name: E2E Tests Playwright on: push: branches: main pull request: branches: main jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 20 - name: Install dependencies run: npm install - name: Install Playwright browsers run: npx playwright install --with-deps - name: Run Playwright tests run: npx playwright test --- 🧠 7. What happens in CI plaintext id="flow1" Push code ↓ GitHub Actions starts ↓ Install dependencies ↓ Install browsers Chromium, Firefox, WebKit ↓ Run E2E tests ↓ Pass β†’ allow merge Fail β†’ block PR + show report Enable report: ts id="rep1" reporter: 'html' , 'list' Then in CI: yaml id="rep2" - name: Upload Playwright report uses: actions/upload-artifact@v4 if: always with: name: playwright-report path: playwright-report Instead of localhost: ts id="prod1" await page.goto 'https://your-app.vercel.app/login' ; πŸ‘‰ This turns it into true production E2E testing --- πŸ§ͺ 10. Advanced real-world test examples --- 🟒 UI navigation test ts id="nav1" test 'navigate to dashboard', async { page } = { await page.goto '/' ; await page.click 'text=Dashboard' ; await expect page .toHaveURL /dashboard/ ; } ; ts id="form1" test 'shows error for empty email', async { page } = { await page.goto '/login' ; await page.click 'button type="submit" ' ; await expect page.locator '.error' .toContainText 'Email is required' ; } ; --- πŸ”΅ API + UI combined test ts id="api1" test 'data loads after API call', async { page } = { await page.goto '/dashboard' ; await expect page.locator '.loading' .toBeHidden ; await expect page.locator '.chart' .toBeVisible ; } ; yaml id="bp1" ts id="rt1" retries: 2 --- ⚠️ 12. Common mistakes ❌ Testing implementation instead of behavior Bad: ts id="bad1" expect component.state .toBe true Good: ts id="good1" expect page .toHaveText 'Welcome' --- ❌ No stable selectors Use: html id="sel1" data-testid="login-button" Then: ts id="sel2" page.getByTestId 'login-button' --- ❌ Running E2E without CI Always run in GitHub Actions --- 🧠 Final architecture plaintext id="final1" Push / PR ↓ CI unit tests ↓ E2E tests Playwright ↓ Build app ↓ Deploy to staging/production ↓ Report + screenshots stored in GitHub You now have: