Token导航 LogoToken导航TokenDH.com
开发操作浏览器github未标认证来源可访问clear审计异常

e2e-testing端到端测试

Agent Skill

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

总安装

261

周安装

11

GitHub Stars

20

下载量

92
CodexClaudeCursorGemini CLI

安装说明

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

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

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

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

命令行安装

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

skills.shnpx skills
npx skills add https://github.com/sgcarstrends/sgcarstrends --skill e2e-testing

简介

e2e-testing 用于辅助测试设计、自动化测试和回归验证,适合在 Codex、Claude、Cursor、Gemini CLI 中编写测试用例或定位问题时使用。

  • 适用于单元测试、端到端测试和测试计划整理等场景,帮助 Agent 提升代码质量。
  • 通过 npx skills add 命令从指定 GitHub 仓库安装,需确认权限范围和操作边界。
  • 使用时需确认项目测试框架和运行命令,避免为了通过测试而破坏真实逻辑;涉及浏览器或服务时应区分模拟与生产环境。
  • 建议安装前核实维护状态,避免触发联网、命令执行或文件读写等敏感操作。

SKILL.md

End-to-End Testing Skill

This skill helps you write and run comprehensive end-to-end tests using Playwright.

When to Use This Skill

  • Testing complete user flows
  • Verifying page interactions and navigation
  • Testing form submissions
  • Checking API integrations from the UI
  • Visual regression testing
  • Cross-browser compatibility testing
  • Mobile responsiveness testing
  • Before production deployments

Playwright Overview

Playwright is a modern E2E testing framework that provides:

  • Cross-browser: Chromium, Firefox, WebKit
  • Auto-waiting: Intelligent element waiting
  • Network interception: Mock API responses
  • Screenshots & videos: Visual debugging
  • Parallel execution: Fast test runs
  • TypeScript support: Type-safe tests

Project Configuration

Installation

# Install Playwright (if not already installed)
pnpm add -D -w @playwright/test

# Install browsers
pnpm exec playwright install

Configuration File

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

export default defineConfig({
  testDir: "./apps/web/__tests__/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" }],
  ],
  use: {
    baseURL: process.env.BASE_URL || "http://localhost:3001",
    trace: "on-first-retry",
    screenshot: "only-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: "pnpm -F @sgcarstrends/web dev",
    url: "http://localhost:3001",
    reuseExistingServer: !process.env.CI,
  },
});

Test Structure

File Organization

apps/web/
├── __tests__/
│   └── e2e/
│       ├── home.spec.ts              # Homepage tests
│       ├── cars/
│       │   ├── makes.spec.ts         # Car makes listing
│       │   └── models.spec.ts        # Car models listing
│       ├── coe/
│       │   └── bidding.spec.ts       # COE bidding results
│       ├── blog/
│       │   ├── list.spec.ts          # Blog listing
│       │   └── post.spec.ts          # Blog post detail
│       └── fixtures/
│           ├── mock-data.ts          # Test data
│           └── page-objects.ts       # Page object models

Basic Test Example

// apps/web/__tests__/e2e/home.spec.ts
import { test, expect } from "@playwright/test";

test.describe("Homepage", () => {
  test("should load successfully", async ({ page }) => {
    await page.goto("/");

    // Check page title
    await expect(page).toHaveTitle(/SG Cars Trends/);

    // Check main heading
    const heading = page.getByRole("heading", { name: /SG Cars Trends/ });
    await expect(heading).toBeVisible();
  });

  test("should display navigation menu", async ({ page }) => {
    await page.goto("/");

    // Check nav links
    await expect(page.getByRole("link", { name: "Cars" })).toBeVisible();
    await expect(page.getByRole("link", { name: "COE" })).toBeVisible();
    await expect(page.getByRole("link", { name: "Blog" })).toBeVisible();
  });

  test("should navigate to cars page", async ({ page }) => {
    await page.goto("/");

    // Click cars link
    await page.getByRole("link", { name: "Cars" }).click();

    // Verify URL
    await expect(page).toHaveURL(/\/cars/);

    // Verify page content
    await expect(page.getByRole("heading", { name: /Cars/ })).toBeVisible();
  });
});

Page Object Pattern

Create Page Objects

