# Install — tested.dev docs

Node 24+. Local `tested diff` and `tested check` need no account. Same gate in CI without a token.

No account for the gate. Local: `tested diff` / `tested check`. CI: the Action with `push` default `false`. Token + `push: true` is optional. App: [tested-dev-app](https://github.com/apps/tested-dev-app).

## Before you start

- Node 24 or newer.
- Tests that write Istanbul/V8 `coverage/coverage-final.json` (Vitest/Jest default).
- CI can skip a local CLI install and use the Action.

## Local, no account

Add the CLI as a dev dependency. After install, `td` is the same binary as `tested`. One-off: `npx @tested/cli`.

```
pnpm add -D @tested/cli
```

```
tested setup
# writes .tested.yaml if missing
# runs doctor (Node 24+, git, config, coverage path, origin)
# prints CI snippet

pnpm test -- --coverage
# or: tested run   # writes coverage even if tests fail

tested diff              # report. exit 0. no account
tested check             # gate. exit 1 under thresholds. no account
```

`tested run` writes `coverage/coverage-final.json` even if tests fail.

## CI gate, no account

`push` defaults to `false`. No token. Pin `uses:` to a SHA in production. `fetch-depth: 0` is optional. The Action fetches the PR base. Full inputs on [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 URL (optional)

1. Install the GitHub App. Install [tested-dev-app](https://github.com/apps/tested-dev-app) on the org or user that owns the repo. Sign in at [app.tested.dev/sign-in](https://app.tested.dev/sign-in), then open the repo once in the dashboard so membership syncs. If `/repos` shows 0 repos after install, refresh once and confirm the App was installed on the right account.
2. Mint a token. Open the repo in the app, then Settings. Direct path: `https://app.tested.dev/repos/{owner}/{name}/settings`. Generate a token. Settings shows the 64-character hex once. Never commit it.

```
# local (preferred)
export TESTED_TOKEN=…             # paste from Settings (shown once)
# or file (chmod 600):
# export TESTED_TOKEN_FILE=$HOME/.config/tested/token
# optional: TESTED_API_URL=https://app.tested.dev
```

In GitHub Actions, add repository secret `TESTED_TOKEN`. `tested push --pr N` needs this token.

3. Push a share URL. Same workflow as the gate, plus `push: true` and the token.

```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 }}
```

Open a PR. You should get a share URL in the job log. Sticky comments and App-posted required checks need Hobby or Team.

Optional test analytics (flakes / slowest): emit JUnit from the runner, then pass `junit: junit.xml` on the Action (or drop `junit.xml` in the working directory). Share + sticky show duration, failures, flakes, and top slow tests.

## Require the check

After the Action has run once on a PR: repo Settings → Branches → protection rule or ruleset on the default branch → require status checks → add the job name (`coverage` in the snippet above).

App-posted required checks and sticky comments need Hobby or Team. The Action job can be required in branch protection on any plan.

## Fork PRs

The gate (`push` default `false`) works on fork PRs. `push: true` fails without secrets. GitHub does not pass repository secrets to workflows from forks.

## Optional: mainline chart

The 30-day project chart is **default-branch only**. PR uploads do not move it. On push to main:

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

Or locally: `tested push --mainline` (no PR number).

## You're done when

- `tested doctor` has no FAIL
- `tested check` runs locally or the Action job posts a conclusion
- Optional: a PR produces `https://app.tested.dev/share/…`

Next: [MCP for agents](https://tested.dev/docs/mcp) · [Troubleshooting](https://tested.dev/docs/troubleshooting)
