
cypress-playwright-setup
Sets up end-to-end testing with Cypress or Playwright including page objects, fixtures, and CI integration. Use when users request "E2E testing", "Cypress setup", "Playwright setup", "browser testing", or "integration tests".
- Installs
- 300
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/patricio0312rev/skills --skill cypress-playwright-setup- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads cypress-playwright-setup automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
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
FAQ
What does the cypress-playwright-setup skill do?
Sets up end-to-end testing with Cypress or Playwright including page objects, fixtures, and CI integration. Use when users request "E2E testing", "Cypress setup", "Playwright setup", "browser testing", or "integration tests".
How do I install cypress-playwright-setup?
Run npx skills add https://github.com/patricio0312rev/skills --skill cypress-playwright-setup in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/cypress-playwright-setup/ (personal) or .claude/skills/cypress-playwright-setup/ (project).
Which AI agents can use cypress-playwright-setup?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is cypress-playwright-setup safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains cypress-playwright-setup?
It is published by patricio0312rev in patricio0312rev/skills. It has 300 recorded installs.
More from patricio0312rev/skills
framer-motion-animator
patricio0312rev/skills
Creates smooth animations and micro-interactions using Framer Motion including page transitions, gestures, scroll-based animations, and orchestrated sequences. Use when users request "add animation", "framer motion", "page transition", "animate component", or "micro-interactions".
eslint-prettier-config
patricio0312rev/skills
Configures ESLint and Prettier for consistent code quality with TypeScript, React, and modern best practices. Use when users request "ESLint setup", "Prettier config", "linting configuration", "code formatting", or "lint rules".
postman-collection-generator
patricio0312rev/skills
Generates Postman collection JSON files from Express, Next.js, Fastify, Hono, or other API routes. Scans route definitions, extracts endpoints, methods, params, and creates importable collections. Use when users request "generate postman collection", "export to postman", "create postman file", or "postman import".
changelog-writer
patricio0312rev/skills
Generates changelogs and release notes from git commits, PR titles, and issue references. Organizes changes by impact type (breaking, features, fixes, improvements), formats according to Keep a Changelog standard, and provides version tagging and semantic versioning suggestions. Use when users request "create changelog", "write release notes", "document version changes", or "prepare release".
bruno-collection-generator
patricio0312rev/skills
Generates Bruno collection files (.bru) from Express, Next.js, Fastify, or other API routes. Creates organized collections with environments, authentication, and folder structure for the open-source Bruno API client. Use when users request "generate bruno collection", "bruno api testing", "create bru files", or "bruno import".
jsdoc-typescript-docs
patricio0312rev/skills
Documents TypeScript code with JSDoc comments, generates API documentation, and creates type-safe documentation. Use when users request "JSDoc", "code documentation", "API docs", "TypeDoc", or "inline documentation".
Popular testing & qa skills
tdd
mattpocock/skills
Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.
grilling
mattpocock/skills
Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
migrate-to-shoehorn
mattpocock/skills
Migrate test files from as type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace as in tests, or needs partial test data.
run-train
lllllllama/rigorpilot-skills
Rigor Train skill for deep learning research repositories. Use when a documented or selected training command should be run conservatively for startup verification, short-run verification, full kickoff, or resume, with command, config, seed, log, checkpoint, status, and metric evidence written to standardized trainoutputs/. Do not use for environment setup, exploratory sweeps, speculative idea implementation, or end-to-end orchestration.
test-driven-development
obra/superpowers
Use when implementing any feature or bugfix, before writing implementation code
finishing-a-development-branch
obra/superpowers
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work