
Security News
GitHub Actions Checkout Now Blocks Risky pull_request_target Checkouts
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.
AST-based static analysis CLI for Playwright test files and Page Object Models.
npm install -g pw-eslint
# Analyze the current directory
pw-eslint .
# Analyze a specific path
pw-eslint src/tests
# Auto-fix fixable violations
pw-eslint . --fix
# Preview fixes without writing
pw-eslint . --dry-run
# Output as JSON
pw-eslint . --format json
# Only show errors (suppress warnings)
pw-eslint . --quiet
# Filter by rule category
pw-eslint . --category flakiness
# Analyze only git-staged files
pw-eslint . --staged
# Generate a config file
pw-eslint --init
# Explain a rule
pw-eslint explain no-hard-wait
# List all rules
pw-eslint explain --list
# Compare against a baseline
pw-eslint . --format json > baseline.json
# ... make changes ...
pw-eslint . --compare baseline.json
| Flag | Description | Default |
|---|---|---|
[path] | Path to analyze (file or directory) | . |
--format <fmt> | Output format: pretty, json, junit, github | pretty |
--fix | Apply auto-fixes in place | false |
--dry-run | Preview fixes as a unified diff | false |
--rule <id...> | Run only the specified rule(s) (repeatable) | all enabled rules |
--category <name...> | Run only rules in the specified category(s) (repeatable) | all categories |
--severity <level> | Filter output to error or warn | all |
--quiet | Show only error findings (same as --severity error) | false |
--no-color | Disable colored output | false |
--config <path> | Path to a specific config file | auto-discovered |
--staged | Analyze only git-staged files | false |
--output-file <path> | Write report to a file instead of stdout | stdout |
--max-warnings <n> | Exit 1 if warning count exceeds threshold | unlimited |
--compare <path> | Compare against a baseline JSON for regression detection | disabled |
--init | Generate .pw-eslintrc.json with all rules | — |
--init-config <path> | Custom path for generated config file | .pw-eslintrc.json |
Create .pw-eslintrc.json at your project root (next to package.json):
{
"include": ["**/*.spec.ts"],
"exclude": ["node_modules", "dist"],
"pageObjectPattern": "pages/**/*.ts",
"specPattern": "**/*.spec.ts",
"rules": {
"no-hard-wait": "error",
"deep-locator": ["warn", { "maxDepth": 3 }],
"unawaited-action": "error",
"web-first-assertion": "error",
"leaky-page-object": "warn",
"zombie-locator": "warn",
"no-page-pause": "error",
"no-focused-test": "error",
"no-hardcoded-base-url": "warn",
"no-hardcoded-timeout": ["warn", { "maxTimeout": 5000 }],
"no-console-in-test": "warn",
"no-skipped-test": "warn"
}
}
You can also generate a starter config with pw-eslint --init.
| Key | Type | Description |
|---|---|---|
include | string[] | Glob patterns for files to analyze |
exclude | string[] | Glob patterns for files to exclude |
pageObjectPattern | string | Glob for Page Object Model files |
specPattern | string | Glob for test spec files |
rules | object | Rule severities — "error", "warn", "off", or ["warn", { options }] |
failOn | "error" | "warn" | Exit 1 when findings of this severity exist (default: "error") |
maxWarnings | number | Max warnings before exit 1 (default: unlimited) |
categoryFilter | string[] | Run only rules in the specified categories |
overrides | OverrideEntry[] | Per-directory rule overrides (see below) |
Override rule severity for specific file patterns:
{
"overrides": [
{
"files": ["tests/smoke/**"],
"rules": { "no-hard-wait": "warn" }
},
{
"files": ["tests/e2e/**"],
"rules": { "no-hardcoded-timeout": "off" }
}
]
}
The first matching override wins. Unspecified rules inherit the global config.
| Rule ID | Default | Fixable | Category | Description |
|---|---|---|---|---|
no-hard-wait | error | Yes | flakiness | Disallows page.waitForTimeout() and bare setTimeout() |
deep-locator | warn | No | flakiness | Flags locator chains deeper than maxDepth (default: 3) |
unawaited-action | error | No | flakiness | Flags Playwright actions called without await |
zombie-locator | warn | No | flakiness | Flags locators assigned but never used |
web-first-assertion | error | Yes | correctness | Enforces web-first assertions (expect(locator).toBeVisible()) over manual waits |
leaky-page-object | warn | No | hygiene | Flags Page Object methods that return raw Playwright types |
no-page-pause | error | Yes | hygiene | Flags page.pause() debug calls left in test code |
no-focused-test | error | Yes | style | Flags test.only() / it.only() / describe.only() |
no-hardcoded-base-url | warn | No | hygiene | Flags hardcoded http:// / https:// URLs in page.goto() |
no-hardcoded-timeout | warn | No | hygiene | Flags hardcoded timeout values (configurable maxTimeout) |
no-console-in-test | warn | No | hygiene | Flags console.log/warn/error/debug/info in spec files |
no-skipped-test | warn | Yes | style | Flags test.skip() and test.fixme() |
Rules are grouped into four categories for filtering with --category:
| Category | Rules | Purpose |
|---|---|---|
flakiness | no-hard-wait, deep-locator, unawaited-action, zombie-locator | Prevent flaky tests |
correctness | web-first-assertion | Enforce correct Playwright patterns |
hygiene | leaky-page-object, no-page-pause, no-hardcoded-base-url, no-hardcoded-timeout, no-console-in-test | Keep code clean |
style | no-focused-test, no-skipped-test | Enforce consistent style |
Some rules accept configuration options:
{
"rules": {
"deep-locator": ["warn", { "maxDepth": 4 }],
"no-hardcoded-timeout": ["warn", { "maxTimeout": 5000 }]
}
}
| Rule | Option | Type | Default | Description |
|---|---|---|---|---|
deep-locator | maxDepth | number | 3 | Maximum allowed locator chain depth |
no-hardcoded-timeout | maxTimeout | number | 0 | Only flag timeouts exceeding this value (0 = flag all) |
Five rules support automatic fixing:
| Rule | Fix Action |
|---|---|
no-hard-wait | Replaces page.waitForTimeout() with a TODO comment |
web-first-assertion | Rewrites expect(await loc.isVisible()).toBe(true) to await expect(loc).toBeVisible() |
no-page-pause | Removes page.pause() statements |
no-focused-test | Removes .only modifier (test.only() -> test()) |
no-skipped-test | Removes .skip / .fixme modifier (test.skip() -> test()) |
# Apply fixes
pw-eslint . --fix
# Preview fixes as diff
pw-eslint . --dry-run
All fixes are idempotent — running --fix twice produces the same result.
Suppress a finding on the next line:
// pw-eslint-disable-next-line no-hard-wait
await page.waitForTimeout(1000);
Suppress multiple rules:
// pw-eslint-disable-next-line no-hard-wait, deep-locator
await page.locator('div > span > a').waitForTimeout(1000);
Rule IDs are case-insensitive.
Get detailed rationale, examples, and fix guidance for any rule:
pw-eslint explain no-hard-wait
pw-eslint explain --list
The explain command shows:
Use --format github to produce inline annotations in GitHub Actions:
- run: npx pw-eslint . --format github
Analyze only git-staged files:
pw-eslint . --staged
pw-eslint . --format junit --output-file reports/pw-eslint.xml
Fail the build if warnings exceed a limit:
pw-eslint . --max-warnings 10
Configure when the CLI exits with code 1:
{
"failOn": "warn"
}
Save current findings as a baseline, then detect regressions:
# Save baseline
pw-eslint . --format json > baseline.json
# Later, compare against baseline
pw-eslint . --compare baseline.json
With --compare:
Custom rules are loaded from .pw-eslint/rules/*.js relative to your project root.
Security Warning: Custom rules are trusted code. They run in the same Node.js process as the CLI with no sandboxing. Do not load rules from untrusted sources. Only use rules written and reviewed by your own team.
Each rule file must export a RuleDefinition object as its default export:
// .pw-eslint/rules/no-console-log.js
export default {
apiVersion: 1,
id: 'no-console-log',
description: 'Disallows console.log in test files',
defaultSeverity: 'warn',
fixable: false,
check(context) {
const calls = context.sourceFile.getDescendantsOfKind(/* SyntaxKind.CallExpression */ 213);
for (const call of calls) {
if (call.getExpression().getText() === 'console.log') {
context.report(call, 'Avoid console.log in tests');
}
}
},
};
RuleDefinition Interfaceinterface RuleDefinition {
apiVersion: 1; // must be exactly 1
id: string; // unique rule identifier
description: string; // human-readable description
defaultSeverity: 'error' | 'warn'; // default severity when enabled
fixable: boolean; // whether fix() is implemented
category?: string; // rule category for filtering
explain?: RuleExplainData; // rationale, examples, fix guidance
check(context: RuleContext): void; // analysis logic
fix?(context: FixContext): void; // optional auto-fix logic
}
RuleContext Interfaceinterface RuleContext {
sourceFile: SourceFile; // ts-morph SourceFile
project: Project; // ts-morph Project
config: ResolvedConfig; // resolved CLI config
report(node: Node, message: string, suggestion?: string): void;
}
Once a custom rule file exists, configure it in .pw-eslintrc.json just like any built-in rule:
{
"rules": {
"no-console-log": "warn"
}
}
The apiVersion field guards against breaking changes. The current supported version is 1. If you upgrade the CLI and the API changes, the CLI will exit with code 2 and tell you which rule needs updating.
| Code | Meaning |
|---|---|
0 | Success — no violations matching failOn severity |
1 | Analysis completed — violations found, --max-warnings exceeded, or regressions detected with --compare |
2 | Configuration error, plugin load failure, file not found, or usage error |
pw-eslint requires filesystem access to read configurations and analyze test files. If you're using this as a library and want to restrict access, see SECURITY.md for how to provide a custom FileSystem implementation.
MIT — see LICENSE.
FAQs
AST-based static analysis CLI for Playwright test files
We found that pw-eslint demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.