cypress-playwright-setup

Cypress & Playwright Setup

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "cypress-playwright-setup" with this command: npx skills add monkey1sai/openai-cli/monkey1sai-openai-cli-cypress-playwright-setup

Cypress & Playwright Setup

Configure comprehensive end-to-end testing for web applications.

Core Workflow

  • Choose tool: Cypress or Playwright

  • Configure project: Browser and test settings

  • Create page objects: Reusable selectors

  • Write tests: User journey coverage

  • Setup fixtures: Test data

  • Integrate CI: Automated testing

Playwright Setup

Installation

npm init playwright@latest

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'], ['json', { outputFile: 'test-results/results.json' }], ['junit', { outputFile: 'test-results/junit.xml' }], ],

use: { baseURL: process.env.BASE_URL || 'http://localhost:3000', 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'] }, }, { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] }, }, { name: 'Mobile Safari', use: { ...devices['iPhone 12'] }, }, ],

webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI, timeout: 120 * 1000, }, });

Page Object Model

// e2e/pages/BasePage.ts import { Page, Locator } from '@playwright/test';

export abstract class BasePage { readonly page: Page;

constructor(page: Page) { this.page = page; }

async goto(path: string = '') { await this.page.goto(path); }

async waitForLoad() { await this.page.waitForLoadState('networkidle'); }

getByTestId(testId: string): Locator { return this.page.getByTestId(testId); } }

// e2e/pages/LoginPage.ts import { Page, Locator, expect } from '@playwright/test'; import { BasePage } from './BasePage';

export class LoginPage extends BasePage { readonly emailInput: Locator; readonly passwordInput: Locator; readonly submitButton: Locator; readonly errorMessage: Locator; readonly forgotPasswordLink: Locator;

constructor(page: Page) { super(page); this.emailInput = page.getByLabel('Email'); this.passwordInput = page.getByLabel('Password'); this.submitButton = page.getByRole('button', { name: 'Sign in' }); this.errorMessage = page.getByTestId('error-message'); this.forgotPasswordLink = page.getByRole('link', { name: 'Forgot password?' }); }

async goto() { await super.goto('/login'); await this.waitForLoad(); }

async login(email: string, password: string) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); }

async expectError(message: string) { await expect(this.errorMessage).toBeVisible(); await expect(this.errorMessage).toContainText(message); }