// apps/web/__tests__/e2e/fixtures/page-objects.ts
import { Page, Locator } from "@playwright/test";

export class HomePage {
  readonly page: Page;
  readonly heading: Locator;
  readonly carsLink: Locator;
  readonly coeLink: Locator;
  readonly blogLink: Locator;

  constructor(page: Page) {
    this.page = page;
    this.heading = page.getByRole("heading", { name: /SG Cars Trends/ });
    this.carsLink = page.getByRole("link", { name: "Cars" });
    this.coeLink = page.getByRole("link", { name: "COE" });
    this.blogLink = page.getByRole("link", { name: "Blog" });
  }

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

  async navigateToCars() {
    await this.carsLink.click();
  }

  async navigateToCOE() {
    await this.coeLink.click();
  }

  async navigateToBlog() {
    await this.blogLink.click();
  }
}

export class CarsPage {
  readonly page: Page;
  readonly heading: Locator;
  readonly makeSelect: Locator;
  readonly modelSelect: Locator;
  readonly resultsTable: Locator;

  constructor(page: Page) {
    this.page = page;
    this.heading = page.getByRole("heading", { name: /Cars/ });
    this.makeSelect = page.getByLabel("Make");
    this.modelSelect = page.getByLabel("Model");
    this.resultsTable = page.getByRole("table");
  }

  async goto() {
    await this.page.goto("/cars");
  }

  async selectMake(make: string) {
    await this.makeSelect.click();
    await this.page.getByRole("option", { name: make }).click();
  }

  async selectModel(model: string) {
    await this.modelSelect.click();
    await this.page.getByRole("option", { name: model }).click();
  }

  async getResultsCount(): Promise<number> {
    const rows = await this.resultsTable.locator("tbody tr").count();
    return rows;
  }
}

Use Page Objects

// apps/web/__tests__/e2e/cars/makes.spec.ts
import { test, expect } from "@playwright/test";
import { HomePage, CarsPage } from "../fixtures/page-objects";

test.describe("Cars Page", () => {
  test("should filter by make", async ({ page }) => {
    const homePage = new HomePage(page);
    const carsPage = new CarsPage(page);

    // Navigate to cars page
    await homePage.goto();
    await homePage.navigateToCars();

    // Select Toyota
    await carsPage.selectMake("Toyota");

    // Wait for results
    await expect(carsPage.resultsTable).toBeVisible();

    // Verify results contain Toyota
    const firstRow = page.locator("tbody tr").first();
    await expect(firstRow).toContainText("Toyota");
  });

  test("should filter by make and model", async ({ page }) => {
    const carsPage = new CarsPage(page);

    await carsPage.goto();
    await carsPage.selectMake("Toyota");
    await carsPage.selectModel("Corolla");

    // Verify results
    const count = await carsPage.getResultsCount();
    expect(count).toBeGreaterThan(0);
  });
});

API Mocking

Mock API Responses

// apps/web/__tests__/e2e/cars/mocked.spec.ts
import { test, expect } from "@playwright/test";

test.describe("Cars Page with Mocked API", () => {
  test("should display mocked car data", async ({ page }) => {
    // Mock API response
    await page.route("**/api/v1/cars/makes", async (route) => {
      await route.fulfill({
        status: 200,
        contentType: "application/json",
        body: JSON.stringify([
          { make: "Toyota", count: 1000 },
          { make: "Honda", count: 800 },
          { make: "BMW", count: 600 },
        ]),
      });
    });

    await page.goto("/cars");

    // Verify mocked data is displayed
    await expect(page.getByText("Toyota")).toBeVisible();
    await expect(page.getByText("1000")).toBeVisible();
  });

  test("should handle API errors gracefully", async ({ page }) => {
    // Mock API error
    await page.route("**/api/v1/cars/makes", async (route) => {
      await route.fulfill({
        status: 500,
        contentType: "application/json",
        body: JSON.stringify({ error: "Internal server error" }),
      });
    });

    await page.goto("/cars");

    // Verify error message is displayed
    await expect(page.getByText(/error|failed/i)).toBeVisible();
  });
});

Form Testing

Test Form Submissions

// apps/web/__tests__/e2e/blog/comment.spec.ts
import { test, expect } from "@playwright/test";

