Files
gh-hopeoverture-worldbuildi…/skills/testing-next-stack/assets/examples/e2e-test.ts
2025-11-29 18:47:01 +08:00

197 lines
6.3 KiB
TypeScript

import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'
/**
* Example E2E test demonstrating Playwright best practices
* with accessibility testing via axe-core
*/
test.describe('Entity Management', () => {
test.beforeEach(async ({ page }) => {
// Navigate to entities page
await page.goto('/entities')
})
test('displays entity list', async ({ page }) => {
// Wait for content to load
await page.waitForSelector('[role="list"]')
// Verify entities are displayed
const entities = page.getByRole('listitem')
await expect(entities).not.toHaveCount(0)
// Verify entity cards have proper structure
const firstEntity = entities.first()
await expect(firstEntity.getByRole('heading')).toBeVisible()
})
test('creates new entity', async ({ page }) => {
// Click create button
await page.getByRole('button', { name: /create entity/i }).click()
// Verify form is displayed
await expect(page.getByRole('heading', { name: /new entity/i })).toBeVisible()
// Fill in form
await page.getByLabel(/name/i).fill('Mysterious Stranger')
await page.getByLabel(/type/i).selectOption('character')
await page.getByLabel(/description/i).fill('A traveler from distant lands')
// Submit form
await page.getByRole('button', { name: /save|create/i }).click()
// Verify success message or redirect
await expect(
page.getByText(/entity created|success/i)
).toBeVisible({ timeout: 5000 })
// Verify new entity appears in list
await page.goto('/entities')
await expect(page.getByText('Mysterious Stranger')).toBeVisible()
})
test('edits existing entity', async ({ page }) => {
// Find and click edit button for first entity
const firstEntity = page.getByRole('listitem').first()
const entityName = await firstEntity.getByRole('heading').textContent()
await firstEntity.getByRole('button', { name: /edit/i }).click()
// Update name
const nameInput = page.getByLabel(/name/i)
await nameInput.clear()
await nameInput.fill(`${entityName} (Updated)`)
// Save changes
await page.getByRole('button', { name: /save|update/i }).click()
// Verify update
await expect(page.getByText(/updated|success/i)).toBeVisible()
await page.goto('/entities')
await expect(page.getByText(`${entityName} (Updated)`)).toBeVisible()
})
test('deletes entity with confirmation', async ({ page }) => {
// Click delete button
const firstEntity = page.getByRole('listitem').first()
const entityName = await firstEntity.getByRole('heading').textContent()
await firstEntity.getByRole('button', { name: /delete/i }).click()
// Confirm deletion in dialog
const dialog = page.getByRole('dialog')
await expect(dialog.getByText(/confirm|sure/i)).toBeVisible()
await dialog.getByRole('button', { name: /delete|confirm/i }).click()
// Verify entity is removed
await expect(page.getByText(entityName!)).not.toBeVisible()
})
test('searches entities', async ({ page }) => {
// Enter search query
const searchInput = page.getByRole('searchbox', { name: /search/i })
await searchInput.fill('character')
// Wait for filtered results
await page.waitForTimeout(500) // Debounce
// Verify filtered results
const results = page.getByRole('listitem')
const count = await results.count()
// All visible results should match search
for (let i = 0; i < count; i++) {
const item = results.nth(i)
await expect(item.getByText(/character/i)).toBeVisible()
}
})
test('filters entities by type', async ({ page }) => {
// Select filter
await page.getByLabel(/filter by type/i).selectOption('location')
// Wait for filtered results
await page.waitForSelector('[role="listitem"]')
// Verify all results are locations
const badges = page.locator('.badge')
const count = await badges.count()
for (let i = 0; i < count; i++) {
await expect(badges.nth(i)).toHaveText('location')
}
})
test('keyboard navigation works', async ({ page }) => {
// Focus first interactive element
await page.keyboard.press('Tab')
// Navigate through entities with arrow keys
await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown')
// Activate focused element with Enter
await page.keyboard.press('Enter')
// Verify navigation worked
await expect(page.getByRole('heading', { name: /entity/i })).toBeVisible()
})
test('meets accessibility standards', async ({ page }) => {
// Run axe accessibility scan
const accessibilityScanResults = await new AxeBuilder({ page }).analyze()
// Expect no violations
expect(accessibilityScanResults.violations).toEqual([])
})
test('is responsive on mobile', async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 })
// Verify mobile layout
await expect(page.getByRole('button', { name: /menu/i })).toBeVisible()
// Test mobile navigation
await page.getByRole('button', { name: /menu/i }).click()
await expect(page.getByRole('navigation')).toBeVisible()
})
})
test.describe('Entity Relationships', () => {
test('creates relationship between entities', async ({ page }) => {
// Navigate to first entity detail page
await page.goto('/entities')
await page.getByRole('listitem').first().click()
// Open relationship creation
await page.getByRole('button', { name: /add relationship/i }).click()
// Select related entity
await page.getByLabel(/related entity/i).fill('Location')
await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter')
// Select relationship type
await page.getByLabel(/relationship type/i).selectOption('lives_in')
// Save relationship
await page.getByRole('button', { name: /create|save/i }).click()
// Verify relationship appears
await expect(page.getByText(/lives_in/i)).toBeVisible()
})
test('relationship section is accessible', async ({ page }) => {
await page.goto('/entities')
await page.getByRole('listitem').first().click()
// Scan relationships section
const accessibilityScanResults = await new AxeBuilder({ page })
.include('#relationships')
.analyze()
expect(accessibilityScanResults.violations).toEqual([])
})
})