Resource library

QA How-To

ESLint and Prettier for tests: A QA Guide (2026)

Learn ESLint and Prettier for tests qa setup, flat config, TypeScript rules, CI checks, and practical patterns for maintainable automation code in 2026.

20 min read | 2,964 words

TL;DR

ESLint finds suspicious or inconsistent test code, while Prettier formats it. Configure them as separate tools, scope test globals and rules with ESLint flat config, and make `eslint .` plus `prettier . --check` required CI checks.

Key Takeaways

  • Use ESLint for correctness and maintainability rules, and Prettier for deterministic formatting.
  • Adopt ESLint flat config with explicit file globs for source, test, and configuration files.
  • Apply type-aware TypeScript linting only where its extra signal justifies the performance cost.
  • Keep formatting rules out of ESLint to avoid duplicate diagnostics and conflicting fixes.
  • Run fast checks locally, then enforce lint and Prettier checks as independent CI gates.
  • Treat test code as production code while allowing deliberate test-specific exceptions.

ESLint and Prettier for tests qa teams are complementary controls, not competing formatters. ESLint detects code patterns that can hide defects or make automation unreliable, while Prettier produces one stable layout. A strong 2026 setup assigns each tool one job and applies test-aware rules through ESLint flat config.

This guide builds a practical JavaScript and TypeScript configuration, explains which rules matter in automation, and shows how to enforce the result without turning every test review into a style debate. The examples work as a small project and can be adapted to Jest, Playwright, Vitest, or API test suites.

TL;DR

Need Use Typical command
Find likely defects ESLint npx eslint .
Apply safe lint fixes ESLint npx eslint . --fix
Verify formatting Prettier npx prettier . --check
Rewrite formatting Prettier npx prettier . --write
Protect the main branch Both, as separate CI steps npm run quality

Use eslint.config.mjs as an ordered array of configuration objects. Give test files their framework globals, keep generated artifacts ignored, and let Prettier own whitespace. Do not add formatting rules merely to make ESLint report Prettier differences.

1. ESLint and Prettier for tests qa: Separate Responsibilities

ESLint analyzes syntax and, with suitable plugins, semantics. It can flag an unused fixture, an accidentally floating promise, a constant condition, or an assertion that is not awaited. These findings can represent real false positives and false negatives in a test suite. Prettier parses code and prints it in a consistent form. It decides line breaks, indentation, quotes where configurable, and other layout details, but it does not decide whether a promise should be awaited.

That boundary matters. When two tools enforce the same formatting concern, developers see duplicate messages and fixes can fight each other. A useful operating model is: ESLint owns code quality, Prettier owns presentation, TypeScript owns type checking, and the test runner owns behavior. Each failure then has a clear meaning.

Test code deserves serious linting because it controls release evidence. A swallowed exception can make a negative test pass for the wrong reason. A missing await can let a browser test finish before the assertion runs. An unused setup variable can reveal that the intended precondition was never applied. At the same time, tests use callbacks, fixtures, parameterized data, and intentionally duplicated setup more often than application code. Good configuration recognizes those patterns rather than copying an application preset unchanged.

For broader framework design, pair this guide with test automation framework best practices. Linting is most valuable when it reinforces a clear test architecture instead of compensating for a confusing one.

2. Install a Minimal, Reproducible Toolchain

Start with local development dependencies so every workstation and CI runner uses the versions recorded in the lockfile. For a TypeScript suite, install ESLint, its JavaScript rules package, typescript-eslint, TypeScript, Prettier, and the globals package. The globals package supplies accurate identifiers for Node and test environments.

npm install --save-dev eslint @eslint/js typescript typescript-eslint prettier globals

Use package scripts rather than asking contributors to remember command flags. A minimal package.json can be initialized and then updated as follows.

{
  "type": "module",
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "format": "prettier . --write",
    "format:check": "prettier . --check",
    "quality": "npm run lint && npm run format:check"
  },
  "devDependencies": {}
}

The empty devDependencies shown here is only a compact illustration. npm install writes the actual versions. Commit package-lock.json, and use npm ci in CI for a clean, locked install. Avoid global installations because they make local and pipeline results drift.

Check the tools before adding complex rules: run npm run lint and npm run format:check. A missing config or unsupported file should fail now, not after a large migration. If the repository mixes module systems, name the configuration eslint.config.mjs so Node interprets it as ESM regardless of the package default.

3. Build an ESLint Flat Config for Test Files

