@playforge/eslint-plugin
v0.2.0
Published
ESLint plugin enforcing Playwright test architecture patterns
Maintainers
Readme
@playforge/eslint-plugin
ESLint plugin that enforces Playwright test architecture patterns. Part of the PlayForge framework.
Prevents common anti-patterns in Playwright projects by enforcing the three-layer Page Object Model:
- Page objects (
*-page.ts) — locators only - Steps (
*-steps.ts) — business logic + assertions - Specs (
*.spec.ts) — test orchestration
Installation
npm install --save-dev @playforge/eslint-pluginUsage
Add to your eslint.config.ts (ESLint v9+ flat config):
import playforge from '@playforge/eslint-plugin';
export default [
playforge.configs.recommended,
];Rules
playforge/no-locators-in-steps (error)
Step files (*-steps.ts) must not contain inline locators like page.locator(), page.getByRole(), etc. All locators must come from page objects.
// Bad (in login-steps.ts)
await this.page.getByRole('button', { name: 'Login' }).click();
// Good (in login-steps.ts)
await this.loginPage.submitButton.click();playforge/no-assertions-in-pages (error)
Page object files (*-page.ts) must not contain expect() assertions. Pages define locators and navigation only.
// Bad (in login-page.ts)
await expect(this.submitButton).toBeVisible();
// Good — move to steps or specsplayforge/require-test-step (warn)
In spec files (*.spec.ts), assertions should be wrapped in test.step() for self-documenting test reports.
// Bad (in login.spec.ts)
test('login', async () => {
await expect(page).toHaveTitle('Home');
});
// Good
test('login', async () => {
await test.step('verify home page', async () => {
await expect(page).toHaveTitle('Home');
});
});playforge/consistent-file-naming (warn)
Enforces naming conventions:
- Files in
pages/directories:*-page.ts,*-steps.ts, orindex.ts - Files in
tests/directories:*.spec.ts(ortestSetup.ts/testTeardown.ts)
Recommended Config
The recommended config enables all rules with sensible defaults:
| Rule | Level |
|------|-------|
| playforge/no-locators-in-steps | error |
| playforge/no-assertions-in-pages | error |
| playforge/require-test-step | warn |
| playforge/consistent-file-naming | warn |
Pro Rules
If you use @bltn-consulting/pro, compose the pro config preset alongside recommended:
import playforge from '@playforge/eslint-plugin';
export default [
playforge.configs.recommended,
playforge.configs.pro,
];playforge/require-pro-annotations (warn)
Tests that import @bltn-consulting/pro must call at least one annotate.*() method. Unannotated tests produce reports indistinguishable from pre-Pro tests.
// Bad
import { test } from '@bltn-consulting/pro';
test('login', async ({ page }) => {
await page.goto('/');
});
// Good
import { test } from '@bltn-consulting/pro';
test('login', async ({ page, annotate }) => {
annotate.workflow('user lands on home');
await page.goto('/');
});v0.2.x limitation: For accurate detection, destructure the fixture as
{ annotate }without aliasing. The rule matches the literal identifierannotate— aliased forms like{ annotate: a }will produce a false-positive. Alias resolution is scheduled for v0.3.
playforge/prefer-timeouts-fixture (warn)
Flags hardcoded numeric { timeout: N } options in Pro-importing files. Use the timeouts fixture instead so values adapt to PLAYFORGE_ENV (local = 1×, UAT = 1.5×, stress = 2.5×, ...).
// Bad
await page.waitForSelector('.x', { timeout: 15_000 });
// Good
await page.waitForSelector('.x', { timeout: timeouts.medium });
// Valid exception — timeout: 0 is an explicit disable
await page.waitForSelector('.x', { timeout: 0 });playforge/no-untyped-custom-annotations (warn, auto-fixable)
Flags annotate.custom('<typed-name>', ...) calls that shadow Pro's typed annotation methods. Auto-fix rewrites the call to the typed form (except for union-typed priority / severity, where mechanical rewrite could introduce a type error).
// Bad — 'step' is a typed method
annotate.custom('step', 'visit home');
// Good — auto-fix converts to:
annotate.step('visit home');
// Valid — org-specific types bypass the registry
annotate.custom('sap-transaction', 'VA01');v0.2.x limitation: The rule matches only the literal identifier
annotate. If you alias the fixture in destructuring ({ annotate: a }), calls likea.custom('step', 'x')will NOT be flagged or auto-fixed. Use the default destructure{ annotate }for full coverage. Alias resolution is scheduled for v0.3.
Pro preset rules table
| Rule | Level | Auto-fix |
|------|-------|---------|
| playforge/require-pro-annotations | warn | — |
| playforge/prefer-timeouts-fixture | warn | — |
| playforge/no-untyped-custom-annotations | warn | yes (except priority, severity) |
Requirements
- ESLint >= 9.0.0
- TypeScript (recommended)
License
MIT