async expectLoginSuccess() { await expect(this.page).toHaveURL(//dashboard/); } }

// e2e/pages/DashboardPage.ts import { Page, Locator, expect } from '@playwright/test'; import { BasePage } from './BasePage';

export class DashboardPage extends BasePage { readonly welcomeMessage: Locator; readonly userMenu: Locator; readonly logoutButton: Locator; readonly sidebar: Locator;

constructor(page: Page) { super(page); this.welcomeMessage = page.getByTestId('welcome-message'); this.userMenu = page.getByTestId('user-menu'); this.logoutButton = page.getByRole('button', { name: 'Logout' }); this.sidebar = page.getByTestId('sidebar'); }

async goto() { await super.goto('/dashboard'); await this.waitForLoad(); }

async logout() { await this.userMenu.click(); await this.logoutButton.click(); await expect(this.page).toHaveURL('/login'); }

async expectWelcome(name: string) { await expect(this.welcomeMessage).toContainText(Welcome, ${name}); } }

Test Examples

// e2e/auth.spec.ts import { test, expect } from '@playwright/test'; import { LoginPage } from './pages/LoginPage'; import { DashboardPage } from './pages/DashboardPage';

test.describe('Authentication', () => { let loginPage: LoginPage;

test.beforeEach(async ({ page }) => { loginPage = new LoginPage(page); await loginPage.goto(); });

test('successful login', async ({ page }) => { await loginPage.login('test@example.com', 'password123'); await loginPage.expectLoginSuccess();

const dashboard = new DashboardPage(page);
await dashboard.expectWelcome('Test User');

});

test('invalid credentials', async () => { await loginPage.login('test@example.com', 'wrongpassword'); await loginPage.expectError('Invalid email or password'); });

test('empty fields validation', async () => { await loginPage.submitButton.click(); await expect(loginPage.page.getByText('Email is required')).toBeVisible(); await expect(loginPage.page.getByText('Password is required')).toBeVisible(); });

test('forgot password flow', async ({ page }) => { await loginPage.forgotPasswordLink.click(); await expect(page).toHaveURL('/forgot-password'); }); });

Fixtures

// e2e/fixtures/auth.fixture.ts import { test as base, expect } from '@playwright/test'; import { LoginPage } from '../pages/LoginPage'; import { DashboardPage } from '../pages/DashboardPage';

interface AuthFixtures { loginPage: LoginPage; dashboardPage: DashboardPage; authenticatedPage: DashboardPage; }

export const test = base.extend<AuthFixtures>({ loginPage: async ({ page }, use) => { const loginPage = new LoginPage(page); await use(loginPage); },

dashboardPage: async ({ page }, use) => { const dashboardPage = new DashboardPage(page); await use(dashboardPage); },

authenticatedPage: async ({ page }, use) => { // Login before test const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.login('test@example.com', 'password123'); await loginPage.expectLoginSuccess();

const dashboardPage = new DashboardPage(page);
await use(dashboardPage);

}, });

export { expect };

// e2e/dashboard.spec.ts import { test, expect } from './fixtures/auth.fixture';

test.describe('Dashboard', () => { test('shows user data', async ({ authenticatedPage }) => { await authenticatedPage.expectWelcome('Test User'); });

test('logout redirects to login', async ({ authenticatedPage }) => { await authenticatedPage.logout(); }); });

Cypress Setup

Installation

npm install -D cypress @testing-library/cypress npx cypress open

Configuration

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

export default defineConfig({ e2e: { baseUrl: 'http://localhost:3000', viewportWidth: 1280, viewportHeight: 720, video: true, screenshotOnRunFailure: true, retries: { runMode: 2, openMode: 0, }, experimentalStudio: true, setupNodeEvents(on, config) { // Tasks and plugins }, },

component: { devServer: { framework: 'react', bundler: 'vite', }, }, });

Support Commands

// cypress/support/commands.ts import '@testing-library/cypress/add-commands';

declare global { namespace Cypress { interface Chainable { login(email: string, password: string): Chainable<void>; getByTestId(testId: string): Chainable<JQuery<HTMLElement>>; mockApi(fixture: string): Chainable<void>; } } }

Cypress.Commands.add('login', (email: string, password: string) => { cy.session([email, password], () => { cy.visit('/login'); cy.get('[data-testid="email-input"]').type(email); cy.get('[data-testid="password-input"]').type(password); cy.get('[data-testid="submit-button"]').click(); cy.url().should('include', '/dashboard'); }); });

Cypress.Commands.add('getByTestId', (testId: string) => { return cy.get([data-testid="${testId}"]); });

Cypress.Commands.add('mockApi', (fixture: string) => { cy.intercept('GET', '/api/**', { fixture }).as('apiCall'); });

Cypress Tests

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

it('logs in successfully', () => { cy.get('[data-testid="email-input"]').type('test@example.com'); cy.get('[data-testid="password-input"]').type('password123'); cy.get('[data-testid="submit-button"]').click();

cy.url().should('include', '/dashboard');
cy.getByTestId('welcome-message').should('contain', 'Welcome');

});

it('shows error for invalid credentials', () => { cy.get('[data-testid="email-input"]').type('test@example.com'); cy.get('[data-testid="password-input"]').type('wrongpassword'); cy.get('[data-testid="submit-button"]').click();

cy.getByTestId('error-message').should('be.visible');
cy.url().should('include', '/login');

});

it('validates required fields', () => { cy.get('[data-testid="submit-button"]').click(); cy.contains('Email is required').should('be.visible'); cy.contains('Password is required').should('be.visible'); }); });

API Mocking in Cypress

// cypress/e2e/products.cy.ts describe('Products', () => { beforeEach(() => { cy.login('test@example.com', 'password123'); });

it('displays products from API', () => { cy.intercept('GET', '/api/products', { fixture: 'products.json', }).as('getProducts');

cy.visit('/products');
cy.wait('@getProducts');

cy.getByTestId('product-card').should('have.length', 3);

});

it('handles API error gracefully', () => { cy.intercept('GET', '/api/products', { statusCode: 500, body: { error: 'Server Error' }, }).as('getProductsError');

cy.visit('/products');
cy.wait('@getProductsError');

cy.getByTestId('error-message').should('contain', 'Failed to load products');

});

it('filters products', () => { cy.intercept('GET', '/api/products?category=electronics', { fixture: 'products-electronics.json', }).as('getElectronics');

cy.visit('/products');
cy.getByTestId('category-filter').select('electronics');

cy.wait('@getElectronics');
cy.getByTestId('product-card').should('have.length', 2);

}); });

