Eslint testing
QA Skills Directory QA Skills is a curated directory of testing-specific skills for AI coding agents (Claude Code, Cursor, Copilot, etc.).
npx -y skills add PramodDutta/qaskills --skill eslint-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Enforce test quality with ESLint - eslint-plugin-jest, eslint-plugin-playwright, and eslint-plugin-testing-library rules in flat config, blocking focused tests, missing assertions, and flaky waits via a CI lint gate.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.1 KB, as published. Nobody here has run it
ESLint for Test Quality
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".
Core Principles
- A committed
.onlyis a silent CI outage.it.onlymakes every other test in the file stop running while the build stays green.no-focused-testsset toerroris the single highest-value test lint rule; it is non-negotiable. - Tests without assertions must fail the lint. A test that calls code and asserts nothing passes forever.
expect-expect(Jest/Playwright) turns these into lint errors and accepts custom assertion wrappers via configuration. - Lint rules encode review comments you are tired of writing. "Do not put expects in conditionals", "use userEvent not fireEvent", "no waitForTimeout" - each has a rule; automate the comment.
- Scope test rules to test files only. Apply plugin configs with
files: ['**/*.test.ts']globs in flat config so production code is not subjected to test rules and vice versa. - Warnings are noise; errors are gates. CI must run with
--max-warnings 0or set every rule you care about toerror. A warning that scrolls by in CI logs changes nothing. - Adopt recommended configs first, then tighten. Start from
flat/recommendedfor each plugin, then promote the high-signal rules (no-conditional-expect,no-standalone-expect,prefer-user-event) to error as the suite cleans up.
Setup
npm install --save-dev eslint eslint-plugin-jest eslint-plugin-testing-library \
eslint-plugin-jest-dom eslint-plugin-playwright
Flat config with per-suite scoping
// 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',
},
},
];
Patterns
1. What the rules actually catch
// 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
}
});
2. Teaching expect-expect about custom assertion helpers
// 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.
3. CI lint gate that blocks merges
# .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 0
4. Pre-commit guard for the fast feedback loop
npm install --save-dev husky lint-staged
npx husky init
{
"lint-staged": {
"*.{ts,tsx,js}": ["eslint --fix --max-warnings 0"]
}
}
# .husky/pre-commit
npx lint-staged
Best Practices
- Run
eslint --fixfirst when adopting rules on an existing suite; many testing-library rules (such as query preferences) auto-fix. - Pair
jest/no-disabled-testsaswarnwith a tracking convention: everyit.skiprequires a linked ticket in a comment. - Add
playwright/no-networkidleandprefer-web-first-assertionsearly; they remove the two most common Playwright flake sources. - For Vitest projects, use
eslint-plugin-vitest(largely rule-compatible with eslint-plugin-jest) and the same scoping strategy. - Keep the lint job separate from the test job in CI so a lint failure reports in seconds, not after a 10-minute test run.
- Re-run
npx eslint . --max-warnings 0locally before pushing; the gate exists to be unreachable, not to be hit.
Anti-Patterns
- Putting test rules in the global config so production files get flagged for "missing expect" and developers disable the plugin entirely.
- Setting
no-focused-teststowarn: the one severity that cannot stop the exact accident the rule exists for. - Blanket
// eslint-disable-next-linecomments without a reason; require--report-unused-disable-directives(orlinterOptions.reportUnusedDisableDirectives) to keep disables honest. - Linting only changed files in CI while the repository still contains
.onlyfrom last month - run the full lint, it is cheap. - Treating the lint gate as optional ("we will fix warnings later"): later never arrives; gate at zero from day one on new projects.
- Writing a custom rule for something
eslint-plugin-jestalready ships; check the plugin's rule list first.
When to Trigger This Skill
- A repository has Jest, Vitest, Playwright, or Testing Library tests but
eslint.config.jscontains no test-specific plugins. - An
it.only,test.only, orfdescribewas found on the main branch, or CI passed while most tests silently did not run. - The user asks to "lint tests", "ban waitForTimeout", "enforce userEvent", "block focused tests", or "add a lint gate to CI".
- Migrating from
.eslintrcto flat config and the test-file overrides need translating intofiles-scoped config objects. - Code review keeps repeating the same test-hygiene comments; convert each into the matching rule and let the linter say it.