## TL;DR
The toBeInTheDocument matcher is registered by @testing-library/jest-dom, which loads through a setup file wired into the jest config. When CI uses a different jest config than local runs, a common monorepo accident, the setup file never loads and every jest-dom matcher is undefined. Find the exact config CI invokes, verify its setupFilesAfterEnv entry points at a file that imports jest-dom, and rerun a single test file to confirm.

## The query
```text
jest TypeError: expect(...).toBeInTheDocument is not a function  -  setup file missing in CI
```

## Use this when
- expect(...).toBeInTheDocument is not a function fails only in CI
- other jest-dom matchers (toHaveTextContent, toBeDisabled) fail the same way
- the repo is a monorepo with more than one jest config

## Not for
- the matchers fail locally too (the package is not installed, or the setup file is missing everywhere)
- TypeScript complains about the matcher types but jest runs fine (a types problem, add the jest-dom types)
- the error is expect is not defined (the test environment itself is wrong)

## Steps

### 1. Confirm which jest config the CI job actually uses
Read the test script in package.json and any --config flag. In monorepos, the CI job may run from a package directory with its own config that shadows the root one.
```bash
grep -A5 '"test"' package.json
ls jest.config.* packages/*/jest.config.* 2>/dev/null
```
Expected output: you know the exact config file CI loads, and whether a second config shadows it.

### 2. Check the setupFilesAfterEnv entry and the path it points to
Open that config and confirm the setup key exists and the file path resolves from the config's directory.
```js
// jest.config.js
module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['[rootDir]/jest.setup.js'],
};
```
Expected output: the entry is present and the path matches the real file location relative to the config.

### 3. Verify the setup file actually imports jest-dom
A setup file that exists but never imports the matchers produces exactly this error. One line is all it needs.
```js
// jest.setup.js
import '@testing-library/jest-dom';
```
Expected output: the import is present; a missing import is fixed by adding it.

### 4. Rerun one failing test file in the CI environment
Run a single spec with the CI config to confirm the matchers register before running the whole suite.
```bash
npx jest src/components/Button.test.js --ci
```
Expected output: toBeInTheDocument resolves and the spec passes.

## Tool compatibility
- jest 26 - 30
- @testing-library/jest-dom 5 - 6
- jsdom test environment

## Variant phrasings
- toBeInTheDocument is not a function in CI
- TypeError: expect(...).toHaveTextContent is not a function
- jest-dom matchers undefined on CI but work locally

## Why it happens
Jest only knows the custom matchers if the setup file runs, and it only runs the setup file listed in the config it loads. Monorepos and CI-specific configs (a --config flag, a per-package config, or config shadowing) break this chain silently: jest starts, the setup file is skipped, and the first jest-dom assertion throws. Local runs use the root config where everything is wired, so nobody notices until CI.

## Edge cases / pitfalls
- setupFiles runs before the test framework is installed, setupFilesAfterEnv runs after: matchers must go in setupFilesAfterEnv
- with pnpm, @testing-library/jest-dom must be a declared dependency of the package whose config references it, or the import fails in CI's strict node_modules layout
- an ESM setup file with a CJS jest config (or vice versa) fails to load; keep the module systems consistent
- create-react-app and next/jest manage this wiring for you; ejecting or overriding their config can drop the setup file

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst__lVLjo61EU6IKGnOXN3PWw
