ESLint for Test Quality — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited ESLint for Test Quality (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
This skill makes an AI agent wire ESLint plugins that lint the tests themselves - catching focused tests that silently skip entire suites in CI, assertion-free tests, conditional expects, and flaky waitForTimeout calls before they merge. Trigger it when a project has test files but no test-specific lint rules, when an it.only ever reaches main, or when the user asks to "lint tests", "block .only", or "enforce testing best practices".
it.only makes every other test in the file stop running while the build stays green. no-focused-tests set to error is the single highest-value test lint rule; it is non-negotiable.expect-expect (Jest/Playwright) turns these into lint errors and accepts custom assertion wrappers via configuration.files: ['**/*.test.ts'] globs in flat config so production code is not subjected to test rules and vice versa.--max-warnings 0 or set every rule you care about to error. A warning that scrolls by in CI logs changes nothing.flat/recommended for each plugin, then promote the high-signal rules (no-conditional-expect, no-standalone-expect, prefer-user-event) to error as the suite cleans up.npm install --save-dev eslint eslint-plugin-jest eslint-plugin-testing-library \
eslint-plugin-jest-dom eslint-plugin-playwright// eslint.config.js
import jest from 'eslint-plugin-jest';
import testingLibrary from 'eslint-plugin-testing-library';
import jestDom from 'eslint-plugin-jest-dom';
import playwright from 'eslint-plugin-playwright';
export default [
// Unit and component tests (Jest + Testing Library)
{
files: ['src/**/*.test.{ts,tsx}', 'src/**/__tests__/**/*.{ts,tsx}'],
plugins: { jest, 'testing-library': testingLibrary, 'jest-dom': jestDom },
languageOptions: { globals: jest.environments.globals.globals },
rules: {
...jest.configs['flat/recommended'].rules,
...testingLibrary.configs['flat/react'].rules,
...jestDom.configs['flat/recommended'].rules,
'jest/no-focused-tests': 'error',
'jest/no-disabled-tests': 'warn',
'jest/no-conditional-expect': 'error',
'jest/no-standalone-expect': 'error',
'jest/valid-title': 'error',
'jest/prefer-hooks-on-top': 'error',
'jest/expect-expect': [
'error',
{ assertFunctionNames: ['expect', 'expectTypeOf', 'assertOrderShape'] },
],
'testing-library/prefer-user-event': 'error',
'testing-library/no-wait-for-side-effects': 'error',
'testing-library/no-manual-cleanup': 'error',
},
},
// Playwright E2E specs
{
files: ['e2e/**/*.spec.ts'],
plugins: { playwright },
rules: {
...playwright.configs['flat/recommended'].rules,
'playwright/no-focused-test': 'error',
'playwright/no-skipped-test': 'warn',
'playwright/no-wait-for-timeout': 'error',
'playwright/no-conditional-in-test': 'error',
'playwright/no-force-option': 'error',
'playwright/expect-expect': 'error',
'playwright/no-networkidle': 'error',
'playwright/prefer-web-first-assertions': 'error',
},
},
];// e2e/checkout.spec.ts - every line below is a lint ERROR with the config above
test.only('applies a coupon', async ({ page }) => {
// playwright/no-focused-test: the other 84 specs in this project
// would silently not run in CI while the build stays green.
});
test('waits for the cart to update', async ({ page }) => {
await page.waitForTimeout(3000); // playwright/no-wait-for-timeout: flaky AND slow
await page.click('#checkout', { force: true }); // playwright/no-force-option: bypasses actionability
});
test('shows totals', async ({ page }) => {
const rows = await page.locator('.row').count();
if (rows > 0) {
expect(rows).toBeGreaterThan(0); // playwright/no-conditional-in-test
}
// playwright/expect-expect also fires if no assertion is reachable
});// src/components/CouponForm.test.tsx - Jest + Testing Library violations
it('test 1', async () => {
// jest/valid-title: meaningless title
render(<CouponForm />);
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'SAVE10' } });
// testing-library/prefer-user-event: fireEvent skips focus/keyboard semantics
});
it('submits the coupon', async () => {
try {
await submitCoupon('SAVE10');
expect(api.apply).toHaveBeenCalled();
} catch {
expect(true).toBe(false); // jest/no-conditional-expect: may never run
}
});// tests/helpers/assert-order-shape.ts
import { expect } from 'vitest';
// Custom assertion helper used across suites
export function assertOrderShape(order: unknown): void {
expect(order).toMatchObject({
id: expect.stringMatching(/^ord_/),
total: expect.any(Number),
items: expect.arrayContaining([expect.objectContaining({ sku: expect.any(String) })]),
});
}
// Registered above via expect-expect assertFunctionNames so tests
// that only call assertOrderShape(...) do not trip the rule.# .github/workflows/lint.yml
name: lint
on: [pull_request]
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- name: Lint (zero warnings allowed)
run: npx eslint . --max-warnings 0npm install --save-dev husky lint-staged
npx husky init{
"lint-staged": {
"*.{ts,tsx,js}": ["eslint --fix --max-warnings 0"]
}
}# .husky/pre-commit
npx lint-stagedeslint --fix first when adopting rules on an existing suite; many testing-library rules (such as query preferences) auto-fix.jest/no-disabled-tests as warn with a tracking convention: every it.skip requires a linked ticket in a comment.playwright/no-networkidle and prefer-web-first-assertions early; they remove the two most common Playwright flake sources.eslint-plugin-vitest (largely rule-compatible with eslint-plugin-jest) and the same scoping strategy.npx eslint . --max-warnings 0 locally before pushing; the gate exists to be unreachable, not to be hit.no-focused-tests to warn: the one severity that cannot stop the exact accident the rule exists for.// eslint-disable-next-line comments without a reason; require --report-unused-disable-directives (or linterOptions.reportUnusedDisableDirectives) to keep disables honest..only from last month - run the full lint, it is cheap.eslint-plugin-jest already ships; check the plugin's rule list first.eslint.config.js contains no test-specific plugins.it.only, test.only, or fdescribe was found on the main branch, or CI passed while most tests silently did not run..eslintrc to flat config and the test-file overrides need translating into files-scoped config objects.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.