
verdex-playwright-complete
by verdexhq
MCP Server for Playwright Test Writing
SKILL.md
name: verdex-playwright-complete description: 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.
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.
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon