Token导航 LogoToken导航TokenDH.com
待分类操作浏览器github未标认证来源可访问clear审计通过

e2e-playwrightE2E Playwright 测试

Agent Skill

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

总安装

424

周安装

17

GitHub Stars

127

下载量

137
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:e2e-playwright(E2E Playwright 测试)
来源仓库:https://github.com/anton-abyzov/specweave
仓库路径:skills/e2e-playwright
安装命令:
npx skills add https://github.com/anton-abyzov/specweave --skill e2e-playwright
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/anton-abyzov/specweave --skill e2e-playwright

简介

提供 Playwright 端到端测试的专业指导与最佳实践规范。

  • 适用于浏览器自动化、多框架适配及测试结构设计。
  • 包含定位策略、认证复用、表单测试与 CI/CD 配置等完整参考。
  • 需根据项目实际技术栈调整选择器与辅助函数,确保与现有代码兼容。
  • e2e-playwright 属于待分类类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

E2E Playwright Testing Expert

Core Expertise

1. Playwright Fundamentals

Browser Automation:

  • Multi-browser support (Chromium, Firefox, WebKit)
  • Context isolation and parallel execution
  • Auto-waiting and actionability checks
  • Network interception and mocking
  • File downloads and uploads
  • Geolocation and permissions
  • Authentication state management

Test Structure:

import { test, expect } from '@playwright/test';

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

  test('should login successfully', async ({ page }) => {
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Login' }).click();

    await expect(page).toHaveURL('/dashboard');
    await expect(page.getByText('Welcome back')).toBeVisible();
  });

  test('should show validation errors', async ({ page }) => {
    await page.getByRole('button', { name: 'Login' }).click();

    await expect(page.getByText('Email is required')).toBeVisible();
    await expect(page.getByText('Password is required')).toBeVisible();
  });
});

2. Page Object Model (POM)

Pattern: Encapsulate page interactions for maintainability

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

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.loginButton = page.getByRole('button', { name: 'Login' });
    this.errorMessage = page.getByRole('alert');
  }

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

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

  async loginWithGoogle() {
    await this.page.getByRole('button', { name: 'Continue with Google' }).click();
    // Handle OAuth popup
  }

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

// Usage in tests
test('login flow', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login('user@example.com', 'password123');
  await expect(page).toHaveURL('/dashboard');
});

3. Test Fixtures & Custom Contexts

Fixtures: Reusable setup/teardown logic

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

type AuthFixtures = {
  authenticatedPage: Page;
  loginPage: LoginPage;
};

export const test = base.extend<AuthFixtures>({
  authenticatedPage: async ({ page }, use) => {
    // Setup: Login before test
    await page.goto('/login');
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Login' }).click();
    await page.waitForURL('/dashboard');

    await use(page);

    // Teardown: Logout after test
    await page.getByRole('button', { name: 'Logout' }).click();
  },

  loginPage: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await use(loginPage);
  },
});

export { expect } from '@playwright/test';

// Usage
test('authenticated user can view profile', async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/profile');
  await expect(authenticatedPage.getByText('Profile Settings')).toBeVisible();
});

4. API Testing with Playwright

Pattern: Test backend APIs alongside E2E flows

import { test, expect } from '@playwright/test';

test.describe('API Testing', () => {
  test('should fetch user data', async ({ request }) => {
    const response = await request.get('/api/users/123');

    expect(response.ok()).toBeTruthy();
    expect(response.status()).toBe(200);

    const data = await response.json();
    expect(data).toMatchObject({
      id: 123,
      email: expect.any(String),
      name: expect.any(String),
    });
  });

  test('should handle authentication', async ({ request }) => {
    const response = await request.post('/api/auth/login', {
      data: {
        email: 'user@example.com',
        password: 'password123',
      },
    });

    expect(response.ok()).toBeTruthy();
    const { token } = await response.json();
    expect(token).toBeTruthy();

    // Use token in subsequent requests
    const profileResponse = await request.get('/api/profile', {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });

    expect(profileResponse.ok()).toBeTruthy();
  });

  test('should mock API responses', async ({ page }) => {
    await page.route('/api/users', async (route) => {
      await route.fulfill({
        status: 200,
        body: JSON.stringify([
          { id: 1, name: 'John Doe' },
          { id: 2, name: 'Jane Smith' },
        ]),
      });
    });

    await page.goto('/users');
    await expect(page.getByText('John Doe')).toBeVisible();
    await expect(page.getByText('Jane Smith')).toBeVisible();
  });
});

5. Visual Regression Testing

Pattern: Screenshot comparison for UI changes

import { test, expect } from '@playwright/test';

test.describe('Visual Regression', () => {
  test('homepage matches baseline', async ({ page }) => {
    await page.goto('/');
    await expect(page).toHaveScreenshot('homepage.png', {
      fullPage: true,
      animations: 'disabled',
    });
  });

  test('component states', async ({ page }) => {
    await page.goto('/components');

    // Default state
    const button = page.getByRole('button', { name: 'Submit' });
    await expect(button).toHaveScreenshot('button-default.png');

    // Hover state
    await button.hover();
    await expect(button).toHaveScreenshot('button-hover.png');

    // Disabled state
    await page.evaluate(() => {
      document.querySelector('button')?.setAttribute('disabled', 'true');
    });
    await expect(button).toHaveScreenshot('button-disabled.png');
  });

  test('responsive screenshots', async ({ page }) => {
    await page.goto('/');

    // Desktop
    await page.setViewportSize({ width: 1920, height: 1080 });
    await expect(page).toHaveScreenshot('homepage-desktop.png');

    // Tablet
    await page.setViewportSize({ width: 768, height: 1024 });
    await expect(page).toHaveScreenshot('homepage-tablet.png');

    // Mobile
    await page.setViewportSize({ width: 375, height: 667 });
    await expect(page).toHaveScreenshot('homepage-mobile.png');
  });
});

6. Mobile Emulation & Device Testing

Pattern: Test responsive behavior and touch interactions

import { test, expect, devices } from '@playwright/test';

test.use(devices['iPhone 13 Pro']);

test.describe('Mobile Experience', () => {
  test('should render mobile navigation', async ({ page }) => {
    await page.goto('/');

    // Mobile menu should be visible
    await expect(page.getByRole('button', { name: 'Menu' })).toBeVisible();

    // Desktop nav should be hidden
    await expect(page.getByRole('navigation').first()).toBeHidden();
  });

  test('touch gestures', async ({ page }) => {
    await page.goto('/gallery');

    const image = page.getByRole('img').first();

    // Swipe left
    await image.dispatchEvent('touchstart', { touches: [{ clientX: 300, clientY: 200 }] });
    await image.dispatchEvent('touchmove', { touches: [{ clientX: 100, clientY: 200 }] });
    await image.dispatchEvent('touchend');

    await expect(page.getByText('Next Image')).toBeVisible();
  });

  test('landscape orientation', async ({ page }) => {
    await page.setViewportSize({ width: 812, height: 375 }); // iPhone landscape
    await page.goto('/video');

    await expect(page.locator('video')).toHaveCSS('width', '100%');
  });
});

// Test across multiple devices
for (const deviceName of ['iPhone 13', 'Pixel 5', 'iPad Pro']) {
  test.describe(`Device: ${deviceName}`, () => {
    test.use(devices[deviceName]);

    test('critical user flow', async ({ page }) => {
      await page.goto('/');
      // Test critical flow on each device
    });
  });
}

7. Accessibility Testing

