# Migrate from Codecov — tested.dev docs

Keep generating coverage in CI. Put tested.dev on the PR. Require the job. Then Codecov comes out.

## 1. What you already have

- Node 24+.
- Tests that write coverage. Vitest/Jest default is Istanbul/V8 `coverage/coverage-final.json`. The CLI also reads lcov, Cobertura, JaCoCo, gcov text, and SimpleCov. Set `coverage.path` if it isn't the default file.

Keep the test step. Full path: [Install](https://tested.dev/docs/install). CI: [GitHub Action](https://tested.dev/docs/github-action).

## 2. Install

```
pnpm add -D @tested/cli
tested setup
# writes .tested.yaml
# runs doctor (Node 24+, git, config, coverage path, origin)
```

Local `tested diff` / `tested check` need no account, no token. Same for the Action with `push` default `false`.

## 3. Thresholds

Floors live in `.tested.yaml`. The Action has no threshold inputs. Fields: [Config](https://tested.dev/docs/config).

```yaml
# .tested.yaml
base: origin/main
testRunner: pnpm test -- --coverage
thresholds:
  patch: 80   # new executable lines
  project: 60 # overall floor
  paths:
    - glob: src/api/**
      patch: 90
      project: 90
    - glob: src/cli/**
      patch: 70
      project: 70
```

Optional `thresholds.paths`: independent floors per glob. Omitted `patch` / `project` inherit the global floors. A glob with no files this run is skipped (not 0%). Not flags. `tested check` fails when any matched path is under its floor. Coverage stays the only PR gate.

If `codecov.yml` has flags, put them here. `tested check` and `tested push` grade each package from this run.

```yaml
flags:
  frontend:
    paths: ["apps/web/**", "packages/ui/**"]
    thresholds:
      patch: 90
  backend:
    paths: ["apps/api/**"]
```

A job already scoped to one package: Action `flag: frontend`.

## 4. GitHub Action

Gate: no account, no token. Pin `uses:` to a SHA in production. Same snippet as [GitHub Action](https://tested.dev/docs/github-action).

```yaml
# .github/workflows/tested.yml
name: tested
on:
  pull_request:
jobs:
  coverage:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 24
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test -- --coverage
      - uses: tested-hq/cli/action@main   # pin uses: to a SHA in production
        with:
          version: 0.1.10
```

Share / push: install [tested-dev-app](https://github.com/apps/tested-dev-app), mint `TESTED_TOKEN`. The project page is `/repos/{owner}/{name}` — 30-day mainline coverage and the PRs. `push: true` on the PR. `mainline: true` on default branch. Inputs on [GitHub Action](https://tested.dev/docs/github-action).

```yaml
- uses: tested-hq/cli/action@main   # pin uses: to a SHA in production
  with:
    version: 0.1.10
    push: 'true'
    pr-number: ${{ github.event.pull_request.number }}
    token: ${{ secrets.TESTED_TOKEN }}
```

If CI writes more than one coverage file, list them in `coverage.path` or Action `files`. The CLI merges them and checks once.

## 5. Require, then remove Codecov

### Require the job

Open a PR so `coverage` runs once. Repo Settings → Branches → require status checks → add `coverage`.

### Delete Codecov

- Codecov Action step
- `codecov.yml`
- `CODECOV_TOKEN`
- Required `codecov/*` checks
- Codecov badge last

tested.dev badge:

```
[![tested](https://app.tested.dev/badge/{owner}/{name})](https://app.tested.dev/repos/{owner}/{name})
```

Done when `tested doctor` is clean and that job is the required check.

## 6. Verify

```
tested doctor
# No FAIL. WARN is fine if you know why.
```

The `coverage` job posts a conclusion. With `push: true`, the log has a share URL. If it fails: [Troubleshooting](https://tested.dev/docs/troubleshooting).
