Token导航 LogoToken导航TokenDH.com
前端设计操作浏览器github未标认证来源可访问许可证需确认审计通过

e2e-tester端到端测试仪

Agent Skill

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

总安装

672

周安装

28

GitHub Stars

209

下载量

224
CodexClaudeCursorGemini CLI

安装说明

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

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

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

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

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/pageai-pro/ralph-loop --skill e2e-tester

简介

e2e-tester 用于辅助测试设计、自动化测试、用例整理和回归验证。

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

SKILL.md

Before you start:

  • check if any existing tests cover the functionality you are testing
  • check if a unit tests can cover the feature better
  • analyze if a end to end test brings any value over a unit test

If the answer is yes to any of the above, proceed with the end to end test creation.

MCP Workflow (MANDATORY If Available)

⚠️ If you have Playwright MCP tools, ALWAYS use them BEFORE creating any test:

  1. Navigate to target page
  2. Take snapshot to see page structure and elements
  3. Interact with forms/elements to verify exact user flow
  4. Take screenshots to document expected states
  5. Verify page transitions through complete flow (loading, success, error)
  6. Document actual selectors from snapshots (use real refs and labels)
  7. Only after exploring create test code with verified selectors

If MCP NOT available: Proceed with test creation based on docs and code analysis.

Why This Matters:

  • ✅ Precise tests - exact steps needed, no assumptions
  • ✅ Accurate selectors - real DOM structure, not imagined
  • ✅ Real flow validation - verify journey actually works
  • ✅ Avoid over-engineering - minimal tests for what exists
  • ✅ Prevent flaky tests - real exploration = stable tests
  • ❌ Never assume how UI "should" work

Waiting Strategies (CRITICAL)

// ❌ NEVER use fixed waits
await page.waitForTimeout(2000);

// ✅ Wait for specific conditions
await expect(element).toBeVisible();
await page.waitForResponse(resp => resp.url().includes('/api/data'));
await page.waitForURL('**/dashboard');
await expect(page.getByText('Success')).toBeVisible({ timeout: 10000 });

// ✅ For SPAs, prefer explicit waits over networkidle
await page.goto('/app', { waitUntil: 'domcontentloaded' });
await expect(page.getByRole('main')).toBeVisible();

File Structure

tests/
├── base-page.ts              # Parent class for ALL pages
├── helpers.ts                # Shared utilities
└── {page-name}/
    ├── {page-name}-page.ts   # Page Object Model
    ├── {page-name}.spec.ts   # ALL tests here (NO separate files!)
    └── {page-name}.md        # Test documentation

File Naming:

  • sign-up.spec.ts (all sign-up tests)
  • sign-up-page.ts (page object)
  • sign-up.md (documentation)
  • sign-up-critical-path.spec.ts (WRONG - no separate files)
  • sign-up-validation.spec.ts (WRONG)

Selector Priority (REQUIRED)

// 1. BEST - getByRole for interactive elements
this.submitButton = page.getByRole("button", { name: "Submit" });
this.navLink = page.getByRole("link", { name: "Dashboard" });

// 2. BEST - getByLabel for form controls
this.emailInput = page.getByLabel("Email");
this.passwordInput = page.getByLabel("Password");

// 3. SPARINGLY - getByText for static content only
this.errorMessage = page.getByText("Invalid credentials");
this.pageTitle = page.getByText("Welcome");

// 4. LAST RESORT - getByTestId when above fail
this.customWidget = page.getByTestId("date-picker");

// ❌ AVOID fragile selectors
this.button = page.locator(".btn-primary");  // NO
this.input = page.locator("#email");         // NO

Scope Detection (ASK IF AMBIGUOUS)

User SaysAction
"a test", "one test", "new test", "add test"Create ONE test() in existing spec
"comprehensive tests", "all tests", "test suite", "generate tests"Create full suite

Examples:

  • "Create a test for user sign-up" → ONE test only
  • "Generate E2E tests for login page" → Full suite
  • "Add a test to verify form validation" → ONE test to existing spec

Page Object Pattern

import { Page, Locator, expect } from "@playwright/test";

// BasePage - ALL pages extend this
export class BasePage {
  constructor(protected page: Page) {}

  async goto(path: string): Promise<void> {
    await this.page.goto(path);
    await this.page.waitForLoadState("networkidle");
  }

