mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-01 11:42:06 +00:00
Amp-Thread-ID: https://ampcode.com/threads/T-019c1640-f128-732b-b621-2621ce8b48d1 Co-authored-by: Amp <amp@ampcode.com>
5.1 KiB
5.1 KiB
Playwright Best Practices
Official Playwright patterns. ComfyUI-specific patterns in ../SKILL.md.
Locator Priority
Use locators in this order of preference:
| Priority | Method | Use Case |
|---|---|---|
| 1 | page.getByRole() |
Buttons, links, headings (accessibility) |
| 2 | page.getByLabel() |
Form controls with labels |
| 3 | page.getByPlaceholder() |
Inputs by placeholder |
| 4 | page.getByText() |
Elements by text content |
| 5 | page.getByAltText() |
Images by alt text |
| 6 | page.getByTitle() |
Elements by title attribute |
| 7 | page.getByTestId() |
data-testid fallback |
// ✅ Good - uses role and accessible name
await page.getByRole('button', { name: 'Submit' }).click()
// ❌ Bad - tied to implementation details
await page.locator('button.btn-primary.submit-btn').click()
Web-First Assertions
Always use auto-retrying assertions:
// ✅ Good - auto-waits and retries
await expect(page.getByText('Welcome')).toBeVisible()
await expect(page).toHaveTitle(/Dashboard/)
// ❌ Bad - doesn't wait, leads to flaky tests
expect(await page.getByText('Welcome').isVisible()).toBe(true)
Auto-Retrying Assertions
| Assertion | Purpose |
|---|---|
toBeVisible() |
Element is visible |
toBeHidden() |
Element is hidden |
toBeEnabled() |
Element is enabled |
toBeDisabled() |
Element is disabled |
toHaveText() |
Exact text match |
toContainText() |
Partial text match |
toHaveValue() |
Input value |
toHaveAttribute() |
Attribute value |
toHaveCount() |
Number of elements |
toHaveURL() |
Page URL |
toHaveTitle() |
Page title |
Soft Assertions
Non-blocking checks:
await expect.soft(page.getByTestId('status')).toHaveText('Success')
await expect.soft(page.getByTestId('count')).toHaveText('5')
// Test continues even if above fail
Anti-Patterns
❌ Manual Waits
// Bad
await page.waitForTimeout(2000)
// Good
await expect(page.getByText('Loaded')).toBeVisible()
❌ Implementation-Tied Selectors
// Bad
await page.locator('div.container > button.btn-primary').click()
// Good
await page.getByRole('button', { name: 'Submit' }).click()
❌ Using first/last/nth Without Reason
// Bad - fragile
await page.getByRole('button').first().click()
// Good - uniquely identify
await page.getByRole('button', { name: 'Submit' }).click()
❌ Non-Awaited Assertions
// Bad
expect(await page.getByText('Hello').isVisible()).toBe(true)
// Good
await expect(page.getByText('Hello')).toBeVisible()
❌ Shared State Between Tests
// Bad
let sharedData
test('first', async () => {
sharedData = 'value'
})
test('second', async () => {
expect(sharedData).toBe('value')
})
// Good - each test independent
test('first', async ({ page }) => {
/* complete setup */
})
test('second', async ({ page }) => {
/* complete setup */
})
Locator Chaining
// Within a container
const dialog = page.getByRole('dialog')
await dialog.getByRole('button', { name: 'Submit' }).click()
// Filter
const product = page.getByRole('listitem').filter({ hasText: 'Product 2' })
await product.getByRole('button', { name: 'Add' }).click()
Network Mocking
// Mock API
await page.route('**/api/users', (route) =>
route.fulfill({
status: 200,
body: JSON.stringify([{ id: 1, name: 'Test' }])
})
)
// Block resources
await context.route('**/*.{png,jpg}', (route) => route.abort())
Test Annotations
// Skip conditionally
test('firefox only', async ({ browserName }) => {
test.skip(browserName !== 'firefox', 'Firefox only')
})
// Slow test (triples timeout)
test('complex', async () => {
test.slow()
})
// Tag
test('login', { tag: '@smoke' }, async () => {})
Retry Configuration
// Per-describe
test.describe(() => {
test.describe.configure({ retries: 2 })
test('flaky', async () => {})
})
// Detect retry
test('cleanup aware', async ({}, testInfo) => {
if (testInfo.retry) await cleanup()
})
Parallel Execution
// Parallel (default)
test.describe.configure({ mode: 'parallel' })
// Serial (dependent tests)
test.describe.configure({ mode: 'serial' })
Debugging
# Debug mode
npx playwright test --debug
# Trace
npx playwright test --trace on
npx playwright show-report
// Pause in test
await page.pause()