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

epic-testing史诗般的测试

Agent Skill

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

总安装

514

周安装

21

GitHub Stars

5,472

下载量

166
CodexClaudeCursorGemini CLI

安装说明

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

GitHub

来源数

3

许可证

MIT

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

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

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

命令行安装

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

skills.shnpx skills
npx skills add https://github.com/epicweb-dev/epic-stack --skill epic-testing

简介

Epic Testing 提供单元测试、Playwright E2E 测试和 MSW 模拟服务方案,强调测试应模仿真实用户行为。

  • 适合需要验证表单提交、权限流程或外部依赖交互的完整端到端测试场景。
  • 测试用例应聚焦用户旅程而非实现细节,避免因框架升级导致测试频繁失效。
  • 安装命令为 npx skills add https://github.com/epicweb-dev/epic-stack --skill epic-testing。
  • 运行浏览器测试时应区分本地模拟与远程执行环境,避免占用过多资源或产生副作用。

SKILL.md

Epic Stack: Testing

When to use this skill

Use this skill when you need to:

  • Write unit tests for utilities and components
  • Create E2E tests with Playwright
  • Test forms and validation
  • Test routes and loaders
  • Mock external services with MSW
  • Test authentication and permissions
  • Configure test database

Patterns and conventions

Testing Philosophy

Following Epic Web principles:

Tests should resemble users - Write tests that mirror how real users interact with your application. Test user workflows, not implementation details. If a user would click a button, your test should click that button. If a user would see an error message, your test should check for that specific message.

Make assertions specific - Be explicit about what you're testing. Instead of vague assertions, use specific, meaningful checks that clearly communicate the expected behavior. This makes tests easier to understand and debug when they fail.

Example - Tests that resemble users:

// ✅ Good - Tests user workflow
test('User can sign up and create their first note', async ({ page, navigate }) => {
	// User visits signup page
	await navigate('/signup')

	// User fills out form like a real person would
	await page.getByRole('textbox', { name: /email/i }).fill('newuser@example.com')
	await page.getByRole('textbox', { name: /username/i }).fill('newuser')
	await page.getByRole('textbox', { name: /^password$/i }).fill('securepassword123')
	await page.getByRole('textbox', { name: /confirm/i }).fill('securepassword123')

	// User submits form
	await page.getByRole('button', { name: /sign up/i }).click()

	// User is redirected to onboarding
	await expect(page).toHaveURL(/\/onboarding/)

	// User creates their first note
	await navigate('/notes/new')
	await page.getByRole('textbox', { name: /title/i }).fill('My First Note')
	await page.getByRole('textbox', { name: /content/i }).fill('This is my first note!')
	await page.getByRole('button', { name: /create/i }).click()

	// User sees their note
	await expect(page.getByRole('heading', { name: 'My First Note' })).toBeVisible()
	await expect(page.getByText('This is my first note!')).toBeVisible()
})

// ❌ Avoid - Testing implementation details
test('Signup form calls API endpoint', async ({ page }) => {
	// This tests implementation, not user experience
	const response = await page.request.post('/signup', { data: {...} })
	expect(response.status()).toBe(200)
})

Example - Specific assertions:

// ✅ Good - Specific assertions
test('Form shows specific validation errors', async ({ page, navigate }) => {
	await navigate('/signup')
	await page.getByRole('button', { name: /sign up/i }).click()

	// Specific error messages that users would see
	await expect(page.getByText(/email is required/i)).toBeVisible()
	await expect(
		page.getByText(/username must be at least 3 characters/i),
	).toBeVisible()
	await expect(
		page.getByText(/password must be at least 6 characters/i),
	).toBeVisible()
})

// ❌ Avoid - Vague assertions
test('Form shows errors', async ({ page, navigate }) => {
	await navigate('/signup')
	await page.getByRole('button', { name: /sign up/i }).click()

	// Too vague - what errors? where?
	expect(page.locator('.error')).toBeVisible()
})

Two Types of Tests

Epic Stack uses two types of tests:

  1. Unit Tests with Vitest - Tests for individual components and utilities
  2. E2E Tests with Playwright - End-to-end tests of the complete flow

Unit Tests with Vitest

Basic setup:

// app/utils/my-util.test.ts
import { describe, expect, it } from 'vitest'
import { myUtil } from './my-util.ts'

describe('myUtil', () => {
	it('should do something', () => {
		expect(myUtil('input')).toBe('expected')
	})
})

Testing con DOM:

import { describe, expect, it } from 'vitest'
import { render, screen } from '@testing-library/react'
import { MyComponent } from './my-component.tsx'

describe('MyComponent', () => {
	it('should render correctly', () => {
		render(<MyComponent />)
		expect(screen.getByText('Hello')).toBeInTheDocument()
	})
})

E2E Tests with Playwright

Basic setup:

// tests/e2e/my-feature.test.ts
import { expect, test } from '#tests/playwright-utils.ts'

test('Users can do something', async ({ page, navigate, login }) => {
	const user = await login()
	await navigate('/my-page')

	// Interact with the page
	await page.getByRole('button', { name: /Submit/i }).click()

	// Verificar resultado
	await expect(page).toHaveURL('/success')
})

Login Fixture

Epic Stack provides a login fixture for authenticated tests.

Use login fixture:

test('Protected route', async ({ page, navigate, login }) => {
	const user = await login() // Creates user and session automatically
	await navigate('/protected')

	// User is authenticated
	await expect(page.getByText(`Welcome ${user.username}`)).toBeVisible()
})

Login with options:

const user = await login({
	username: 'testuser',
	email: 'test@example.com',
	password: 'password123',
})

Note: The user is automatically deleted when the test completes.

Insert User without Login

To create user without authentication:

test('Public content', async ({ page, navigate, insertNewUser }) => {
	const user = await insertNewUser({
		username: 'publicuser',
		email: 'public@example.com',
	})

	await navigate(`/users/${user.username}`)
	await expect(page.getByText(user.username)).toBeVisible()
})

Navigate Helper

Use the navigate helper to navigate with type-safety:

// Type-safe navigation
await navigate('/users/:username/notes', { username: user.username })
await navigate('/users/:username/notes/:noteId', {
	username: user.username,
	noteId: note.id,
})

// Also works with routes without parameters
await navigate('/login')

Test Database

Epic Stack uses a separate test database.

Automatic configuration:

  • The test database is configured automatically
  • It's cleaned between tests
  • Data created in tests is automatically deleted

Create data in tests:

import { prisma } from '#app/utils/db.server.ts'

test('User can see notes', async ({ page, navigate, login }) => {
	const user = await login()

	// Create note in database
	const note = await prisma.note.create({
		data: {
			title: 'Test Note',
			content: 'Test Content',
			ownerId: user.id,
		},
	})

	await navigate('/users/:username/notes/:noteId', {
		username: user.username,
		noteId: note.id,
	})

	await expect(page.getByText('Test Note')).toBeVisible()
})

MSW (Mock Service Worker)

Epic Stack uses MSW to mock external services.

Mock example:

// tests/mocks/github.ts
import { http, HttpResponse } from 'msw'

export const handlers = [
	http.get('https://api.github.com/user', () => {
		return HttpResponse.json({
			id: '123',
			login: 'testuser',
			email: 'test@example.com',
		})
	}),
]

Use in tests: Mocks are automatically applied when MOCKS=true is configured.

Testing Forms

Test form:

test('User can submit form', async ({ page, navigate, login }) => {
	const user = await login()
	await navigate('/notes/new')

	// Fill form
	await page.getByRole('textbox', { name: /title/i }).fill('New Note')
	await page.getByRole('textbox', { name: /content/i }).fill('Note content')

	// Submit
	await page.getByRole('button', { name: /submit/i }).click()

	// Verificar redirect
	await expect(page).toHaveURL(new RegExp('/users/.*/notes/.*'))
})

Test validation:

test('Form shows validation errors', async ({ page, navigate }) => {
	await navigate('/signup')

	// Submit sin llenar
	await page.getByRole('button', { name: /submit/i }).click()

	// Verificar errores
	await expect(page.getByText(/email is required/i)).toBeVisible()
})

Testing Loaders

Test loader:

// app/utils/my-util.test.ts
import { describe, expect, it } from 'vitest'
import { loader } from '../routes/my-route.ts'
import { prisma } from '../utils/db.server.ts'

describe('loader', () => {
	it('should load data', async () => {
		// Create data
		const user = await prisma.user.create({
			data: {
				email: 'test@example.com',
				username: 'testuser',
				roles: { connect: { name: 'user' } },
			},
		})

		// Mock request
		const request = new Request('http://localhost/my-route')

		// Execute loader
		const result = await loader({ request, params: {}, context: {} })

		// Verify result
		expect(result.data).toBeDefined()
	})
})

Testing Actions

Test action:

// tests/e2e/notes.test.ts
test('User can create note', async ({ page, navigate, login }) => {
	const user = await login()
	await navigate('/users/:username/notes', { username: user.username })

	await page.getByRole('link', { name: /new note/i }).click()

	const formData = new FormData()
	formData.set('title', 'Test Note')
	formData.set('content', 'Test Content')

	await page.getByRole('textbox', { name: /title/i }).fill('Test Note')
	await page.getByRole('textbox', { name: /content/i }).fill('Test Content')
	await page.getByRole('button', { name: /submit/i }).click()

	// Verify that note was created
	await expect(page.getByText('Test Note')).toBeVisible()
})

Testing Permissions

Test permissions:

test('Only owner can delete note', async ({
	page,
	navigate,
	login,
	insertNewUser,
}) => {
	const owner = await login()
	const otherUser = await insertNewUser()

	const note = await prisma.note.create({
		data: {
			title: 'Test Note',
			content: 'Test',
			ownerId: owner.id,
		},
	})

	// Login as other user
	const session = await createSession(otherUser.id)
	await page.context().addCookies([getCookie(session)])

	await navigate('/users/:username/notes/:noteId', {
		username: owner.username,
		noteId: note.id,
	})

	// Verify that can't delete
	await expect(page.getByRole('button', { name: /delete/i })).not.toBeVisible()
})

DB Helpers

Create user:

import { createUser } from '#tests/db-utils.ts'

const userData = createUser() // Generates unique random data

Create password:

import { createPassword } from '#tests/db-utils.ts'

const password = createPassword('mypassword') // { hash: '...' }

Wait For Helper

To wait for async conditions:

import { waitFor } from '#tests/playwright-utils.ts'

await waitFor(
	async () => {
		const element = await page.getByText('Content loaded').first()
		expect(element).toBeVisible()
		return element
	},
	{ timeout: 5000, errorMessage: 'Content never loaded' },
)

Testing GitHub OAuth

Prepare GitHub user:

test('User can login with GitHub', async ({
	page,
	navigate,
	prepareGitHubUser,
}) => {
	const ghUser = await prepareGitHubUser()

	await navigate('/login')
	await page.getByRole('link', { name: /github/i }).click()

	// GitHub user is automatically prepared
	await expect(page).toHaveURL('/onboarding/github')
})

Common examples

Example 1: Complete E2E test (resembling user workflow)

// tests/e2e/notes.test.ts
import { expect, test } from '#tests/playwright-utils.ts'
import { prisma } from '#app/utils/db.server.ts'
import { faker } from '@faker-js/faker'

test('Users can create, edit, and delete notes', async ({
	page,
	navigate,
	login,
}) => {
	// User logs in (realistic workflow)
	const user = await login()
	await navigate('/users/:username/notes', { username: user.username })

	// User creates a new note (clicking link, filling form, submitting)
	await page.getByRole('link', { name: /new note/i }).click()
	const newNote = {
		title: faker.lorem.words(3),
		content: faker.lorem.paragraphs(2),
	}
	await page.getByRole('textbox', { name: /title/i }).fill(newNote.title)
	await page.getByRole('textbox', { name: /content/i }).fill(newNote.content)
	await page.getByRole('button', { name: /submit/i }).click()

	// Specific assertions: user sees their note with correct title and content
	await expect(page.getByRole('heading', { name: newNote.title })).toBeVisible()
	await expect(page.getByText(newNote.content)).toBeVisible()
	const noteUrl = page.url()
	const noteId = noteUrl.split('/').pop()

	// User edits the note (clicking edit, updating fields, saving)
	await page.getByRole('link', { name: /edit/i }).click()
	const updatedNote = {
		title: faker.lorem.words(3),
		content: faker.lorem.paragraphs(2),
	}
	await page.getByRole('textbox', { name: /title/i }).fill(updatedNote.title)
	await page
		.getByRole('textbox', { name: /content/i })
		.fill(updatedNote.content)
	await page.getByRole('button', { name: /submit/i }).click()

	// Specific assertions: user sees updated content
	await expect(
		page.getByRole('heading', { name: updatedNote.title }),
	).toBeVisible()
	await expect(page.getByText(updatedNote.content)).toBeVisible()

	// User deletes the note (clicking delete button)
	await page.getByRole('button', { name: /delete/i }).click()

	// Specific assertion: user is redirected back to notes list
	await expect(page).toHaveURL(`/users/${user.username}/notes`)
	await expect(page.getByText(updatedNote.title)).not.toBeVisible()
})

Example 2: Unit test for utility

// app/utils/misc.test.ts
import { describe, expect, it } from 'vitest'
import { cn } from './misc.tsx'

describe('cn', () => {
	it('should merge class names', () => {
		expect(cn('foo', 'bar')).toBe('foo bar')
		expect(cn('foo', undefined, 'bar')).toBe('foo bar')
		expect(cn('foo', false && 'bar', 'baz')).toBe('foo baz')
	})
})

Example 3: Form validation test

// tests/e2e/signup.test.ts
test('Signup form validation', async ({ page, navigate }) => {
	await navigate('/signup')

	// Submit without filling
	await page.getByRole('button', { name: /submit/i }).click()

	// Verify errors
	await expect(page.getByText(/email is required/i)).toBeVisible()

	// Fill invalid email
	await page.getByRole('textbox', { name: /email/i }).fill('invalid')
	await page.getByRole('button', { name: /submit/i }).click()

	// Verify email error
	await expect(page.getByText(/email is invalid/i)).toBeVisible()

	// Fill valid email
	await page.getByRole('textbox', { name: /email/i }).fill('test@example.com')
	await page.getByRole('button', { name: /submit/i }).click()

	// Verify redirect to onboarding
	await expect(page).toHaveURL(/\/onboarding/)
})

Example 4: Permissions test

// tests/e2e/permissions.test.ts
test('Only admin can access admin routes', async ({
	page,
	navigate,
	login,
	insertNewUser,
}) => {
	// Test with normal user
	const normalUser = await login()

	await navigate('/admin/users')

	// Should redirect or show error
	await expect(page).toHaveURL('/') // Or verify error message

	// Test with admin
	await page.context().clearCookies()
	const admin = await insertNewUser()
	await prisma.user.update({
		where: { id: admin.id },
		data: {
			roles: {
				connect: { name: 'admin' },
			},
		},
	})

	// Login as admin
	const adminSession = await createSession(admin.id)
	await page.context().addCookies([getCookie(adminSession)])

	await navigate('/admin/users')

	// Now should work
	await expect(page.getByText('All Users')).toBeVisible()
})

Common mistakes to avoid

  • Testing implementation details instead of user workflows: Write tests that mirror how users actually use your app
  • Vague assertions: Use specific, meaningful assertions that clearly communicate expected behavior
  • Not cleaning data after tests: Epic Stack cleans automatically, but make sure not to depend on data between tests
  • Assuming execution order: Tests must be independent
  • Not using fixtures: Use login, insertNewUser, etc. instead of creating everything manually
  • Hardcoding data: Use faker to generate unique data
  • Not waiting for elements: Use expect with toBeVisible() instead of assuming it exists
  • Not using type-safe navigation: Use navigate helper instead of page.goto() directly
  • Forgetting MSW in tests: External services are automatically mocked when MOCKS=true
  • Not testing error cases: Test both happy path and errors
  • Testing internal state instead of user-visible behavior: Focus on what users see and do

References

适合场景

01

用户想查找某类 Agent Skill 时

02

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

03

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

04

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

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

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

能力 4

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

能力 5

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

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

平台分布

Gemini CLI

29.3%
按下载量换算49

Claude Code

23.79%
按下载量换算39

Antigravity

17.55%
按下载量换算29

Codex

12.39%
按下载量换算21

OpenCode

8.32%
按下载量换算14

qoder

3.31%
按下载量换算5

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

操作浏览器

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

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

来源信息

继续浏览同类 Skills