Documentation structure for LLMs (llms.txt)

Set up test impact analysis Beta

Cloud

Smarter Testing is available in beta. This means the product is in early stages and you may encounter bugs, unexpected behavior, or incomplete features. When the feature is made generally available, there will be a cost associated with access and usage.

Refer to our Discuss post for more information about our beta launch.

Test impact analysis speeds up CI by running only the tests affected by your code changes. CircleCI tracks which tests exercise which source files, then skips tests that cover unchanged source files.

Is my project a good fit for test impact analysis?

This section outlines several project features that indicate your project is a good candidate for test impact analysis:

Built-in coverage support

Test impact analysis relies on code coverage data to determine which tests affect which files. Frameworks with built-in coverage support - Jest, pytest, Go test, Vitest and RSpec - make this straightforward.

If your framework has no native coverage support, generating the data test impact analysis needs may require significant rework of your test setup.

Tests run in the same process

Coverage data works best when tests directly import and run the source code in the same process.

When tests call code running in separate containers or services, collecting and consolidating coverage data across those service boundaries is not straightforward.

How it works

Test impact analysis is split into two phases:

  • Analysis runs on your default branch with coverage instrumentation, producing impact data. Impact data describes the relationship between files and tests.

  • Selection runs on feature branches, using the impact data to choose which tests to run based on what changed.

Analysis and selection can be configured to run on any change through CLI flags.

Analysis on default branches

The analysis phase is run on your default branch (or the base branch of a pull request). It runs tests with coverage instrumentation and updates the impact data with the latest test-to-file relationships.

Keeping impact data fresh on the default branch allows the selection phase to select fewer tests without other unrelated changes interfering.

For each file found during coverage analysis, a fast non-crypto hash of its contents is stored in the impact data. The data also tracks which files impact the tests being selected.

Example impact data relationship between a handler and repository.
{
  "version": 1,
  "files": {
    "1": {
      "path": "src/api/handlers.ts",
      "hash": "c9684be83632a628"
    },
    "2": {
      "path": "src/api/handlers.test.ts",
      "hash": "5b8e1a04c7d2f391"
    },
    "3": {
      "path": "src/data/repository.ts",
      "hash": "2e7c4d18a9f6b052"
    },
    "4": {
      "path": "src/data/repository.test.ts",
      "hash": "f1d39e62a08c5b74"
    }
  },
  "edges": {
    "src/api/handlers.test.ts": ["1", "2", "3"],
    "src/data/repository.test.ts": ["3", "4"]
  }
}
  • files records every file that analysis has seen, along with the hash of its contents at the time it was last analyzed.

  • edges records, for each test, the IDs of the files that are covered by the test.

The analysis phase typically runs slower than a normal test run because it executes tests with coverage instrumentation. However, this cost pays for itself by enabling the selection phase to skip unaffected tests on subsequent runs.

Selection on feature branches

By default, all tests run on default branches. Test selection runs only affected tests on feature branches, using the impact data produced by analysis on the default branch.

During test selection, the current state of the branch’s checked-out code is compared against the latest impact data. Tests are selected in the following scenarios:

  • The test is not found in the impact data. This indicates a new test in the checked-out code.

  • The test failed on the previous run for this branch.

  • A file affecting the test is not found in the impact data. This indicates the file was removed from the checked-out code.

  • A file affecting the test hash has changed.

flowchart TD Start([Discovered Test Atoms\nFor each test]) Start --> New{New test?} New -- Yes --> Selected New -- No --> Failed{Previously failed?} Failed -- Yes --> Selected Failed -- No --> Files{Covered files\nchanged or removed?} Files -- Yes --> Selected Files -- No --> Skipped Selected([Selected]) Skipped([Skipped])

The selection rules can be extended to select tests beyond those identified in the impact data.

Prerequisites

Before enabling test impact analysis, ensure you have completed the Getting Started With Smarter Testing guide and have:

  • Installed the testsuite CLI plugin.

  • Configured your .circleci/test-suites.yml with discover and run commands.

  • Verified your tests run successfully with the testsuite command.

1. Enable test impact analysis in your test-suites.yml file

Update your test suite configuration to include an analysis command and the test-impact-analysis option. Keep your existing discover and run commands.

Choose the starter configuration for your test runner and add the analysis line to your .circleci/test-suites.yml.

  • Vitest

  • Jest

  • Mocha

  • pytest

  • Go with gotestsum

  • RSpec

  • Other

Follow the instructions to add the Vitest CircleCI Coverage plugin as a dev dependency.

Add the analysis command to your .circleci/test-suites.yml.

---
name: ci tests
# ...
analysis: CIRCLECI_COVERAGE=<< outputs.circleci-coverage >> vitest run --silent --bail 0 << test.atoms >>
options:
  test-impact-analysis: true

Follow the instructions to add the Jest CircleCI Coverage plugin as a dev dependency. The plugin is compatible with Jest 28 through 30. It requires jest, jest-environment-node, and jest-environment-jsdom as peer dependencies.

Add the analysis command to your .circleci/test-suites.yml.

---
name: ci tests
# ...
analysis: CIRCLECI_COVERAGE=<< outputs.circleci-coverage >> jest --runInBand --silent --bail << test.atoms >>
options:
  test-impact-analysis: true

Follow the instructions to add the Mocha CircleCI Coverage plugin as a dev dependency.

Add the analysis command to your .circleci/test-suites.yml.

---
name: ci tests
# ...
analysis: CIRCLECI_COVERAGE="<< outputs.circleci-coverage >>" mocha << test.atoms >>
options:
  test-impact-analysis: true

Follow the instructions to add the pytest CircleCI Coverage plugin as a dev dependency.

Add the analysis command to your .circleci/test-suites.yml.

---
name: ci tests
# ...
analysis: pytest --disable-pytest-warnings --no-header --quiet --tb=short --cov=myproj --cov-context=test --circleci-coverage=<< outputs.circleci-coverage >> << test.atoms >>
options:
  test-impact-analysis: true
---
name: ci tests
# ...
file-mapper: go list -json="Dir,ImportPath,TestGoFiles,XTestGoFiles" ./... > << outputs.go-list-json >>
analysis: go tool gotestsum -- -coverprofile="<< outputs.go-coverage >>" -cover -coverpkg ./... << test.atoms >>
options:
  test-impact-analysis: true

Follow the instructions to add the RSpec CircleCI Coverage plugin as a dev dependency.

Add the analysis command to your .circleci/test-suites.yml.

---
name: ci tests
# ...
analysis: CIRCLECI_COVERAGE="<< outputs.circleci-coverage >>" bundle exec rspec << test.atoms >>
options:
  test-impact-analysis: true

Smarter Testing is test runner agnostic. Replace the analysis example below with your test runner. The next step validates that the commands are set up correctly.

---
name: ci tests
# ...
analysis: my-test-runner --coverage=<< outputs.coverage >> --run << test.atoms >>
options:
  test-impact-analysis: true

Configure the test suite

Code coverage cannot detect every relationship between source files and test atoms. The following options let you extend test selection for edge cases.

For the full list of available options, see the Options section in the test suite configuration reference.

Full test run paths

Some project files affect the running system without being directly covered by tests. Examples include dependency manifests, database migration files, or CI configuration. Use full-test-run-paths to list files that cause all test atoms to be selected and run.

Example test suite with full test run paths configured
# .circleci/test-suites.yml
---
name: ci tests
# ...
options:
  test-impact-analysis: true
  full-test-run-paths:
    - package.json
    - go.mod
    - .circleci/*.yml
    - database-migrations/**/*.sql

Test selection rules

Use test-selection-rules to extend test selection to cover non-source files, or to always run specific test atoms. For example, run integration tests when database migrations change, or always run acceptance tests regardless of which files changed.

Example test suite with test selection rules
# .circleci/test-suites.yml
---
name: ci tests
# ...
options:
  test-impact-analysis: true
  test-selection-rules:
    - test-atom: db/integration_test.ts
      include: database-migrations/**/*.sql
    - test-atom: acceptance/test.ts
      include: true

2. Run locally

Use --doctor locally to validate the test-suites.yml is set up correctly. The CLI runs additional analysis checks when test impact analysis is enabled. If any results look incorrect, an action item is provided to resolve it.

$ circleci run testsuite "ci tests" --doctor

Follow the steps until all checks pass.

Run the circleci run testsuite CLI tooling from the directory where your tests are located. This is typically your repository root, but for monorepos it can be the root of a subpackage (for example, cd service-1 && circleci run testsuite "ci tests").