  // Common methods go here (see Refactoring Guidelines)
  async waitForNotification(): Promise<void> {
    await this.page.waitForSelector('[role="status"]');
  }

  async verifyNotificationMessage(message: string): Promise<void> {
    const notification = this.page.locator('[role="status"]');
    await expect(notification).toContainText(message);
  }
}

// Page-specific implementation
export interface LoginData {
  email: string;
  password: string;
}

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

  constructor(page: Page) {
    super(page);
    this.emailInput = page.getByLabel("Email");
    this.passwordInput = page.getByLabel("Password");
    this.submitButton = page.getByRole("button", { name: "Sign in" });
  }

  async goto(): Promise<void> {
    await super.goto("/login");
  }

  async login(data: LoginData): Promise<void> {
    await this.emailInput.fill(data.email);
    await this.passwordInput.fill(data.password);
    await this.submitButton.click();
  }

  async verifyCriticalOutcome(): Promise<void> {
    await expect(this.page).toHaveURL("/dashboard");
  }
}

Page Object Reuse (CRITICAL)

Always check existing page objects before creating new ones!

// ✅ GOOD: Reuse existing page objects
import { SignInPage } from "../sign-in/sign-in-page";
import { HomePage } from "../home/home-page";

test("User can sign up and login", async ({ page }) => {
  const signUpPage = new SignUpPage(page);
  const signInPage = new SignInPage(page);  // REUSE
  const homePage = new HomePage(page);      // REUSE

  await signUpPage.signUp(userData);
  await homePage.verifyPageLoaded();  // REUSE method
  await homePage.signOut();           // REUSE method
  await signInPage.login(credentials); // REUSE method
});

// ❌ BAD: Recreating existing functionality
export class SignUpPage extends BasePage {
  async logout() { /* ... */ }  // ❌ HomePage already has this
  async login() { /* ... */ }   // ❌ SignInPage already has this
}

Guidelines:

  • Check tests/ for existing page objects first
  • Import and reuse existing pages
  • Create page objects only when page doesn't exist
  • If test requires multiple pages, ensure all page objects exist (create if needed)

Refactoring Guidelines

Move to BasePage when:

  • ✅ Navigation helpers used by multiple pages (waitForPageLoad(), getCurrentUrl())
  • ✅ Common UI interactions (notifications, modals, theme toggles)
  • ✅ Verification patterns repeated across pages (isVisible(), waitForVisible())
  • ✅ Error handling that applies to all pages
  • ✅ Screenshot utilities for debugging

Move to helpers.ts when:

  • ✅ Test data generation (generateUniqueEmail(), generateTestUser())
  • ✅ Setup/teardown utilities (createTestUser(), cleanupTestData())
  • ✅ Custom assertions (expectNotificationToContain())
  • ✅ API helpers for test setup (seedDatabase(), resetState())
  • ✅ Time utilities (waitForCondition(), retryAction())

Before (BAD):

// Repeated in multiple page objects
export class SignUpPage extends BasePage {
  async waitForNotification(): Promise<void> {
    await this.page.waitForSelector('[role="status"]');
  }
}
export class SignInPage extends BasePage {
  async waitForNotification(): Promise<void> {
    await this.page.waitForSelector('[role="status"]');  // DUPLICATED!
  }
}

After (GOOD):

// BasePage - shared across all pages
export class BasePage {
  async waitForNotification(): Promise<void> {
    await this.page.waitForSelector('[role="status"]');
  }
}

// helpers.ts - data generation
export function generateUniqueEmail(): string {
  return `test.${Date.now()}@example.com`;
}

export function generateTestUser() {
  return {
    name: "Test User",
    email: generateUniqueEmail(),
    password: "TestPassword123!",
  };
}

Test Pattern with Tags

import { test, expect } from "@playwright/test";
import { LoginPage } from "./login-page";

test.describe("Login", () => {
  test("User can login successfully",
    { tag: ["@critical", "@e2e", "@login", "@LOGIN-E2E-001"] },
    async ({ page }) => {
      const loginPage = new LoginPage(page);

      await loginPage.goto();
      await loginPage.login({ email: "user@test.com", password: "pass123" });

      await expect(page).toHaveURL("/dashboard");
    }
  );
});

Tag Categories:

  • Priority: @critical, @high, @medium, @low
  • Type: @e2e
  • Feature: @signup, @signin, @dashboard
  • Test ID: @SIGNUP-E2E-001, @LOGIN-E2E-002

Test Documentation Format ({page-name}.md)

### E2E Tests: {Feature Name}

**Suite ID:** `{SUITE-ID}`
**Feature:** {Feature description}

---

## Test Case: `{TEST-ID}` - {Test case title}

**Priority:** `{critical|high|medium|low}`

**Tags:**
- type → @e2e
- feature → @{feature-name}

**Description/Objective:** {Brief description}

**Preconditions:**
- {Prerequisites for test to run}
- {Required data or state}

### Flow Steps:
1. {Step 1}
2. {Step 2}
3. {Step 3}

### Expected Result:
- {Expected outcome 1}
- {Expected outcome 2}

### Key verification points:
- {Assertion 1}
- {Assertion 2}

### Notes:
- {Additional considerations}

Documentation Rules:

  • ❌ NO general test running instructions
  • ❌ NO file structure explanations
  • ❌ NO code examples or tutorials
  • ❌ NO troubleshooting sections
  • ✅ Focus ONLY on specific test case
  • ✅ Keep under 60 lines when possible

Authentication State Reuse

// auth.setup.ts - Run once, reuse across tests
import { test as setup } from "@playwright/test";

setup("authenticate", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel("Email").fill("user@test.com");
  await page.getByLabel("Password").fill("password");
  await page.getByRole("button", { name: "Sign in" }).click();
  await page.waitForURL("/dashboard");
  await page.context().storageState({ path: ".auth/user.json" });
});

// playwright.config.ts
projects: [
  { name: "auth", testMatch: /auth\.setup\.ts/ },
  { name: "logged-in", dependencies: ["auth"], use: { storageState: ".auth/user.json" } },
  { name: "logged-out", use: { storageState: { cookies: [], origins: [] } } }
]

API Mocking

// Mock API responses for isolated tests
await page.route("**/api/users", route =>
  route.fulfill({ json: { users: [{ id: 1, name: "Test" }] } })
);

// Mock error states
await page.route("**/api/submit", route =>
  route.fulfill({ status: 500, json: { error: "Server error" } })
);

// Abort requests (e.g., block analytics)
await page.route("**/analytics/**", route => route.abort());

Test Fixtures

// fixtures.ts - Custom fixtures for setup/teardown
import { test as base } from "@playwright/test";
import { AdminPage } from "./admin/admin-page";

export const test = base.extend<{ adminPage: AdminPage }>({
  adminPage: async ({ page }, use) => {
    const admin = new AdminPage(page);
    await admin.goto();
    await use(admin);
    // Teardown runs after test
  },
});

// Usage in tests
test("admin can manage users", async ({ adminPage }) => {
  await adminPage.deleteUser("test@example.com");
});

Parallel Execution

// Tests run in parallel by default. Use serial when tests share state:
test.describe.configure({ mode: "serial" });

// Isolate test data to prevent conflicts
test("create user", async ({ page }) => {
  const uniqueEmail = `user-${Date.now()}@test.com`; // ✅ Unique per run
});

Assertions

// Soft assertions - collect multiple failures
await expect.soft(page.getByText("Title")).toBeVisible();
await expect.soft(page.getByText("Subtitle")).toBeVisible();
// Test continues, reports all failures at end

// Polling assertions - retry until condition met
await expect(async () => {
  const count = await page.getByRole("listitem").count();
  expect(count).toBeGreaterThan(5);
}).toPass({ timeout: 10000 });

// Visual regression
await expect(page).toHaveScreenshot("dashboard.png");
await expect(page.getByRole("dialog")).toHaveScreenshot();

Common Pitfalls

// ❌ Element detached after navigation
const button = page.getByRole("button", { name: "Submit" });
await button.click();
await button.click(); // May fail if page navigated

// ✅ Re-query after navigation
await page.getByRole("button", { name: "Submit" }).click();
await page.getByRole("button", { name: "Confirm" }).click();

// ❌ Race condition with animations
await element.click();

// ✅ Wait for animation to complete
await element.click();
await expect(modal).toBeVisible();

// Iframes
const frame = page.frameLocator("#iframe-id");
await frame.getByRole("button").click();

