Token导航 LogoToken导航

Agent Skill · 开发

e2e-testing(端到端测试)

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

e2e-testing 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为操作浏览器;许可证为 MIT,第三方扫描有未通过项,按安装量在开发类的 21,744 个 Skill 里排第 19,668。

总安装 261周安装 11GitHub Stars 20下载 92核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 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

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

e2e-testing 用于辅助测试设计、自动化测试和回归验证,适合在 Codex、Claude、Cursor、Gemini CLI 中编写测试用例或定位问题时使用。适用于单元测试、端到端测试和测试计划整理等场景,帮助 Agent 提升代码质量。

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

来源仓库内路径:skills/e2e-testing

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/sgcarstrends/sgcarstrends --skill e2e-testing;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要操作浏览器,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

e2e-testing 怎么安装?

在终端执行 npx skills add https://github.com/sgcarstrends/sgcarstrends --skill e2e-testing。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

e2e-testing 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

e2e-testing 需要什么权限?

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

e2e-testing 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

e2e-testing 用的人多吗?

公开数据是累计安装 261 次,近一周安装 11 次,下载 92 次,GitHub 20 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

e2e-testing 安全吗?

第三方扫描有未通过项,风险较高。除非你确认过源码,否则不建议直接安装到常用环境。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。

先想清楚的地方

  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
e2e-testing

端到端测试 · 本页

26120操作浏览器MIT未通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

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

平台分布

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 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。

继续浏览同类 Skills