ESLint flat config is an exported array. Matching objects are combined in order, so start with global ignores, apply broad JavaScript recommendations, then add TypeScript and test-specific overrides. The following version-agnostic setup lints common source and test locations without inventing framework rules.

// eslint.config.mjs
import eslint from '@eslint/js';
import globals from 'globals';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  {
    ignores: [
      'coverage/**',
      'dist/**',
      'playwright-report/**',
      'test-results/**',
      '**/*.generated.*',
    ],
  },
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  {
    files: ['**/*.{js,mjs,cjs,ts,tsx}'],
    languageOptions: {
      globals: globals.node,
    },
    rules: {
      'no-console': ['warn', {allow: ['warn', 'error']}],
    },
  },
  {
    files: ['**/*.{test,spec}.{js,ts,tsx}', 'tests/**/*.{js,ts,tsx}'],
    rules: {
      '@typescript-eslint/no-non-null-assertion': 'off',
    },
  },
);

This config does not add Jest or Playwright globals. Modern TypeScript suites often import test, expect, and lifecycle functions explicitly, which improves discoverability and avoids global ambiguity. If a framework exposes globals, use its maintained ESLint plugin or add only the exact globals required for matching test files. Never mark every unknown name writable merely to silence errors.

Flat config globs are relative to the config location. Inspect the effective result with npx eslint --print-config tests/example.spec.ts when an override appears inactive. This single command catches ordering, glob, and parser assumptions faster than random rule changes.

4. Configure Prettier Without Style Theater

Prettier intentionally offers a small option set. Use a short configuration, apply it repository-wide, and resist tuning every default. A defensible .prettierrc.json for test code is:

{
  "printWidth": 100,
  "singleQuote": true,
  "trailingComma": "all",
  "semi": true
}

Add a .prettierignore for generated reports, snapshots that another tool owns, compiled output, and large recorded fixtures. Ignoring source tests to preserve personal formatting defeats the purpose.

coverage/
dist/
playwright-report/
test-results/
package-lock.json

Decide deliberately whether to ignore the lockfile. Prettier can format JSON, but the package manager owns lockfile content and reviewing reformat-only lockfile changes can be noisy. The best choice is the one applied consistently.

Run prettier . --write once in a dedicated formatting commit when adopting it. Keeping that mechanical change separate preserves useful history for later git blame work. Afterward, prettier . --check should be the CI gate. The check command reports differences without editing the runner workspace.

Do not use Prettier as a substitute for readable test structure. It cannot choose a better test name, extract a page object, or remove a brittle conditional. It can, however, make diffs predictable so reviewers spend attention on those higher-value concerns.

5. Add Type-Aware Linting Where It Pays

Syntax-aware TypeScript linting is fast and valuable, but some important promise and type rules require the TypeScript program. For automation, no-floating-promises and await-thenable can catch failures that otherwise become flaky or silently ineffective tests. Enable type-aware presets only for TypeScript files and ensure the project configuration includes those files.

// Add inside tseslint.config(...) after the recommended entries
{
  files: ['**/*.{ts,tsx}'],
  extends: [...tseslint.configs.recommendedTypeChecked],
  languageOptions: {
    parserOptions: {
      projectService: true,
      tsconfigRootDir: import.meta.dirname,
    },
  },
  rules: {
    '@typescript-eslint/no-floating-promises': 'error',
    '@typescript-eslint/await-thenable': 'error',
  },
}

A type-aware rule can detect the defect in this test: page.goto('/account') returns a promise, and forgetting await allows the assertion to race navigation. TypeScript alone does not always reject an unused promise expression, but the lint rule does.

test('shows the account heading', async ({page}) => {
  await page.goto('/account');
  await expect(page.getByRole('heading', {name: 'Account'})).toBeVisible();
});

Type-aware linting costs more CPU and memory. In a monorepo, target the test packages that benefit and keep a faster syntax-only editor path if measurements justify it. Do not disable a promise rule globally because one helper intentionally launches background work. Make that intent explicit with void startTelemetry() or a focused disable comment that explains ownership of rejection handling.

6. Choose Test-Aware Rules With QA Signal

The best rule set maps to failure modes, not aesthetic preferences. Start with unused variables, unreachable code, unsafe promises, accidental equality coercion, and framework-specific assertion rules. Evaluate each rule against real examples before changing hundreds of files.

