Agent skill
vue-playwright-testing
Comprehensive guide for testing Vue 3 applications with Playwright (2025). This skill should be used when writing end-to-end tests or component tests for Vue apps, testing Vue Router navigation, reactive state changes, authentication flows, or setting up Playwright in Vue projects.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/vue-playwright-testing
SKILL.md
Vue 3 + Playwright Testing
Overview
To build reliable tests for Vue 3 applications, use Playwright for both end-to-end testing and experimental component testing. Playwright integrates seamlessly with Vue's reactivity system, Vue Router navigation, and Vite build pipeline. This skill provides Vue-specific guidance for writing maintainable tests while avoiding common pitfalls like timeout anti-patterns.
Current Versions: Playwright 1.48+, Vue 3.5+, Vite 6+ Standard Approach: Semantic locators, conditional waits, API mocking Key Philosophy: Never use arbitrary timeouts to fix flaky tests
Decision Tree: What Type of Test?
Vue Test Request → What are you testing?
|
├─ End-to-end user flow (login, forms, navigation)?
│ ├─ Single page interaction? → Use Quick Start: E2E Test
│ └─ Multi-page workflow? → Load references/e2e-patterns.md
│
├─ Individual Vue component in isolation?
│ └─ Load references/component-testing.md
│
├─ Vue Router navigation or page transitions?
│ └─ Load references/vue-specific-patterns.md → Router section
│
├─ Reactive state changes or Pinia store?
│ └─ Load references/vue-specific-patterns.md → State Management
│
├─ Authentication flow?
│ └─ Load references/vue-specific-patterns.md → Authentication
│
├─ Test is failing intermittently/flaky?
│ └─ Load references/best-practices.md → Debugging Flaky Tests section
│
└─ Setting up Playwright in Vue project?
└─ Use Quick Start: Project Setup
Quick Start: Project Setup
Initialize Playwright in Vue Project
npm init playwright@latest -- --ct
This command:
- Installs
@playwright/testand@playwright/experimental-ct-vue - Downloads browsers
- Creates
playwright.config.ts - Sets up test structure
For Vue projects with Vite, ensure playwright.config.ts includes:
import { defineConfig, devices } from '@playwright/test';
import react from '@vitejs/plugin-react';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
webServer: {
command: 'npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
Quick Start: E2E Test
Basic E2E Test Example
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('user can login successfully', async ({ page }) => {
// Use semantic locators (getByLabel, getByRole)
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('password123');
// Click submit button
await page.getByRole('button', { name: 'Sign in' }).click();
// Wait for URL change (condition-based, not timeout)
await expect(page).toHaveURL('/dashboard');
// Assert Vue app rendered correctly
await expect(page.getByRole('heading', { name: /Welcome/i })).toBeVisible();
});
test('invalid credentials show error', async ({ page }) => {
await page.getByLabel('Email').fill('bad@email.com');
await page.getByLabel('Password').fill('wrong');
await page.getByRole('button', { name: 'Sign in' }).click();
// Wait for error message to appear
await expect(page.getByRole('alert')).toContainText('Invalid credentials');
});
});
Quick Start: Component Test
Testing Vue Component in Isolation
import { test, expect } from '@playwright/experimental-ct-vue';
import Button from './Button.vue';
test('button emits click event', async ({ mount }) => {
const messages: string[] = [];
const component = await mount(Button, {
props: { label: 'Click me' },
on: {
click: () => messages.push('clicked')
}
});
await component.getByRole('button').click();
expect(messages).toEqual(['clicked']);
});
Core Vue 3 + Playwright Principles
1. Never Use Arbitrary Timeouts to Fix Flaky Tests
❌ BAD: Masking the real issue
// This is a code smell - something is wrong
await page.waitForTimeout(3000);
await page.click('button');
✅ GOOD: Wait for the specific condition
// Wait for button to be enabled
await page.locator('button:not([disabled])').waitFor();
await page.getByRole('button').click();
// Or use Playwright's auto-waiting
await page.getByRole('button', { name: 'Submit' }).click();
Why? Arbitrary timeouts hide race conditions, selector problems, or network issues. When you see a flaky test, investigate the root cause:
- Is the selector wrong? Use dev tools to verify
- Is data loading? Wait for the API response
- Is Vue not mounted yet? Check the condition
- Is the element disabled? Wait for the disabled attribute to be removed
2. Use Semantic Locators for Stability
Priority order (most stable to least):
// ✅ 1. User-facing text/roles (most stable)
await page.getByRole('button', { name: 'Save' });
await page.getByLabel('Email');
await page.getByPlaceholder('Search...');
await page.getByText('Welcome');
// ✅ 2. Test IDs (when semantic locators insufficient)
await page.getByTestId('product-card');
// ❌ 3. CSS/XPath selectors (avoid - fragile)
await page.locator('css=.dynamic-button');
3. Test Vue-Specific Behavior
Wait for Vue app to be ready before interactions:
// Wait for Vue app to mount
test('app loads correctly', async ({ page }) => {
await page.goto('/');
// Playwright auto-waits, but be explicit for Vue:
await page.waitForFunction(() => {
// Check Vue app is mounted
return !!(window as any).__VUE_DEVTOOLS_GLOBAL_HOOK__?.enabled;
});
// Now safe to interact
await page.getByRole('button').click();
});
4. Test Reactive State Changes
Test observable behavior from reactive updates:
test('counter increments', async ({ page }) => {
await page.goto('/counter');
// Initial state
await expect(page.getByText('Count: 0')).toBeVisible();
// Click increment
await page.getByRole('button', { name: 'Increment' }).click();
// Reactive update (Playwright auto-waits for DOM change)
await expect(page.getByText('Count: 1')).toBeVisible();
});
5. Mock Network Requests
Control external APIs for consistent tests:
test('fetches and displays data', async ({ page }) => {
// Intercept API calls
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Test User' }])
});
});
await page.goto('/users');
// Assert mocked data is displayed
await expect(page.getByText('Test User')).toBeVisible();
});
Common Vue Testing Patterns
Pattern: Testing Vue Router Navigation
test('navigates to product details', async ({ page }) => {
await page.goto('/products');
// Click product link
await page.getByRole('link', { name: 'Product A' }).click();
// Wait for URL change (conditional, not timeout)
await expect(page).toHaveURL('/products/1');
// Assert component mounted with correct data
await expect(page.getByRole('heading', { name: 'Product A' })).toBeVisible();
});
Pattern: Testing Form Validation
test('validates form before submit', async ({ page }) => {
await page.goto('/contact');
// Leave required field empty
await page.getByLabel('Email').fill('');
await page.getByLabel('Message').fill('Hello');
// Try to submit
await page.getByRole('button', { name: 'Send' }).click();
// Error message should appear
await expect(page.getByText('Email is required')).toBeVisible();
// Form should not submit
await expect(page).toHaveURL('/contact');
});
Pattern: Testing Modal Dialogs
test('modal closes on cancel', async ({ page }) => {
await page.goto('/');
// Open modal
await page.getByRole('button', { name: 'Open Dialog' }).click();
await expect(page.getByRole('dialog')).toBeVisible();
// Close modal
await page.getByRole('button', { name: 'Cancel' }).click();
// Modal should disappear
await expect(page.getByRole('dialog')).not.toBeVisible();
});
When to Load Reference Files
Load these strategically to optimize context usage:
references/e2e-patterns.md
- Building multi-page E2E workflows
- Implementing Page Object Model for Vue tests
- Testing complex user journeys
- Multi-step authentication flows
references/component-testing.md
- Testing Vue components in isolation
- Setting up experimental component testing
- Testing props, events, slots
- Testing composables with components
references/vue-specific-patterns.md
- Vue Router testing patterns
- Pinia state management testing
- Vue reactivity and computed properties
- Authentication testing in Vue
- Understanding wait patterns for Vue
references/best-practices.md
- Debugging flaky tests (and why timeouts aren't the answer)
- Selector strategies and troubleshooting
- API mocking patterns
- CI/CD configuration
- Performance optimization
Tips for Success
- Start with semantic locators - They're more stable and maintainable
- Never reach for timeouts first - If tests are flaky, investigate the root cause
- Test user behavior - Not implementation details or Vue internals
- Mock external APIs - For consistency and speed
- Run tests in parallel - Playwright supports parallel execution by default
- Use traces and videos - For debugging failed tests
- Review flaky tests immediately - Don't ignore intermittent failures
- Keep tests focused - One test should verify one behavior
- Set up CI/CD early - Use provided GitHub Actions workflow
- Reference the Vue-specific patterns - Understand how to wait for Vue correctly
File Structure
my-vue-app/
├── tests/
│ ├── e2e/
│ │ ├── login.spec.ts
│ │ ├── products.spec.ts
│ │ └── checkout.spec.ts
│ ├── fixtures/
│ │ ├── auth.ts
│ │ └── data.ts
│ └── pages/
│ ├── LoginPage.ts
│ ├── ProductsPage.ts
│ └── CheckoutPage.ts
├── playwright.config.ts
└── package.json
Resources
This skill includes references, templates, and scripts:
references/e2e-patterns.md
Comprehensive guide to E2E testing for Vue applications using Page Object Model and multi-page workflows.
references/component-testing.md
Guide to testing Vue components in isolation using Playwright's experimental component testing.
references/vue-specific-patterns.md
Vue-specific testing patterns including Router navigation, Pinia state management, reactivity testing, and proper wait strategies.
references/best-practices.md
Best practices including timeout anti-patterns, debugging flaky tests, selector strategies, and CI/CD setup.
assets/playwright.config.template.ts
Production-ready Playwright configuration optimized for Vue with Vite.
assets/workflows/playwright-vue.yml
GitHub Actions workflow for automated Playwright testing in CI/CD pipeline.
scripts/init-vue-playwright.ts
Setup script to initialize Playwright in an existing Vue project with proper configuration.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?