Files
2025-11-29 18:25:05 +08:00

8.9 KiB

Run E2E Tests Command

Set up and execute end-to-end tests using Cypress or Playwright for Angular applications.

Command Usage

/angular-testing:run-e2e [cypress|playwright] [setup|run|debug]

Natural Language Examples

  • "Set up Cypress for E2E testing"
  • "Create E2E tests for login flow"
  • "Run Playwright tests in headless mode"
  • "Debug failing E2E tests"

What This Command Does

  1. Framework Setup - Install and configure Cypress/Playwright
  2. Test Generation - Create E2E test files for user flows
  3. Custom Commands - Reusable helpers and utilities
  4. CI/CD Integration - GitHub Actions/GitLab CI configuration
  5. Debugging - Tools and strategies for flaky tests

Cypress Setup

Installation

npm install --save-dev cypress @cypress/schematic
ng add @cypress/schematic

Configuration

// cypress.config.ts
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:4200',
    supportFile: 'cypress/support/e2e.ts',
    specPattern: 'cypress/e2e/**/*.cy.ts',
    viewportWidth: 1280,
    viewportHeight: 720,
    video: true,
    screenshotOnRunFailure: true,
    retries: {
      runMode: 2,
      openMode: 0
    },
    env: {
      apiUrl: 'http://localhost:3000/api'
    }
  }
});

Custom Commands

// cypress/support/commands.ts
declare global {
  namespace Cypress {
    interface Chainable {
      login(email: string, password: string): Chainable<void>;
      logout(): Chainable<void>;
      getBySel(selector: string): Chainable<JQuery<HTMLElement>>;
      checkAccessibility(): Chainable<void>;
    }
  }
}

// Login command
Cypress.Commands.add('login', (email: string, password: string) => {
  cy.session([email, password], () => {
    cy.visit('/login');
    cy.getBySel('email-input').type(email);
    cy.getBySel('password-input').type(password);
    cy.getBySel('login-button').click();
    cy.url().should('include', '/dashboard');
    cy.window().its('localStorage.token').should('exist');
  });
});

// Logout command
Cypress.Commands.add('logout', () => {
  cy.clearLocalStorage();
  cy.clearCookies();
});

// Get by data-cy attribute
Cypress.Commands.add('getBySel', (selector: string) => {
  return cy.get(`[data-cy="${selector}"]`);
});

// Accessibility check
Cypress.Commands.add('checkAccessibility', () => {
  cy.injectAxe();
  cy.checkA11y();
});

Example E2E Test

// cypress/e2e/login.cy.ts
describe('Login Flow', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('should display login form', () => {
    cy.getBySel('email-input').should('be.visible');
    cy.getBySel('password-input').should('be.visible');
    cy.getBySel('login-button').should('be.visible');
  });

  it('should show validation errors for empty fields', () => {
    cy.getBySel('login-button').click();
    cy.getBySel('email-error').should('contain', 'Email is required');
    cy.getBySel('password-error').should('contain', 'Password is required');
  });

  it('should show error for invalid email format', () => {
    cy.getBySel('email-input').type('invalid-email');
    cy.getBySel('password-input').type('password123');
    cy.getBySel('login-button').click();
    cy.getBySel('email-error').should('contain', 'Invalid email format');
  });

  it('should successfully login with valid credentials', () => {
    cy.intercept('POST', '/api/auth/login', {
      statusCode: 200,
      body: {
        token: 'fake-jwt-token',
        user: { id: 1, email: 'test@example.com' }
      }
    }).as('loginRequest');

    cy.getBySel('email-input').type('test@example.com');
    cy.getBySel('password-input').type('password123');
    cy.getBySel('login-button').click();

    cy.wait('@loginRequest');
    cy.url().should('include', '/dashboard');
    cy.window().its('localStorage.token').should('exist');
  });

  it('should show error message for invalid credentials', () => {
    cy.intercept('POST', '/api/auth/login', {
      statusCode: 401,
      body: { message: 'Invalid credentials' }
    }).as('loginRequest');

    cy.getBySel('email-input').type('test@example.com');
    cy.getBySel('password-input').type('wrong-password');
    cy.getBySel('login-button').click();

    cy.wait('@loginRequest');
    cy.getBySel('error-message').should('contain', 'Invalid credentials');
    cy.url().should('include', '/login');
  });

  it('should toggle password visibility', () => {
    cy.getBySel('password-input').should('have.attr', 'type', 'password');
    cy.getBySel('toggle-password').click();
    cy.getBySel('password-input').should('have.attr', 'type', 'text');
    cy.getBySel('toggle-password').click();
    cy.getBySel('password-input').should('have.attr', 'type', 'password');
  });

  it('should remember me functionality', () => {
    cy.getBySel('remember-me-checkbox').check();
    cy.login('test@example.com', 'password123');
    
    cy.reload();
    cy.url().should('include', '/dashboard');
  });
});

Running Cypress Tests

# Open Cypress Test Runner
npx cypress open

# Run all tests headless
npx cypress run

# Run specific test
npx cypress run --spec "cypress/e2e/login.cy.ts"

# Run with specific browser
npx cypress run --browser chrome

Playwright Setup

Installation

npm install --save-dev @playwright/test
npx playwright install

Configuration

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:4200',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] }
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] }
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] }
    }
  ],
  webServer: {
    command: 'npm start',
    url: 'http://localhost:4200',
    reuseExistingServer: !process.env.CI
  }
});

Example Playwright Test

// e2e/login.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/login');
  });

  test('should display login form', async ({ page }) => {
    await expect(page.getByTestId('email-input')).toBeVisible();
    await expect(page.getByTestId('password-input')).toBeVisible();
    await expect(page.getByTestId('login-button')).toBeVisible();
  });

  test('should login successfully', async ({ page }) => {
    await page.route('**/api/auth/login', async route => {
      await route.fulfill({
        status: 200,
        contentType: 'application/json',
        body: JSON.stringify({
          token: 'fake-jwt-token',
          user: { id: 1, email: 'test@example.com' }
        })
      });
    });

    await page.fill('[data-testid="email-input"]', 'test@example.com');
    await page.fill('[data-testid="password-input"]', 'password123');
    await page.click('[data-testid="login-button"]');

    await expect(page).toHaveURL(/.*dashboard/);
    
    const token = await page.evaluate(() => localStorage.getItem('token'));
    expect(token).toBeTruthy();
  });
});

Running Playwright Tests

# Run all tests
npx playwright test

# Run with UI
npx playwright test --ui

# Debug mode
npx playwright test --debug

# Generate report
npx playwright show-report

CI/CD Integration

GitHub Actions

# .github/workflows/e2e.yml
name: E2E Tests

on: [push, pull_request]

jobs:
  cypress:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - uses: actions/setup-node@v3
        with:
          node-version: '20'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Start Angular dev server
        run: npm start &
        
      - name: Wait for server
        run: npx wait-on http://localhost:4200
      
      - name: Run Cypress tests
        run: npm run cypress:run
      
      - name: Upload screenshots
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: cypress-screenshots
          path: cypress/screenshots

Best Practices

  1. Use data-cy/data-testid attributes - For stable selectors
  2. Avoid waits - Use Cypress auto-retry
  3. Test user flows - Not individual features
  4. Mock external APIs - For consistency
  5. Keep tests independent - No shared state
  6. Use fixtures - For test data
  7. Clean up after tests - Reset state

Usage

/angular-testing:run-e2e

# Examples
"Set up Cypress for testing login flow"
"Create E2E tests for product CRUD operations"
"Run E2E tests in CI/CD pipeline"

Comprehensive E2E testing made easy! 🎭