Rule area Defect it can expose Sensible test policy
Floating promises Test ends before action or assertion Error
Unused variables Fixture or response never validated Error, allow _ prefix if deliberate
Console output Logs hide useful runner output Warn, allow diagnostics
Non-null assertions Test bypasses useful type guard Allow selectively in fixtures
Conditional assertions Test can pass without asserting Use a maintained framework plugin
Duplicate setup Divergent test preconditions Review manually, avoid rigid limits

Framework plugins provide signal that core ESLint cannot. A Jest plugin can recognize matchers and focused tests. A Playwright plugin can recognize missing awaits and discouraged APIs. Install only the plugin for the runner actually used, follow its flat-config documentation, and pin it in the lockfile. Plugin presets evolve, so review newly enabled rules during upgrades rather than accepting large automated changes blindly.

Rule severity expresses policy. Use error for behavior that must block merging, warn for staged adoption or genuine judgment calls, and off when a rule contradicts the project. A pipeline that permits warnings should set an explicit maximum warning count once the existing baseline is clean.

7. Format Data-Driven and Async Tests for Reviewability

Test files contain tables, nested fixtures, selectors, and long matcher messages. Prettier makes layout stable, but authors still control the shape of data. Prefer named case objects over positional arrays once values need explanation. This improves failure output and avoids swapping two strings that share the same type.

const cases = [
  {role: 'viewer', expectedStatus: 403},
  {role: 'editor', expectedStatus: 200},
  {role: 'owner', expectedStatus: 200},
] as const;

test.each(cases)(
  '$role receives $expectedStatus',
  async ({role, expectedStatus}) => {
    const response = await requestAs(role, '/api/projects/42');
    expect(response.status).toBe(expectedStatus);
  },
);

Keep the assertion near the action whose result it validates. A formatter will wrap a huge chain, but a well-named intermediate variable often communicates more. Similarly, avoid suppressing no-await-in-loop by default in end-to-end tests. Sequential actions may be required, while independent API setup may be safely parallelized with Promise.all. Decide from product behavior.

Generated snapshots are a special case. Let the framework serialize its snapshot format, and exclude it from Prettier if formatting changes cause churn. Handwritten inline snapshots can stay formatted with the test if the runner and Prettier integration support that workflow. Review snapshot content as evidence, not as a file that merely needs to pass lint.

A clean style helps when applying JavaScript test debugging techniques, because control flow and missing awaits become easier to see in small diffs.

8. Integrate Editors, Pre-Commit Checks, and CI

Local automation should reduce feedback time without becoming a hidden source of truth. Configure the editor to format on save and apply ESLint fixes, but ensure CI runs the same repository scripts. Contributors who use another editor must get identical results. Avoid editor-only rules or unpublished workspace settings.

A simple GitHub Actions job can enforce the locked environment:

name: quality
on:
  pull_request:
  push:
    branches: [main]

jobs:
  lint-and-format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: npm
      - run: npm ci
      - run: npm run lint -- --max-warnings=0
      - run: npm run format:check

Choose a Node release supported by the project and dependencies. The sample uses an even-numbered release line suitable for a 2026 project, but the repository should define its actual runtime through engines, a version file, or the workflow.

Pre-commit tools can run ESLint and Prettier on staged files for speed. They are an optimization, not the only gate. CI must scan the intended repository scope because staged-file hooks can be skipped and may miss configuration interactions. Keep lint, formatting, type checking, and tests as distinguishable steps so a developer can reproduce the exact failure. For pipeline strategy beyond formatting, see CI practices for automated tests.

9. Migrate an Existing Suite Without Freezing Delivery

A large legacy suite rarely becomes clean in one afternoon. First, record the current tool versions and exclude generated output. Add Prettier in a mechanical commit. Then enable ESLint recommended rules and fix correctness issues before debating strict stylistic preferences. Keep temporary warnings visible and assign an owner and removal condition.

Do not add a blanket eslint-disable at the top of old directories. It creates a permanent blind spot. A better transition is to scope a small set of noisy rules to new or migrated files, then expand coverage. ESLint flat config makes this explicit with file globs. Track the remaining exception rather than pretending the repository is protected.

Review autofixes. ESLint labels fixes as safe according to rule behavior, but a large batch can still alter code in unexpected ways or expose test assumptions. Run the affected tests after --fix, especially when rules rewrite imports or promise handling. Prettier output should be behavior-neutral, yet the suite still needs a smoke run if snapshots or generated artifacts are involved.

Upgrades deserve the same discipline. Read migration notes, update one layer at a time, print the effective config for representative files, and compare warnings. Shared presets can add rules without a direct config diff. Lockfiles provide reproducibility, not immunity from behavior changes when someone intentionally upgrades dependencies.

10. ESLint and Prettier for tests qa: A Daily Workflow

A sustainable workflow is boring in the best sense. Before opening a pull request, run the formatter, lint with zero unexpected warnings, type-check, and execute the relevant tests. In CI, check rather than modify. If a check fails, fix the source and commit the result rather than formatting files inside the pipeline.

Use this sequence for a typical change:

  1. Write or update the test with explicit imports and awaited operations.
  2. Run the focused test until its behavior is correct.
  3. Run npm run lint:fix to apply safe lint fixes.
  4. Run npm run format to normalize layout.
  5. Run npm run quality and the relevant test command.
  6. Inspect the diff for unintended snapshots, disabled rules, or mass formatting.

During review, separate tool compliance from test quality. A green lint job does not prove that assertions are meaningful, data is isolated, or selectors are resilient. It proves that the code satisfied an agreed static policy. Reviewers still evaluate risk and coverage.

Document exceptions next to the smallest possible scope. A disable comment should name the reason, not repeat the rule name. If the same exception appears repeatedly, revisit the configuration or encapsulate the pattern in a helper. That feedback loop keeps the configuration aligned with how QA engineers actually work.

A practical pull request quality policy

A team policy should specify commands, ownership, and exception handling in plain language. For example, changed JavaScript and TypeScript must pass the repository lint command with no warnings, all supported text files must pass the formatting check, and suppressions need a local explanation. The policy should also name which generated directories are excluded and who reviews changes to shared configuration. This prevents an engineer from weakening a rule just to merge one pull request.

Measure configuration value through the defects and review friction it removes. Useful observations include recurring missing awaits, stale disable comments, files that escape matching globs, and frequent rules that contributors cannot interpret. Do not turn lint counts into an individual performance metric. That encourages superficial fixes and discourages honest cleanup. Use trends to improve tooling, documentation, and code patterns.

Treat the config itself as tested infrastructure. Keep a tiny intentionally invalid fixture outside normal lint scope, or use a script in a temporary directory, to confirm critical rules activate after upgrades. At minimum, run --print-config on a JavaScript source file, a TypeScript test, and a config file during major changes. Check that ignores do not swallow source directories and that the runner plugin applies only where intended.

Finally, teach contributors how to diagnose failures. A formatting difference should lead to the write command. A lint finding should lead to the rule documentation and effective config. A parser error should lead to file scope, module mode, and TypeScript project checks. When the response is predictable, quality gates feel like engineering feedback rather than arbitrary pipeline obstacles.

Interview Questions and Answers

Q: What is the difference between ESLint and Prettier in a test project?

ESLint analyzes code patterns and can find likely correctness or maintainability defects. Prettier parses and reprints code into a consistent layout. I let Prettier own formatting and use ESLint for behavioral signal such as unused setup and unhandled promises.

Q: Why use flat config for test automation?

Flat config makes file matching, ignores, imported plugins, and override order explicit in JavaScript. I can apply Node settings to config files, TypeScript parsing to source, and runner-specific rules only to tests. I verify surprising cases with eslint --print-config.

Q: Which lint rule prevents the most async test defects?

For TypeScript suites, @typescript-eslint/no-floating-promises is especially valuable because a missing await can let a test complete early. I pair it with framework rules that understand async assertions. It requires type-aware configuration, so I measure its cost in large repositories.

Q: Should ESLint run Prettier as a lint rule?

Usually I run them independently. Separate checks give clearer ownership, reduce duplicate diagnostics, and let each tool use its native cache and output. If a team integrates them, it should still disable conflicting formatting rules and understand the extra lint workload.

Q: How do you introduce strict linting to a legacy suite?

I establish ignores, separate the formatting change, enable high-signal recommended rules, and fix correctness findings first. Temporary warnings or scoped overrides need owners and an exit plan. I avoid repository-wide disable comments because they hide new defects.

Q: Is a lint-clean suite necessarily reliable?

No. Static analysis cannot prove business coverage, data independence, selector quality, or correct environment behavior. It is one quality gate that prevents known code patterns, while reviews and executions provide different evidence.

Q: How do you handle a false positive in test code?

I confirm the rule and effective config, then make the code's intent explicit if possible. If an exception is justified, I suppress the smallest statement with a reason. Repeated exceptions indicate that the rule policy or test design needs adjustment.

