epic-testing
Provides patterns for testing Epic Stack apps with Vitest and Playwright.
Install
mkdir -p .claude/skills/epic-testing && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/1939" && unzip -o skill.zip -d .claude/skills/epic-testing && rm skill.zipInstalls to .claude/skills/epic-testing
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Guide on testing with Vitest and Playwright for Epic StackKey capabilities
- →Write unit tests for utilities and components
- →Create E2E tests with Playwright
- →Test forms and validation
- →Mock external services with MSW
- →Test authentication and permissions
- →Configure test database
How it works
This skill guides testing by emphasizing user workflow simulation and specific assertions, using Vitest for unit tests and Playwright for end-to-end tests.
Inputs & outputs
When to use epic-testing
- →Write a unit test for a utility function
- →Create an E2E test for user login
- →Mock an external service with MSW
About this skill
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('[email protected]')
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:
- Unit Tests with Vitest - Tests for individual components and utilities
- 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: '[email protected]',
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: '[email protected]',
})
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: '[email protected]',
})
}),
]
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: '[email protected]',
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.getByRo
---
*Content truncated.*
When not to use it
- →Testing implementation details instead of user workflows
- →Using vague assertions
- →Relying on data between tests
Limitations
- →Tests must be independent
- →Execution order cannot be assumed
- →Data is automatically cleaned after tests
How it compares
This workflow tests user interactions and visible outcomes rather than internal code structures or API calls.
Compared to similar skills
epic-testing side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| epic-testing (this skill) | 2 | 6mo | No flags | Intermediate |
| ui-ux-expert-skill | 91 | 9mo | Review | Advanced |
| dependency-upgrade | 26 | 5mo | Review | Intermediate |
| vitest | 41 | 6mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by epicweb-dev
View all by epicweb-dev →You might also like
ui-ux-expert-skill
fercracix33
Technical workflow for implementing accessible React user interfaces with shadcn/ui, Tailwind CSS, and TanStack Query. Includes 6-phase process with mandatory Style Guide compliance, Context7 best practices consultation, Chrome DevTools validation, and WCAG 2.1 AA accessibility standards. Use after Test Agent, Implementer, and Supabase agents complete their work.
dependency-upgrade
wshobson
Manage major dependency version upgrades with compatibility analysis, staged rollout, and comprehensive testing. Use when upgrading framework versions, updating major dependencies, or managing breaking changes in libraries.
vitest
antfu
Vitest fast unit testing framework powered by Vite with Jest-compatible API. Use when writing tests, mocking, configuring coverage, or working with test filtering and fixtures.
chrome-devtools
mrgoonie
Browser automation, debugging, and performance analysis using Puppeteer CLI scripts. Use for automating browsers, taking screenshots, analyzing performance, monitoring network traffic, web scraping, form automation, and JavaScript debugging.
playwright-browser-automation
lackeyjb
Complete browser automation with Playwright. Auto-detects dev servers, writes clean test scripts to /tmp. Test pages, fill forms, take screenshots, check responsive design, validate UX, test login flows, check links, automate any browser task. Use when user wants to test websites, automate browser interactions, validate web functionality, or perform any browser-based testing.
angular-best-practices
sickn33
Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.