webapp-testing
Skill30 starsChanged 53 days agoMITView on GitHub
Playwright E2E patterns, Testing Library component tests, test selectors. Use when writing browser tests, component tests, or setting up an E2E testing pipeline for a Next.js or React app.
- Reads credentials
- Installs packages
196 other projects use a version of this.
Adopted by
Most recently taken up by
- rvdbreemen/OTGW-firmwaretheir own version, first seen Nov 8, 2026
- BrightGold70/skillstheir own version, first seen Nov 8, 2026
- DnsQueen/orcinus-skillstheir own version, first seen Nov 8, 2026
- Liang-MH/skillstheir own version, first seen Nov 8, 2026
- smart-coder-labs/nexus-mindtheir own version, first seen Nov 8, 2026
- codewhale-hq/codewhale-plugin-marketplacetheir own version, first seen Nov 8, 2026
Used with
Projects with it also use academy-guide, skill-share and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
AnthropicSkillacademy-guide
Stop and check this skill before finishing any reply to a question about how to use Claude or a Claude product — it recommends matching courses, tutorials, and use cases from Claude Academy (academy.claude.com), Anthropic's learning hub. Trigger on: "how do I", "how can I", "getting started with", "what can Claude do", "teach me", "learn to use"; questions about artifacts, projects, skills, plugins, connectors, MCP; requests about rolling Claude out to a team, class, or organization; and any ask for training materials, onboarding content, or learning resources. Use it when the user is learning how to use a feature or product — not when they are mid-task and just want the task done. This skill composes with other skills: after consulting product documentation to answer how a Claude feature works, also check here for a matching course or tutorial — a docs-grounded answer and an Academy recommendation belong together. Only recommend on a strong match; never invent Academy content.
Also in 8 projects that use this
ArogyaReddySkillskill-share
A skill that creates new Claude skills and automatically shares them on Slack using Rube for seamless team collaboration and skill discovery.
Also in 23 projects that use this
SKB3002Skillintelligent-routing
Automatic agent selection and intelligent task routing. Analyzes user requests and automatically selects the best specialist agent(s) without requiring explicit user mentions.
Also in 5 projects that use this
ArogyaReddySkillraffle-winner-picker
Picks random winners from lists, spreadsheets, or Google Sheets for giveaways, raffles, and contests. Ensures fair, unbiased selection with transparency.
Also in 26 projects that use this
zephyrwang6Skilltopic-collector
AI热点采集工具。从Twitter/X、Product Hunt、Reddit、Hacker News、博客等采集AI相关热点内容。当用户说"开始今日选题"、"采集热点"、"看看今天有什么新闻"、"今日AI热点"时触发。聚焦领域:Vibe Coding、Claude Skill、AI知识管理、AI模型更新、AI新产品、海外热点。
Also in 4 projects that use this
zephyrwang6Skilldeepl
Übersetzt Texte, Dokumente und XLIFF-Dateien via DeepL API. Verwende für Übersetzungen mit /deepl translate, /deepl file oder /deepl xliff.
Also in 4 projects that use this
- +4 more from zephyrwang6
- +1 more from ArogyaReddy
Other versions196 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 196
- anthropics/skills★ 189ksame name
- ComposioHQ/awesome-claude-skills★ 75ksame name
- github/awesome-copilot★ 41ksame name
- ComposioHQ/awesome-codex-skills★ 17ksame name
- composio-community/awesome-codex-skills★ 16ksame name
- vudovn/ag-kit★ 8.2ksame name
- vudovn/antigravity-kit★ 8.2ksame name
- Galaxy-Dawn/claude-scholar★ 5.9ksame name
- microsoft/FluidFramework★ 4.9ksame name
- Prat011/awesome-llm-skills★ 1.7ksame name
186 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository30 other files it gives its agents.
- AGENTS.mdAGENTS.md
- claude/CLAUDE.mdCLAUDE.md
- llms.txtllms.txt
- api-design-patternsSkill
- auth-patternsSkill
- backend-dev-guidelinesSkill
- brainstormingSkill
- code-review-patternsSkill
- deployment-guideSkill
- docker-patternsSkill
- docx-officialSkill
- frontend-designSkill
- github-actionsSkill
- git-workflowSkill
- nestjs-patternsSkill
- nextjs-app-routerSkill
- pdf-officialSkill
- pptx-officialSkill
- prisma-workflowSkill
- prompt-engineeringSkill
- react-best-practicesSkill
- security-checklistSkill
- skill-creatorSkill
- software-architectureSkill
- systematic-debuggingSkill
- tailwind-patternsSkill
- test-driven-developmentSkill
- typescript-patternsSkill
- web-design-guidelinesSkill
- xlsx-officialSkill
The file
We split this skill into the rules it gives an agent
7 rules. 0 are common, 5 are distinctive, and 2 are very specific to this project.
0
Common
Most of the 190 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 346
Never use getByTestId when a role/label selector would work
- Distinctive: line 347
Never test implementation details (state, method calls) — test user-visible behavior
- Distinctive: line 348
Never write Playwright tests that depend on hardcoded data IDs — use dynamic selectors
- Very specific: line 349
Never add sleep / waitForTimeout — use proper waitFor / findBy
- Distinctive: line 350
Never skip error state tests — test the unhappy path
- Distinctive: line 351
Never share browser context between tests — each test should be isolated
- Distinctive: line 352
Never test styling with RTL — that belongs in visual regression (Chromatic/Percy)
“Common” is measured: kept by at least 6 in 10 of the 190 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Web App Testing
Testing Pyramid
/\
/E2E\ ← Few, slow, high confidence (Playwright)
/------\
/ Integr \ ← Some, medium speed (RTL + MSW)
/----------\
/ Unit/Comp \ ← Many, fast, isolated (Vitest + RTL)
/--------------\Playwright Setup
npm i -D @playwright/test
npx playwright install chromium firefox webkit// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html'], ['list']],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'mobile', use: { ...devices['iPhone 14'] } },
],
webServer: {
command: 'npm run build && npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})Page Object Model
// e2e/pages/login.page.ts
import { Page, Locator, expect } from '@playwright/test'
export class LoginPage {
readonly emailInput: Locator
readonly passwordInput: Locator
readonly submitButton: Locator
readonly errorMessage: Locator
constructor(private page: Page) {
this.emailInput = page.getByLabel('Email')
this.passwordInput = page.getByLabel('Password')
this.submitButton = page.getByRole('button', { name: 'Sign in' })
this.errorMessage = page.getByRole('alert')
}
async goto() {
await this.page.goto('/login')
}
async login(email: string, password: string) {
await this.emailInput.fill(email)
await this.passwordInput.fill(password)
await this.submitButton.click()
}
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message)
}
}
// e2e/pages/dashboard.page.ts
export class DashboardPage {
constructor(private page: Page) {}
async expectWelcome(name: string) {
await expect(this.page.getByRole('heading', { level: 1 })).toContainText(`Welcome, ${name}`)
}
}Playwright Test Examples
// e2e/auth/login.spec.ts
import { test, expect } from '@playwright/test'
import { LoginPage } from '../pages/login.page'
import { DashboardPage } from '../pages/dashboard.page'
test.describe('Login flow', () => {
test('successful login redirects to dashboard', async ({ page }) => {
const loginPage = new LoginPage(page)
const dashboard = new DashboardPage(page)
await loginPage.goto()
await loginPage.login('admin@example.com', 'password123')
await expect(page).toHaveURL('/dashboard')
await dashboard.expectWelcome('Admin')
})
test('invalid credentials shows error message', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.goto()
await loginPage.login('bad@example.com', 'wrongpassword')
await loginPage.expectError('Invalid email or password')
await expect(page).toHaveURL('/login')
})
test('email field is required', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.goto()
await loginPage.submitButton.click()
// HTML5 validation
await expect(loginPage.emailInput).toBeFocused()
})
})Authenticated Tests
// e2e/fixtures/auth.fixture.ts
import { test as base, Page } from '@playwright/test'
interface AuthFixtures {
authenticatedPage: Page
adminPage: Page
}
export const test = base.extend<AuthFixtures>({
// Regular user
authenticatedPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'e2e/.auth/user.json',
})
const page = await context.newPage()
await use(page)
await context.close()
},
// Admin user
adminPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'e2e/.auth/admin.json',
})
const page = await context.newPage()
await use(page)
await context.close()
},
})
// e2e/auth/setup.ts — run once before all tests
import { chromium } from '@playwright/test'
async function globalSetup() {
const browser = await chromium.launch()
// Save user session
const userCtx = await browser.newContext()
const userPage = await userCtx.newPage()
await userPage.goto('http://localhost:3000/login')
await userPage.fill('[name=email]', 'user@example.com')
await userPage.fill('[name=password]', 'password')
await userPage.click('button[type=submit]')
await userPage.waitForURL('**/dashboard')
await userCtx.storageState({ path: 'e2e/.auth/user.json' })
await browser.close()
}
export default globalSetupReact Testing Library
// components/UserCard/UserCard.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { UserCard } from './UserCard'
describe('UserCard', () => {
const user = {
id: 1,
name: 'Jane Doe',
email: 'jane@example.com',
role: 'admin' as const,
}
it('renders user name and email', () => {
render(<UserCard user={user} />)
expect(screen.getByRole('heading', { name: 'Jane Doe' })).toBeInTheDocument()
expect(screen.getByText('jane@example.com')).toBeInTheDocument()
expect(screen.getByText('admin')).toBeInTheDocument()
})
it('calls onDelete when delete button is clicked', async () => {
const onDelete = vi.fn()
render(<UserCard user={user} onDelete={onDelete} />)
const deleteButton = screen.getByRole('button', { name: /delete/i })
await userEvent.click(deleteButton)
expect(onDelete).toHaveBeenCalledWith(user.id)
})
it('shows confirmation dialog before deleting', async () => {
const onDelete = vi.fn()
render(<UserCard user={user} onDelete={onDelete} />)
await userEvent.click(screen.getByRole('button', { name: /delete/i }))
expect(screen.getByRole('dialog')).toBeInTheDocument()
expect(screen.getByText(/are you sure/i)).toBeInTheDocument()
expect(onDelete).not.toHaveBeenCalled()
await userEvent.click(screen.getByRole('button', { name: /confirm/i }))
expect(onDelete).toHaveBeenCalledWith(user.id)
})
})Mocking API Calls with MSW
// test/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/v1/users', () => {
return HttpResponse.json({
success: true,
data: [
{ id: 1, name: 'Jane Doe', email: 'jane@example.com' },
{ id: 2, name: 'John Smith', email: 'john@example.com' },
],
meta: { total: 2, page: 1, limit: 20, pages: 1 },
})
}),
http.post('/api/v1/users', async ({ request }) => {
const body = await request.json() as CreateUserDto
return HttpResponse.json({
success: true,
data: { id: 3, ...body },
}, { status: 201 })
}),
http.get('/api/v1/users/:id', ({ params }) => {
if (params.id === '999') {
return HttpResponse.json(
{ success: false, error: { code: 'USER_NOT_FOUND', message: 'Not found' } },
{ status: 404 }
)
}
return HttpResponse.json({ success: true, data: { id: Number(params.id), name: 'Test' } })
}),
]
// test/mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
// test/setup.ts
import { server } from './mocks/server'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())Selector Priority
// Priority order (highest to lowest confidence):
// 1. getByRole — reflects what users and screen readers see
// 2. getByLabelText — form fields
// 3. getByPlaceholderText
// 4. getByText — visible text
// 5. getByDisplayValue
// 6. getByAltText — img alt
// 7. getByTitle
// 8. getByTestId — last resort, use data-testid sparingly
// Good
screen.getByRole('button', { name: /submit/i })
screen.getByLabelText('Email address')
screen.getByRole('heading', { name: 'Dashboard', level: 1 })
// Avoid (brittle, tied to implementation)
document.querySelector('.btn-primary')
container.firstChild
screen.getByTestId('submit-btn') // only when roles don't workAsync Patterns
// Wait for element to appear
await screen.findByText('Loading complete')
await screen.findByRole('table')
// Wait for element to disappear
await waitForElementToBeRemoved(() => screen.queryByText('Loading...'))
// Wait for DOM change
await waitFor(() => {
expect(screen.getByText('3 items')).toBeInTheDocument()
}, { timeout: 3000 })
// Playwright: wait for network
await page.waitForResponse('**/api/v1/users')
await page.waitForLoadState('networkidle')Forbidden Patterns
- Never use
getByTestIdwhen a role/label selector would work - Never test implementation details (state, method calls) — test user-visible behavior
- Never write Playwright tests that depend on hardcoded data IDs — use dynamic selectors
- Never add
sleep/waitForTimeout— use proper waitFor / findBy - Never skip error state tests — test the unhappy path
- Never share browser context between tests — each test should be isolated
- Never test styling with RTL — that belongs in visual regression (Chromatic/Percy)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