Pattern: Automated accessibility checks

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('Accessibility', () => {
  test('should not have accessibility violations', async ({ page }) => {
    await page.goto('/');

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

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

  test('keyboard navigation', async ({ page }) => {
    await page.goto('/form');

    // Tab through form fields
    await page.keyboard.press('Tab');
    await expect(page.getByLabel('Email')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.getByLabel('Password')).toBeFocused();

    await page.keyboard.press('Tab');
    await expect(page.getByRole('button', { name: 'Submit' })).toBeFocused();

    // Submit with Enter
    await page.keyboard.press('Enter');
  });

  test('screen reader support', async ({ page }) => {
    await page.goto('/');

    // Check ARIA labels
    await expect(page.getByRole('navigation', { name: 'Main' })).toBeVisible();
    await expect(page.getByRole('main')).toHaveAttribute('aria-label', 'Main content');

    // Check alt text
    const images = page.getByRole('img');
    for (const img of await images.all()) {
      await expect(img).toHaveAttribute('alt');
    }
  });
});

8. Performance Testing

Pattern: Monitor performance metrics

import { test, expect } from '@playwright/test';

test.describe('Performance', () => {
  test('page load performance', async ({ page }) => {
    await page.goto('/');

    const performanceMetrics = await page.evaluate(() => {
      const perfData = window.performance.timing;
      return {
        loadTime: perfData.loadEventEnd - perfData.navigationStart,
        domContentLoaded: perfData.domContentLoadedEventEnd - perfData.navigationStart,
        firstPaint: performance.getEntriesByType('paint')[0]?.startTime || 0,
      };
    });

    expect(performanceMetrics.loadTime).toBeLessThan(3000); // 3s max
    expect(performanceMetrics.domContentLoaded).toBeLessThan(2000); // 2s max
  });

  test('Core Web Vitals', async ({ page }) => {
    await page.goto('/');

    const vitals = await page.evaluate(() => {
      return new Promise((resolve) => {
        new PerformanceObserver((list) => {
          const entries = list.getEntries();
          const lcp = entries.find(e => e.entryType === 'largest-contentful-paint');
          const fid = entries.find(e => e.entryType === 'first-input');
          const cls = entries.find(e => e.entryType === 'layout-shift');

          resolve({ lcp: lcp?.startTime, fid: fid?.processingStart, cls: cls?.value });
        }).observe({ entryTypes: ['largest-contentful-paint', 'first-input', 'layout-shift'] });
      });
    });

    expect(vitals.lcp).toBeLessThan(2500); // Good LCP
    expect(vitals.fid).toBeLessThan(100);  // Good FID
    expect(vitals.cls).toBeLessThan(0.1);  // Good CLS
  });
});

9. Advanced 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'],
    ['junit', { outputFile: 'test-results/junit.xml' }],
    ['json', { outputFile: 'test-results/results.json' }],
  ],
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  projects: [
    // Desktop browsers
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    // Mobile browsers
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'Mobile Safari',
      use: { ...devices['iPhone 13'] },
    },
    // Tablet browsers
    {
      name: 'iPad',
      use: { ...devices['iPad Pro'] },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120 * 1000,
  },
});

10. CI/CD Integration

GitHub Actions:

name: E2E Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Install dependencies
        run: npm ci

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

      - name: Run E2E tests
        run: npm run test:e2e
        env:
          BASE_URL: https://staging.example.com

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

      - name: Upload traces
        if: failure()
        uses: actions/upload-artifact@v3
        with:
          name: playwright-traces
          path: test-results/

11. Debugging Strategies

Tools & Techniques:

// 1. Debug mode (headed browser + slow motion)
test('debug example', async ({ page }) => {
  await page.goto('/');
  await page.pause(); // Pauses execution, opens inspector
});

// 2. Console logs
test('capture console', async ({ page }) => {
  page.on('console', msg => console.log(`Browser: ${msg.text()}`));
  await page.goto('/');
});

// 3. Network inspection
test('inspect network', async ({ page }) => {
  page.on('request', request => console.log('Request:', request.url()));
  page.on('response', response => console.log('Response:', response.status()));
  await page.goto('/');
});

// 4. Screenshots on failure
test.afterEach(async ({ page }, testInfo) => {
  if (testInfo.status !== testInfo.expectedStatus) {
    await page.screenshot({
      path: `screenshots/${testInfo.title}.png`,
      fullPage: true
    });
  }
});

// 5. Trace viewer
// Run: npx playwright test --trace on
// View: npx playwright show-trace trace.zip

Common Debugging Commands:

# Run in headed mode (see browser)
npx playwright test --headed

# Run with UI mode (interactive debugging)
npx playwright test --ui

# Run single test
npx playwright test tests/login.spec.ts

# Debug specific test
npx playwright test tests/login.spec.ts --debug

# Generate test code
npx playwright codegen http://localhost:3000

12. Handling Flaky Tests

Patterns for Reliability:

// 1. Proper waiting strategies
test('wait for content', async ({ page }) => {
  await page.goto('/');

  // ❌ BAD: Fixed delays
  // await page.waitForTimeout(5000);

  // ✅ GOOD: Wait for specific conditions
  await page.waitForLoadState('networkidle');
  await page.waitForSelector('.content', { state: 'visible' });
  await page.getByText('Welcome').waitFor();
});

// 2. Retry logic for external dependencies
test('api with retry', async ({ page }) => {
  await page.goto('/');

  let retries = 3;
  while (retries > 0) {
    try {
      const response = await page.waitForResponse(
        response => response.url().includes('/api/data') && response.ok(),
        { timeout: 5000 }
      );
      expect(response.ok()).toBeTruthy();
      break;
    } catch (error) {
      retries--;
      if (retries === 0) throw error;
      await page.reload();
    }
  }
});

// 3. Test isolation
test.describe.configure({ mode: 'parallel' });

test.beforeEach(async ({ page }) => {
  // Clear state before each test
  await page.context().clearCookies();
  await page.context().clearPermissions();
});

