Token导航 LogoToken导航

Agent Skill · 研究检索

webapp-testing(Web 应用测试)

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

webapp-testing 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为接触敏感数据;许可证为 MIT,暂无第三方扫描结果,按安装量在研究检索类的 73,675 个 Skill 里排第 46,860。

总安装 759周安装 31GitHub Stars 公开资料未说明下载 243核验 2026-05-01

安装说明

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

安装方式

通过对话安装

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

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

命令行安装

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

AgentSkills.tonpx skills
npx skills add vapvarun/claude-backup --skill "webapp-testing"

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

用于辅助 Web 应用测试设计、自动化测试用例编写和回归验证流程。适合让 Agent 编写单元测试、端到端测试或根据日志定位问题,需确认项目测试框架和运行命令。

通过 npx skills add vapvarun/claude-backup --skill "webapp-testing" 安装,建议区分本地模拟与生产环境。涉及浏览器或外部服务时,应避免为了通过测试而改坏真实逻辑。使用时需确认权限范围、维护状态及是否会触发联网或文件读写操作。

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

上手步骤

4 步
  1. 1

    确认自己的宿主工具

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

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add vapvarun/claude-backup --skill "webapp-testing";命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

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

  4. 4

    按最小权限试用

    它需要接触敏感数据,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

webapp-testing 怎么安装?

在终端执行 npx skills add vapvarun/claude-backup --skill "webapp-testing"。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

webapp-testing 支持哪些 AI 工具?

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

webapp-testing 需要什么权限?

权限级别是接触敏感数据。该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

webapp-testing 是免费的吗?

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

webapp-testing 用的人多吗?

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

webapp-testing 安全吗?

没有查到第三方扫描结果,安全性无法从公开资料判断,安装前请自行审查源码、依赖和命令行为。

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

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 许可证明确为 MIT,商用和分发边界好判断。

先想清楚的地方

  • 需要接触敏感数据,装到常用环境前要先评估影响面。
  • 没有第三方扫描结果,安全性只能靠自己读源码判断。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

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

不太适合

  • 环境里有大量密钥、凭证,不方便给第三方 Skill 读取的人。
  • 要求有安全扫描结论才肯安装的团队。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

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

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
webapp-testing

Web 应用测试 · 本页

759—接触敏感数据MIT未收录

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

SKILL.md 原文

name
webapp-testing
description
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.

Web Application Testing with Playwright

Comprehensive E2E testing patterns for web applications.

Quick Start

Python Setup

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    page = browser.new_page()
    page.goto('http://localhost:3000')
    page.wait_for_load_state('networkidle')
    # ... test logic
    browser.close()

JavaScript/TypeScript Setup

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

test('example test', async ({ page }) => {
  await page.goto('http://localhost:3000');
  await expect(page.locator('h1')).toContainText('Welcome');
});

Server Management

Using Helper Scripts

Single server:

python scripts/with_server.py --server "npm run dev" --port 5173 -- python your_test.py

Multiple servers:

python scripts/with_server.py \
  --server "cd backend && python server.py" --port 3000 \
  --server "cd frontend && npm run dev" --port 5173 \
  -- python your_test.py

Playwright Config (playwright.config.ts)

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',

  use: {
    baseURL: 'http://localhost:3000',
    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: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
});

Selectors

Best Practices

// BEST: Test IDs (most reliable)
page.locator('[data-testid="submit-button"]')
page.getByTestId('submit-button')

// GOOD: Role-based (accessible)
page.getByRole('button', { name: 'Submit' })
page.getByRole('heading', { level: 1 })
page.getByRole('link', { name: 'Learn more' })

// GOOD: Label-based (forms)
page.getByLabel('Email address')
page.getByPlaceholder('Enter your email')

// GOOD: Text content
page.getByText('Welcome back')
page.getByText(/welcome/i) // Case-insensitive regex

// AVOID: CSS selectors (brittle)
page.locator('.btn-primary') // Class might change
page.locator('#submit') // ID might change

Selector Chaining

// Find within a container
const form = page.locator('form[data-testid="login-form"]');
await form.getByLabel('Email').fill(' [email protected] ');
await form.getByRole('button', { name: 'Log in' }).click();

// Filter results
await page.getByRole('listitem')
  .filter({ hasText: 'Product 1' })
  .getByRole('button', { name: 'Add to cart' })
  .click();

Common Test Patterns