test.describe("Blog Comment Form", () => {
  test("should submit comment successfully", async ({ page }) => {
    await page.goto("/blog/test-post");

    // Fill form
    await page.getByLabel("Name").fill("John Doe");
    await page.getByLabel("Email").fill("john@example.com");
    await page.getByLabel("Comment").fill("Great article!");

    // Submit
    await page.getByRole("button", { name: "Submit" }).click();

    // Verify success message
    await expect(page.getByText(/comment submitted/i)).toBeVisible();
  });

  test("should validate required fields", async ({ page }) => {
    await page.goto("/blog/test-post");

    // Submit empty form
    await page.getByRole("button", { name: "Submit" }).click();

    // Verify validation errors
    await expect(page.getByText(/name is required/i)).toBeVisible();
    await expect(page.getByText(/email is required/i)).toBeVisible();
  });

  test("should validate email format", async ({ page }) => {
    await page.goto("/blog/test-post");

    await page.getByLabel("Email").fill("invalid-email");
    await page.getByRole("button", { name: "Submit" }).click();

    await expect(page.getByText(/invalid email/i)).toBeVisible();
  });
});

Visual Testing

Screenshot Comparison

// apps/web/__tests__/e2e/visual/homepage.spec.ts
import { test, expect } from "@playwright/test";

test.describe("Visual Regression", () => {
  test("homepage should match snapshot", async ({ page }) => {
    await page.goto("/");

    // Wait for page to be fully loaded
    await page.waitForLoadState("networkidle");

    // Take screenshot and compare
    await expect(page).toHaveScreenshot("homepage.png", {
      fullPage: true,
      maxDiffPixels: 100,
    });
  });

  test("cars page should match snapshot", async ({ page }) => {
    await page.goto("/cars");
    await page.waitForLoadState("networkidle");

    await expect(page).toHaveScreenshot("cars-page.png", {
      fullPage: true,
    });
  });

  test("mobile homepage should match snapshot", async ({ page }) => {
    await page.setViewportSize({ width: 375, height: 667 });
    await page.goto("/");
    await page.waitForLoadState("networkidle");

    await expect(page).toHaveScreenshot("homepage-mobile.png", {
      fullPage: true,
    });
  });
});

Accessibility Testing

Test Accessibility

// apps/web/__tests__/e2e/a11y/homepage.spec.ts
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

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

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

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

  test("should have proper heading hierarchy", async ({ page }) => {
    await page.goto("/");

    // Check h1 exists and is unique
    const h1Count = await page.locator("h1").count();
    expect(h1Count).toBe(1);

    // Check heading order
    const headings = await page.locator("h1, h2, h3, h4, h5, h6").all();
    const headingLevels = await Promise.all(
      headings.map((h) => h.evaluate((el) => el.tagName))
    );

    // H1 should come first
    expect(headingLevels[0]).toBe("H1");
  });

  test("should have alt text on images", async ({ page }) => {
    await page.goto("/");

    const images = await page.locator("img").all();

    for (const img of images) {
      const alt = await img.getAttribute("alt");
      expect(alt).toBeTruthy();
    }
  });
});

Running Tests

Common Commands

# Run all tests
pnpm exec playwright test

# Run specific test file
pnpm exec playwright test home.spec.ts

# Run tests in headed mode
pnpm exec playwright test --headed

# Run tests in specific browser
pnpm exec playwright test --project=chromium
pnpm exec playwright test --project=firefox

# Run tests in debug mode
pnpm exec playwright test --debug

# Run tests with UI
pnpm exec playwright test --ui

# Generate test report
pnpm exec playwright show-report

# Update screenshots
pnpm exec playwright test --update-snapshots

Package.json Scripts

{
  "scripts": {
    "test:e2e": "playwright test",
    "test:e2e:headed": "playwright test --headed",
    "test:e2e:debug": "playwright test --debug",
    "test:e2e:ui": "playwright test --ui",
    "test:e2e:report": "playwright show-report"
  }
}

CI Configuration

GitHub Actions

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

on: [push, pull_request]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v2
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: "pnpm"

      - run: pnpm install
      - run: pnpm exec playwright install --with-deps

      - run: pnpm test:e2e
        env:
          BASE_URL: http://localhost:3001

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