// 4. Deterministic test data
test('use fixtures', async ({ page }) => {
  // Seed database with known data
  await page.request.post('/api/test/seed', {
    data: { userId: 'test-123', email: 'test@example.com' }
  });

  await page.goto('/users/test-123');
  await expect(page.getByText('test@example.com')).toBeVisible();

  // Cleanup
  await page.request.delete('/api/test/users/test-123');
});

Best Practices

Test Organization

e2e/
├── fixtures/
│   ├── auth.fixture.ts
│   ├── data.fixture.ts
│   └── mock.fixture.ts
├── pages/
│   ├── LoginPage.ts
│   ├── DashboardPage.ts
│   └── ProfilePage.ts
├── tests/
│   ├── auth/
│   │   ├── login.spec.ts
│   │   ├── signup.spec.ts
│   │   └── logout.spec.ts
│   ├── user/
│   │   ├── profile.spec.ts
│   │   └── settings.spec.ts
│   └── api/
│       ├── users.spec.ts
│       └── posts.spec.ts
└── playwright.config.ts

Naming Conventions

  • Test files: *.spec.ts or *.test.ts
  • Page objects: *Page.ts
  • Fixtures: *.fixture.ts
  • Descriptive test names: should allow user to login with valid credentials

Performance Optimization

  1. Parallel execution: Run tests in parallel across workers
  2. Test sharding: Split tests across CI machines
  3. Selective testing: Use tags/annotations for smoke tests
  4. Reuse authentication: Save auth state, reuse across tests
  5. Mock external APIs: Reduce network latency and flakiness

Security Considerations

  • Never commit credentials in test files
  • Use environment variables for sensitive data
  • Isolate test data from production
  • Clear cookies/storage between tests
  • Use disposable test accounts

Common Patterns

Authentication State Reuse

// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';

async function globalSetup(config: FullConfig) {
  const browser = await chromium.launch();
  const page = await browser.newPage();
  await page.goto('http://localhost:3000/login');
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Login' }).click();
  await page.waitForURL('http://localhost:3000/dashboard');

  // Save signed-in state
  await page.context().storageState({ path: 'auth.json' });
  await browser.close();
}

export default globalSetup;

// playwright.config.ts
export default defineConfig({
  globalSetup: require.resolve('./global-setup'),
  use: {
    storageState: 'auth.json',
  },
});

Multi-Tab/Window Testing

test('open in new tab', async ({ context }) => {
  const page = await context.newPage();
  await page.goto('/');

  const [newPage] = await Promise.all([
    context.waitForEvent('page'),
    page.getByRole('link', { name: 'Open in new tab' }).click()
  ]);

  await newPage.waitForLoadState();
  await expect(newPage).toHaveURL('/new-page');
});

File Upload/Download

test('upload file', async ({ page }) => {
  await page.goto('/upload');

  const fileChooserPromise = page.waitForEvent('filechooser');
  await page.getByRole('button', { name: 'Upload' }).click();
  const fileChooser = await fileChooserPromise;
  await fileChooser.setFiles('path/to/file.pdf');

  await expect(page.getByText('file.pdf uploaded')).toBeVisible();
});

test('download file', async ({ page }) => {
  await page.goto('/downloads');

  const downloadPromise = page.waitForEvent('download');
  await page.getByRole('link', { name: 'Download Report' }).click();
  const download = await downloadPromise;

  await download.saveAs(`/tmp/${download.suggestedFilename()}`);
  expect(download.suggestedFilename()).toBe('report.pdf');
});

Troubleshooting

Common Issues

  1. Timeouts: Increase timeout, use proper wait strategies
  2. Flaky selectors: Use stable locators (roles, labels, test IDs)
  3. Race conditions: Wait for network idle, use explicit waits
  4. Authentication failures: Clear cookies, check auth state
  5. Screenshot mismatches: Update baselines, disable animations

Debug Checklist

  • Test passes locally in headed mode?
  • Network requests succeed (check DevTools)?
  • Selectors are stable and unique?
  • Proper waits before assertions?
  • Test data is deterministic?
  • No race conditions with async operations?
  • Traces/screenshots captured on failure?

Resources

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

04

需要参考平台分布和安装热度时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude Code

25.85%
按下载量换算35

Cursor

23.36%
按下载量换算32

Antigravity

17.55%
按下载量换算24

OpenCode

11.47%
按下载量换算16

Gemini CLI

7.83%
按下载量换算11

windsurf

3.44%
按下载量换算5

安全审计

Gen Agent Trust Hub

通过

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills