Instruction file imported from dailykaran/Playwright_MCP_Prompts (
.github/instructions/playwright_POM.instructions.md). Copyright stays with the author.
Playwright Test Automation Framework with POM and MCP
description: 'Playwright test generation instructions' applyTo: '**'
Test Writing Guidelines
Code Quality Standards
- Locators: Prioritize user-facing, role-based locators (
getByRole,getByLabel,getByText, etc.) for resilience and accessibility. Usetest.step()to group interactions and improve test readability and reporting. - Assertions: Use auto-retrying web-first assertions. These assertions start with the
awaitkeyword (e.g.,await expect(locator).toHaveText()). Avoidexpect(locator).toBeVisible()unless specifically testing for visibility changes. - Timeouts: Rely on Playwright's built-in auto-waiting mechanisms. Avoid hard-coded waits or increased default timeouts.
- Clarity: Use descriptive test and step titles that clearly state the intent. Add comments only to explain complex logic or non-obvious interactions.
Test Structure
- Imports: Start with
import { test, expect } from '@playwright/test';. - Organization: Group related tests for a feature under a
test.describe()block. - Hooks: Use
beforeEachfor setup actions common to all tests in adescribeblock (e.g., navigating to a page). - Titles: Follow a clear naming convention, such as
Feature - Specific action or scenario.
File Organization
- Location: Store all test files in the
tests/directory. - Naming: Use the convention
<feature-or-page>.spec.ts(e.g.,login.spec.ts,search.spec.ts). - Scope: Aim for one test file per major application feature or page.
Assertion Best Practices
- Element Counts: Use
toHaveCountto assert the number of elements found by a locator. - Text Content: Use
toHaveTextfor exact text matches andtoContainTextfor partial matches. - Navigation: Use
toHaveURLto verify the page URL after an action.
Overview
This document provides step-by-step instructions for creating a Playwright test automation framework using the Page Object Model (POM) pattern and integrating with Playwright Model Context Protocol (MCP).
Prerequisites
- Node.js (version 16 or higher)
- npm or yarn package manager
- Basic knowledge of JavaScript/TypeScript
- Understanding of Playwright and POM concepts
Example Test Structure
- follow the examples below step 1 to 12
Step 1: Project Initialization
Create a new directory for your project and initialize it:
mkdir playwright-pom-framework
cd playwright-pom-framework
npm init -y
Step 2: Install Dependencies
Install the required packages:
npm install @playwright/test
# If playwright-mcp is available as a separate package
npm install playwright-mcp
Step 3: Project Structure
Create the following folder structure:
project-root/
├── pages/
│ ├── base-page.ts
│ ├── login-page.ts
│ └── dashboard-page.ts
├── models/
│ └── test-data.ts
├── tests/
│ └── auth-flow.spec.ts
├── fixtures/
│ └── test-setup.ts
├── utils/
│ └── helpers.ts
├── playwright.config.ts
└── package.json
Step 4: Create Base Page Class
In pages/base-page.ts, create a base page class that other page classes will extend:
import { Page, expect } from '@playwright/test';
export class BasePage {
protected page: Page;
constructor(page: Page) {
this.page = page;
}
async navigateTo(url: string): Promise<void> {
await this.page.goto(url);
await this.page.waitForLoadState('networkidle');
}
async getTitle(): Promise<string> {
return await this.page.title();
}
async takeScreenshot(name: string): Promise<void> {
await this.page.screenshot({ path: `screenshots/${name}.png` });
}
}
Step 5: Create Page Objects
Create pages/login-page.js:
import { Page, Locator } from '@playwright/test';
import { BasePage } from './base-page';
export class LoginPage extends BasePage {
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
super(page);
this.usernameInput = page.locator('#username');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('button[type="submit"]');
this.errorMessage = page.locator('.error-message');
}
async login(username: string, password: string): Promise<void> {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async isErrorMessageVisible(): Promise<boolean> {
return await this.errorMessage.isVisible();
}
}
Step 6: Implement MCP Integration
Create models/test-data.js:
// Example MCP integration for test data generation
import { generateWithMCP } from 'playwright-mcp'; // Hypothetical MCP function
export class TestData {
static async getValidCredentials(): Promise<any> {
return await generateWithMCP('valid_login_credentials');
}
static async getInvalidCredentials(): Promise<any> {
return await generateWithMCP('invalid_login_credentials');
}
static async getTestUserProfile(): Promise<any> {
return await generateWithMCP('user_profile_data');
}
}
Step 7: Create Test Scripts
Create tests/auth-flow.spec.js:
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/login-page';
import { DashboardPage } from '../pages/dashboard-page';
import { TestData } from '../models/test-data';
test.describe('Authentication Flow', () => {
let loginPage: LoginPage;
let dashboardPage: DashboardPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
dashboardPage = new DashboardPage(page);
await loginPage.navigateTo('/login');
});
test('Successful login with valid credentials', async () => {
const credentials = await TestData.getValidCredentials();
await loginPage.login(credentials.username, credentials.password);
await expect(dashboardPage.welcomeMessage).toBeVisible();
await expect(dashboardPage.userProfile).toContainText(credentials.username);
});
test('Failed login with invalid credentials', async () => {
const credentials = await TestData.getInvalidCredentials();
await loginPage.login(credentials.username, credentials.password);
await expect(loginPage.errorMessage).toBeVisible();
await expect(loginPage.errorMessage).toContainText('Invalid credentials');
});
});
Step 8: Configure Playwright
Create playwright.config.js:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
timeout: 30000,
retries: 1,
use: {
headless: true,
viewport: { width: 1280, height: 720 },
actionTimeout: 10000,
ignoreHTTPSErrors: true,
video: 'retain-on-failure',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
],
});
Step 9: Create Test Fixtures
Create fixtures/test-setup.ts:
import { test as base, expect as baseExpect, Page } from '@playwright/test';
import { LoginPage } from '../pages/login-page';
import { TestData } from '../models/test-data';
// Extend base test with custom fixtures
type Fixtures = {
loginPage: LoginPage;
authenticatedPage: Page;
};
export const test = base.extend<Fixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
},
authenticatedPage: async ({ page, loginPage }, use) => {
// Perform login before tests that need authentication
const credentials = await TestData.getValidCredentials();
await loginPage.navigateTo('/login');
await loginPage.login(credentials.username, credentials.password);
await use(page);
},
});
export const expect = baseExpect;
Step 10: Add Utility Functions
Create utils/helpers.ts:
// Utility functions for tests
export class Helpers {
static generateRandomEmail(): string {
return `test${Math.floor(Math.random() * 1000000)}@example.com`;
}
static waitForTimeout(timeout: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, timeout));
}
static formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
}
Step 11: Update Package.json Scripts
Add these scripts to your package.json:
{
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"test:chrome": "playwright test --project=chromium",
"test:firefox": "playwright test --project=firefox",
"test:debug": "playwright test --debug",
"test:report": "playwright test --reporter=html"
}
}
Step 12: Run Tests
Execute your tests with:
bash
npm test
Additional Configuration
Set up environment variables in a .env file:
MCP_ENDPOINT=http://localhost:8080
MCP_API_KEY=your_api_key_here
BASE_URL=https://your-app.com
Add a .gitignore file:
node_modules/
screenshots/
test-results/
playwright-report/
.env
Troubleshooting
If you encounter issues with MCP integration, check:
MCP endpoint is accessible
API keys are correctly set
Network connectivity
For Playwright issues:
Ensure browsers are installed: npx playwright install
Check browser compatibility
Next Steps
Add more page objects for other application features
Implement API testing alongside UI tests
Set up CI/CD integration
Add visual regression testing
Implement custom reporters
Conclusion
You now have a Playwright test automation framework using the Page Object Model (POM) pattern and integrated with Playwright MCP for dynamic test data generation. You can expand this framework by adding more page objects, test cases, and utilities as needed.
Best Practices
- Use meaningful names for classes, methods, and variables.
- Keep tests independent and idempotent.
- Regularly refactor page objects to avoid duplication.
- Use MCP to generate diverse and realistic test data.
- Maintain a clean and organized project structure.
- Always follow the guidelines in
.github/instructions/playwright.instructions.mdfor writing tests and assertions.
Test Execution Strategy
- Initial Run: Execute tests with
npx playwright test --project=chromium - Debug Failures: Analyze test failures and identify root causes
- Iterate: Refine locators, assertions, or test logic as needed
- Validate: Ensure tests pass consistently and cover the intended functionality
- Report: Provide feedback on test results and any issues discovered
Quality Checklist
Before finalizing tests, ensure:
- All locators are accessible and specific and avoid strict mode violations
- Tests are grouped logically and follow a clear structure
- Assertions are meaningful and reflect user expectations
- Tests follow consistent naming conventions
- Code is properly formatted and commented