Authentication Flow

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

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

    await page.getByLabel('Email').fill(' [email protected] ');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Log in' }).click();

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

  test('invalid credentials show error', async ({ page }) => {
    await page.goto('/login');

    await page.getByLabel('Email').fill(' [email protected] ');
    await page.getByLabel('Password').fill('wrongpassword');
    await page.getByRole('button', { name: 'Log in' }).click();

    await expect(page.getByText('Invalid credentials')).toBeVisible();
    await expect(page).toHaveURL('/login'); // Still on login page
  });

  test('logout', async ({ page }) => {
    // Login first (or use authenticated state)
    await page.goto('/dashboard');
    await page.getByRole('button', { name: 'Logout' }).click();

    await expect(page).toHaveURL('/login');
  });
});

Form Submission

test.describe('Contact Form', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/contact');
  });

  test('submits form with valid data', async ({ page }) => {
    await page.getByLabel('Name').fill('John Doe');
    await page.getByLabel('Email').fill(' [email protected] ');
    await page.getByLabel('Message').fill('This is a test message');
    await page.getByRole('button', { name: 'Send' }).click();

    await expect(page.getByText('Message sent successfully')).toBeVisible();
  });

  test('shows validation errors for empty fields', async ({ page }) => {
    await page.getByRole('button', { name: 'Send' }).click();

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

  test('validates email format', async ({ page }) => {
    await page.getByLabel('Name').fill('John');
    await page.getByLabel('Email').fill('invalid-email');
    await page.getByRole('button', { name: 'Send' }).click();

    await expect(page.getByText('Invalid email address')).toBeVisible();
  });
});

Navigation Testing

test.describe('Navigation', () => {
  test('main menu links work', async ({ page }) => {
    await page.goto('/');

    // Test each navigation link
    const navLinks = [
      { name: 'Home', url: '/' },
      { name: 'About', url: '/about' },
      { name: 'Products', url: '/products' },
      { name: 'Contact', url: '/contact' },
    ];

    for (const link of navLinks) {
      await page.getByRole('link', { name: link.name }).click();
      await expect(page).toHaveURL(link.url);
    }
  });

  test('breadcrumbs show correct path', async ({ page }) => {
    await page.goto('/products/category/item-1');

    const breadcrumbs = page.getByRole('navigation', { name: 'Breadcrumb' });
    await expect(breadcrumbs.getByText('Home')).toBeVisible();
    await expect(breadcrumbs.getByText('Products')).toBeVisible();
    await expect(breadcrumbs.getByText('Category')).toBeVisible();
  });
});

CRUD Operations

test.describe('Product Management', () => {
  test('creates new product', async ({ page }) => {
    await page.goto('/admin/products');
    await page.getByRole('button', { name: 'Add Product' }).click();

    await page.getByLabel('Name').fill('New Product');
    await page.getByLabel('Price').fill('29.99');
    await page.getByLabel('Description').fill('Product description');
    await page.getByRole('button', { name: 'Save' }).click();

    await expect(page.getByText('Product created')).toBeVisible();
    await expect(page.getByText('New Product')).toBeVisible();
  });

  test('edits existing product', async ({ page }) => {
    await page.goto('/admin/products');

    // Find product row and click edit
    await page.getByRole('row', { name: /Existing Product/ })
      .getByRole('button', { name: 'Edit' })
      .click();

    await page.getByLabel('Name').fill('Updated Product');
    await page.getByRole('button', { name: 'Save' }).click();

    await expect(page.getByText('Product updated')).toBeVisible();
    await expect(page.getByText('Updated Product')).toBeVisible();
  });

  test('deletes product with confirmation', async ({ page }) => {
    await page.goto('/admin/products');

    // Click delete button
    await page.getByRole('row', { name: /Product to Delete/ })
      .getByRole('button', { name: 'Delete' })
      .click();

    // Handle confirmation dialog
    await page.getByRole('button', { name: 'Confirm' }).click();

    await expect(page.getByText('Product deleted')).toBeVisible();
    await expect(page.getByText('Product to Delete')).not.toBeVisible();
  });
});

Modal/Dialog Testing

test.describe('Modal Dialogs', () => {
  test('opens and closes modal', async ({ page }) => {
    await page.goto('/');

    // Open modal
    await page.getByRole('button', { name: 'Open Modal' }).click();
    await expect(page.getByRole('dialog')).toBeVisible();

    // Close with X button
    await page.getByRole('button', { name: 'Close' }).click();
    await expect(page.getByRole('dialog')).not.toBeVisible();
  });

  test('closes modal on escape key', async ({ page }) => {
    await page.goto('/');

    await page.getByRole('button', { name: 'Open Modal' }).click();
    await expect(page.getByRole('dialog')).toBeVisible();

    await page.keyboard.press('Escape');
    await expect(page.getByRole('dialog')).not.toBeVisible();
  });

  test('closes modal on backdrop click', async ({ page }) => {
    await page.goto('/');

    await page.getByRole('button', { name: 'Open Modal' }).click();
    await expect(page.getByRole('dialog')).toBeVisible();

    // Click outside modal
    await page.locator('.modal-backdrop').click({ position: { x: 10, y: 10 } });
    await expect(page.getByRole('dialog')).not.toBeVisible();
  });
});

Waiting Strategies

Explicit Waits

// Wait for element
await page.waitForSelector('[data-testid="content"]');

// Wait for element state
await page.getByRole('button').waitFor({ state: 'visible' });
await page.getByRole('button').waitFor({ state: 'hidden' });

// Wait for navigation
await page.waitForURL('/dashboard');
await page.waitForURL(/\/user\/\d+/);

// Wait for network
await page.waitForResponse('/api/users');
await page.waitForResponse(response =>
  response.url().includes('/api/') && response.status() === 200
);

// Wait for load state
await page.waitForLoadState('networkidle');
await page.waitForLoadState('domcontentloaded');

Auto-Waiting

// Playwright auto-waits for these
await page.click('button'); // Waits for button to be actionable
await page.fill('input', 'text'); // Waits for input to be editable
await expect(locator).toBeVisible(); // Waits up to timeout

Assertions

Common Assertions

// Visibility
await expect(locator).toBeVisible();
await expect(locator).toBeHidden();
await expect(locator).not.toBeVisible();

// Text content
await expect(locator).toHaveText('exact text');
await expect(locator).toContainText('partial');
await expect(locator).toHaveText(/regex/i);

// Attributes
await expect(locator).toHaveAttribute('href', '/about');
await expect(locator).toHaveClass(/active/);
await expect(locator).toHaveId('main-content');

// Input values
await expect(locator).toHaveValue('input value');
await expect(locator).toBeChecked();
await expect(locator).toBeDisabled();
await expect(locator).toBeEditable();

// Count
await expect(locator).toHaveCount(5);

// Page assertions
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle('Dashboard | My App');

Soft Assertions

// Continue test even if assertion fails
await expect.soft(locator).toHaveText('text');
await expect.soft(locator).toBeVisible();

// Check all soft assertions at end
expect(test.info().errors).toHaveLength(0);

API Testing Integration

Mock API Responses

test('shows loading and data states', async ({ page }) => {
  // Intercept API request
  await page.route('/api/users', async route => {
    await route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify([
        { id: 1, name: 'John' },
        { id: 2, name: 'Jane' },
      ]),
    });
  });

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

test('handles API errors gracefully', async ({ page }) => {
  await page.route('/api/users', route =>
    route.fulfill({ status: 500 })
  );

  await page.goto('/users');
  await expect(page.getByText('Failed to load users')).toBeVisible();
});

Wait for API Calls

test('submits form and waits for API', async ({ page }) => {
  await page.goto('/contact');

  // Start waiting for API response before triggering it
  const responsePromise = page.waitForResponse('/api/contact');

  await page.getByLabel('Email').fill(' [email protected] ');
  await page.getByRole('button', { name: 'Submit' }).click();

  const response = await responsePromise;
  expect(response.status()).toBe(200);
});

Visual Testing

Screenshots

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

  // Full page screenshot
  await expect(page).toHaveScreenshot('homepage.png', {
    fullPage: true,
  });
});

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

  // Element screenshot
  await expect(page.getByTestId('header')).toHaveScreenshot('header.png');
});

Screenshot Options

await page.screenshot({
  path: 'screenshots/test.png',
  fullPage: true,
  animations: 'disabled', // Reduce flakiness
  mask: [page.locator('.dynamic-content')], // Hide changing content
});

Authentication Reuse

Save Auth State

// auth.setup.ts
import { test as setup, expect } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(' [email protected] ');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await page.waitForURL('/dashboard');

  // Save signed-in state
  await page.context().storageState({ path: authFile });
});