Common Mistakes

  • Installing ESLint globally, which makes results depend on each machine.
  • Combining formatting and correctness rules until fixes conflict or diagnostics duplicate.
  • Applying browser or test globals to every file, which hides accidental undeclared names.
  • Enabling type-aware presets without including tests in the TypeScript project.
  • Ignoring every legacy test directory instead of planning incremental coverage.
  • Running prettier --write in CI and allowing an uncommitted workspace to pass.
  • Treating warnings as harmless forever, with no baseline or removal plan.
  • Accepting automated fixes without running impacted tests or reviewing the diff.
  • Assuming a green lint check means assertions and test coverage are adequate.

Conclusion

A reliable ESLint and Prettier for tests qa setup gives each tool a narrow responsibility: ESLint protects code quality, Prettier stabilizes layout, TypeScript checks types, and the runner validates behavior. Use flat config to express test boundaries, prioritize async and unused-code rules, and enforce both checks from locked local dependencies.

Start with one representative test directory, run eslint --print-config on a real file, and add eslint . plus prettier . --check to CI. Once the baseline is clean, every new warning becomes useful feedback instead of background noise.

Interview Questions and Answers

How do ESLint and Prettier differ in a QA automation repository?

ESLint analyzes code and flags configured correctness or maintainability patterns. Prettier prints code in a deterministic format. I keep formatting with Prettier and reserve ESLint for high-signal concerns such as floating promises, unused setup, and framework misuse.

Why would you use ESLint flat config?

Flat config is ESLint's current configuration model and makes ordering and file scopes explicit. Plugins are imported as JavaScript objects, and overrides can target tests without leaking globals into production code. I confirm the merged result with `eslint --print-config` for representative files.

How do you prevent missing await defects in TypeScript tests?

I enable type-aware typescript-eslint rules such as `no-floating-promises` and framework-specific async assertion rules. I also require explicit async functions and review promise-returning helpers. The relevant TypeScript project must include the tests for type-aware linting to work.

Would you integrate Prettier into ESLint?

My default is separate commands because ownership and failure output remain clear. Prettier checks formatting, while ESLint checks code quality. If an existing project combines them, I ensure conflicting stylistic rules are disabled and evaluate the performance cost.

How would you roll out linting in a legacy automation suite?

I first exclude generated files and capture reproducible dependency versions. I commit repository-wide formatting separately, then enable high-signal lint rules and fix behavior-related findings. Any temporary override is scoped, documented, and tracked toward removal.

What should happen when a lint rule conflicts with a valid test pattern?

I verify the effective configuration and try to express intent more clearly in code. If the pattern is valid, I use the narrowest suppression with a reason or adjust the test-file override. A repeated exception prompts a policy review instead of repeated inline disables.

Does passing lint prove test quality?

No. Linting proves conformance to static rules, not business coverage or runtime reliability. I combine it with type checking, focused reviews, deterministic test data, and actual execution in appropriate environments.

Frequently Asked Questions

Do I need both ESLint and Prettier for test automation?

Yes, when you want both static code-quality checks and consistent formatting. ESLint can catch suspicious test code, while Prettier controls layout. Running them separately keeps failures easy to understand.

What ESLint config format should a new test project use in 2026?

Use ESLint flat config in `eslint.config.js`, `.mjs`, or `.cjs`, based on the repository module mode. It supports ordered file-specific configuration and direct plugin imports. Use `--print-config` to verify the result for a real test file.

Should Prettier format generated snapshots and test reports?

Usually no for generated reports, and only if the snapshot workflow explicitly supports it. Exclude tool-owned artifacts to avoid noisy diffs. Continue reviewing snapshot content as test evidence.

Which ESLint rules are most useful for async tests?

Type-aware `@typescript-eslint/no-floating-promises` and `await-thenable` provide strong general signal. Add a maintained runner plugin for framework-specific rules such as awaited assertions. Validate rule behavior on representative tests before enforcing it.

Should CI fix lint and formatting problems automatically?

CI should normally check and fail, not edit its workspace. Developers can run `eslint --fix` and `prettier --write` locally, inspect the diff, and commit it. This keeps the branch content identical to the reviewed content.

How can I make ESLint faster in a large test repository?

Scope type-aware linting to TypeScript packages that need it, ignore generated output, and use the ESLint cache where appropriate. Keep correctness coverage intact and measure before weakening rules. Separate fast local feedback from the authoritative full CI scan.

Can I use ESLint with Playwright or Jest globals?

Yes. Prefer explicit imports, or use the maintained framework plugin and apply its environment only to matching test files. Avoid enabling broad globals for source and configuration files because that can hide naming errors.

Related Guides