The testsuite will automatically find the .circleci/test-suites.yml configuration file by walking up the directory tree. All commands (discover, run, analysis) execute relative to where you run the CLI, so avoid using cd or --directory flags within your commands.

Once all checks pass, build the impact data locally. The following command runs coverage analysis on impacted test atoms without executing your test suite:

$ circleci run testsuite "ci tests" --verbose --local --select-tests=none --analyze-tests=impacted

This command:

  • --local — stores impact data in the .circleci/ directory instead of fetching from the CircleCI API.

  • --select-tests=none — skips running your test suite (no test results are produced).

  • --analyze-tests=impacted — runs coverage analysis on test atoms whose impact data is outdated.

Inspect the locally stored impact data in .circleci/impact-ci tests.json to see the mapping between test atoms and files.

Next, modify a source file whose ID appears in the edges, then run test selection to verify only impacted test atoms are selected:

$ circleci run testsuite "ci tests" --verbose --local --select-tests=impacted

This command uses the existing impact data to select only the test atoms that cover the file you modified. Look for the "Selecting tests…​" section in the output to confirm the correct tests are selected:

==> Selecting tests...
--> Selecting 'test-atom-one' due to modified file: 'src/foo.ts'
==> - 0 new test atoms
==> - 0 test atoms impacted by new files
==> - 1 test atoms impacted by modified files
==> - 0 test atoms impacted by removed files
==> - 0 test atoms failed previously
==> - 0 test atoms with no source file mappings in impact data
==> - 0 test atoms impacted by include rule
==> - 0 test atoms impacted by full test run paths
==> Selected 1 test atoms, Skipped 123 test atoms in 168ms

3. Run in CI

When adding the testsuite command to your CircleCI jobs, there is no need to install the testsuite plugin as it is already available in CircleCI Docker containers.

For most projects, no extra flags are needed — the defaults handle analysis and selection automatically. You only need the --select-tests and --analyze-tests flags if you want to verify analysis on your feature branch before merging, or if you need a non-default setup (see Common Setup Examples).

To verify analysis on your feature branch, temporarily override the defaults so the job only runs analysis without executing tests:

Example: run analysis on a feature branch without executing tests
version: 2.1
jobs:
  test:
    executor: node-with-service
    steps:
      - setup
      - run: circleci run testsuite "ci tests" --select-tests=none --analyze-tests=impacted
      - store_test_results:
          # This directory must match the directory of `outputs.junit` in your
          # test-suites.yml
          path: test-reports

This configuration skips running tests (--select-tests=none) and only runs coverage analysis (--analyze-tests=impacted), which lets you verify that analysis works before merging.

Analysis must run on your default branch to keep impact data current. Once you have verified analysis on a feature branch, remove the flags to use the defaults, or see the Common Setup Examples section to dynamically change flags.

Commit both .circleci/test-suites.yml and .circleci/config.yml to your feature branch and push to your VCS.

Depending on the test runner, the first run of analysis can take a long time because every test atom needs to run analysis. Consider temporarily increasing the job parallelism on the feature branch.

Once analysis has completed on the feature branch, restore the .circleci/config.yml back to its original state without the CLI flags and parallelism changes.

Example: default CircleCI test job configuration with the testsuite command
version: 2.1
jobs:
  test:
    executor: node-with-service
    steps:
      - setup
      - run: circleci run testsuite "ci tests"
      - store_test_results:
          # This directory must match the directory of `outputs.junit` in your
          # test-suites.yml
          path: test-reports

Commit the restored .circleci/config.yml to your feature branch and push to your VCS. Follow your usual process to merge to your default branch.

Do not check in the local impact JSON files generated by the local CLI (for example, .circleci/ci tests-impact.json) to your VCS.

Verify in CI

After analysis completes on your feature branch, verify test impact analysis is working correctly:

  1. In the CircleCI web app, navigate to your pipeline and open the test job.

  2. Check that the job was successful and analyzed all test atoms. A successful analysis run outputs "Found n files impacting tests" for each test atom.

  3. Modify a source file that appears in the impact data, then push. Only the test atoms that cover the modified file are selected to run. Look for the "Selecting tests…​" output in the job to confirm the correct test atoms are selected and the reason for selection.

Test impact analysis is now set up for your test suite. Feature branches run the test atoms impacted by code changes, and your default branch runs all tests while also updating impact data.