Use Auth State

// playwright.config.ts
export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'tests',
      dependencies: ['setup'],
      use: {
        storageState: 'playwright/.auth/user.json',
      },
    },
  ],
});

Accessibility Testing

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

test.describe('Accessibility', () => {
  test('homepage has no a11y violations', async ({ page }) => {
    await page.goto('/');

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

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

  test('form is accessible', async ({ page }) => {
    await page.goto('/contact');

    const results = await new AxeBuilder({ page })
      .include('form')
      .analyze();

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

Performance Testing

test('page loads within performance budget', async ({ page }) => {
  await page.goto('/');

  const metrics = await page.evaluate(() =>
    JSON.stringify(window.performance.timing)
  );
  const timing = JSON.parse(metrics);

  const loadTime = timing.loadEventEnd - timing.navigationStart;
  expect(loadTime).toBeLessThan(3000); // 3 seconds
});

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

  const lcp = await page.evaluate(() => {
    return new Promise(resolve => {
      new PerformanceObserver(list => {
        const entries = list.getEntries();
        resolve(entries[entries.length - 1].startTime);
      }).observe({ type: 'largest-contentful-paint', buffered: true });
    });
  });

  expect(lcp).toBeLessThan(2500); // Good LCP is < 2.5s
});

Debug Helpers

Debugging Commands

# Run with headed browser
npx playwright test --headed

# Run with debugging UI
npx playwright test --debug

# Run specific test
npx playwright test -g "test name"

# Show report
npx playwright show-report

In-Test Debugging

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

  // Pause execution
  await page.pause();

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

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

  // Slow down
  await page.setDefaultTimeout(30000);
});

Console Logs

# Python: Capture browser console
page.on('console', lambda msg: print(f'Browser log: {msg.text}'))
page.on('pageerror', lambda err: print(f'Browser error: {err}'))
// TypeScript: Capture browser console
page.on('console', msg => console.log('Browser:', msg.text()));
page.on('pageerror', err => console.log('Error:', err));

Test Organization

File Structure

tests/
├── e2e/
│   ├── auth/
│   │   ├── login.spec.ts
│   │   └── logout.spec.ts
│   ├── products/
│   │   ├── listing.spec.ts
│   │   └── details.spec.ts
│   └── checkout/
│       └── flow.spec.ts
├── fixtures/
│   └── test-data.ts
└── utils/
    └── helpers.ts

Custom Fixtures

// fixtures/test-fixtures.ts
import { test as base } from '@playwright/test';

type Fixtures = {
  authenticatedPage: Page;
};

export const test = base.extend<Fixtures>({
  authenticatedPage: async ({ page }, use) => {
    await page.goto('/login');
    await page.fill('[name="email"]', ' [email protected] ');
    await page.fill('[name="password"]', 'password');
    await page.click('button[type="submit"]');
    await page.waitForURL('/dashboard');
    await use(page);
  },
});

// Usage
test('authenticated test', async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/profile');
  // Already logged in
});

Common Pitfalls

PitfallProblemSolution
Race conditionsTest checks before page updatesUse waitFor or expect with retries
Flaky selectorsCSS classes changeUse data-testid or role selectors
Hard-coded waitspage.waitForTimeout(3000)Wait for specific conditions
Not waiting for hydrationJS not executedwaitForLoadState('networkidle')
Shared stateTests affect each otherUse isolated storage/auth per test
Ignoring errorsUncaught exceptionsCheck page.on('pageerror')

Checklist

  • [ ] Use stable selectors (test IDs, roles, labels)
  • [ ] Wait for appropriate conditions (not arbitrary timeouts)
  • [ ] Test both happy path and error states
  • [ ] Include accessibility checks
  • [ ] Test responsive breakpoints
  • [ ] Mock external API dependencies
  • [ ] Capture screenshots on failure
  • [ ] Run tests in CI/CD
  • [ ] Keep tests independent
  • [ ] Organize tests by feature

平台分布

Claude Code

29.48%
按下载量换算72

windsurf

21.51%
按下载量换算52

OpenCode

17.77%
按下载量换算43

Codex

12.73%
按下载量换算31

Antigravity

8.17%
按下载量换算20

Gemini CLI

3%
按下载量换算7

安全审计

暂无安全审计结果可展示。

权限和风险

接触敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

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

继续浏览同类 Skills