Best Practices

1. Use Locators Wisely

// ❌ Fragile CSS selectors
await page.locator(".btn-primary").click();

// ✅ Semantic selectors
await page.getByRole("button", { name: "Submit" }).click();

// ✅ Text content
await page.getByText("Welcome").click();

// ✅ Label
await page.getByLabel("Email").fill("test@example.com");

2. Auto-waiting

// ❌ Manual waiting
await page.waitForTimeout(1000);
await page.click("button");

// ✅ Auto-waiting
await page.getByRole("button").click();

// ✅ Wait for specific state
await page.getByRole("button").waitFor({ state: "visible" });

3. Isolate Tests

// ❌ Tests depend on each other
test("create user", async ({ page }) => {
  // Creates user
});

test("login user", async ({ page }) => {
  // Assumes user from previous test exists
});

// ✅ Independent tests
test("create user", async ({ page }) => {
  // Creates user and cleans up
});

test("login user", async ({ page }) => {
  // Creates its own user, logs in, cleans up
});

4. Use Fixtures

// apps/web/__tests__/e2e/fixtures/test.ts
import { test as base } from "@playwright/test";
import { HomePage, CarsPage } from "./page-objects";

type Fixtures = {
  homePage: HomePage;
  carsPage: CarsPage;
};

export const test = base.extend<Fixtures>({
  homePage: async ({ page }, use) => {
    await use(new HomePage(page));
  },
  carsPage: async ({ page }, use) => {
    await use(new CarsPage(page));
  },
});

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

// Use in tests
import { test, expect } from "./fixtures/test";

test("test with fixtures", async ({ homePage, carsPage }) => {
  await homePage.goto();
  await homePage.navigateToCars();
  // ...
});

Debugging

Debug Mode

# Run with debugger
pnpm exec playwright test --debug

# Debug specific test
pnpm exec playwright test home.spec.ts --debug

Inspector

// Add breakpoint
await page.pause();

// Log to console
console.log(await page.title());

// Take screenshot
await page.screenshot({ path: "debug.png" });

Trace Viewer

# Generate trace
pnpm exec playwright test --trace on

# View trace
pnpm exec playwright show-trace trace.zip

Troubleshooting

Tests Timing Out

// Increase timeout
test("slow test", async ({ page }) => {
  test.setTimeout(60000); // 60 seconds

  await page.goto("/slow-page");
});

// Or in config
export default defineConfig({
  timeout: 30000, // 30 seconds
});

Flaky Tests

// Use waitForLoadState
await page.goto("/");
await page.waitForLoadState("networkidle");

// Wait for specific elements
await page.getByRole("button").waitFor({ state: "visible" });

// Retry assertions
await expect(page.getByText("Welcome")).toBeVisible({ timeout: 10000 });

Selector Not Found

// Check element exists
const button = page.getByRole("button", { name: "Submit" });
console.log(await button.count()); // 0 if not found

// Use has
await expect(page.getByRole("button")).toHaveCount(1);

// Debug selectors
await page.pause(); // Open inspector

References

- playwright.config.ts - Playwright configuration - Root CLAUDE.md - Testing guidelines

Best Practices Summary

  1. Use Semantic Selectors: Prefer role, text, label over CSS selectors
  2. Isolate Tests: Each test should be independent
  3. Auto-waiting: Let Playwright wait for elements
  4. Page Objects: Encapsulate page logic
  5. Mock APIs: Use route mocking for predictable tests
  6. Visual Testing: Compare screenshots for UI changes
  7. Accessibility: Test with axe-core
  8. CI Integration: Run tests in continuous integration

适合场景

01

用户想查找某类 Agent Skill 时

02

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

03

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

04

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

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

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

能力 4

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

能力 5

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

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

平台分布

Claude Code

26.64%
按下载量换算25

Antigravity

21.47%
按下载量换算20

OpenCode

18.25%
按下载量换算17

Gemini CLI

12.15%
按下载量换算11

windsurf

7.48%
按下载量换算7

Codex

3.66%
按下载量换算3

安全审计

Gen Agent Trust Hub

未通过

Socket

通过

Snyk

通过

权限和风险

操作浏览器

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

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。

来源信息

继续浏览同类 Skills