If these defaults do not suit your project, see the Common Setup Examples section for alternative configurations.

Next steps

Common setup examples

The --analyze-tests and --select-tests flags give you fine-grained control over how the testsuite command behaves. Most projects do not need to set these flags — the defaults work automatically.

Flag reference

--analyze-tests controls whether to build or update impact data by running tests with coverage instrumentation. --select-tests controls whether to choose and run tests based on existing impact data.

Each flag accepts three values:

Value Meaning for --analyze-tests Meaning for --select-tests

impacted

Analyze only test atoms whose impact data needs updating — either the test changed, or the files it covers changed.

Select and run only the test atoms impacted by a change.

none

Skip analysis entirely.

Skip running tests entirely.

all

Analyze ALL discovered test atoms. Rarely needed — only use to rebuild impact data from scratch.

Select and run all discovered test atoms (full test suite).

When you do not pass these flags, the defaults depend on which branch is running:

Branch --analyze-tests default --select-tests default

Default branch (for example, main)

impacted

all

Feature branches

none

impacted

By default, feature branches run only impacted tests, and the default branch runs all tests while also updating impact data.

Analyze impacted tests and run all tests on your default branch

No changes are required — the default behavior already handles both.

Analyze impacted tests as a non-blocking job in the same workflow

This approach runs analysis concurrently with the rest of your workflow jobs. It can reduce overall workflow time if analyzing tests takes longer than running tests.

CircleCI configuration with a separate analysis job
# .circleci/config.yml
version: 2.1
jobs:
  test:
    executor: my-executor
    parallelism: 4
    steps:
      - setup
      # Disable analysis, run all tests on default branches, impacted tests on feature branches.
      - run: circleci run testsuite "ci tests" --analyze-tests="none"
      - store_test_results:
          path: test-reports

  analysis:
    executor: my-executor
    steps:
      - setup
      # Disable running tests, analyze impacted tests.
      - run: circleci run testsuite "ci tests" --select-tests="none" --analyze-tests="impacted"

  deploy:
    executor: my-executor
    steps:
      - setup
      - run: ./deploy.sh

workflows:
  build-and-deploy:
    jobs:
      - test
      # Only analyze tests on main.
      - analysis:
          filters pipeline.git.branch == "main"
      - deploy:
          requires:
            - test
          filters: pipeline.git.branch == "main"

Analyze impacted tests in a separate workflow in the same pipeline

This approach runs analysis concurrently with your main workflow, which is useful if you need to avoid any additional latency on your main workflow.

Only use this approach if analyzing impacted tests in a non-blocking job is not sufficient.

CircleCI configuration with separate workflows
# .circleci/config.yml
version: 2.1
jobs:
  test:
    executor: my-executor
    parallelism: 4
    steps:
      - setup
      # Disable analysis.
      # (Default) Run all tests on default branches, run impacted tests on feature branches.
      - run: circleci run testsuite "ci tests" --analyze-tests="none"
      - store_test_results:
          path: test-reports

  analysis:
    executor: my-executor
    steps:
      - setup
      # Disable running tests.
      # Analyze impacted tests.
      - run: circleci run testsuite "ci tests" --select-tests="none" --analyze-tests="impacted"

  deploy:
    executor: my-executor
    steps:
      - setup
      - run: ./deploy.sh

workflows:
  build-and-deploy:
    jobs:
      - test
      - deploy:
          requires:
            - test
          filters: pipeline.git.branch == "main"

  analysis-workflow:
    jobs:
      # Only analyze tests on main.
      - analysis:
          filters: pipeline.git.branch == "main"

Analyze impacted tests on a non-default branch and run tests on all other branches

This approach is useful if you use a non-default branch as the base of development, for example in the "git flow" development model.

CircleCI configuration for running analysis on a branch named develop and selection on all other branches
# .circleci/config.yml
version: 2.1
jobs:
  test:
    executor: node-with-service
    parallelism: 4
    steps:
      - setup
      # Analyze impacted tests on "develop" branch, otherwise disable.
      # (Default) Run all tests on default branches, run impacted tests on feature branches.
      - run: circleci run testsuite "ci tests" --analyze-tests=<< pipeline.git.branch == "develop" and "impacted" or "none" >>
      - store_test_results:
          path: test-reports