Agent skill
verdex-playwright-complete
Complete Playwright test authoring workflow with Verdex MCP tools. Three phases - explore apps interactively to map user journeys, build stable role-first selectors via progressive DOM exploration, write idiomatic Playwright tests. Includes workflow discovery, selector patterns, and testing best practices.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/testing/verdex-playwright-complete-verdexhq-verdex-mcp-313db3d1
SKILL.md
Verdex Playwright Complete Workflow
Complete test authoring in three phases: Explore β Select β Test
When to Use This Skill
Use this skill when you need to:
- Explore unfamiliar web applications and map user workflows
- Write stable, maintainable Playwright selectors that avoid positional
.nth()selectors - Create production-ready Playwright tests following best practices
- Understand complex UIs through progressive DOM exploration
The Complete Workflow
Phase 1: Explore & Map Workflows πΊοΈ
Goal: Understand the application and user journeys before writing any code
When to read the detailed guide: When starting work on a new application or feature
- Read guides/workflow-discovery.md for interactive exploration techniques
- Use Verdex MCP browser tools (
browser_navigate,browser_click,browser_snapshot) with refs - Document user journeys, state transitions, and assertion points
- Identify all interactive elements and their behaviors
Phase 2: Build Stable Selectors π―
Goal: Convert exploration discoveries into production-ready Playwright selectors
When to read the detailed guide: After workflow exploration, when you have refs and need selectors
- Read guides/selector-writing.md for comprehensive selector patterns
- Use the Container β Content β Role pattern
- Apply progressive DOM exploration (
resolve_container,inspect_pattern,extract_anchors) - Check uniqueness firstβmany elements need only simple selectors
Phase 3: Write Idiomatic Tests βοΈ
Goal: Transform selectors and workflows into clean, maintainable Playwright tests
When to read the detailed guide: When you have selectors and workflows ready to code
- Read guides/playwright-patterns.md for test structure and patterns
- Follow Arrange-Act-Assert pattern
- Use Playwright's auto-waiting (avoid manual waits)
- Create independent, focused tests
Quick Start (Most Common Path)
Step 1: Explore the Application
await browser_initialize()
await browser_navigate("https://your-app.com")
// Snapshot shows interactive elements with refs (e1, e2, e3...)
// Click and interact using refs to explore
await browser_click("e5")
await browser_snapshot() // See what changed
Document what you learn: URLs, actions, expected outcomes
Step 2: Build Selectors
Check uniqueness FIRST:
// Is element unique? If YES β use simple selector
page.getByRole("button", { name: "Proceed to Checkout" })
// If NO β use Container β Content β Role pattern
page
.getByTestId("product-card")
.filter({ hasText: "iPhone 15 Pro" })
.getByRole("button", { name: "Add to Cart" })
Progressive exploration when needed:
resolve_container("e25") // Find stable containers
inspect_pattern("e25", 2) // Understand siblings
extract_anchors("e25", 1) // Mine deep content (optional)
Step 3: Write Tests
test('should add product to cart', async ({ page }) => {
// Arrange
await page.goto('/products')
// Act
await page
.getByTestId('product-card')
.filter({ hasText: 'iPhone 15 Pro' })
.getByRole('button', { name: 'Add to Cart' })
.click()
// Assert
await expect(page.getByText('Item added to cart')).toBeVisible()
await expect(page.getByRole('link', { name: /Cart \(1\)/ })).toBeVisible()
})
Core Principles (Keep These in Mind)
The Winning Pattern: Container β Content β Role
page
.getByTestId("stable-container") // 1. Scope to container
.filter({ hasText: "unique-content" }) // 2. Filter by content
.getByRole("button", { name: "Action" }) // 3. Target by role
Critical Anti-Patterns to Avoid
// β NEVER: Positional selectors
page.getByRole("button").nth(5)
page.getByRole("button").first() // .first() = .nth(0), still brittle
// β NEVER: Filter in wrong place
page.getByRole("button").filter({ hasText: "text-in-sibling" })
// β NEVER: Parent traversal
page.getByText("text").locator("..")
// β
ALWAYS: Check uniqueness first
const count = await page.getByRole("button", { name: "Submit" }).count()
// If count === 1, use simple selector. If > 1, add container scoping.
Decision Trees
Which Guide Should I Read?
Are you starting work on a new app/feature?
ββ YES β Read guides/workflow-discovery.md
β Learn: Interactive exploration, mapping user journeys
β Tools: browser_navigate, browser_click, browser_snapshot
β Output: Workflow documentation with refs
β
ββ NO β Do you have refs and need to build selectors?
ββ YES β Read guides/selector-writing.md
β Learn: Container β Content β Role pattern
β Tools: resolve_container, inspect_pattern, extract_anchors
β Output: Stable Playwright selectors
β
ββ NO β Ready to write test code?
ββ YES β Read guides/playwright-patterns.md
Learn: Test structure, assertions, best practices
Output: Production-ready Playwright tests
Which Selector Approach Should I Use?
Take browser_snapshot() first, then ask:
Is my target element unique on the page?
ββ YES β Use simple selector β
β page.getByRole("button", { name: "Unique Text" })
β Done! No exploration needed.
β
ββ NO β Element appears multiple times
β
Call resolve_container(ref) to find stable containers
β
Found data-testid or semantic element?
ββ YES β Use as container scope
β β
β Call inspect_pattern(ref, level) to see siblings
β β
β Found unique text to filter by?
β ββ YES β Build selector β
β β page.getByTestId("container")
β β .filter({ hasText: "unique" })
β β .getByRole("button")
β β
β ββ NO β Call extract_anchors(ref, level) for deeper analysis
β Then build selector with discovered anchors
β
ββ NO β Found only generic divs
Use locator("div").filter() pattern (last resort)
Token Efficiency Guide
| Scenario | Tools Needed | Avg Cost | When to Use |
|---|---|---|---|
| Unique element | Snapshot only | ~800 | Element appears once on page |
| Test ID scoped | Snapshot + resolve + inspect | ~1,200 | Well-structured apps with test IDs |
| Semantic scoped | Snapshot + resolve + inspect | ~1,500 | Apps with good semantic HTML |
| Structure-based | Snapshot + resolve + inspect + extract | ~2,500 | Legacy code, poorly structured DOM |
Optimization tip: Always check uniqueness first. Skip extract_anchors if inspect_pattern shows clear unique text.
Essential Quick Reference
Verdex MCP Tool Commands
// === EXPLORATION PHASE ===
await browser_initialize() // Start fresh browser
await browser_navigate("https://app.com") // Go to URL, auto-snapshots
await browser_snapshot() // See current page state with refs
await browser_click("e5") // Click element by ref
await browser_type("e1", "text@example.com") // Type into input by ref
await wait_for_browser(2000) // Wait 2 seconds for dynamic content
// === SELECTOR BUILDING PHASE ===
resolve_container("e25") // Find stable containers (test IDs, landmarks)
inspect_pattern("e25", 2) // Analyze sibling structure at level
extract_anchors("e25", 1) // Mine deep content (use if inspect insufficient)
Common Selector Patterns
// Pattern 1: Unique element (simplest)
page.getByRole("button", { name: "Proceed to Checkout" })
// Pattern 2: Test ID container (most common)
page
.getByTestId("product-card")
.filter({ hasText: "iPhone 15 Pro" })
.getByRole("button", { name: "Add to Cart" })
// Pattern 3: Semantic container
page
.getByRole("article", { name: /John Doe/ })
.getByRole("button", { name: "Helpful" })
// Pattern 4: Generic container (last resort)
page
.locator("div")
.filter({ hasText: "Order #ORD-2024-1234" })
.getByRole("button", { name: "Track" })
Test Structure Template
test.describe('Feature Name', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/starting-point')
})
test('should do specific thing', async ({ page }) => {
// Arrange: Set up state
await expect(page.getByRole('heading', { name: 'Title' })).toBeVisible()
// Act: Perform action
await page.getByRole('button', { name: 'Submit' }).click()
// Assert: Verify outcome
await expect(page).toHaveURL(/success/)
await expect(page.getByText('Success message')).toBeVisible()
})
})
Selector Quality Checklist
Before finalizing any selector:
- Returns exactly 1 element (verify with
.count()) - Uses test IDs or semantic elements (not classes)
- Ends with
getByRole()or semantic locator - Chain is β€ 3 levels deep
- No
.nth(),.first(),.last(), orlocator('..') - No filter in wrong place
- Survives DOM reordering
Progressive Disclosure Resources
Start here and read guides as needed:
-
guides/workflow-discovery.md - Interactive exploration and journey mapping
- When to read: Starting work on new app or unfamiliar feature
- What you'll learn: Using browser tools with refs, documenting workflows
- Output: Workflow maps and assertion points
-
guides/selector-writing.md - Building stable selectors
- When to read: After exploration, have refs, need production selectors
- What you'll learn: Container β Content β Role pattern, progressive DOM exploration
- Output: Stable, maintainable Playwright selectors
-
guides/playwright-patterns.md - Writing idiomatic tests
- When to read: Have selectors and workflows, ready to code tests
- What you'll learn: Test structure, assertions, authentication, patterns
- Output: Production-ready Playwright test code
Key Principle
Container β Content β Role beats positional selectors every time.
Complete workflow:
- Explore: Map user journeys with browser tools and refs
- Check uniqueness: Many elements need only simple selectors
- Find containers: Use
resolve_containerfor stable scoping - Understand patterns: Use
inspect_patternfor sibling analysis - Extract anchors: Use
extract_anchorsonly when needed - Build selectors: Apply Container β Content β Role pattern
- Write tests: Follow Playwright best practices
Don't dump the entire DOM. Ask targeted questions with progressive disclosure.
Didn't find tool you were looking for?