// Shadow DOM - Playwright pierces by default, but for closed shadow:
await page.locator("custom-element").locator("internal:shadow=button").click();

Mobile & Viewport Testing

// In test file
test.use({ viewport: { width: 375, height: 667 } });

// Device emulation
import { devices } from "@playwright/test";
test.use({ ...devices["iPhone 13"] });

// Or in playwright.config.ts projects
projects: [
  { name: "desktop", use: { viewport: { width: 1280, height: 720 } } },
  { name: "mobile", use: { ...devices["iPhone 13"] } },
]

Debugging & Traces

# Enable traces on failure (recommended for CI)
npx playwright test --trace on-first-retry

# View trace file
npx playwright show-trace trace.zip

# Debug mode - step through test
npx playwright test --debug

# Headed mode to see browser
npx playwright test --headed
// In playwright.config.ts
use: {
  trace: "on-first-retry",      // Capture trace on retry
  screenshot: "only-on-failure", // Screenshot on failure
  video: "retain-on-failure",    // Video on failure
}

CI/CD Configuration

// playwright.config.ts for CI
export default defineConfig({
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: process.env.CI
    ? [["html"], ["github"], ["junit", { outputFile: "results.xml" }]]
    : [["html"]],
  use: {
    trace: "on-first-retry",
    screenshot: "only-on-failure",
  },
});
# GitHub Actions example
- name: Run Playwright tests
  run: npx playwright test
- uses: actions/upload-artifact@v4
  if: always()
  with:
    name: playwright-report
    path: playwright-report/

Running Tests (PREFER PARTIAL RUNS)

Always prefer running specific tests over the entire suite for faster feedback:

# Run ALL tests
npx playwright test

# ✅ PREFERRED: Run specific file
npx playwright test tests/login/login.spec.ts

# ✅ PREFERRED: Run specific folder
npx playwright test tests/login/

# ✅ PREFERRED: Run by test name (grep)
npx playwright test --grep "login"
npx playwright test --grep "user can sign up"

# ✅ PREFERRED: Run by tag
npx playwright test --grep "@critical"
npx playwright test --grep "@LOGIN-E2E-001"

# ✅ Run single test by line number
npx playwright test tests/login/login.spec.ts:42

# ✅ Run tests matching multiple patterns
npx playwright test --grep "login|signup"

# ✅ Exclude tests by pattern
npx playwright test --grep-invert "slow"

Other Commands

npx playwright test --debug            # Debug mode (step through)
npx playwright test --headed           # See browser
npx playwright test --trace on         # Enable tracing
npx playwright test --project=mobile   # Run specific project
npx playwright codegen                 # Record tests

Partial String Matching (CRITICAL for Robustness)

Always use partial/pattern matching instead of exact strings to reduce maintenance:

// ❌ FRAGILE: Exact string matches break easily
await expect(page.getByText("Welcome to Our Application!")).toBeVisible();
await expect(page.getByRole("button", { name: "Submit Form" })).toBeVisible();
await expect(notification).toHaveText("Your account has been created successfully.");

// ✅ ROBUST: Partial matches survive copy changes
await expect(page.getByText(/welcome/i)).toBeVisible();
await expect(page.getByRole("button", { name: /submit/i })).toBeVisible();
await expect(notification).toContainText("account");
await expect(notification).toContainText(/created/i);

// ✅ ROBUST: Use toContainText over toHaveText
await expect(page.locator(".message")).toContainText("success");

// ✅ ROBUST: Pattern matching for dynamic content
await expect(page.getByText(/order #\d+/i)).toBeVisible();
await expect(page.getByText(/\d+ items? in cart/i)).toBeVisible();

Why Partial Matching:

  • ✅ Survives minor copy/text changes
  • ✅ Works across locales (case-insensitive)
  • ✅ Less brittle to whitespace changes
  • ✅ Easier to maintain long-term
  • ❌ Exact matches break on punctuation, capitalization, or wording tweaks

适合场景

01

用户想查找某类 Agent Skill 时

02

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

03

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

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

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

能力 4

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

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

平台分布

Codex

33.94%
按下载量换算76

Claude

34.05%
按下载量换算76

Cursor

18.85%
按下载量换算42

Gemini CLI

9.49%
按下载量换算21

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

操作浏览器

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

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills