EP

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.zip

Installs 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 Stack
58 charsno explicit “when” trigger
Intermediate

Key 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

You give it
User workflow or component behavior
You get back
Test results indicating pass or fail

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:

  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: '[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.

SkillInstallsUpdatedSafetyDifficulty
epic-testing (this skill)26moNo flagsIntermediate
ui-ux-expert-skill919moReviewAdvanced
dependency-upgrade265moReviewIntermediate
vitest416moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

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.

91244

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.

26240

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.

41183

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.

41157

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.

29146

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.

2192

Search skills

Search the agent skills registry