Token导航 LogoToken导航TokenDH.com
研究检索敏感数据github未标认证来源可访问clear审计未展示

testing-qa测试质量保证

Agent Skill

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

总安装

5,760

周安装

240

GitHub Stars

公开资料未说明

下载量

1,920
CodexClaudeCursorGemini CLI

安装说明

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

GitHub

来源数

2

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

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

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

命令行安装

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

AgentSkills.tonpx skills
npx skills add eddiebe147/claude-settings --skill "testing-qa"

简介

聚焦于质量保证(QA)的测试支持。

  • 协助制定测试计划和验收标准。testing-qa 属于研究检索类 Skill,可作为该场景下的辅助能力补充。
  • 可用于评审测试用例的完整性与有效性。
  • 建议与开发流程紧密结合使用。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。
  • 注意保护敏感数据和隐私信息。

SKILL.md

name
testing-qa
description
Expert guide for testing Next.js applications with Playwright, Jest, and React Testing Library. Use when writing tests, debugging test failures, or setting up test infrastructure.

Testing & QA Skill

Overview

This skill helps you write comprehensive tests for Next.js applications using Playwright for E2E tests, Jest for unit tests, and React Testing Library for component tests.

Testing Philosophy

Testing Pyramid

  1. E2E Tests (10%): Critical user journeys
  2. Integration Tests (30%): Component interactions
  3. Unit Tests (60%): Individual functions and utilities

What to Test

  • DO: Test behavior, not implementation
  • DO: Test user interactions and outcomes
  • DO: Test error states and edge cases
  • DO: Test accessibility
  • DON'T: Test internal implementation details
  • DON'T: Test third-party libraries
  • DON'T: Over-test simple presentational components

Playwright E2E Tests

Setup

// 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',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'Mobile Chrome',
      use: { ...devices['Pixel 5'] },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
})

Basic E2E Test

// e2e/auth.spec.ts
import { test, expect } from '@playwright/test'

test.describe('Authentication', () => {
  test('should sign up new user', async ({ page }) => {
    await page.goto('/signup')

    // Fill form
    await page.fill('input[name="email"]', ' [email protected] ')
    await page.fill('input[name="password"]', 'password123')
    await page.fill('input[name="confirmPassword"]', 'password123')

    // Submit
    await page.click('button[type="submit"]')

    // Verify redirect to dashboard
    await expect(page).toHaveURL('/dashboard')

    // Verify welcome message
    await expect(page.getByText('Welcome')).toBeVisible()
  })

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

    await page.fill('input[name="email"]', ' [email protected] ')
    await page.fill('input[name="password"]', 'wrongpassword')
    await page.click('button[type="submit"]')

    // Verify error message
    await expect(page.getByText('Invalid credentials')).toBeVisible()
  })
})

Advanced Playwright Patterns

// Page Object Model
// e2e/pages/login.page.ts
export class LoginPage {
  constructor(private page: Page) {}

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

  async login(email: string, password: string) {
    await this.page.fill('input[name="email"]', email)
    await this.page.fill('input[name="password"]', password)
    await this.page.click('button[type="submit"]')
  }

  async getErrorMessage() {
    return await this.page.locator('[role="alert"]').textContent()
  }
}

// Usage
test('login with page object', async ({ page }) => {
  const loginPage = new LoginPage(page)
  await loginPage.goto()
  await loginPage.login(' [email protected] ', 'password')
  await expect(page).toHaveURL('/dashboard')
})

// Fixtures for authenticated state
// e2e/fixtures.ts
export const test = base.extend<{ authenticatedPage: Page }>({
  authenticatedPage: async ({ page }, use) => {
    // Login
    await page.goto('/login')
    await page.fill('input[name="email"]', ' [email protected] ')
    await page.fill('input[name="password"]', 'password')
    await page.click('button[type="submit"]')
    await page.waitForURL('/dashboard')

    await use(page)
  },
})

// Usage
test('dashboard test', async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/dashboard')
  // Test authenticated functionality
})

Common Playwright Patterns

// Wait for network
await page.waitForResponse(resp => resp.url().includes('/api/items'))

// Test file upload
await page.setInputFiles('input[type="file"]', 'path/to/file.jpg')

// Test download
const downloadPromise = page.waitForEvent('download')
await page.click('button:has-text("Download")')
const download = await downloadPromise
await download.saveAs('/path/to/save')

// Mock API responses
await page.route('**/api/items', route => {
  route.fulfill({
    status: 200,
    body: JSON.stringify({ items: [] }),
  })
})

// Screenshot for debugging
await page.screenshot({ path: 'debug.png', fullPage: true })

// Test responsive design
await page.setViewportSize({ width: 375, height: 667 }) // iPhone size

// Test accessibility
const accessibilityScanResults = await new AxeBuilder({ page }).analyze()
expect(accessibilityScanResults.violations).toEqual([])

Component Testing

Setup React Testing Library

// jest.config.js
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
  },
}

// jest.setup.js
import '@testing-library/jest-dom'

Basic Component Test

// components/button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './button'

describe('Button', () => {
  it('renders with text', () => {
    render(<Button>Click me</Button>)
    expect(screen.getByText('Click me')).toBeInTheDocument()
  })

  it('calls onClick when clicked', () => {
    const handleClick = jest.fn()
    render(<Button onClick={handleClick}>Click me</Button>)

    fireEvent.click(screen.getByText('Click me'))
    expect(handleClick).toHaveBeenCalledTimes(1)
  })

  it('is disabled when disabled prop is true', () => {
    render(<Button disabled>Click me</Button>)
    expect(screen.getByText('Click me')).toBeDisabled()
  })

  it('applies variant styles', () => {
    render(<Button variant="destructive">Delete</Button>)
    const button = screen.getByText('Delete')
    expect(button).toHaveClass('bg-red-600')
  })
})

Testing Async Components

// components/user-profile.test.tsx
import { render, screen, waitFor } from '@testing-library/react'
import { UserProfile } from './user-profile'

// Mock fetch
global.fetch = jest.fn()

describe('UserProfile', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  it('displays loading state initially', () => {
    (fetch as jest.Mock).mockImplementation(() =>
      new Promise(() => {}) // Never resolves
    )

    render(<UserProfile userId="123" />)
    expect(screen.getByText('Loading...')).toBeInTheDocument()
  })

  it('displays user data when loaded', async () => {
    (fetch as jest.Mock).mockResolvedValueOnce({
      ok: true,
      json: async () => ({ name: 'John Doe', email: ' [email protected] ' }),
    })

    render(<UserProfile userId="123" />)

    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument()
      expect(screen.getByText(' [email protected] ')).toBeInTheDocument()
    })
  })

  it('displays error message when fetch fails', async () => {
    (fetch as jest.Mock).mockRejectedValueOnce(new Error('Failed to fetch'))

    render(<UserProfile userId="123" />)

    await waitFor(() => {
      expect(screen.getByText('Error loading user')).toBeInTheDocument()
    })
  })
})

Testing Forms

// components/contact-form.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ContactForm } from './contact-form'

describe('ContactForm', () => {
  it('validates required fields', async () => {
    render(<ContactForm />)

    const submitButton = screen.getByText('Submit')
    fireEvent.click(submitButton)

    await waitFor(() => {
      expect(screen.getByText('Email is required')).toBeInTheDocument()
      expect(screen.getByText('Message is required')).toBeInTheDocument()
    })
  })

  it('submits form with valid data', async () => {
    const user = userEvent.setup()
    const onSubmit = jest.fn()

    render(<ContactForm onSubmit={onSubmit} />)

    await user.type(screen.getByLabelText('Email'), ' [email protected] ')
    await user.type(screen.getByLabelText('Message'), 'Test message')
    await user.click(screen.getByText('Submit'))

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({
        email: ' [email protected] ',
        message: 'Test message',
      })
    })
  })

  it('disables submit button while submitting', async () => {
    const user = userEvent.setup()

    render(<ContactForm />)

    await user.type(screen.getByLabelText('Email'), ' [email protected] ')
    await user.type(screen.getByLabelText('Message'), 'Test message')

    const submitButton = screen.getByText('Submit')
    await user.click(submitButton)

    expect(submitButton).toBeDisabled()
  })
})

Testing Hooks

// hooks/use-counter.test.ts
import { renderHook, act } from '@testing-library/react'
import { useCounter } from './use-counter'

describe('useCounter', () => {
  it('initializes with default value', () => {
    const { result } = renderHook(() => useCounter())
    expect(result.current.count).toBe(0)
  })

  it('initializes with custom value', () => {
    const { result } = renderHook(() => useCounter(10))
    expect(result.current.count).toBe(10)
  })

  it('increments count', () => {
    const { result } = renderHook(() => useCounter())

    act(() => {
      result.current.increment()
    })

    expect(result.current.count).toBe(1)
  })

  it('decrements count', () => {
    const { result } = renderHook(() => useCounter(5))

    act(() => {
      result.current.decrement()
    })

    expect(result.current.count).toBe(4)
  })

  it('resets count', () => {
    const { result } = renderHook(() => useCounter(10))

    act(() => {
      result.current.reset()
    })

    expect(result.current.count).toBe(10)
  })
})

Unit Testing

Testing Utilities

// lib/utils.test.ts
import { formatDate, slugify, truncate } from './utils'

describe('formatDate', () => {
  it('formats date correctly', () => {
    const date = new Date('2024-01-15')
    expect(formatDate(date)).toBe('January 15, 2024')
  })

  it('handles invalid date', () => {
    expect(formatDate(new Date('invalid'))).toBe('Invalid Date')
  })
})

describe('slugify', () => {
  it('converts string to slug', () => {
    expect(slugify('Hello World')).toBe('hello-world')
    expect(slugify('Next.js App!')).toBe('next-js-app')
  })

  it('removes special characters', () => {
    expect(slugify('Test@#$%')).toBe('test')
  })
})

describe('truncate', () => {
  it('truncates long strings', () => {
    const text = 'This is a very long text'
    expect(truncate(text, 10)).toBe('This is a...')
  })

  it('does not truncate short strings', () => {
    const text = 'Short'
    expect(truncate(text, 10)).toBe('Short')
  })
})

Testing API Routes

// app/api/items/route.test.ts
import { GET, POST } from './route'
import { NextRequest } from 'next/server'

describe('/api/items', () => {
  describe('GET', () => {
    it('returns items', async () => {
      const request = new NextRequest('http://localhost:3000/api/items')
      const response = await GET(request)
      const data = await response.json()

      expect(response.status).toBe(200)
      expect(data.items).toBeDefined()
      expect(Array.isArray(data.items)).toBe(true)
    })
  })

  describe('POST', () => {
    it('creates new item', async () => {
      const request = new NextRequest('http://localhost:3000/api/items', {
        method: 'POST',
        body: JSON.stringify({ title: 'Test Item' }),
      })

      const response = await POST(request)
      const data = await response.json()

      expect(response.status).toBe(201)
      expect(data.item).toBeDefined()
      expect(data.item.title).toBe('Test Item')
    })

    it('validates required fields', async () => {
      const request = new NextRequest('http://localhost:3000/api/items', {
        method: 'POST',
        body: JSON.stringify({}),
      })

      const response = await POST(request)

      expect(response.status).toBe(400)
    })
  })
})

Mocking

Mock Supabase

// __mocks__/supabase.ts
export const createClient = jest.fn(() => ({
  from: jest.fn(() => ({
    select: jest.fn().mockReturnThis(),
    insert: jest.fn().mockReturnThis(),
    update: jest.fn().mockReturnThis(),
    delete: jest.fn().mockReturnThis(),
    eq: jest.fn().mockReturnThis(),
    single: jest.fn().mockResolvedValue({
      data: { id: '1', title: 'Test' },
      error: null,
    }),
  })),
  auth: {
    getUser: jest.fn().mockResolvedValue({
      data: { user: { id: '123', email: ' [email protected] ' } },
      error: null,
    }),
  },
}))

Mock Next.js Router

// Mock useRouter
jest.mock('next/navigation', () => ({
  useRouter: () => ({
    push: jest.fn(),
    replace: jest.fn(),
    back: jest.fn(),
    forward: jest.fn(),
    refresh: jest.fn(),
    prefetch: jest.fn(),
  }),
  usePathname: () => '/test-path',
  useSearchParams: () => new URLSearchParams(),
}))

Mock External API

// Use MSW (Mock Service Worker)
import { rest } from 'msw'
import { setupServer } from 'msw/node'

const server = setupServer(
  rest.get('/api/items', (req, res, ctx) => {
    return res(
      ctx.json({
        items: [
          { id: '1', title: 'Item 1' },
          { id: '2', title: 'Item 2' },
        ],
      })
    )
  })
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

Testing Checklist

E2E Tests

  • [ ] Test critical user journeys (signup, login, checkout)
  • [ ] Test on multiple browsers
  • [ ] Test on mobile viewport
  • [ ] Test error states
  • [ ] Test with slow network
  • [ ] Test with disabled JavaScript (where applicable)
  • [ ] Test accessibility

Component Tests

  • [ ] Test rendering
  • [ ] Test user interactions
  • [ ] Test props
  • [ ] Test conditional rendering
  • [ ] Test error states
  • [ ] Test loading states
  • [ ] Test accessibility

Unit Tests

  • [ ] Test pure functions
  • [ ] Test edge cases
  • [ ] Test error handling
  • [ ] Test with various inputs
  • [ ] Test boundary conditions

Common Testing Patterns

Test IDs for Reliable Selection

// Component
<button data-testid="submit-button">Submit</button>

// Test
const button = screen.getByTestId('submit-button')

Accessible Queries (Preferred)

// By role (best)
screen.getByRole('button', { name: /submit/i })

// By label
screen.getByLabelText('Email')

// By text
screen.getByText('Welcome')

// By placeholder
screen.getByPlaceholderText('Enter email')

Testing Loading States

it('shows loading then content', async () => {
  render(<Component />)

  // Loading state
  expect(screen.getByText('Loading...')).toBeInTheDocument()

  // Wait for content
  await waitFor(() => {
    expect(screen.getByText('Content')).toBeInTheDocument()
    expect(screen.queryByText('Loading...')).not.toBeInTheDocument()
  })
})

Testing Error Boundaries

it('renders error boundary on error', () => {
  const spy = jest.spyOn(console, 'error').mockImplementation()

  render(
    <ErrorBoundary>
      <ComponentThatThrows />
    </ErrorBoundary>
  )

  expect(screen.getByText('Something went wrong')).toBeInTheDocument()

  spy.mockRestore()
})

Debugging Tests

Debug Output

import { screen, render } from '@testing-library/react'

// Print component tree
render(<Component />)
screen.debug()

// Print specific element
screen.debug(screen.getByRole('button'))

Playwright Debug

# Run in debug mode with browser
npx playwright test --debug

# Run specific test
npx playwright test auth.spec.ts --debug

# Run with headed browser
npx playwright test --headed

Common Issues

Element not found:

  • Check if element exists: screen.getByText vs screen.queryByText
  • Use findBy for async elements: screen.findByText
  • Check accessibility tree: screen.debug()

Timing issues:

  • Use waitFor for async updates
  • Use findBy queries (built-in wait)
  • Increase timeout if needed

State updates not reflected:

  • Wrap in act() if updating state manually
  • Use userEvent instead of fireEvent for more realistic events

Performance Testing

// Lighthouse CI
// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000/'],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }],
        'categories:accessibility': ['error', { minScore: 0.9 }],
        'categories:best-practices': ['error', { minScore: 0.9 }],
        'categories:seo': ['error', { minScore: 0.9 }],
      },
    },
  },
}

When to Use This Skill

Invoke this skill when:

  • Writing new tests
  • Debugging test failures
  • Setting up test infrastructure
  • Testing specific scenarios (forms, async, auth)
  • Implementing E2E tests
  • Testing accessibility
  • Mocking dependencies
  • Improving test coverage

适合场景

01

用户想查找某类 Agent Skill 时

02

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

03

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

04

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

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

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

能力 4

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

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

平台分布

Claude Code

29.31%
按下载量换算563

OpenCode

21.81%
按下载量换算419

Gemini CLI

16%
按下载量换算307

Antigravity

12.18%
按下载量换算234

Cursor

7.15%
按下载量换算137

Codex

3.09%
按下载量换算59

安全审计

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

权限和风险

敏感数据

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

安装前确认

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

来源信息

继续浏览同类 Skills