Explain the E2E test loop technique and Playwright best practices
Explains E2E test loop technique and Playwright best practices for iterative test development.
/plugin marketplace add SomtoUgeh/somto-dev-toolkit/plugin install somto-dev-toolkit@somto-dev-toolkitExplain the following to the user:
An iterative loop for developing Playwright E2E tests:
*.e2e.page.ts)*.e2e.ts)*.e2e.page.ts - Page objects (locators, setup, actions)*.e2e.ts - Test files (concise, use page objects)Example:
e2e/
├── login.e2e.page.ts # Page object
├── login.e2e.ts # Tests
├── base.e2e.page.ts # Base page object
/e2e [OPTIONS]Start an E2E test development loop.
Options:
--max-iterations N - Max iterations before auto-stop--test-command "cmd" - Override test command (default: npx playwright test)--completion-promise "text" - Custom promise phrase (default: E2E COMPLETE)Examples:
/e2e --max-iterations 15
/e2e --test-command "pnpm test:e2e"
/e2e --completion-promise "ALL FLOWS COVERED" --max-iterations 20
/cancel-e2eStop an active E2E loop.
Page objects encapsulate locators and actions:
// login.e2e.page.ts
import { Page } from '@playwright/test';
export class LoginPage {
constructor(private page: Page) {}
// Locators
emailInput = () => this.page.getByLabel('Email');
passwordInput = () => this.page.getByLabel('Password');
submitButton = () => this.page.getByRole('button', { name: 'Sign in' });
// Navigation
async goto() {
await this.page.goto('/login');
}
// Actions
async login(email: string, password: string) {
await this.emailInput().fill(email);
await this.passwordInput().fill(password);
await this.submitButton().click();
}
}
Tests become concise:
// login.e2e.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from './login.e2e.page';
test('user can login', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password');
await expect(page).toHaveURL('/dashboard');
});
getByRole() - buttons, links, headings (most resilient)getByLabel() - form inputsgetByText() - static textgetByTestId() - when semantic locators failUse setup projects for one-time auth:
// auth.setup.ts
import { test as setup } from '@playwright/test';
setup('authenticate', async ({ page }) => {
await page.goto('/login');
// ... login steps
await page.context().storageState({ path: 'playwright/.auth/user.json' });
});
In playwright.config.ts:
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{ name: 'chromium', dependencies: ['setup'], use: { storageState: 'playwright/.auth/user.json' } },
]
expect(locator).toBeVisible() auto-waitspage.route() for third-party servicesThe loop stops when:
<promise>E2E COMPLETE</promise> is output--max-iterations is reached/cancel-e2e is run