CI Integration

GitHub Actions

.github/workflows/e2e.yml

name: E2E Tests

on: push: branches: [main] pull_request: branches: [main]

jobs: playwright: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: 20
      cache: 'npm'

  - run: npm ci

  - name: Install Playwright Browsers
    run: npx playwright install --with-deps

  - name: Build
    run: npm run build

  - name: Run Playwright tests
    run: npx playwright test
    env:
      CI: true

  - uses: actions/upload-artifact@v4
    if: always()
    with:
      name: playwright-report
      path: playwright-report/
      retention-days: 30

cypress: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4

  - uses: actions/setup-node@v4
    with:
      node-version: 20
      cache: 'npm'

  - run: npm ci

  - name: Cypress run
    uses: cypress-io/github-action@v6
    with:
      build: npm run build
      start: npm start
      wait-on: 'http://localhost:3000'
      browser: chrome
      record: true
    env:
      CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  - uses: actions/upload-artifact@v4
    if: failure()
    with:
      name: cypress-screenshots
      path: cypress/screenshots

Accessibility Testing

// e2e/accessibility.spec.ts (Playwright) import { test, expect } from '@playwright/test'; import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility', () => { test('homepage has no accessibility violations', async ({ page }) => { await page.goto('/');

const accessibilityScanResults = await new AxeBuilder({ page }).analyze();

expect(accessibilityScanResults.violations).toEqual([]);

});

test('login page has no accessibility violations', async ({ page }) => { await page.goto('/login');

const accessibilityScanResults = await new AxeBuilder({ page })
  .withTags(['wcag2a', 'wcag2aa'])
  .analyze();

expect(accessibilityScanResults.violations).toEqual([]);

}); });

// cypress/e2e/accessibility.cy.ts import 'cypress-axe';

describe('Accessibility', () => { beforeEach(() => { cy.injectAxe(); });

it('homepage has no accessibility violations', () => { cy.visit('/'); cy.checkA11y(); });

it('login form is accessible', () => { cy.visit('/login'); cy.checkA11y('[data-testid="login-form"]'); }); });

Best Practices

  • Use page objects: Maintainable selectors

  • Use test IDs: Stable element selection

  • Avoid sleep: Use proper waits

  • Isolate tests: No dependencies between tests

  • Mock external APIs: Reliable, fast tests

  • Test accessibility: Include a11y checks

  • Parallel execution: Faster CI

  • Meaningful assertions: Clear expectations

Output Checklist

Every E2E setup should include:

  • Playwright/Cypress configuration

  • Page object model

  • Custom commands/fixtures

  • API mocking setup

  • Authentication handling

  • Multi-browser testing

  • Accessibility tests

  • CI integration

  • Reporting configuration

  • Screenshot/video on failure

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

readme-generator

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

bruno-collection-generator

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

redis-patterns

No summary provided by upstream source.

Repository SourceNeeds Review