> This is part 1 of 2 of the full documentation (pages 1–100 of 123).
> The content is paginated: fetch every part to see all of it.
> Next part: https://argos-ci.com/docs/llms-full.txt/1
> Page index: https://argos-ci.com/docs/llms.txt

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
This site has an agentic ask interface you may use to query the documentation dynamically by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://argos-ci.com/docs/index.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

---

# Overview

Argos is an open-source visual testing platform: your tests capture screenshots, Argos compares them against a baseline, and you review the changes on every pull request.

Argos is a visual testing platform that catches visual regressions before they reach production. Your tests capture screenshots, Argos compares them against a baseline, and you or your AI agents review the changes on your pull requests.

It works with the tools you already use — Playwright, Storybook, Cypress, Vitest, or any pipeline that produces screenshots. Argos also [compares non-image files](/docs/learn/how-to-guides/visual-coverage/compare-non-image-files), [detects flaky tests](/docs/learn/reliability-and-flakiness/flaky-test-detection), and [deploys a preview](/docs/learn/deployments) of your Storybook or static site on every pull request.

### How it works

<figure><img src="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2FBK8MibaEs0q56xCMRYvf%2Fargos-schema-web.png?alt=media&amp;token=19df2ad1-fa07-4e73-b205-4948d08ae2c9" alt="Diagram of the Argos workflow, from capturing screenshots in CI to approving changes on the pull request"><figcaption></figcaption></figure>

1. **Your tests capture** screenshots in CI.
2. **Your CI uploads** the screenshots to Argos.
3. **Argos compares** them against the [baseline build](/docs/learn/platform-fundamentals/baseline-build).
4. **You review** visual diffs on the pull request.
5. **You approve** expected changes or reject regressions.
6. **Argos updates** the pull request check, so you can merge with confidence.

### When to use Argos

Use Argos when visual changes affect product quality or release confidence:

* **UI regressions**: Catch broken layouts, CSS changes, and missing images, icons, or fonts.
* **Design systems**: Track component changes across themes, browsers, and viewports.
* **Product variants**: Validate white-labeled interfaces, translations, and localized layouts.
* **AI-generated UI**: Visually validate generated changes before merging them.

### Open source and self-hosting

Argos is open source: the whole platform — backend, frontend, and the screenshot comparison engine — is developed in the open in the MIT-licensed [argos-ci/argos](https://github.com/argos-ci/argos) repository. There is no closed-source component behind the diff service.

Argos is operated as a managed cloud service, and that is the supported way to run it. Self-hosting is not officially supported or documented: the production setup targets AWS and depends on PostgreSQL, RabbitMQ, Redis, S3, and DynamoDB, plus integrations such as the GitHub App and Stripe — you would be on your own to deploy and operate them. If your organization has strict hosting or compliance requirements, [contact us](https://argos-ci.com/contact) about the Enterprise plan.

For where the cloud service stores your data, its SOC 2 and GDPR compliance, and data retention, see [Security and compliance](/docs/learn/account-and-access/security-and-compliance).

### Get started

Ready to add Argos to your project? Pick your framework in the [Quickstart](/docs/quickstart) — you'll upload your first build and see it on a pull request in a few minutes.

### Explore more

Once Argos is set up, go deeper:

* [Core concepts](/docs/learn/platform-fundamentals) – How Argos compares screenshots and picks a baseline
* [Review workflow](/docs/learn/review-workflow) – Review builds in the dashboard and on your pull requests
* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Keep your visual tests free of flakiness
* [Integrations](/docs/learn/integrations) – Connect GitHub, GitLab, and Slack


# Visual testing quickstart

Install Argos, upload your first screenshots, and review your first build.

By the end of this guide, you will have Argos connected to your CI, your first build uploaded, and a pull request check ready to review.

Pick the guide that matches your test setup. If your framework isn't listed, [any test framework](/docs/quickstart/any-test-framework) shows how to upload screenshots with the Argos CLI.

<table data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="image">Cover image</th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td>Playwright</td><td data-object-fit="contain"><a href="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2FP5PlIriAfdzqG7qkturc%2Fplaywright.png?alt=media&amp;token=ad96cfbe-8a63-4915-bd2b-fe886a5fe997">playwright.png</a></td><td><a href="/docs/quickstart/playwright-quickstart">Playwright</a></td></tr><tr><td>Vitest</td><td data-object-fit="contain"><a href="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-23bf94377fb1f7dc8a8e8501652119f5223b93c8%2Fvitest.png?alt=media">vitest.png</a></td><td><a href="/docs/quickstart/vitest-quickstart">Vitest</a></td></tr><tr><td>Storybook</td><td data-object-fit="contain"><a href="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2FQhtcOVuVunTMyKWPefz1%2Fstorybook.png?alt=media&amp;token=0a39c535-1d77-4031-962d-fc0e0be9375d">storybook.png</a></td><td><a href="/docs/quickstart/storybook-quickstart">Storybook</a></td></tr><tr><td>Cypress</td><td data-object-fit="contain"><a href="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2FEQBC3rvscuX9bd14suvL%2Fcypress.png?alt=media&amp;token=1737d5b7-36fa-4361-98f0-a10f2a72e175">cypress.png</a></td><td><a href="/docs/quickstart/cypress-quickstart">Cypress</a></td></tr><tr><td>WebdriverIO</td><td data-object-fit="contain"><a href="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2FxjWPiLIAAI2foORrP0jr%2Fwebdriverio.png?alt=media&amp;token=7e881efc-7bb6-4b00-b67c-3c8338998a9e">webdriverio.png</a></td><td><a href="/docs/quickstart/webdriverio-quickstart">WebdriverIO</a></td></tr><tr><td>Puppeteer</td><td data-object-fit="contain"><a href="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2F5sfTOS92vPh32WFS6aL5%2Fpuppeteer.png?alt=media&amp;token=4aeb25c5-c0f9-4fe5-9753-c56205417399">puppeteer.png</a></td><td><a href="/docs/quickstart/puppeteer-quickstart">Puppeteer</a></td></tr><tr><td>Any test framework</td><td data-object-fit="contain"><a href="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2F4sNp9ua4OSUWQq6G6A0J%2Fdots.png?alt=media&amp;token=496206a5-9247-46db-846c-bca71cace250">dots.png</a></td><td><a href="/docs/quickstart/any-test-framework">Any test framework</a></td></tr></tbody></table>


# Playwright visual testing quickstart

Set up visual testing in your Playwright tests with the Argos Playwright SDK.

To add visual testing to [Playwright](https://playwright.dev/), install `@argos-ci/playwright`, add the Argos reporter to `playwright.config.ts`, call `argosScreenshot(page, "name")` in your tests, and run `npx playwright test` in CI with the `ARGOS_TOKEN` environment variable set. Argos compares every screenshot with a [baseline build](/docs/learn/platform-fundamentals/baseline-build) picked from your Git history and reports the changes as a check on your pull request.

### What Argos adds over `toHaveScreenshot()`

Playwright's built-in `toHaveScreenshot()` already compares screenshots. Argos keeps your Playwright tests and changes what happens around them:

* **No baselines in your repository.** `toHaveScreenshot()` stores reference PNGs in `*-snapshots/` folders that you commit, with a separate image per platform. Argos picks the baseline from your Git history, so there are no images to commit or keep in sync.
* **Review on the pull request.** A `toHaveScreenshot()` mismatch fails the test until you re-run with `--update-snapshots` and commit new images. Argos turns visual changes into a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).
* **Fewer flaky diffs.** `argosScreenshot` waits for fonts, images, and `aria-busy` loaders before capturing, and Argos [flags unstable tests](/docs/learn/reliability-and-flakiness/flaky-test-detection) with a flaky badge and a stability score.

Already using `toHaveScreenshot()`? Follow [Migrate from Playwright toHaveScreenshot to Argos](/docs/learn/how-to-guides/migrate-to-argos/from-playwright-native-screenshots), or read the [Argos vs Playwright screenshots](https://argos-ci.com/compare/playwright) comparison.

### Prerequisites

* Node.js 22 or later
* [Playwright](https://playwright.dev/docs/intro#installing-playwright) set up in your project
* [Playwright running on your CI](https://playwright.dev/docs/ci-intro#on-pushpull_request)
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos Playwright SDK:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/playwright
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/playwright
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add the Argos reporter to your Playwright config

The Argos reporter uploads screenshots and traces to Argos as your tests run:

{% code title="playwright.config.ts" %}

```js
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  // ... other configuration

  // Reporter to use
  reporter: [
    // Use "dot" reporter on CI, "list" otherwise (Playwright default).
    process.env.CI ? ["dot"] : ["list"],
    // Add Argos reporter.
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        // Upload to Argos on CI only.
        uploadToArgos: !!process.env.CI,
      }),
    ],
  ],

  // Start your app before the tests run, locally and on CI.
  webServer: {
    command: "npm run start",
    url: "http://localhost:3000",
    reuseExistingServer: !process.env.CI,
  },

  // Setup recording option to enable test debugging features.
  use: {
    // Resolve relative URLs like page.goto("/") against your app.
    baseURL: "http://localhost:3000",

    // Collect trace when retrying the failed test.
    trace: "on-first-retry",

    // Capture screenshot after each test failure.
    screenshot: "only-on-failure",

    // Reduce text rendering differences between machines.
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },
});
```

{% endcode %}

`webServer` starts your app before the tests run, on your machine and on CI, so `page.goto("/")` reaches it. Replace `npm run start` with the command that serves your app, and the URL with the one it listens on.

With `trace` and `screenshot` enabled, Playwright records failure screenshots and traces — the reporter uploads them to Argos automatically, so you can debug failed tests visually.

{% hint style="success" %}
The `launchOptions` above disable subpixel text and font hinting, which reduces text rendering differences between your machine and CI. This single change prevents one of the most common causes of flaky screenshots — learn why in [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering).
{% endhint %}
{% endstep %}

{% step %}

### Capture screenshots

Use the `argosScreenshot` helper to capture stable screenshots in your tests:

{% code title="tests/example.spec.ts" %}

```js
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("screenshot homepage", async ({ page }) => {
  await page.goto("/");
  await argosScreenshot(page, "homepage");
});
```

{% endcode %}

Screenshots are written to the `./screenshots` directory by default. Add `./screenshots` to your `.gitignore` file to avoid committing them.

Tip: Check out our guides to [screenshot multiple pages](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls), [capture multiple viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports), or [capture user journeys](/docs/learn/how-to-guides/visual-coverage/capture-user-journeys).
{% endstep %}

{% step %}

### Set up CI

Run your Playwright tests in CI with `ARGOS_TOKEN` set. The Argos reporter uploads screenshots automatically when it detects a CI environment:

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret. On other CI providers, pass the token with the `ARGOS_TOKEN` environment variable or the reporter's `token` option. For GitLab CI, CircleCI, Buildkite, and other providers, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci).
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The Argos reporter uploads to Argos only from CI (`uploadToArgos: !!process.env.CI`), so Argos only compares screenshots captured on CI, and the `launchOptions` flags make text render the same way on every machine. See [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering) and [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [Playwright SDK reference](/docs/reference/playwright) – All options and helpers
* [Playwright example](https://github.com/argos-ci/argos-javascript/tree/main/examples/playwright) – A complete working setup
* [Playwright visual regression testing in CI](https://argos-ci.com/blog/playwright-visual-regression-testing-ci) – The complete guide, on the Argos blog

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Vitest browser mode visual testing quickstart

Set up visual testing in your Vitest browser tests with the Argos Vitest SDK.

To add visual testing to [Vitest](https://vitest.dev/), install `@argos-ci/vitest`, add `argosVitestPlugin()` to your Vitest config, call `argosScreenshot()` in a [browser mode](https://vitest.dev/guide/browser/) test, and run `npx vitest run` in CI with the `ARGOS_TOKEN` environment variable set. Argos compares every screenshot with a [baseline build](/docs/learn/platform-fundamentals/baseline-build) picked from your Git history and reports the changes as a check on your pull request.

The Argos Vitest SDK captures two kinds of files:

* **Screenshots** of your rendered components, captured from [Vitest browser tests](https://vitest.dev/guide/browser/).
* **Snapshots** of any value — objects, JSON, HTML, and more — captured from browser **or** plain Node tests. No browser required.

### What Argos adds over `toMatchScreenshot()`

Vitest's built-in `toMatchScreenshot()` already compares screenshots in browser mode. Argos keeps your Vitest tests and changes what happens around them:

* **No reference images in your repository.** `toMatchScreenshot()` stores references in `__screenshots__` folders that you commit, named per browser and platform (for example `button-chromium-darwin.png`), so references captured on macOS are not used on Linux CI. Argos picks the baseline from your Git history.
* **Review on the pull request.** A `toMatchScreenshot()` mismatch fails the test until you re-run with `--update`. Argos turns visual changes into a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).
* **Diff more than pixels.** `argosSnapshot()` compares any value — API responses, generated HTML, Markdown — from browser or Node tests.

Already using `toMatchScreenshot()`? Follow [Migrate from Vitest toMatchScreenshot to Argos](/docs/learn/how-to-guides/migrate-to-argos/from-vitest-native-screenshots).

{% hint style="info" %}
Using **Storybook**? Follow the [Storybook Quickstart](/docs/quickstart/storybook-quickstart) instead — it builds on this same Vitest integration.
{% endhint %}

### Prerequisites

* Node.js 22 or later
* [Vitest](https://vitest.dev/) set up in your project
* [Vitest running on your CI](https://vitest.dev/guide/cli)
* [A project created in Argos](https://app.argos-ci.com/new)

Capturing **screenshots** additionally requires [Vitest browser mode](https://vitest.dev/guide/browser/) with the [Playwright provider](https://vitest.dev/guide/browser/playwright). **Snapshots** run in any Vitest test and need neither.

{% stepper %}
{% step %}

### Install

Install the Argos Vitest SDK:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/vitest
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/vitest
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/vitest
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/vitest
```

{% endtab %}
{% endtabs %}

To capture **screenshots**, also install the [Vitest browser mode](https://vitest.dev/guide/browser/) peer dependencies, plus [`vitest-browser-react`](https://www.npmjs.com/package/vitest-browser-react) to render the React component in the example below (skip this if you only need snapshots). On Vue or Svelte, install [`vitest-browser-vue`](https://www.npmjs.com/package/vitest-browser-vue) or [`vitest-browser-svelte`](https://www.npmjs.com/package/vitest-browser-svelte) instead:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev vitest @vitest/browser @vitest/browser-playwright playwright vitest-browser-react
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev vitest @vitest/browser @vitest/browser-playwright playwright vitest-browser-react
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev vitest @vitest/browser @vitest/browser-playwright playwright vitest-browser-react
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev vitest @vitest/browser @vitest/browser-playwright playwright vitest-browser-react
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add the Argos plugin to your Vitest config

The Argos plugin registers the `argosScreenshot` browser command and, when `uploadToArgos` is enabled, uploads the captured screenshots to Argos at the end of the run:

{% code title="vitest.config.ts" %}

```ts
import { defineConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";
import { argosVitestPlugin } from "@argos-ci/vitest/plugin";

export default defineConfig({
  plugins: [
    argosVitestPlugin({
      // Upload to Argos on CI only.
      uploadToArgos: !!process.env.CI,
    }),
  ],
  test: {
    browser: {
      enabled: true,
      headless: true,
      provider: playwright({
        // Reduce text rendering differences between machines.
        launchOptions: {
          args: ["--disable-lcd-text", "--font-render-hinting=none"],
        },
      }),
      instances: [{ browser: "chromium" }],
    },
  },
});
```

{% endcode %}

{% hint style="success" %}
The `launchOptions` above disable subpixel text and font hinting, which reduces text rendering differences between your machine and CI. This single change prevents one of the most common causes of flaky screenshots — learn why in [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering).
{% endhint %}

{% hint style="info" %}
The `test.browser` block is only required for screenshots. If you only capture snapshots, you can omit it — the plugin still uploads your snapshots to Argos.
{% endhint %}
{% endstep %}

{% step %}

### Capture screenshots and snapshots

Use the `argosScreenshot` helper to capture a screenshot in a **browser** test:

{% code title="Button.test.tsx" %}

```tsx
import { test } from "vitest";
import { render } from "vitest-browser-react";
import { argosScreenshot } from "@argos-ci/vitest";
import { Button } from "./Button";

test("Button", async () => {
  render(<Button>Click me</Button>);
  await argosScreenshot("button");
});
```

{% endcode %}

{% hint style="info" %}
Unlike other Argos SDKs, `argosScreenshot` takes no `page` argument here — Vitest browser tests already run in the page context. The name is optional too: omit it and Argos derives one from the current test.
{% endhint %}

Use `argosSnapshot` to capture a snapshot of any value — it works in **browser and Node** tests, no browser required. The value comes first and the name is optional (omit it to auto-name from the current test, or pass `options.name`). Strings are written verbatim; any other value is serialized automatically:

{% code title="user.test.ts" %}

```ts
import { test } from "vitest";
import { argosSnapshot } from "@argos-ci/vitest";
import { fetchUser } from "./api";

test("API response", async () => {
  const user = await fetchUser();
  await argosSnapshot(user); // -> "src/user.test.ts > API response 1"
});
```

{% endcode %}

{% hint style="success" %}
`argosSnapshot` lets you visually diff **anything**, not just UI — API responses, generated HTML, config files, Markdown, and more. Pass the `extension` option (e.g. `.json`, `.html`, `.yml`) to control how Argos renders and diffs it. See the [SDK reference](/docs/reference/vitest#capturing-snapshots) for details.
{% endhint %}

Both are written to the `./snapshots` directory by default. Add `./snapshots` to your `.gitignore` file to avoid committing them.

Tip: Check out our guides to [capture multiple viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports), [add ARIA snapshots](/docs/learn/how-to-guides/visual-coverage/adding-aria-snapshots-manually), or [capture user journeys](/docs/learn/how-to-guides/visual-coverage/capture-user-journeys).
{% endstep %}

{% step %}

### Set up CI

Run your Vitest tests in CI with `ARGOS_TOKEN` set. The Argos plugin uploads screenshots automatically when `uploadToArgos` is enabled:

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx vitest run
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret. On other CI providers, pass the token with the `ARGOS_TOKEN` environment variable or the plugin's `token` option. For GitLab CI, CircleCI, Buildkite, and other providers, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci).
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The Argos plugin uploads to Argos only from CI (`uploadToArgos: !!process.env.CI`), so Argos only compares screenshots captured on CI, and the `launchOptions` flags make text render the same way on every machine. See [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering) and [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [Vitest SDK reference](/docs/reference/vitest) – All options, including `argosSnapshot`
* [Vitest browser mode documentation](https://vitest.dev/guide/browser/) – Vitest's own browser testing guide
* [Vitest visual testing](https://argos-ci.com/blog/vitest-visual-testing) – A guide to browser mode screenshots, on the Argos blog

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Storybook visual testing quickstart

Set up Argos with Storybook and Vitest to get visual testing and live deployment URLs on every pull request.

To add visual testing to [Storybook](https://storybook.js.org/), install `@argos-ci/storybook`, add `argosVitestPlugin()` next to `storybookTest()` in your Vitest config, and run `npx vitest run --project=storybook` in CI with the `ARGOS_TOKEN` environment variable set: Argos captures every story automatically. Add `npx argos deploy ./storybook-static` to publish your built Storybook on a unique URL for each pull request.

By the end of this guide, every pull request will:

* Run visual tests on your stories with Vitest.
* Deploy your Storybook to a unique URL you can share with your team.

### What Argos adds over Chromatic

* **Capture in your own CI.** The Storybook Vitest addon renders each story in a real Playwright browser on your CI, and Argos diffs exactly what your tests rendered. Chromatic renders stories on its own infrastructure.
* **Your Chromatic modes keep working.** Argos reads `parameters.chromatic.modes`, so existing [story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes) carry over.
* **Open source.** The whole platform, diff engine included, is MIT-licensed on [GitHub](https://github.com/argos-ci/argos).

Coming from Chromatic? Follow [Migrate from Chromatic to Argos](/docs/learn/how-to-guides/migrate-to-argos/from-chromatic), or read the [Argos vs Chromatic](https://argos-ci.com/compare/chromatic) comparison.

{% hint style="info" %}
This guide uses the Storybook Vitest addon, which only runs on a Storybook framework that uses Vite. On a Webpack-based framework such as `@storybook/angular` or `@storybook/nextjs`, on Storybook 8, or if you use Test Runner instead of Vitest, follow the [Storybook Test Runner quickstart](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart): Test Runner works with every Storybook framework. On Angular 21 or later, Storybook's Vite-based `@storybook/angular-vite` framework (in preview) supports the Vitest addon.

If you use a legacy version of Storybook (\<v8), follow the [legacy Storybook Quickstart](/docs/quickstart/storybook-quickstart/storybook-legacy-less-than-v8-quickstart).
{% endhint %}

### Prerequisites

* Node.js 22 or later
* [Storybook v9+](https://storybook.js.org/docs/get-started/install) set up in your project, with a Vite-based framework such as `@storybook/react-vite`, `@storybook/vue3-vite`, `@storybook/nextjs-vite` or `@storybook/sveltekit`
* [The Storybook Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon) installed
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos Storybook SDK, plus the Argos CLI that deploys your Storybook later in this guide:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}
{% endtabs %}

If they are not already part of your Storybook Vitest addon setup, also install the browser-mode peer dependencies the configuration below relies on:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add the Argos plugin to your Vitest config

The Argos plugin captures screenshots of your stories and uploads them to Argos. Add it to your Vitest or Vite configuration file (e.g., `vitest.config.ts` or `vite.config.ts`):

{% code title="vitest.config.ts" %}

```ts
import path from "node:path";
import { fileURLToPath } from "node:url";

import { defineConfig } from "vitest/config";

import { playwright } from "@vitest/browser-playwright";

import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
import { argosVitestPlugin } from "@argos-ci/storybook/vitest-plugin";

const dirname =
  typeof __dirname !== "undefined"
    ? __dirname
    : path.dirname(fileURLToPath(import.meta.url));

// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
export default defineConfig({
  test: {
    projects: [
      {
        extends: true,
        plugins: [
          // The plugin will run tests for the stories defined in your Storybook config
          // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest
          storybookTest({ configDir: path.join(dirname, ".storybook") }),

          // The plugin will capture screenshots of your stories and upload them to Argos.
          // See options at: https://argos-ci.com/docs/storybook
          argosVitestPlugin({
            // Upload to Argos on CI only.
            uploadToArgos: !!process.env.CI,
          }),
        ],
        test: {
          name: "storybook",
          browser: {
            enabled: true,
            headless: true,
            provider: playwright({
              // Reduce text rendering differences between machines.
              launchOptions: {
                args: ["--disable-lcd-text", "--font-render-hinting=none"],
              },
            }),
            instances: [{ browser: "chromium" }],
          },
          // Storybook 9 to 10.2 only: since 10.3, the addon applies .storybook/preview itself.
          // setupFiles: [".storybook/vitest.setup.ts"],
        },
      },
    ],
  },
});
```

{% endcode %}

{% hint style="success" %}
The `launchOptions` above disable subpixel text and font hinting, which reduces text rendering differences between your machine and CI. This single change prevents one of the most common causes of flaky screenshots — learn why in [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering).
{% endhint %}
{% endstep %}

{% step %}

### Capture screenshots

All your stories are captured automatically when the tests run — no code change required.

You can also capture additional screenshots in a [play function](https://storybook.js.org/docs/writing-stories/play-function) with the `argosScreenshot` helper, for example after an interaction:

{% code title="example.stories.ts" %}

```ts
import { argosScreenshot } from "@argos-ci/storybook/vitest";

export const Example: Story = {
  play: async (ctx) => {
    // Captures a screenshot of the current story and uploads it to Argos
    await argosScreenshot(ctx, "example-screenshot");
  },
};
```

{% endcode %}

Screenshots are written to the `./screenshots` directory. Add `./screenshots` to your `.gitignore` file to avoid committing them.
{% endstep %}

{% step %}

### Set up CI

Add a workflow that runs your visual tests **and** deploys your Storybook on every pull request. Argos serves the built Storybook on a unique URL — no extra hosting required:

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci

      # Run visual tests
      - run: npx playwright install --with-deps chromium
      - run: npx vitest run --project=storybook
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}

      # Build and deploy the Storybook
      - run: npm run build-storybook
      - run: npx --no-install argos deploy ./storybook-static
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret.

For other CI providers, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci) for the tests and [Use deployments in CI](/docs/learn/deployments/use-deployments-in-ci) for the deployment.
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded, along with a link to your deployed Storybook. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The Argos plugin uploads to Argos only from CI (`uploadToArgos: !!process.env.CI`), so Argos only compares screenshots captured on CI, and the `launchOptions` flags make text render the same way on every machine. See [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering) and [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [Storybook story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes) – Capture stories in multiple themes and viewports
* [Storybook SDK reference](/docs/reference/storybook) – All options and helpers
* [Storybook + Vitest example](https://github.com/argos-ci/argos-javascript/tree/main/examples/storybook-vitest) – A complete working setup
* [Storybook visual testing without Chromatic](https://argos-ci.com/blog/storybook-visual-testing-without-chromatic) – The guide on the Argos blog

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Storybook Test Runner visual testing quickstart

Set up visual testing in Storybook with Test Runner and the Argos CLI.

To add visual testing with [Storybook Test Runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner), install `@argos-ci/storybook` and `@argos-ci/cli`, call `argosScreenshot(page, context)` in the `postVisit` hook of `.storybook/test-runner.ts`, run `test-storybook` against your built Storybook, and upload the screenshots with `npm exec -- argos upload ./screenshots` in CI with the `ARGOS_TOKEN` environment variable set.

### What Argos adds

* **A screenshot of every story.** The `postVisit` hook captures each story after Test Runner has rendered it and run its `play` function.
* **Baselines from your Git history.** Argos picks the [baseline build](/docs/learn/platform-fundamentals/baseline-build) automatically, so there are no images to commit.
* **Review on the pull request.** Visual changes become a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).

{% hint style="info" %}
If you use the Storybook Vitest addon (Storybook 9 or later), follow the [Storybook quickstart](/docs/quickstart/storybook-quickstart) instead.

If you use a legacy version of Storybook (\<v8), follow the [legacy Storybook Quickstart](/docs/quickstart/storybook-quickstart/storybook-legacy-less-than-v8-quickstart).
{% endhint %}

### Prerequisites

* Node.js 22 or later
* [Storybook v8+](https://storybook.js.org/docs/get-started/install) set up in your project
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos CLI, the Argos Storybook SDK, and Storybook Test Runner:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cli @argos-ci/storybook @storybook/test-runner
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cli @argos-ci/storybook @storybook/test-runner
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cli @argos-ci/storybook @storybook/test-runner
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cli @argos-ci/storybook @storybook/test-runner
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add a test script

Add the following script to your `package.json`:

{% code title="package.json" %}

```json
{
  "scripts": {
    "test-storybook": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-vm-modules test-storybook"
  }
}
```

{% endcode %}

{% hint style="info" %}
`NODE_OPTIONS=--experimental-vm-modules` is required because Storybook Test Runner uses Jest, which needs this flag to run modern packages like the Argos Storybook SDK.
{% endhint %}
{% endstep %}

{% step %}

### Capture screenshots

Add a `.storybook/test-runner.ts` file to your project. The `postVisit` hook captures a screenshot of every story:

{% code title=".storybook/test-runner.ts" %}

```ts
import type { TestRunnerConfig } from "@storybook/test-runner";
import { argosScreenshot } from "@argos-ci/storybook/test-runner";

const config: TestRunnerConfig = {
  async postVisit(page, context) {
    await argosScreenshot(page, context);
  },
};

export default config;
```

{% endcode %}

Screenshots are written to the `./screenshots` directory. Add `./screenshots` to your `.gitignore` file to avoid committing them.
{% endstep %}

{% step %}

### Set up CI

Add a workflow that builds your Storybook, runs the tests, and uploads the screenshots to Argos. If you use another CI provider, adapt the steps with [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci):

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22

      - name: Install dependencies
        run: npm ci

      - name: Build Storybook
        run: npm run build-storybook

      - name: Install Playwright dependencies
        run: npx playwright install --with-deps chromium

      - name: Run Storybook tests and capture screenshots
        run: |
          npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
            "npx http-server ./storybook-static --port 6006 --silent" \
            "npx wait-on tcp:127.0.0.1:6006 && npm run test-storybook"

      - name: Upload screenshots to Argos
        run: npm exec -- argos upload ./screenshots
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret.

To learn how to run tests on a deployed Storybook, refer to the [Storybook Test Runner documentation](https://storybook.js.org/docs/writing-tests/integrations/test-runner).
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The workflow above uploads screenshots only from CI, so Argos only compares screenshots captured on CI. Keep your CI on the same image and browser version from one run to the next. See [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches) and [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [Storybook SDK reference](/docs/reference/storybook) – All options and helpers
* [Storybook + Test Runner example](https://github.com/argos-ci/argos-javascript/tree/main/examples/storybook-test-runner) – A complete working setup

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Legacy Storybook (\<v8) visual testing quickstart

Set up visual testing in a legacy Storybook (\<v8) with Storycap and the Argos CLI.

To add visual testing to a legacy Storybook (\<v8), capture every story with [Storycap](https://github.com/reg-viz/storycap), then upload the screenshots with `npm exec -- argos upload ./screenshots` in CI with the `ARGOS_TOKEN` environment variable set. Storycap crawls your Storybook and captures a screenshot of each story; Argos compares them with a [baseline build](/docs/learn/platform-fundamentals/baseline-build) picked from your Git history and reports the changes on your pull request.

### What Argos adds

* **Baselines from your Git history.** There are no reference images to commit or update.
* **Review on the pull request.** Visual changes become a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).

{% hint style="info" %}
On Storybook 9 or later, follow the [Storybook quickstart](/docs/quickstart/storybook-quickstart) instead. On Storybook 8, follow the [Storybook Test Runner quickstart](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart).
{% endhint %}

### Prerequisites

* Node.js 22 or later
* [Storybook \<v8](https://storybook.js.org/docs/get-started/install) set up in your project
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos CLI and Storycap:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cli storycap
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cli storycap
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cli storycap
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cli storycap
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Capture screenshots

There are two ways to capture screenshots of your Storybook.

If your Storybook is already running and accessible via a URL, point Storycap at it:

```bash
npm exec -- storycap <STORYBOOK-URL> --outDir ./screenshots
```

If it isn't deployed, build and serve it before capturing:

```bash
# Build Storybook
npm exec -- storybook build --output-dir ./storybook-static

# Screenshot Storybook with Storycap
npm exec -- storycap --serverCmd "npx http-server ./storybook-static --port 6006" http://127.0.0.1:6006/ --outDir ./screenshots
```

Read the [Storycap documentation](https://github.com/reg-viz/storycap) for advanced usage.

Screenshots are written to the `./screenshots` directory. Add `./screenshots` to your `.gitignore` file to avoid committing them.
{% endstep %}

{% step %}

### Set up CI

Add a workflow that captures the screenshots and uploads them to Argos. If you use another CI provider, adapt the steps with [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci):

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci

      - name: Build Storybook
        run: npm exec -- storybook build --output-dir ./storybook-static

      - name: Capture screenshots with Storycap
        run: npm exec -- storycap --serverCmd "npx http-server ./storybook-static --port 6006" http://127.0.0.1:6006/ --outDir ./screenshots

      - name: Upload screenshots to Argos
        run: npm exec -- argos upload ./screenshots
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret.
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The workflow above uploads screenshots only from CI, so Argos only compares screenshots captured on CI. Keep your CI on the same image and browser version from one run to the next. See [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches) and [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [CLI reference](/docs/reference/argos-command-line-interface-cli) – All upload options
* [Storybook legacy example](https://github.com/argos-ci/argos-javascript/tree/main/examples/storybook-legacy) – A complete working setup

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Cypress visual testing quickstart

Set up visual testing in your Cypress tests with the Argos Cypress SDK.

To add visual testing to [Cypress](https://www.cypress.io/), install `@argos-ci/cypress`, import `@argos-ci/cypress/support` in your support file, register the Argos task with `registerArgosTask()` in `cypress.config.js`, call `cy.argosScreenshot("name")` in your tests, and run Cypress in CI with the `ARGOS_TOKEN` environment variable set. Argos compares every screenshot with a [baseline build](/docs/learn/platform-fundamentals/baseline-build) picked from your Git history and reports the changes as a check on your pull request.

### What Argos adds to Cypress

Cypress takes screenshots with `cy.screenshot()`, but it doesn't compare them with a baseline. Argos adds:

* **Visual comparison with automatic baselines.** Argos picks the baseline from your Git history, so there are no images to commit.
* **Review on the pull request.** Visual changes become a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).
* **Stable screenshots.** `cy.argosScreenshot` waits for images and fonts to load before capturing.

Coming from another tool? Follow [Migrate from Percy to Argos](/docs/learn/how-to-guides/migrate-to-argos/from-percy) or [Migrate from Applitools to Argos](/docs/learn/how-to-guides/migrate-to-argos/from-applitools); both cover Cypress.

### Prerequisites

* Node.js 22 or later
* [Cypress](https://docs.cypress.io/guides/getting-started/installing-cypress) set up in your project
* [Cypress running on your CI](https://learn.cypress.io/advanced-cypress-concepts/running-cypress-in-ci)
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos Cypress SDK:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cypress
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cypress
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cypress
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cypress
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Set up Argos in your Cypress config

Two pieces wire Argos into Cypress: the support file adds the `cy.argosScreenshot` command, and the task uploads the captured screenshots.

Add the command to your `cypress/support/e2e.js` file:

{% code title="cypress/support/e2e.js" %}

```js
import "@argos-ci/cypress/support";
```

{% endcode %}

If you use TypeScript, add the types to your `tsconfig.json`:

{% code title="tsconfig.json" %}

```json
{
  "compilerOptions": {
    "types": ["cypress", "@argos-ci/cypress/support"]
  }
}
```

{% endcode %}

Then register the Argos task in your Cypress config:

{% code title="cypress.config.js" %}

```js
const { defineConfig } = require("cypress");
const { registerArgosTask } = require("@argos-ci/cypress/task");

module.exports = defineConfig({
  // setupNodeEvents can also be defined in "component"
  e2e: {
    async setupNodeEvents(on, config) {
      registerArgosTask(on, config, {
        // Upload to Argos on CI only.
        uploadToArgos: !!process.env.CI,
      });

      // include any other plugin code...
    },
  },
});
```

{% endcode %}
{% endstep %}

{% step %}

### Capture screenshots

Use the `cy.argosScreenshot` command to capture stable screenshots in your tests:

{% code title="cypress/e2e/homepage.cy.js" %}

```js
it("screenshot homepage", () => {
  cy.visit("http://localhost:3000/");
  cy.argosScreenshot("homepage");
});
```

{% endcode %}

Screenshots are written to the `/cypress/screenshots` directory. Add `/cypress/screenshots` to your `.gitignore` file to avoid committing them.

Tip: Check out our guides to [screenshot multiple pages](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls), [capture multiple viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports), or [capture user journeys](/docs/learn/how-to-guides/visual-coverage/capture-user-journeys).
{% endstep %}

{% step %}

### Set up CI

Run your Cypress tests in CI with `ARGOS_TOKEN` set. The Argos task uploads screenshots automatically when it detects a CI environment:

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - name: Run Cypress tests
        uses: cypress-io/github-action@v6
        with:
          start: npm start # command that serves your app
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret. On other CI providers, pass the token with the `ARGOS_TOKEN` environment variable or the task's `token` option. For GitLab CI, CircleCI, Buildkite, and other providers, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci).
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The Argos task uploads only from CI (`uploadToArgos: !!process.env.CI`), so Argos only compares screenshots captured on CI. Keep your CI on the same image and browser version from one run to the next. See [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches) and [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [Cypress SDK reference](/docs/reference/cypress) – All options and helpers
* [Cypress example](https://github.com/argos-ci/argos-javascript/tree/main/examples/cypress) – A complete working setup
* [Cypress visual regression testing](https://argos-ci.com/blog/cypress-visual-regression-testing) – The guide on the Argos blog

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# WebdriverIO visual testing quickstart

Set up visual testing in your WebdriverIO tests with the Argos WebdriverIO SDK.

To add visual testing to [WebdriverIO](https://webdriver.io/), the Node.js test automation framework for web and mobile applications, install `@argos-ci/webdriverio` and `@argos-ci/cli`, call `argosScreenshot(browser, "name")` in your tests, then upload the screenshots with `npm exec -- argos upload ./screenshots/argos` in CI with the `ARGOS_TOKEN` environment variable set. Argos compares every screenshot with a [baseline build](/docs/learn/platform-fundamentals/baseline-build) picked from your Git history and reports the changes as a check on your pull request.

### What Argos adds

* **Baselines from your Git history.** There are no reference images to commit or update.
* **Review on the pull request.** Visual changes become a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).
* **Flaky test detection.** Argos [flags unstable tests](/docs/learn/reliability-and-flakiness/flaky-test-detection) with a flaky badge and a stability score.

### Prerequisites

* Node.js 22 or later
* [WebdriverIO](https://webdriver.io/) set up in your project
* [WebdriverIO running on your CI](https://webdriver.io/docs/automationProtocols/)
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos CLI and the Argos WebdriverIO SDK:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cli @argos-ci/webdriverio
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cli @argos-ci/webdriverio
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cli @argos-ci/webdriverio
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cli @argos-ci/webdriverio
```

{% endtab %}
{% endtabs %}

No configuration is needed — the SDK works directly in your tests.
{% endstep %}

{% step %}

### Capture screenshots

Use the `argosScreenshot` helper to capture screenshots in your tests:

{% code title="test/specs/homepage.e2e.js" %}

```js
import { browser } from "@wdio/globals";
import { argosScreenshot } from "@argos-ci/webdriverio";

describe("Integration test with visual testing", () => {
  it("covers homepage", async () => {
    await browser.url("http://localhost:3000");
    await argosScreenshot(browser, "homepage");
  });
});
```

{% endcode %}

Screenshots are written to the `./screenshots/argos` directory. Add `screenshots/` to your `.gitignore` file to avoid committing them.
{% endstep %}

{% step %}

### Set up CI

Run your WebdriverIO tests in CI, then upload the screenshots to Argos with the CLI:

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - name: Run WebdriverIO tests
        run: npm test

      - name: Upload screenshots to Argos
        run: npm exec -- argos upload ./screenshots/argos
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret. For GitLab CI, CircleCI, Buildkite, and other providers, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci).
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The workflow above uploads screenshots only from CI, so Argos only compares screenshots captured on CI. Keep your CI on the same image and browser version from one run to the next. See [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches) and [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [WebdriverIO SDK reference](/docs/reference/webdriverio) – All options
* [CLI reference](/docs/reference/argos-command-line-interface-cli) – All upload options

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Puppeteer visual testing quickstart

Set up visual testing in your Puppeteer scripts with the Argos Puppeteer SDK.

To add visual testing to [Puppeteer](https://pptr.dev/), install `@argos-ci/puppeteer` and `@argos-ci/cli`, call `argosScreenshot(page, "name")` in your scripts, then upload the screenshots with `npm exec -- argos upload ./screenshots/argos` in CI with the `ARGOS_TOKEN` environment variable set. Argos compares every screenshot with a [baseline build](/docs/learn/platform-fundamentals/baseline-build) picked from your Git history and reports the changes as a check on your pull request.

### What Argos adds to `page.screenshot()`

Puppeteer captures images with `page.screenshot()`, but it doesn't compare them with a baseline. Argos adds:

* **Visual comparison with automatic baselines.** Argos picks the baseline from your Git history, so there are no images to commit.
* **Review on the pull request.** Visual changes become a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).
* **Stable screenshots.** `argosScreenshot` waits for images and fonts to load before capturing.

### Prerequisites

* Node.js 22 or later
* [Puppeteer](https://pptr.dev/#getting-started) set up in your project
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos CLI and the Argos Puppeteer SDK:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cli @argos-ci/puppeteer
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cli @argos-ci/puppeteer
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cli @argos-ci/puppeteer
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cli @argos-ci/puppeteer
```

{% endtab %}
{% endtabs %}

No configuration is needed — the SDK works directly in your scripts.
{% endstep %}

{% step %}

### Capture screenshots

Use the `argosScreenshot` helper to capture stable screenshots in your tests:

{% code title="screenshot.mjs" %}

```js
import puppeteer from "puppeteer";
import { argosScreenshot } from "@argos-ci/puppeteer";

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("http://localhost:3000");
await argosScreenshot(page, "homepage");
await browser.close();
```

{% endcode %}

Screenshots are written to the `./screenshots/argos` directory. Add `screenshots/` to your `.gitignore` file to avoid committing them.

Tip: Check out our guides to [screenshot multiple pages](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls) or [capture multiple viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).
{% endstep %}

{% step %}

### Set up CI

Run your Puppeteer tests in CI, then upload the screenshots to Argos with the CLI:

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - name: Run Puppeteer tests
        run: npm test

      - name: Upload screenshots to Argos
        run: npm exec -- argos upload ./screenshots/argos
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret. For GitLab CI, CircleCI, Buildkite, and other providers, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci).
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The workflow above uploads screenshots only from CI, so Argos only compares screenshots captured on CI. Keep your CI on the same image and browser version from one run to the next. See [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches) and [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [Puppeteer SDK reference](/docs/reference/puppeteer) – All options and helpers
* [Puppeteer example](https://github.com/argos-ci/argos-javascript/tree/main/examples/puppeteer) – A complete working setup

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Visual testing with any test framework

Set up visual testing with any test framework by uploading screenshots with the Argos CLI.

To add visual testing to any test framework, save your screenshots to a folder, install `@argos-ci/cli`, and run `npm exec -- argos upload ./screenshots` in CI with the `ARGOS_TOKEN` environment variable set. Argos compares every screenshot with a [baseline build](/docs/learn/platform-fundamentals/baseline-build) picked from your Git history and reports the changes as a check on your pull request.

Argos works with any tool that produces screenshots. If your framework has no dedicated Argos SDK, capture screenshots however you like and upload the folder with the Argos CLI.

### What Argos adds

* **Baselines from your Git history.** There are no reference images to commit or update.
* **Review on the pull request.** Visual changes become a pull request check that your team [approves or rejects](/docs/learn/review-workflow/review-a-build).
* **Diff more than images.** The CLI also uploads text files such as JSON, HTML, or Markdown with `--files` — see [Compare non-image files](/docs/learn/how-to-guides/visual-coverage/compare-non-image-files).

### Prerequisites

* Node.js 22 or later
* Your tests capture screenshots into a folder (e.g. `./screenshots`)
* Your tests run on CI
* [A project created in Argos](https://app.argos-ci.com/new)

{% stepper %}
{% step %}

### Install

Install the Argos CLI:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cli
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cli
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cli
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cli
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Set up CI

Run your tests, then upload the screenshots folder to Argos with the CLI:

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - name: Run tests and capture screenshots
        run: npm test

      - name: Upload screenshots to Argos
        run: npm exec -- argos upload ./screenshots
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret. On other CI providers, set `ARGOS_TOKEN` as a secret environment variable. For GitLab CI, CircleCI, Buildkite, and other providers, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci).

The CLI detects your CI context (commit, branch, pull request) automatically. See the [CLI reference](/docs/reference/argos-command-line-interface-cli) for all options.
{% endstep %}
{% endstepper %}

### You're all set

Push your changes and open a pull request — the Argos check appears on it once the build is uploaded. Review the visual changes, approve or reject them, and merge with confidence.

{% hint style="info" %}
Argos needs a baseline to compare against. Until a build runs on your default branch, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds). Merge this setup or run the workflow once on your default branch to establish the baseline.
{% endhint %}

### Frequently asked questions

<details>

<summary>How do I update the baseline after an intended change?</summary>

You don't update any file. Review the build in Argos and [approve the changes](/docs/learn/review-workflow/review-a-build): an approved build is eligible as a baseline. Once you merge, the build on your default branch, which Argos approves automatically by default, becomes the baseline for the pull requests that follow. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).

</details>

<details>

<summary>Why do screenshots differ between my machine and CI?</summary>

Fonts, text rendering, and browser versions depend on the operating system, so the same page renders slightly differently on macOS and on a Linux CI runner. The workflow above uploads screenshots only from CI, so Argos only compares screenshots captured on CI. Keep your CI on the same image and browser version from one run to the next. See [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches) and [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests).

</details>

### Next steps

* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Prevent flaky diffs before they reach your pull requests
* [CLI reference](/docs/reference/argos-command-line-interface-cli) – All upload options
* [Screenshot metadata](/docs/reference/screenshot-metadata) – Enrich screenshots with context shown on the build page

***

Need help? [Join our Discord](https://argos-ci.com/discord), [open an issue on GitHub](https://github.com/argos-ci/argos/issues), or [send us an email](mailto:contact@argos-ci.com).


# Core concepts

The core concepts behind Argos — builds, baselines, comparisons, and the statuses you see on your pull requests.

Argos turns the screenshots your tests produce into a review signal on your pull requests. This page defines the concepts you'll meet everywhere else in the docs.

### Builds and snapshots

A **build** is the result of one test run: the set of **screenshots** (and other snapshots, like [ARIA snapshots](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences#what-argos-compares) or [text files](/docs/learn/how-to-guides/visual-coverage/compare-non-image-files)) uploaded together from your CI. Each build belongs to a project and is tied to a commit and branch.

A build goes through three stages:

1. **Upload**: Your CI uploads the snapshots, either automatically through an SDK or with the [CLI](/docs/reference/argos-command-line-interface-cli).
2. **Comparison**: Argos picks a [baseline build](/docs/learn/platform-fundamentals/baseline-build) and diffs each snapshot against its counterpart.
3. **Conclusion**: The build concludes with **No changes detected** or **Changes detected**, and Argos reports it to your Git provider as a commit status.

### Build types

Argos assigns each build a type that describes how it is compared:

* **Check**: The standard build on a feature branch — compared against a baseline and awaiting review when changes are detected.
* **Auto-approved**: A build on an [auto-approved branch](/docs/learn/platform-fundamentals/baseline-build#auto-approved-branches) (your default branch by default) — approved automatically so it can serve as a future baseline.
* **Orphan**: A build with [no baseline to compare against](/docs/learn/platform-fundamentals/baseline-build#orphan-builds) — expected for a project's first builds.
* **Skipped**: A build [intentionally marked as skipped](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) — reports success without screenshots.

### Review

When a build concludes with **Changes detected**, your team [reviews it](/docs/learn/review-workflow/review-a-build): approve the changes to accept them as the new expected state, or reject them to flag a regression. The review outcome updates the commit status on your pull request, so you can [require it before merging](/docs/learn/review-workflow/summary-checks).

### Go deeper

* [How Argos detects visual differences](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) – The deterministic diff algorithm behind comparisons
* [Baseline build](/docs/learn/platform-fundamentals/baseline-build) – How Argos chooses the build to compare against
* [Build modes](/docs/learn/platform-fundamentals/build-modes) – CI mode and Monitoring mode, and when to use each

Once the concepts are clear, head to the [Review workflow](/docs/learn/review-workflow) to see how builds are reviewed day to day.


# How Argos detects visual differences

Discover how Argos uses deterministic pixel diffing with the odiff library to surface meaningful visual changes.

Argos uses **deterministic pixel diffing**, not AI-based visual comparison. Instead of compensating for flakiness, Argos focuses on eliminating it at the source. This keeps visual tests precise, explainable, and reliable over time.

### What Argos compares

Argos compares the snapshots produced by your tests against their counterparts in the [baseline build](/docs/learn/platform-fundamentals/baseline-build):

* **Screenshots** — rendered images of your pages, components, or stories.
* **ARIA snapshots** — text representations of your page's accessibility tree, captured by the [Playwright SDK](/docs/reference/playwright#aria-snapshots), that catch regressions invisible in pixels.
* **Text files** — any [non-image file](/docs/learn/how-to-guides/visual-coverage/compare-non-image-files) you upload, such as JSON or Markdown, diffed as text.

For each pair, the question Argos answers is intentionally simple: did it change, or not? No interpretation, no probability, no guesswork.

### The diff algorithm

Argos relies on the open-source [**odiff** library](https://github.com/dmtrKovalenko/odiff) by Dmitriy Kovalenko. The full diff implementation is also open source — you can [inspect it here](https://github.com/argos-ci/argos/blob/main/apps/backend/src/screenshot-diff/diff/image/index.ts).

Each image comparison runs in three stages:

1. **Size alignment**: When the two images have different sizes, Argos places both at the top-left corner of a canvas as wide as the wider image and as tall as the taller one, and fills the empty area with transparent pixels. That area then shows up as changed. An image smaller in both dimensions is first scaled up to fit, keeping its aspect ratio.
2. **Two diff passes**: odiff compares the images twice, with anti-aliasing detection on, so pixels that only differ by anti-aliasing are not counted.
   * The **tolerant pass** ignores small color differences (odiff color threshold `0.15`). Its result is discarded when fewer than 20 pixels, or 0.02% of the image if that is smaller, have changed.
   * The **color-sensitive pass** catches subtle color shifts (odiff color threshold `0.0225`), but only counts when at least 3% of the image has changed.
3. **Final diff output**: The pass with the higher score produces the diff mask and the score. When neither pass counts, the snapshot is unchanged.

Running two passes lets Argos catch both a small, sharp change and a faint color shift across a large area, while ignoring isolated noise.

When at least one snapshot differs from the baseline, the build concludes with **Changes detected** and the diffs await [review](/docs/learn/review-workflow/review-a-build). When every snapshot matches, the build concludes with **No changes detected** and the commit status passes.

### Sensitivity threshold

The `threshold` option scales the tolerances above. Set it per screenshot in the SDKs (for example `argosScreenshot(page, "chart", { threshold: 0.2 })`) or for a whole upload with the CLI's `--threshold` flag. It takes a value between 0 and 1 and defaults to `0.5`.

Argos multiplies every tolerance by 2 × `threshold`:

* `0.5` (default) uses the values above.
* `0` sets every tolerance to zero: any changed pixel that isn't anti-aliasing counts.
* `1` doubles every tolerance.

Anti-aliasing detection stays on at every threshold.

### Why pixel diffing instead of AI

Some tools use AI or ML models to decide whether a change is acceptable. Argos intentionally does not: AI compensates for flakiness, while Argos removes it. AI-based approaches often hide small changes without clear explanations, mask rendering inconsistencies, and blur the line between what changed and what was approved — which leads to silent regressions and declining trust in the test suite.

Deterministic pixel diffing has the properties that matter in CI:

* **Deterministic**: Same input, same result.
* **Explainable**: The exact pixels that changed are visible.
* **Review-friendly**: Reviewers assess facts, not model guesses.
* **Auditable**: Approvals have a clear meaning.

And because the diff engine is open source, there is no black box and no hidden thresholds — visual testing should be infrastructure, not magic.

### Flakiness is a signal

A flaky visual test usually points to an underlying problem: non-deterministic animations, time-dependent rendering, uncontrolled fonts, async layout shifts, or environment-specific rendering differences. Argos treats flakiness as **technical debt to fix**, not noise to ignore.

Argos gives you the tools for both sides of the problem: [flaky test detection](/docs/learn/reliability-and-flakiness/flaky-test-detection) identifies unstable screenshots automatically, and the [stabilization playbook](/docs/learn/reliability-and-flakiness/flaky-tests) helps you fix them at the source.


# Baseline build

Argos compares screenshots to a chosen baseline build, determined by analyzing the commit history of your Git project.

A **baseline build** is the reference point Argos compares a new build against to detect visual changes. For every new build, Argos looks through the candidate builds in your project and picks the most relevant one.

{% hint style="info" %}
This page covers CI mode, where the baseline comes from your Git history. In [Monitoring mode](/docs/learn/platform-fundamentals/build-modes#monitoring-mode), Argos ignores Git history and compares against the latest approved build instead.
{% endhint %}

### How Argos selects the baseline

Argos selects the baseline by finding the closest build in your Git history that satisfies two sets of criteria.

**Criteria on the candidate build itself:**

* **It is complete.** Argos has finished processing it.
* **Its tests passed.** None of the tests that produced the screenshots failed.
* **It is not a** [**subset**](/docs/learn/how-to-guides/ci-pipelines/subset-builds)**.** It uploaded the full set of snapshots.
* **It has no active rejection.** A build whose changes were rejected (and the rejection not [dismissed](/docs/learn/review-workflow/review-a-build)) can never serve as a baseline, whatever its type.

**Criteria relative to the new build:**

* **Same build name and mode.** Only builds with the same [build name](/docs/learn/how-to-guides/ci-pipelines/monorepos-setup) and [build mode](/docs/learn/platform-fundamentals/build-modes) are considered.
* **Commit ancestry.** The candidate's commit must be the **merge base** or one of its ancestors. The merge base is the closest common commit between your branch and the baseline branch. In practice, this means the baseline reflects the code your branch actually started from.

#### When the baseline must be approved

Among the builds that meet the criteria on the candidate build itself and share the new build's name and mode, Argos looks in this order and stops at the first match:

1. **A build on the merge-base commit.** It must also be approved: an auto-approved build, an [orphan](#orphan-builds), or a check build with an approved review or a merged pull request. Argos skips this approval check when the baseline branch is an [auto-approved branch](#auto-approved-branches) and the new build isn't tied to a pull request.
2. **The closest build on an ancestor of the merge base.** Argos walks back through the history, and builds found this way don't need to be approved.
3. **An approved build on the new build's own commit and branch**, as a last resort. This covers running Argos twice without committing, for example while setting it up.

### Check whether a build can be a baseline

Every completed build shows whether it's **eligible to become a baseline** for future builds. Look for the baseline-eligibility chip next to the build status on the build page, in the [Builds list](/docs/learn/review-workflow/builds-list), and in the build's **Info** panel:

* **Eligible as baseline** — "This build is eligible to be used as a baseline by future builds."
* **Not eligible as baseline** — "This build is not eligible to be used as a baseline by future builds."

![The baseline eligibility chip and its explanation on the build page](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-2ce895a938b12d5a6d4cb34d57d6ece88eaf98b3%2Fbaseline-eligibility-chip.png?alt=media)

When a build isn't eligible, the **Baseline eligibility** section of the Info panel lists which criteria it failed to meet.

![The Baseline eligibility section in the build Info panel listing the unmet criteria](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-f787ccca059c4ab2b94b68311e1a6ed38c57020f%2Fbaseline-eligibility-info-panel.png?alt=media)

{% hint style="info" %}
The chip appears once a build is complete, and it reflects the build on its own. The comparison-dependent criteria — matching build name, mode, and commit ancestry — depend on the build being compared, so Argos evaluates them when a new build looks for its baseline.

The chip also requires approval, which the [search order](#when-the-baseline-must-be-approved) doesn't require from builds on an ancestor of the merge base. A build marked not eligible only because it isn't approved can still become a baseline that way.
{% endhint %}

### The baseline branch

The **baseline branch** is the branch Argos uses as the reference when resolving the merge base:

* For pull request builds, the base branch of the pull request is used.
* For push events, Argos uses the default baseline branch configured in your project.

{% hint style="info" %}
By default, the repository's default branch is used as the baseline branch. You can change this in the Argos project settings.
{% endhint %}

### Auto-approved branches

Builds on **auto-approved branches** are approved automatically, so they can serve as baselines without a manual review. Branches are matched by pattern (e.g., `main`, `master`, or `develop`).

Only builds that aren't tied to a pull request are auto-approved. A pull request build is never auto-approved, even when its branch matches the pattern.

{% hint style="info" %}
By default, Argos auto-approves your default baseline branch. You can configure auto-approved branch patterns in the Argos project settings. There is no setting to turn auto-approval off: the settings form requires a pattern, and a project without one uses the default baseline branch.
{% endhint %}

### Configure branches in project settings

In the project settings, you can configure both the default baseline branch and the auto-approved branch patterns.

![Project branches settings](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/68PKq0mqYzkgWktFLFZe/project%20branches%20settings%20d4c960a48175260947ee018ec2ac371a.png)

### Choose a custom baseline via SDK

To compare against a specific branch or commit instead of the automatically selected baseline, set one of these environment variables in your CI:

* `ARGOS_REFERENCE_BRANCH`: The branch to use as the baseline.
* `ARGOS_REFERENCE_COMMIT`: The commit hash to select a specific baseline build.

If `ARGOS_REFERENCE_COMMIT` is the build's own commit, Argos skips builds on that commit at first and searches its ancestors instead. Only when none of them qualifies does it fall back to an approved build on the same commit and branch (the last step of the [search order](#when-the-baseline-must-be-approved)).

### Orphan builds

An **orphan build** is a build for which Argos found no baseline to compare against. This is expected for a project's first builds: once a build on your baseline branch is approved (or auto-approved), subsequent builds find their baseline automatically.


# Build modes

Compare Argos Continuous Integration mode and Monitoring mode to choose the right one for your workflow.

Argos provides two build modes: **Continuous Integration (CI) mode** and **Monitoring mode**. They differ in one thing — how the baseline is chosen. CI mode follows your Git history to review changes branch by branch; Monitoring mode ignores Git history and tracks drift against the latest approved state.

### CI mode

CI mode is the default. It is designed to review the visual changes introduced by a feature branch and prevent regressions from merging.

On each pull request and on your default branch, your tests capture screenshots and upload them to Argos. Argos compares them against the [baseline build](/docs/learn/platform-fundamentals/baseline-build) selected from your Git history, and reports the result on the pull request.

{% hint style="info" %}
Your CI pipeline must also run on the default branch (main, production, etc.) to keep the baseline up to date.
{% endhint %}

#### Workflow

{% stepper %}
{% step %}

### Develop

A developer creates a feature (or bugfix) branch, commits changes, and opens a pull request.
{% endstep %}

{% step %}

### Capture and upload

During CI, your test framework captures screenshots of the app. At the end of the tests, the [Argos SDK](/docs/reference/playwright) uploads them automatically — or you upload them with the [CLI](/docs/reference/argos-command-line-interface-cli).
{% endstep %}

{% step %}

### Compare

Argos determines the [baseline build](/docs/learn/platform-fundamentals/baseline-build) from your Git history and compares each new screenshot against it.
{% endstep %}

{% step %}

### Report

Once all screenshots are compared, the build concludes:

* **No changes detected** — the commit status passes.
* **Changes detected** — the commit status asks for review: it fails with "waiting for your decision", and the diffs are ready in Argos.

Argos posts the status to your Git provider, along with a [pull request comment](/docs/learn/review-workflow/pull-request-comments) on GitHub. See every status state on [GitHub](/docs/learn/integrations/github-integration#commit-status-states) and [GitLab](/docs/learn/integrations/gitlab-integration#commit-statuses). If your branch protection requires the Argos check, the pull request is blocked until the changes are reviewed.
{% endstep %}

{% step %}

### Review

The team [reviews the build](/docs/learn/review-workflow/review-a-build): approve intended changes or reject regressions. The commit status updates accordingly, and approved builds become eligible as future baselines.
{% endstep %}
{% endstepper %}

#### When to use CI mode

* **Pull request validation**: Block merging until visual changes are reviewed and approved.
* **Default branch verification**: Validate every merge so the baseline stays trustworthy.
* **Continuous feedback**: Surface regressions commit by commit, where developers work.

CI mode is enabled by default — follow the [Quickstart](/docs/quickstart) to set it up.

### Monitoring mode

Monitoring mode is **opt-in**. It is designed to track visual changes outside the standard CI flow — on a schedule, before a release, or after a deployment.

In this mode, your tests capture screenshots on a chosen branch (e.g., `main` or a release branch). Argos compares them **only against the latest approved build**: Git history is ignored, and the approval status alone defines the baseline.

#### Workflow

{% stepper %}
{% step %}

### Run on a schedule

Your tests capture screenshots on the target branch — daily, weekly, or before a release.
{% endstep %}

{% step %}

### Compare with the latest approved build

Argos compares the screenshots against the most recently approved monitoring build.
{% endstep %}

{% step %}

### Get notified and review

If differences are found, Argos notifies you — enable [Slack notifications](/docs/learn/integrations/slack-integration#set-up-slack-notifications) to alert your team immediately. Review the changes, then approve them or fix the regression.
{% endstep %}
{% endstepper %}

#### Commit statuses in Monitoring mode

Monitoring builds report to your Git provider exactly like CI builds: Argos posts a [commit status](/docs/learn/integrations/github-integration#commit-status-names) (`argos`, or `argos/<build-name>`) on the commit the screenshots were captured from — including on a pull request, if the commit belongs to one. When changes are detected, the status is **failure** with the description "waiting for your decision"; it turns to **success** once the build is approved.

The difference with CI mode is auto-approval: a CI build on an auto-approved branch (such as your default branch, outside a pull request) passes automatically, while a monitoring build is never auto-approved — a detected change always waits for a review.

#### When to use Monitoring mode

* **Regular health checks**: Catch unexpected changes on a schedule when running CI on every change isn't practical.
* **Pre-release validation**: Confirm no regressions slipped in before a major release.
* **Post-deployment monitoring**: Detect environment-specific issues after staging or production deployments.
* **Third-party integrations**: Ensure external widgets or services that change independently remain visually stable.

#### Enable Monitoring mode

Set the mode in your SDK configuration or pass `--mode=monitoring` to the CLI:

{% tabs %}
{% tab title="CLI" %}

```bash
npm exec -- argos upload --mode=monitoring ./screenshots
```

{% endtab %}

{% tab title="Playwright" %}
{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  reporter: [
    process.env.CI ? ["dot"] : ["list"],
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
        mode: "monitoring",
      }),
    ],
  ],
});
```

{% endcode %}
{% endtab %}

{% tab title="Cypress" %}
{% code title="cypress.config.js" %}

```js
const { defineConfig } = require("cypress");
const { registerArgosTask } = require("@argos-ci/cypress/task");

module.exports = defineConfig({
  e2e: {
    async setupNodeEvents(on, config) {
      registerArgosTask(on, config, {
        uploadToArgos: !!process.env.CI,
        mode: "monitoring",
      });
    },
  },
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="info" %}
Builds in different modes never share baselines: a monitoring build is only ever compared against another monitoring build.
{% endhint %}


# Review workflow

Bring Argos visual changes into your review process across the dashboard, pull requests, and status checks.

Once your builds run, Argos brings visual changes into your everyday review process — in the dashboard, on your pull requests, and through your status checks.

* [Builds list](/docs/learn/review-workflow/builds-list) – Browse, filter, and open the builds for a project
* [Review a build](/docs/learn/review-workflow/review-a-build) – Request reviewers, comment on exactly what changed, and approve or reject together
* [Pull request comments](/docs/learn/review-workflow/pull-request-comments) – The comment Argos posts on each pull request with the latest build status
* [Summary checks](/docs/learn/review-workflow/summary-checks) – The commit status checks Argos reports back to your Git provider
* [Tags](/docs/learn/review-workflow/tags) – Filter screenshots by tag to focus your review
* [Review builds with AI agents](/docs/learn/review-workflow/review-builds-with-ai-agents) – Let an AI agent triage visual changes from your assistant or CI
* [Automations](/docs/learn/review-workflow/automations) – Rules that react to build events and notify Slack, Microsoft Teams, or Discord


# Builds list

Browse, filter, and open builds for a project to scan status and drill into the runs you care about in Argos.

The Builds list is the main view for tracking every Argos build in a project. It helps you scan status, drill into a specific build, and filter down to exactly the runs you care about.

![Builds list showing build rows with status badges, change counts, and filters](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/x9MUDkRc1AmvVMgyfHht/builds%20list%201c05680bba57d079cfe4fbe60437cb0d.png)

*A project Builds list with status badges, change counts, and filters*

### Open the Builds list

1. Open your project in Argos.
2. Select the **Builds** tab.

### What each row shows

Each row represents a single build:

* **Build number and status** — for example **Auto-approved**, **Changes approved**, **Changes detected**, or **Changes rejected**.
* **Change counts** — changes, additions, and removals when applicable.
* **Pull request metadata** when the build is linked to a PR.
* **Branch and commit** information.
* **Timestamp** for when the build was created.

### Filters

Use the filters at the top of the page to narrow the list:

* **Type**: Filter by [build type](/docs/learn/platform-fundamentals#build-types) — check, orphan, or auto-approved builds.
* **Status**: Filter by review state — approved, changes detected, or rejected.
* **Build name**: Filter to a specific build name when your CI [splits builds](/docs/learn/how-to-guides/ci-pipelines/monorepos-setup).

Build names in the filter are sourced from builds created in the **last month**. Builds can't be deleted, but unused build names drop out of the filter list after about a month of inactivity.

### FAQ

<details>

<summary>Can I delete a build?</summary>

No. Builds are immutable records and cannot be deleted.

</details>

<details>

<summary>Why did a build name disappear from the filter?</summary>

The build name list only includes names used in builds created within the last month. If a build name has no recent activity, it no longer appears.

</details>


# Review a build

Review visual changes as a team — request reviewers, comment on the exact pixel or line that changed, discuss in threads, and approve or reject in real time.

When a build detects visual changes, the build page is where your team decides what to do with them. Reviewing a build is collaborative: request the people you need, leave comments pinned to exactly what changed, discuss in threads, and approve or reject — with everything updating in real time.

Every reviewer's decision counts on its own, so a build reflects your whole team's input rather than just the last person to weigh in.

![The build review page with the baseline and changes panes and the reviewers sidebar](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-bb049bafc91792fec542de55268601fc62572634%2Freview-build-overview.png?alt=media)

### Open a build

You can open a build from several places:

* The [Builds list](/docs/learn/review-workflow/builds-list) in your project.
* The [pull request comment](/docs/learn/review-workflow/pull-request-comments) Argos posts on your PR.
* The [Argos status check](/docs/learn/review-workflow/summary-checks) on your commit or pull request.

The build page shows each screenshot with its **baseline** (the reference) next to the **changes** (the new screenshot), and highlights the visual diff between them. See [How Argos detects visual differences](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) for how the diff is computed, and use [Tags](/docs/learn/review-workflow/tags) to filter the screenshot list down to what you care about.

### Compare baseline and changes

The diff viewer offers several ways to compare a snapshot:

* **Split view** — baseline and changes side by side. This is the default.
* **Single view** — one image at a time, switching between **Baseline** and **Changes**.
* **Changes overlay** — changed pixels are highlighted with a red overlay. Toggle it on and off, and customize its color and opacity from the toolbar.
* **Highlight and navigate changes** — flash the changed regions and jump from one to the next.
* **Fit or expand** — fit the screenshot to the screen or inspect it at full size; zoom and pan stay in sync between the baseline and changes panes.
* **ARIA view** — when a snapshot has an [ARIA snapshot](/docs/learn/how-to-guides/visual-coverage/adding-aria-snapshots-manually), switch between the screenshot and its accessibility tree.

### Review similar changes together

When the same change shows up in several screenshots of a build, Argos groups them in the screenshot list. Diffs with the same [fingerprint](/docs/learn/reliability-and-flakiness/flaky-test-detection#how-argos-recognizes-the-same-change) share one row, with an **N similar** button that counts the other screenshots in the group.

* While the group is collapsed, accepting or rejecting the row applies to every screenshot in it.
* Expand the group with the **N similar** button or `G` to see each screenshot and review them one by one.

### Follow a journey step by step

A test that walks a checkout, a signup or an onboarding captures a screenshot at each screen it visits. Argos groups those screenshots back into the **journey** the test walked, so a change is reviewed next to the screens that come before and after it. There is nothing to configure: the journey is the test that took the screenshot, and each screen the test captured is a step.

The **Journey** button in the toolbar unfolds that journey as a strip of steps above the comparison panes.

![The journey of the active screenshot as a strip of steps above the baseline and changes panes](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-65678cef5cb993d3929fd97115fe5c6c23c16126%2Freview-journey-drawer.png?alt=media)

* **Every step, not only the ones that changed.** The strip is built from the whole build, so the screens that stayed identical are in it too. A dot marks the steps that need review, and names what is waiting there on hover.
* **Jump to a step.** Select a thumbnail to open that step. The strip stays on the variant you are reviewing, so you keep the same browser and viewport as you move along the journey.
* **`⇧` + `←` and `⇧` + `→`** walk the same steps from the keyboard, whether the strip is open or not.

The button appears only when the screenshot belongs to a journey of more than one step: a test that captures a single screen is regular visual testing, not a journey. The strip stays closed until you open it, and then remembers your choice.

#### Where a journey comes from

A journey is derived from the metadata your Argos SDK already records, so nothing changes in your tests:

* **The journey** is the test that took the screenshot, identified by its title path (file, `describe` blocks and test name). Storybook uploads group by component instead, with one step per story.
* **A step** is a screen, not a file. The viewport and browser variants of one screen collapse into a single step, exactly as they do elsewhere in the review.
* **The order** is the order in which the test captured the screens.

{% hint style="info" %}
Capture order is recorded by `@argos-ci/playwright` 7.5.1, `@argos-ci/cypress` 7.2.1 and `@argos-ci/vitest` 0.6.1 and above, as [`capture.index` in the screenshot metadata](/docs/reference/screenshot-metadata#capture). Screenshots uploaded by an earlier version, or by an SDK that does not record it, fall back to alphabetical order within the journey.
{% endhint %}

To write a test that captures a journey, including a flow with several paths, see [Capture user journeys](/docs/learn/how-to-guides/visual-coverage/capture-user-journeys).

### Keyboard shortcuts

Press `?` on a build page to see the full list of shortcuts. The ones you'll use most while reviewing:

| Shortcut          | Action                                        |
| ----------------- | --------------------------------------------- |
| `↑` / `↓`         | Go to the previous / next snapshot            |
| `←` / `→`         | Show only the baseline / only the changes     |
| `⇧` `←` / `⇧` `→` | Go to the previous / next step of the journey |
| `S`               | Toggle side-by-side view                      |
| `D`               | Toggle the changes overlay                    |
| `H`               | Highlight the changed regions                 |
| `J` / `K`         | Go to the previous / next change              |
| `G`               | Expand or collapse a group of similar changes |
| `Space`           | Toggle fit to screen                          |
| `Y` / `N`         | Mark a change as accepted / rejected          |
| `I`               | Ignore a flaky change                         |
| `C`               | Toggle the comment tool                       |
| `↵`               | Open the review popover                       |

### Request reviewers

Use the **Reviewers** section in the build sidebar to request the people you want to weigh in.

1. In the **Reviewers** section, click the **+** button (**Add reviewer**).
2. Search for and select one or more project members.

You can request several reviewers, add or remove them at any time, and each reviewer's verdict is tracked **individually** — a new review never overwrites someone else's.

![Requesting reviewers from the Reviewers section in the build sidebar](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-06ad78332f717334fdba3bf811e0f34f6c3ae246%2Freview-request-reviewers.png?alt=media)

Each reviewer shows one of the following states:

| State         | Meaning                                                    |
| ------------- | ---------------------------------------------------------- |
| **Approved**  | The reviewer approved the changes.                         |
| **Rejected**  | The reviewer rejected the changes.                         |
| **Commented** | The reviewer left feedback without approving or rejecting. |
| **Pending**   | The reviewer was requested but hasn't reviewed yet.        |
| **Dismissed** | The review was dismissed and no longer counts.             |

#### How reviews decide the build status

Only each reviewer's **latest** review counts, and dismissed reviews are ignored. The build status follows two rules:

* **One rejection blocks.** If any reviewer's latest review is a rejection, the build is **rejected** — even if others approved.
* **Otherwise, one approval passes.** With no active rejection, a single approval marks the build **approved**.

Comment reviews and pending requests don't affect the outcome. To unblock a rejected build, the rejecting reviewer can submit a new review, or an administrator can dismiss the rejection.

### Submit a review

Open the review popover from the build header and choose one of three actions:

* **Comment** — submit feedback without approving or rejecting.
* **Reject** — reject the changes.
* **Approve** — approve the changes so the build can be merged.

Add an optional summary in the **Add review summary…** field, written in Markdown. A summary is **required** when you submit a neutral **Comment** review.

![The review popover with the review summary field and Comment, Reject, and Approve actions](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-e5912b7a02b5ff297c32a2155c71319282abe763%2Freview-popover.png?alt=media)

As you work through a build, a review-progress chip (for example, **2 / 3 reviewed**) tracks how many changes you've reviewed so far.

#### Draft reviews

You don't have to publish each comment as you go. By default, the comments you add while reviewing are gathered into a **pending review** that stays private to you — each shows a **Pending** badge noting "Only you can see this. It becomes visible when you submit your review." When you submit, all of them are published at once and your reviewers are notified together — GitHub-style.

To post a single comment immediately instead of adding it to your pending review, hold **Alt** while submitting (**Post comment**).

Your in-progress comments are saved locally, so you won't lose a thought if you navigate away before submitting.

### When new commits are pushed

Each push to your branch or pull request creates a new build. Argos carries your approvals over from earlier builds of the same branch or pull request (with the same build name), so you don't review the same change twice. A change counts as already approved when its diff has the same [fingerprint](/docs/learn/reliability-and-flakiness/flaky-test-detection#how-argos-recognizes-the-same-change) as a change you approved, or when the screenshot file is identical. Argos reads your latest review on those earlier builds, and a dismissed review never carries over.

* **Every change was already approved.** When the new build finishes, Argos approves it on behalf of each previous approver whose approvals cover all of its changes. These reviews are marked as automatic: a badge on the reviewer's avatar explains that the changes were already approved on an earlier build. They count toward the [build status](#how-reviews-decide-the-build-status) like any approval, and don't email the build's subscribers.
* **Only some changes match.** When you open the build, a **Previous approvals detected** dialog offers to **Reapply previous approvals**. It marks the matching changes as approved and takes you to the first change left to review. Submit your review once you've gone through the rest.

Approvals never carry over to builds that run in a [merge queue](/docs/learn/integrations/github-integration#merge-queue-support).

### Comment on exactly what changed

Comments are pinned to precisely what they're about:

* On a screenshot, click a point to **pin the comment to that exact spot**.
* In a text-based snapshot, select a **line or a range of lines** to attach the comment to those lines.

The snapshot you're viewing is automatically attached to your comment, and you can detach or reattach it without losing what you've written.

Comments are written in **Markdown** and support:

* **`/` slash commands** for quick formatting.
* **@mentions** to notify a teammate.

![A comment pinned to a specific point on a screenshot](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-60237cd10e042c14ffa9fbec9aba7c6200015c51%2Freview-comment-on-screenshot.png?alt=media)

### Discuss in threads

Any comment can become a conversation:

* **Reply** to a comment to start a thread.
* **React** with emoji — open the **Add reaction** menu on a comment and pick one.
* **Resolve** a thread once the discussion is settled, and **reopen** it if it comes back up.

### Collaborate in real time

Everything on the build page updates live — comments, replies, reactions, resolutions, and review decisions appear as they happen, with no refresh.

**Presence dots** show who else is on the build and whether they're online, and each teammate's user card shows their role and current local time, so you know who's around before you wait on a review.

Review notifications arrive as a single digest rather than one message per action. To control your own notifications for a build, use the subscribe toggle in the **Activity** section of the sidebar.

![A user card showing a teammate's role, online status, and local time](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-b5e7ccf37b40abc476b69096abbc9064bf8cd104%2Freview-presence-user-card.png?alt=media)

### Reference a screenshot

Each screenshot pane has a **⋮** actions menu — on both the **baseline** and the **changes** — that makes it easy to reference a screenshot in a pull request or share it with a teammate:

* **Copy link** — copy a direct link to the screenshot.
* **Copy embed as Markdown** — copy the screenshot as a Markdown image embed, ready to paste into a PR description or comment.
* **Download** — download the screenshot. For a changed screenshot you can also download the **diff mask** and the **composed changes** (the diff overlaid on the screenshot).

![The screenshot actions menu with Copy link, Copy embed as Markdown, and Download options](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-780de8235c1e9fad15f0db9a10e6eb5d768643b2%2Fscreenshot-actions-menu.png?alt=media)

### Who can review

* Leaving reviews and comments requires a role that can review builds — team **Owners** and **Members**, or **Contributors** assigned the **Project Reviewer** or **Project Administrator** role. On a personal account, only its owner can review. The build must also be in a reviewable state (changes detected, approved, or rejected).
* On a public project, anyone can view builds, but signed-in visitors who aren't on the team can't review them. To let an outside contributor review the build of a pull request they opened from a fork, invite them to the team with a role that can review.
* Dismissing another person's review requires an administrator-level role.

See [Team members & roles](/docs/learn/account-and-access/team-members-and-roles) for the full permission breakdown.

#### Why is Submit review disabled?

Hover the disabled **Submit review** button to see the reason:

| Tooltip                                                | What it means                                                                                                        |
| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| "You must be a reviewer to approve or reject changes." | Your role on the project can't review builds. See the roles above.                                                   |
| "This build was triggered in a merge queue."           | The build ran in a [merge queue](/docs/learn/integrations/github-integration#merge-queue-support).                   |
| "Build is auto-approved"                               | The build ran on an [auto-approved branch](/docs/learn/platform-fundamentals/baseline-build#auto-approved-branches). |
| "No changes to review"                                 | The build has no changes.                                                                                            |

### Review from the API

Every review and comment action is also available in the [Argos REST API](https://argos-ci.com/docs/api-reference): submit, list, and dismiss reviews; create, read, update, and delete comments and replies; add and remove reactions; and resolve or reopen threads. Because these actions are attributed to a user and checked against that user's project permissions, they require a **personal access token** rather than a project token.

You can also submit reviews from the [Argos CLI](/docs/reference/argos-command-line-interface-cli#reviewing-and-commenting) with `argos review create`, or let an [AI agent review builds](/docs/learn/review-workflow/review-builds-with-ai-agents) for you.


# Pull request comments

Get the latest Argos build status and a link to the build page posted automatically on your GitHub pull requests.

Argos posts a comment on your GitHub pull requests with the latest build results, so your team sees visual changes without leaving the PR.

Every time a build status updates, Argos updates the comment with the latest status and a link to the build page.

{% hint style="info" %}
Pull request comments are available on GitHub. On GitLab, Argos reports results through [commit statuses](/docs/learn/review-workflow/summary-checks) instead.
{% endhint %}

![Argos GitHub pull request comment](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/5vmJOkAEzxrqgbuhPJVX/github%20pr%20comment%2054bebb73962dc705b32c33f577fb9dd0.png)

### What the comment shows

For each build, the comment lists its status, a link to inspect it, and a **Details** column that summarizes the changes — for example `2 added` or `4 changed, 3 ignored`. [Ignored screenshots](/docs/learn/reliability-and-flakiness/flaky-test-detection#ignore-changes) are counted here too, so you can see at a glance how much was filtered out as noise.

If you [deploy with Argos](/docs/learn/deployments), the comment also lists your deployments with their preview URLs.

### Silence pull request comments

To stop the Argos bot from commenting on your pull requests:

1. Go to your project **Settings** in Argos.
2. In the **Connected Git Repository** section, uncheck **Enable pull request comments**.
3. Save the settings.

Argos stops posting build status updates on your pull requests. Re-enable the checkbox at any time to turn them back on.


# Summary checks

Configure Argos summary checks to combine commit statuses into one required status check before merging.

A **summary check** is a single commit status — `argos/summary` — that combines the result of all Argos builds on a commit. When your CI produces several builds (for example, one per app in a monorepo), the summary check gives you one status to glance at, and one status to require.

By default, Argos adds the summary check **only when a commit has more than one build** — a single build keeps its individual status (`argos`, or `argos/<build-name>` for a named build) with no summary added. See [Commit status names](/docs/learn/integrations/github-integration#commit-status-names) for the exact context of each status Argos posts.

![Summary status check in GitHub](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/FNRkxoePm6BADbJLuvYW/summary%20check%20d7953362698c802ff3f81de23bfb83cc.png)

Summary checks are particularly useful as a [required status check before merging](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging): require the summary check instead of each individual build check, and the requirement keeps working as you add or rename builds.

![Required Argos status check in GitHub](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/ZMNlRIuPWRHhQbcGFY7b/required%20status%20check%200c8c4bf869c27334b7e13f5decd2ff08.png)

### Options

Argos offers three settings for summary checks:

* **Add a summary check only if there is more than one build** — The default, recommended for most projects. The summary check appears when there are multiple builds to combine, and stays out of the way otherwise.
* **Always add a summary check** — The summary check is posted even for a single build. Choose this if you require the summary check in your branch protection and some commits produce only one build — the required check is then always present.
* **Never add a summary check** — Disable summary checks entirely if they don't add value to your workflow.

Summary checks are an additional layer of status reporting — they never replace the individual build statuses.

### Configure summary checks

1. Go to your project **Settings** in Argos.
2. Locate the **Summary Checks** section.
3. Choose the option that fits your project.
4. Select **Save** to apply the changes.

![Configure summary checks in Argos](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/jCQDBAiCXLBeNQQnCZIH/configure%20summary%20checks%20db8f96ec82fc779a58d5e273c492cf02.png)


# Tags

Categorize and filter screenshots by tag so you can focus a build review on the changes you care about.

Tags let you categorize screenshots so you can quickly filter a build down to the changes you care about. For example, you might tag screenshots by page area, feature, or test suite and then review only the relevant subset during a build review.

### Filtering by tags in the UI

![Filter button](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/jM2lMOsdab5g9gw8TeBE/filter%20button%202%203476f14b8e3670b8e55d112ce91efa7b.png)

1. Open a build in Argos.
2. Click the **Filter button**.
3. Select one or more tags to narrow the screenshot list.
4. Once selected, a filter chip appears.

### Where tags come from

Tags can come from two sources in [screenshot metadata](/docs/reference/screenshot-metadata):

| Source         | Field       | Description                                                                                                                                       |
| -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Screenshot** | `tags`      | Custom tags you define to categorize the screenshot itself.                                                                                       |
| **Test**       | `test.tags` | Tags inherited from the test that generated the screenshot (e.g. Playwright [test tags](https://playwright.dev/docs/test-annotations#tag-tests)). |

### Adding custom tags through SDKs

#### Playwright

Playwright test tags are automatically forwarded to Argos. You have several options for how to add tags to your Playwright tests.

<pre class="language-ts" data-title="tests/homepage.spec.ts"><code class="lang-ts"><strong>import { test } from "@playwright/test";
</strong>import { argosScreenshot } from "@argos-ci/playwright";

// Option 1: At the describe level with the @ prefix
test.describe("Homepage @desktop @homepage", () => {
  // Option 2: In the test name with the @ prefix
  test("homepage @desktop @homepage", async ({ page }) => {
    await page.goto("/");
    await argosScreenshot(page, "homepage");
  });

  // Option 3: As a test option
  test("homepage", { tag: ["@desktop", "@homepage"] }, async ({ page }) => {
    await page.goto("/");
    await argosScreenshot(page, "homepage");
  });

  // Option 4: In the argosScreenshot options
  test("homepage", async ({ page }) => {
    await page.goto("/");
    await argosScreenshot(page, "homepage", {
      tag: ["@desktop", "@homepage"],
    });
  });
});
</code></pre>

Read the [Playwright test tags documentation](https://playwright.dev/docs/test-annotations#tag-tests) for more details on how to use test tags in your tests.

#### Cypress

In Cypress, you can add tags to screenshots through the `tag` option in the `cy.argosScreenshot` command.

{% code title="cypress/e2e/homepage.cy.js" %}

```js
it("screenshot homepage", () => {
  cy.visit("http://localhost:3000/");
  cy.argosScreenshot("homepage", { tag: ["@desktop", "@homepage"] });
});
```

{% endcode %}

#### Puppeteer

In Puppeteer, you can add tags to screenshots through the `tag` option in the `argosScreenshot` function.

{% code title="screenshot.mjs" %}

```js
import puppeteer from "puppeteer";
import { argosScreenshot } from "@argos-ci/puppeteer";

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("http://localhost:3000/");
await argosScreenshot(page, "homepage", {
  tag: ["@desktop", "@homepage"],
});
await browser.close();
```

{% endcode %}


# Review builds with AI agents

Let an AI agent read Argos build data to summarize visual changes, check intent, and approve or request changes.

Argos lets AI agents review visual changes in a pull request. The agent can use Argos build data to understand what changed, check whether the screenshots match the pull request's intent, and detect regressions.

By default, ask the agent to summarize the visual changes first. If you want it to take action, it can also create an Argos build review to approve the changes or request changes.

### What you need

* A pull request with an Argos build.
* The [Argos CLI](/docs/reference/argos-command-line-interface-cli).
* The [Argos agent skills](/docs/agents/agent-skills), when your assistant supports skills.
* An Argos project token to read build data with `build get` and `build snapshots`.
* A user token to create a build review, only if you want the agent to approve or request changes.

### Quick start

{% stepper %}
{% step %}

#### Install the skills

```bash
npx skills add https://argos-ci.com
```

{% endstep %}

{% step %}

#### Set a project token so the agent can read Argos build data

```bash
export ARGOS_TOKEN=<project-token>
```

{% endstep %}

{% step %}

#### If you want the agent to create the Argos build review from your local CLI session, authenticate as your Argos user

```bash
npx @argos-ci/cli login
```

{% endstep %}

{% step %}

#### Start a new AI chat and give the agent the pull request URL

```
Use $argos-pr-review to review this pull request with its Argos build.
https://github.com/acme/app/pull/123
```

The `$argos-pr-review` skill contains the detailed review workflow. The `$argos-cli` skill gives the agent the CLI commands, flags, authentication rules, and output formats.
{% endstep %}
{% endstepper %}

### What the agent does

The agent will inspect the pull request, find the Argos build from the pull request status check or Argos comment, and use Argos build data to understand the visual changes.

It should first summarize what changed visually. Then it compares the screenshots with the pull request intent.

When the screenshots match the pull request intent, the agent can suggest approving the build. When they reveal a regression, it can suggest requesting changes and explain which snapshots are affected.

If the build is still pending, the agent should wait before reviewing. If there are no visual changes or the build is already accepted, no visual review is needed.

### Other cases

#### If the agent cannot access the pull request

You can provide the Argos build URL and a short description of the expected change:

```
Use $argos-pr-review to review this Argos build:
https://app.argos-ci.com/acme/app/builds/456

Expected change:
The login page was redesigned with social login options.
```

The build data still lets the agent explain what changed visually, but the pull request context makes the review decision stronger.

#### If your assistant does not support skills

Use a more explicit prompt:

```
Review the visual changes in this pull request with Argos:
https://github.com/acme/app/pull/123

Use the pull request title, description, linked issue, and code diff to understand the intended change.
Find the Argos build, fetch the build metadata, and fetch the snapshots that need review.
Use the diff mask, baseline file, current file, and metadata as visual evidence.

First summarize what changed visually in the build.
Then compare the screenshots with the pull request intent.

If the snapshots match the intent, suggest approving the Argos build.
If they reveal a regression, suggest requesting changes and list the affected snapshot names.
If they look flaky, explain the flake signal and do not suggest approval until it is resolved.
```

To let the assistant create the Argos build review, add an explicit instruction. The CLI must be authenticated as an Argos user with `argos login`, or the assistant must pass a personal access token to `argos review create`:

```
After summarizing the visual changes, create an Argos build review with the appropriate conclusion.
```

#### Authentication details

Reading build data requires a project token. Creating a build review requires a user token because the review is attributed to an Argos user and checked against that user's project permissions.

For details about token precedence, `--token`, and personal access tokens, see [Argos CLI authentication](/docs/reference/argos-command-line-interface-cli#authentication).

### Limits

Argos still performs deterministic visual comparison. AI agents use Argos build data as review evidence; they do not replace your team's ownership of review decisions.


# Automations

Create rules that react to build events and notify Slack, Microsoft Teams, or Discord, so nobody has to watch Argos to know a build needs review.

An automation is a rule on a project: **when** something happens to a build, **if** it matches your conditions, **then** Argos notifies a channel. It replaces the person who would otherwise have to notice a build finished and relay it.

### Open the Automations tab

1. Open your project in Argos.
2. Select the **Automations** tab.

Automations are available on team projects, and the tab requires permission to view project settings.

### The three parts of a rule

<table><thead><tr><th width="120">Step</th><th>What it does</th></tr></thead><tbody><tr><td><strong>When</strong></td><td>The build events that trigger the rule. A rule fires if <em>any</em> selected event happens.</td></tr><tr><td><strong>If</strong></td><td>Optional conditions that must <em>all</em> match for the rule to act.</td></tr><tr><td><strong>Then</strong></td><td>The actions Argos runs.</td></tr></tbody></table>

### When: build events

* **Build Completed** — the build finished processing, whatever its outcome.
* **Build Reviewed** — someone approved or rejected the build.

### If: conditions

Conditions narrow a rule so it only fires on the builds you care about. Every condition you add must match.

* **Build conclusion** — `no changes` or `changes detected`.
* **Build type** — `reference`, `check`, or `orphan`. See [build types](/docs/learn/platform-fundamentals#build-types).
* **Build mode** — `CI` or `monitoring`. See [build modes](/docs/learn/platform-fundamentals/build-modes).
* **Build name** — an exact build name, useful when your CI [splits builds](/docs/learn/how-to-guides/ci-pipelines/monorepos-setup).
* **Build branch** — an exact branch name, or a glob pattern such as `release/*`.

Each condition can be negated, so you can express "every branch except `main`" as a negated **Build branch** condition.

{% hint style="info" %}
Conditions are combined with AND. To notify on two unrelated cases — say, failures on `main` and anything on `release/*` — create two automations rather than one rule with both conditions.
{% endhint %}

### Then: actions

* **Post in Slack channel** — posts to a Slack channel. Requires the [Slack integration](/docs/learn/integrations/slack-integration).
* **Post in Microsoft Teams channel** — posts an adaptive card to a channel webhook. Requires the [Microsoft Teams integration](/docs/learn/integrations/microsoft-teams-integration).
* **Post in Discord channel** — posts an embed to a channel webhook. Requires the [Discord integration](/docs/learn/integrations/discord-integration).

If the destination isn't connected yet, the action offers the connection flow.

### Create an automation

{% stepper %}
{% step %}

#### Start a new rule

From the **Automations** tab, select **New Automation** and give it a name that says what it does — for example "Notify #frontend when main has changes".
{% endstep %}

{% step %}

#### Choose the events

Under **When**, select one or more build events.
{% endstep %}

{% step %}

#### Narrow it down

Under **If**, add conditions. Skip this step to act on every build that triggers the events.
{% endstep %}

{% step %}

#### Pick the destination

Under **Then**, choose an action and select the channel it posts to.
{% endstep %}

{% step %}

#### Test and save

Send a test notification to confirm the channel receives it, then save the automation.
{% endstep %}
{% endstepper %}

### Common recipes

<details>

<summary>Tell the team when a build needs review</summary>

**When** Build Completed · **If** Build conclusion is `changes detected` and Build type is `check` · **Then** notify your review channel.

This skips builds with no changes and skips reference builds, so the channel only hears about work that actually needs a human.

</details>

<details>

<summary>Watch a release branch only</summary>

**When** Build Completed · **If** Build branch matches `release/*` · **Then** notify your release channel.

</details>

<details>

<summary>Close the loop after review</summary>

**When** Build Reviewed · **Then** notify the channel where the build was announced, so the thread ends with the outcome.

</details>

### FAQ

<details>

<summary>Why didn't my automation fire?</summary>

Check the conditions first: they are combined with AND, so one non-matching condition stops the rule. Then confirm the event you selected is the one that actually happened — a build finishing raises **Build Completed**, not **Build Reviewed**.

</details>

<details>

<summary>Can an automation call my own endpoint?</summary>

Not today. Argos has no user-configurable outgoing webhooks — see [Integrations](/docs/learn/integrations#webhooks) for the alternatives, and tell us if webhooks would unblock your workflow.

</details>

<details>

<summary>Do automations apply to every project in my team?</summary>

No. Automations are configured per project, so a rule you create on one project doesn't run on the others.

</details>


# Reliability & flakiness

Detect unstable tests and use Argos tools and strategies to track and fix flakiness in your visual suite.

Flaky tests fail or change without a meaningful code change and erode trust in your suite. Argos detects unstable screenshots automatically, ranks them so you know where to act, and gives you a playbook to fix the root causes.

* [Flaky test detection](/docs/learn/reliability-and-flakiness/flaky-test-detection) – How Argos flags unstable tests and ignores noisy changes
* [Tests dashboard](/docs/learn/reliability-and-flakiness/tests-dashboard) – A project-wide view that ranks tests by flakiness
* [Test page](/docs/learn/reliability-and-flakiness/test-page) – Everything Argos knows about one test: its metrics, its changes, and its discussion
* [Ignored changes](/docs/learn/reliability-and-flakiness/ignored-changes) – Review what a project ignores and unignore the changes that went quiet
* [Fix flaky tests with AI agents](/docs/learn/reliability-and-flakiness/fix-flaky-tests-with-ai-agents) – Hand a flaky test to an agent and let it find the non-determinism
* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) – Strategies for writing stable, deterministic visual tests


# Flaky test detection

See flaky badges, stability scores, and detailed history so you can review changed tests in Argos with confidence.

Argos flags unstable tests so you can decide with confidence. See a flaky badge next to every changed test and dive into detailed history and stability scores on a dedicated test page.

![Flaky indicator next to a test change](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/N3TiuBm33iEPOf8RsYp3/flaky%20indicator%208b0a3212a9d038be252e87cea60354d8.png)

*Example of the flaky indicator in a build review*

### View flaky indicators in your build review

1. Open any build in Argos.
2. Spot the flaky badge beside each changed test.
3. Hover over the badge to see details about the test's stability.

### Explore the test page

Select the flaky badge to open the [test page](/docs/learn/reliability-and-flakiness/test-page), which gathers everything Argos knows about that one test: its flakiness score, how its metrics moved over time, every change it produced, the discussion your team had about it, and a prompt to hand to an AI agent.

### See all flaky tests at a glance

The test page focuses on a single test. For a project-wide view that ranks every test by flakiness score, open the [Tests dashboard](/docs/learn/reliability-and-flakiness/tests-dashboard).

### Ignore changes

When reviewing a build in Argos, you may encounter changes that are not relevant or are caused by flakiness. You can **ignore a specific change** directly from the UI.

From the build page or the test page, select the **Ignore** button next to the change you want to ignore.

Once ignored, Argos will no longer notify you if this **exact same change** happens again in future builds. This lets you filter out noise while keeping future regressions detectable.

#### How Argos recognizes the same change

Each visual change carries a **fingerprint**: a stable signature computed from the shape of its diff. Visually similar diffs on the same test produce the same fingerprint, and an ignored change is a test-plus-fingerprint pair — any future diff matching the pair is ignored automatically. The fingerprint absorbs pixel-level noise such as antialiasing but distinguishes genuinely different changes, so ignoring one flaky change never hides a new regression elsewhere in the screenshot. You'll encounter the fingerprint in the [API](https://argos-ci.com/docs/api-reference) and [CLI](/docs/reference/argos-command-line-interface-cli#silencing-a-flaky-change) as part of a change's identity.

Every change a project ignores is listed on the [Ignored changes](/docs/learn/reliability-and-flakiness/ignored-changes) page, with how often each one still fires — so an ignore that outlived its flake doesn't stay a blind spot.

Ignored screenshots are also reflected in the [pull request comment](/docs/learn/review-workflow/pull-request-comments): the **Details** column reports how many screenshots were ignored alongside the other counts (for example, `4 changed, 3 ignored`).

### Configure what Argos ignores

You control the ignore feature per project from **Project Settings → Flaky detection**.

The **Ignore changes** card has a single toggle, **Enable the ignore feature for this project**, which is on by default. When you turn it off:

* New builds ignore nothing — every change is treated as not ignored.
* Auto-ignore is turned off.
* The **Ignore** button is hidden when reviewing builds.

{% hint style="info" %}
Disabling the feature only changes how **new** builds are computed. Previous builds are not affected — changes already ignored stay ignored on those builds.
{% endhint %}

### Automatically ignore recurring flaky changes

When the ignore feature is enabled, Argos can automatically ignore recurring flaky changes across builds, so you don't have to ignore each one by hand.

To configure auto-ignore:

1. Open your project in Argos.
2. Go to **Project Settings → Flaky detection**.
3. In the **Automatically ignore flaky changes** card, enable **Auto-ignore flaky changes**.
4. Set **Minimum occurrences to consider a change flaky (last 7 days)**.
5. Select **Save**.

![Auto-ignore flaky changes in project settings](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/b9SQl7BRMA2Dr6oECEU7/auto%20ignore%20fce4000798bcef00801460c36c56dbad.png)

A change is considered flaky once it appears at least this many times in auto-approved builds within the last 7 days. The default threshold is **3** occurrences.

#### Best use cases

* Flaky UI elements that appear/disappear randomly or render inconsistently.
* Non-deterministic image rendering (e.g. base64 previews, antialiasing issues).


# Tests dashboard

Get a project-wide view of test stability, with every test ranked by flakiness score in the Argos dashboard.

The Tests dashboard gives you a view of test stability ranked by flakiness score, so the most flaky tests show up first. It comes in two scopes: a **project** dashboard for a single project, and an **account** dashboard that aggregates tests across every project you can access.

![Tests dashboard showing a list of tests with flakiness metrics](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/SQXAdlnqhP96zoibqxgN/tests%20dashboard%200e272203e4b55b8d11f669564169009f.png)

***A project Tests dashboard with flakiness metrics***

### Open the project dashboard

1. Open your project in Argos.
2. Select the **Tests** tab.

### How tests are ranked

Tests are sorted by **flakiness score** (descending). The tests at the top are the most flaky.

### Columns explained

* **Test**: The latest screenshot uploaded for the test, the test name, and the build name.
* **Last change**: The most recent change detected on an auto-approved build during the selected period.
* **Flakiness**: A score that summarizes how flaky a test is based on its stability and consistency.
* **Changes**: The number of changes detected for the test during the selected period.
* **Stability**: The ratio of changes to total reference builds. A lower stability rate means the test is more likely to be flaky.
* **Consistency**: The ratio of one-off changes to total changes. A lower consistency rate means the test is more likely to be flaky.

### Filter and time range

* Filter tests by **build name** to focus on a subset of runs.
* Choose a **time period** to control which changes and scores are included.

### Open a test page

Select any row to open the [test page](/docs/learn/reliability-and-flakiness/test-page), which breaks the same metrics down over time, lists every change the test produced, and carries its discussion.

### See tests across all your projects

The account-level **Tests** dashboard aggregates the active tests across every project you can see and ranks them by flakiness, so the noisiest tests surface first — wherever they live.

1. Select your account or team in Argos.
2. Select the **Tests** tab.

It shows the same flakiness, stability, and consistency metrics as the project dashboard, plus a **Project** column so you can tell where each test comes from. A test counts as **active** when it appears in the latest reference build for its build name. You only see tests from projects you have access to.


# Test page

Investigate a single test on its own page: flakiness metrics over time, every change it produced, discussion with your team, and a prompt to hand to an AI agent.

The [Tests dashboard](/docs/learn/reliability-and-flakiness/tests-dashboard) tells you *which* tests are flaky. The test page tells you *why*. It gathers everything Argos knows about one test: how often it changed, what those changes looked like, when they started, what your team said about it, and a prompt you can hand to an AI agent to fix it.

![Test page showing history and flaky score](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/hJiA0wIlYwpV0YdMxRP9/test%20page%20example%2044fb44458d2d5a457bad6eb9d6ddb29c.png)

*A test page with its metrics and change history*

### Open a test page

Any of these routes get you there:

* From the [Tests dashboard](/docs/learn/reliability-and-flakiness/tests-dashboard), select a row.
* From a build review, select the [flaky badge](/docs/learn/reliability-and-flakiness/flaky-test-detection) next to a changed test.
* From the [Ignored changes](/docs/learn/reliability-and-flakiness/ignored-changes) page, select the test a change belongs to.

The page header shows the test name and its status:

* **Ongoing** — the test still runs. It appeared in the latest build of its build name.
* **Removed** — it did not, so it was deleted, renamed, or skipped.

### Choose the period

Every number and chart on the page is computed over a period you pick at the top: the last 24 hours, 3 days, 7 days, 30 days, or 90 days. It defaults to the last 7 days, and it lives in the URL — so a link you share opens on the same window you were looking at.

Only **auto-approved builds** count. A change on a branch usually means someone changed the UI on purpose; a change on an auto-approved build means the test moved on its own.

### Read the metrics

| Metric          | What it means                                                                           |
| --------------- | --------------------------------------------------------------------------------------- |
| **Flakiness**   | An overall score from 0 to 100 derived from stability and consistency. Higher is worse. |
| **Builds**      | How many builds ran the test over the period.                                           |
| **Changes**     | How many times it changed over the period.                                              |
| **Stability**   | The share of builds where it did *not* change. Lower means it changes often.            |
| **Consistency** | The share of changes that were one-offs. Lower means the same change keeps coming back. |

The chart beside them breaks the same counts down over time, which is what tells a test that has *always* been flaky apart from one that only started recently. A cliff in the chart usually points at the commit that introduced the instability.

In the sidebar, **First change** and **Last change** link to the builds where the test first and last changed — regardless of the selected period, so you can find the origin of a long-running flake.

### Explore the changes

A **change** is one exact visual difference, not one occurrence of it. Argos groups every diff that looks the same under a single change, so a screenshot flickering between two states is two changes here — however many builds it flickered in. That grouping is what makes a recurring change visible: it's the one with a high occurrence count.

The changes list is ordered by how often each one came back, and every card tells you which kind you're looking at:

* **Recurring — N×** — it came back N times. Nothing in the UI explains a change that reappears, so this is the flakiness signal to chase.
* **One-off** — it happened once. More likely a real, intentional change.
* **Ignored** — Argos skips it when it reappears. See [Ignored changes](/docs/learn/reliability-and-flakiness/ignored-changes).

Select a card to inspect that change: the baseline next to the captured screenshot, its **Occurrences** out of the total build count, and when it was **First seen** and **Last seen**, each linking to the build.

Use the **All** / **Ignored** toggle above the list to switch between every change and only the ignored ones. Like the period, the filter is in the URL.

From the toolbar you can **Ignore** the change you're looking at, exactly as you would from a build review — see [Ignore changes](/docs/learn/reliability-and-flakiness/flaky-test-detection#ignore-changes).

{% hint style="info" %}
Comment pins aren't available in this view. The changes explorer shows a test's history rather than one build's review, so comments belong to the test as a whole — see below.
{% endhint %}

### Discuss the test with your team

The **Activity** section in the sidebar is the test's own conversation, separate from any build review. Use it for what belongs to the test rather than to one run: "this one has been flaky since we added the carousel", "waiting on the upstream fix", "safe to ignore until Q3".

* **Leave a comment** in the composer at the bottom. Comments are written in Markdown, with `/` slash commands for formatting and **@mentions** to notify a teammate.
* **Reply** to a comment to start a thread, **react** with an emoji, and **resolve** a thread once it's settled — reopening it if it comes back.
* The feed opens with when Argos first saw the test, then every thread, oldest first.

Unlike [build comments](/docs/learn/review-workflow/review-a-build#comment-on-exactly-what-changed), a comment on a test posts immediately: there's no pending review to batch it into.

Use the bell in the **Activity** header to **follow** the test and get notified of new comments. Commenting follows it for you.

Leaving comments requires a role that can review — see [Team members & roles](/docs/learn/account-and-access/team-members-and-roles).

### Fix the flakiness with an AI agent

The **Fix with AI** card gives you a prompt to copy into a coding agent working in your repository — Claude Code, Codex, Cursor, or any other. The prompt names the test, carries the flakiness Argos measured, and tells the agent how to pull the recurring changes and their screenshots for itself before it starts editing.

1. Select **Copy prompt**. Use **Preview the prompt** first if you want to read it.
2. Paste it into your agent, in a session that has access to the repository.

The card expands on its own when the test looks flaky. On a stable test there's nothing to fix, so it stays folded into its header — still one click away.

For what the agent needs to authenticate and what to expect from it, see [Fix flaky tests with AI agents](/docs/learn/reliability-and-flakiness/fix-flaky-tests-with-ai-agents).

### Read the same data from the API or CLI

Everything on this page is available outside the UI, so you can script it or hand it to an agent:

```bash
argos test get <testId> --json
argos test changes <testId> --json
```

See [Inspecting builds and tests](/docs/reference/argos-command-line-interface-cli#inspecting-builds-and-tests) in the CLI reference for what each command returns, and [Reviewing and commenting](/docs/reference/argos-command-line-interface-cli#reviewing-and-commenting) for `test comment`. The same data is available over the [REST API](https://argos-ci.com/docs/api-reference) and the [MCP server](/docs/agents/mcp-server).


# Ignored changes

Review every change your project has ignored, see how much review noise each one absorbs, and unignore the ones that went quiet.

Ignoring a change is easy to do and easy to forget. The **Ignored** tab lists every change a project currently ignores, so you can tell which ignores are still earning their keep and which have become blind spots.

### Open the Ignored tab

1. Open your project in Argos.
2. Select the **Ignored** tab.

The tab only appears when the ignore feature is enabled for the project. See [Configure what Argos ignores](/docs/learn/reliability-and-flakiness/flaky-test-detection#configure-what-argos-ignores).

### What each row shows

Changes are listed most recently ignored first.

* **Change** — the most recent screenshot carrying this change, with the test name and build name. Select the row to open the change on the test page.
* **Ignored** — when the change was ignored and who ignored it. Changes that [auto-ignore](/docs/learn/reliability-and-flakiness/flaky-test-detection#automatically-ignore-recurring-flaky-changes) muted are attributed to the Argos bot and carry an **Auto** badge.
* **Occurrences** — how many auto-approved builds have shown this exact change since it was ignored. This is the review noise the ignore has absorbed.
* **Last seen** — the last build in which this exact change appeared.

### Decide what to unignore

The two right-hand columns are what make the list worth revisiting.

A change with a high **Occurrences** count is doing its job: it would have interrupted a review that many times. A change that hasn't been seen in weeks is the opposite — the flake behind it has probably been fixed, and the ignore is now hiding any genuine regression that happens to produce the same diff.

{% hint style="info" %}
An ignore is scoped to one test and one diff fingerprint, so it never hides a different change to the same screenshot. See [How Argos recognizes the same change](/docs/learn/reliability-and-flakiness/flaky-test-detection#how-argos-recognizes-the-same-change).
{% endhint %}

### Unignore a change

1. Hover the row you want to unignore.
2. Select **Unignore**, then confirm.

Argos treats the change as a change again, so the next build showing it will ask for review. The confirmation offers **Undo** if you change your mind.

Unignoring requires the **review** permission on the project.

### FAQ

<details>

<summary>Why does a row show 0 occurrences?</summary>

The change hasn't reappeared in an auto-approved build since it was ignored. Either the flake stopped, or the change simply hasn't run again. Check **Last seen** to tell the two apart.

</details>

<details>

<summary>Does turning the ignore feature off clear this list?</summary>

No. Ignored changes are kept, and the list is still there when you turn the feature back on. While it's off, new builds ignore nothing.

</details>

<details>

<summary>Can I unignore everything at once?</summary>

Not from this page — changes are unignored one at a time. You can also unignore a change from the build page or the test page using the same **Ignore** button that set it.

</details>


# Fix flaky tests with AI agents

Hand a flaky test to an AI agent: it reads the flakiness metrics and recurring changes from Argos, finds what makes the screenshot unstable, and fixes it in your repository.

Fixing a flaky visual test is mostly investigation: work out which change keeps coming back, look at what moves in the screenshot, then find the non-determinism in the test or the UI. An AI agent working in your repository can do all of it, because Argos exposes the evidence it needs over the CLI, the MCP server, and the REST API.

The [test page](/docs/learn/reliability-and-flakiness/test-page) hands you a prompt that does exactly that.

### What you need

* A flaky test on the [Tests dashboard](/docs/learn/reliability-and-flakiness/tests-dashboard).
* An AI agent with access to the repository that contains the test.
* The [Argos CLI](/docs/reference/argos-command-line-interface-cli), the [MCP server](/docs/agents/mcp-server), or plain HTTP access to the [REST API](https://argos-ci.com/docs/api-reference) — whichever your agent already has.
* A token the agent can read with. A **project token** is enough to read a test and its changes; ignoring a change needs a [personal access token](/docs/reference/argos-command-line-interface-cli#project-tokens-and-personal-access-tokens).

### Quick start

{% stepper %}
{% step %}

#### Open the flaky test

From the [Tests dashboard](/docs/learn/reliability-and-flakiness/tests-dashboard), select the test you want to fix.
{% endstep %}

{% step %}

#### Give the agent a token

```bash
export ARGOS_TOKEN=<project-token>
```

Skip this if your agent is already connected to the [MCP server](/docs/agents/mcp-server) or signed in with `argos login`.
{% endstep %}

{% step %}

#### Copy the prompt

In the **Fix with AI** card, select **Copy prompt**. It is filled in with this test's id, its measured flakiness, and the calls to make.
{% endstep %}

{% step %}

#### Paste it into your agent

Run it in a session that has your repository checked out, so the agent can find the test and change it.
{% endstep %}
{% endstepper %}

### What the agent does

The prompt walks the agent through the same investigation you would run by hand:

1. **Read the evidence.** It fetches the test's flakiness metrics and its changes, most frequent first, with the diff mask, baseline, and captured screenshot for each.
2. **Look at what moves.** The changes with the highest occurrence count are the flaky ones — a difference that keeps reappearing while nothing in the UI changed.
3. **Find the non-determinism.** It locates the test in your repository from its name and build name, then looks for the usual culprits: animations and transitions still running, dates and times, random or unordered data, fonts or images that haven't loaded, network timing, scrollbars, and text carets.
4. **Fix the root cause.** Waiting for the real end state instead of a fixed delay, freezing the clock and the random source, ordering the data. The prompt asks it to prefer making the UI deterministic over masking the region.
5. **Report back** what was non-deterministic and what it changed.

If a change turns out to be noise that genuinely cannot be made deterministic, the prompt tells the agent to [ignore it](/docs/learn/reliability-and-flakiness/flaky-test-detection#ignore-changes) instead of contorting the test.

{% hint style="info" %}
[Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) documents the same fixes for a human reader. It's worth pointing your agent at when it needs the details for a specific framework.
{% endhint %}

### Without the prompt

The prompt is a convenience, not a requirement — any agent that can reach Argos can do this. The two calls that carry the evidence are:

```bash
argos test get <testId> --json      # flakiness, stability, consistency, and the trend over time
argos test changes <testId> --json  # each change with its occurrence count and screenshot URLs
```

Over MCP, they're the `getTest` and `listTestChanges` tools. Over HTTP, they're `GET /projects/{owner}/{project}/tests/{testId}` and `GET /projects/{owner}/{project}/tests/{testId}/changes`.

Install the [Argos agent skills](/docs/agents/agent-skills) to teach your assistant the CLI's commands, flags, and token rules up front:

```bash
npx skills add https://argos-ci.com
```

### Limits

An agent is good at spotting non-determinism and proposing a fix, but it is guessing at intent from a screenshot. Review its change like any other: a test that stops changing because it stopped asserting anything is worse than a flaky one. Argos keeps doing the deterministic comparison either way — a fix that doesn't work will show up as a change again on the next build.

### Related

* [Test page](/docs/learn/reliability-and-flakiness/test-page) — where the prompt lives, and the rest of what Argos knows about a test.
* [Review builds with AI agents](/docs/learn/review-workflow/review-builds-with-ai-agents) — the same idea applied to reviewing a pull request's visual changes.
* [Flaky test detection](/docs/learn/reliability-and-flakiness/flaky-test-detection) — how Argos scores flakiness and what ignoring a change does.


# Stabilize screenshots and fix flaky visual tests

A playbook for tackling visual flakiness, with detection, ignoring noisy changes, and stabilization strategies.

The most reliable visual test suite is one where flakiness is fixed at the source. This playbook covers the strategies that make screenshots deterministic — from waiting for loading to freezing dynamic values.

{% hint style="info" %}
Argos also detects and filters flakiness for you: see [Flaky test detection](/docs/learn/reliability-and-flakiness/flaky-test-detection) for flaky badges, ignore, and auto-ignore, and the [Tests dashboard](/docs/learn/reliability-and-flakiness/tests-dashboard) for a project-wide ranking.
{% endhint %}

### Common causes

Visual flakiness usually stems from one of the following:

* **Dynamic content**: Ads, user-generated content, or other data that changes between runs.
* **Asynchronous loading**: Elements or styles that render at inconsistent times.
* **Rendering differences**: Browser, OS, or device variations that alter visual output.
* **External dependencies**: Reliance on third-party systems that introduce variability.
* **Animations and transitions**: Unhandled motion captured mid-frame.
* **Resolution and scaling**: Screen size or pixel density differences between environments.

### Best practices for stable screenshots

Follow these practices — each one targets a common cause above:

{% hint style="info" %}
Most stabilization is automatic. The Argos SDK stabilizes every screenshot by default — waiting for `aria-busy` (off by default [in Storybook](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-loading#storybook)), fonts, and images to settle, forcing font antialiasing, hiding carets and scrollbars, and more. You can customize or disable any of it through the [`stabilize` option](/docs/reference/playwright). The practices below cover what the SDK can't infer on its own, such as which elements are loaders or which values are dynamic.
{% endhint %}

* **Wait until the page is ready before capturing.** Mark loading elements with `aria-busy` so `argosScreenshot()` waits for them. → [Wait for loading](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-loading)
* **Wait for CSS background images to load.** Flag elements with `data-visual-test-wait-bg-img` so Argos waits for their backgrounds — or extend the check to the whole page. → [Wait for background images](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-background-images)
* **Freeze animated GIFs.** GIFs are paused on their first frame by default so they don't capture a random frame on each run. → [Pause animated GIFs](/docs/learn/reliability-and-flakiness/flaky-tests/pause-gifs)
* **Stop animations before capturing.** The Playwright SDK takes screenshots with CSS animations and transitions stopped, and pauses SVG animations that loop forever. → [Playwright SDK reference](/docs/reference/playwright)
* **Make dates and times deterministic.** Hide or freeze any value that changes between runs. → [Stabilize date & time](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-date-and-time)
* **Reduce text rendering differences.** Disable subpixel text and font hinting so glyphs vary less between machines. → [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering)
* **Run the same environment everywhere, and tame rendering quirks.** Use the same OS and browser locally and on CI, and smooth over properties like `border-radius`. → [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches)
* **Mask or hide unavoidable dynamic content.** Use `data-visual-test` attributes for anything you can't stabilize at the source. → [Argos helpers](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers)

{% hint style="success" %}
**New to visual testing?** Start with [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering) and [Wait for loading](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-loading). Together they prevent the large majority of flaky screenshots.
{% endhint %}

### Emphasis on accessibility

At Argos, we advocate for accessibility-focused end-to-end testing. Improving accessibility (semantic markup, `aria-busy`, predictable focus states) not only supports users with disabilities but also makes tests more deterministic and less prone to flakiness.


# Stabilize text rendering

Disable subpixel text and font hinting in headless Chromium to reduce glyph rendering differences between machines.

Reduce glyph rendering differences between machines: disable subpixel (LCD) text and font hinting in headless Chromium. This removes one of the most common sources of screenshot noise. It does not make macOS and Linux render text identically, so capture your screenshots in a single environment, usually CI.

### Why text causes flaky screenshots

By default, Chromium renders text using two techniques that depend on the underlying operating system, GPU, and font stack:

* **Subpixel (LCD) antialiasing** uses the red, green, and blue subpixels of a screen to smooth glyph edges. It leaves faint red/blue color fringing that differs from one machine to another.
* **Font hinting** snaps glyphs to the pixel grid. The result varies between platforms, shifting antialiasing by a pixel here and there.

The consequence: a screenshot captured locally on macOS rarely matches the exact same screenshot captured on a Linux CI runner. Argos sees these sub-pixel differences as real changes and reports false positives, even though no code changed.

### The fix

Launch Chromium with two flags that remove the most variable parts of text rendering:

* `--disable-lcd-text` — forces **grayscale antialiasing** instead of subpixel rendering, removing the red/blue edge fringing.
* `--font-render-hinting=none` — disables font hinting, so glyphs are no longer snapped to each platform's pixel grid. Chromium only reads this flag in headless mode (the headless shell Playwright uses by default); it has no effect on a headed browser.

{% tabs %}
{% tab title="Playwright" %}
Add the launch options to the `use` block of your Playwright configuration:

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
  use: {
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },
});
```

{% endcode %}
{% endtab %}

{% tab title="Storybook (Vitest)" %}
Pass the launch options to the Playwright provider in your Vitest configuration:

{% code title="vitest.config.ts" %}

```ts
import { defineConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";

export default defineConfig({
  test: {
    browser: {
      enabled: true,
      provider: playwright({
        launchOptions: {
          args: ["--disable-lcd-text", "--font-render-hinting=none"],
        },
      }),
      instances: [{ browser: "chromium" }],
    },
  },
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

{% hint style="success" %}
These flags reduce text rendering differences **across** environments. They don't remove them: a system font stack resolves to different fonts on each operating system, and macOS and Linux rasterize glyphs differently. Compare screenshots captured in the same environment; with Argos, baselines come from your CI builds.
{% endhint %}

{% hint style="info" %}
Rendering flakiness is reduced even further when local and CI runs share the **exact same environment** (OS and browser). See [Browser Glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches) for more on standardizing environments.
{% endhint %}


# Wait for loading

Use aria-busy so argosScreenshot waits for full page load before capturing, and see what Argos waits for before each screenshot, in Storybook too.

A screenshot taken while the page is still loading produces a different image on every run. Mark your loading states with `aria-busy`, and `argosScreenshot()` waits until they are gone before capturing.

### Usage

Add `aria-busy` to your loader components:

```jsx
<Loader aria-busy={true} />
```

`argosScreenshot()` delays the capture until no visible element has `aria-busy="true"`, so the screenshot always shows the fully loaded state. Make sure your app removes the attribute, sets it to `false`, or hides the loader once loading completes.

{% hint style="info" %}
Waiting for `aria-busy` is part of the SDK's default stabilization (the `stabilize.waitForAriaBusy` option), so you only need to mark your loaders. Storybook is the exception: see below.
{% endhint %}

### Storybook

In `@argos-ci/storybook`, the screenshots Argos takes automatically of each story don't wait for `aria-busy` by default. Turn it on with the `stabilize` option:

{% tabs %}
{% tab title="Vitest plugin" %}
{% code title="vitest.config.ts" %}

```ts
argosVitestPlugin({
  uploadToArgos: !!process.env.CI,
  stabilize: { waitForAriaBusy: true },
}),
```

{% endcode %}
{% endtab %}

{% tab title="Test Runner" %}
{% code title=".storybook/test-runner.ts" %}

```ts
const config: TestRunnerConfig = {
  async postVisit(page, context) {
    await argosScreenshot(page, context, {
      stabilize: { waitForAriaBusy: true },
    });
  },
};
```

{% endcode %}
{% endtab %}
{% endtabs %}

Any `stabilize` object you pass replaces the Storybook default as a whole, so it turns `aria-busy` waiting back on unless it sets `waitForAriaBusy: false`. Screenshots you take with `argosScreenshot` in a play function use the SDK default and wait for `aria-busy`.

### What Argos waits for

Before each capture, Argos waits until:

* no visible element has `aria-busy="true"`,
* fonts are loaded,
* `<img>` elements, including lazy-loaded ones, have finished loading (a broken image doesn't block),
* CSS background images of elements flagged with `data-visual-test-wait-bg-img` are loaded. See [Wait for background images](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-background-images).

Argos doesn't wait for network requests to finish: there is no equivalent of Playwright's `networkidle`. If your page fetches data, mark its loading state with `aria-busy`.

With the Playwright and Storybook SDKs, a page that doesn't settle in time fails the screenshot instead of producing a half-loaded capture. The Playwright SDK waits up to the test timeout. The error lists the checks that did not pass:

```
Failed to stabilize screenshot, found the following issues:
- Some elements still have `aria-busy='true'`
```


# Wait for background images

Wait for CSS background images to load before capturing with the waitForBackgroundImages stabilization option.

CSS background images have no native load event, so the SDK can't wait for them the way it waits for `<img>` elements. When a background image is still loading—or re-fetches on a viewport change, as responsive backgrounds often do—the screenshot can capture a half-painted element and turn flaky.

The `waitForBackgroundImages` stabilization option closes that gap: it discovers background image URLs (including those set on `::before` and `::after`), preloads them, and waits for them to finish before the screenshot is taken.

{% hint style="info" %}
A full-document scan for background images is expensive, so by default the scan is limited to the elements you flag with the `data-visual-test-wait-bg-img` attribute. This keeps stabilization fast while still covering the elements that need it.
{% endhint %}

### Flagging elements

Add the `data-visual-test-wait-bg-img` attribute to an element to wait for its background image—and the background images of everything nested inside it—before capturing. This works out of the box with the default `stabilize: true`:

```html
<section class="hero" data-visual-test-wait-bg-img>…</section>
```

### Scanning the whole document

Pass `true` to scan every element instead of only the flagged ones:

{% tabs %}
{% tab title="Playwright" %}

```ts
await argosScreenshot(page, "homepage", {
  stabilize: { waitForBackgroundImages: true },
});
```

{% endtab %}

{% tab title="Cypress" %}

```ts
cy.argosScreenshot("homepage", {
  stabilize: { waitForBackgroundImages: true },
});
```

{% endtab %}
{% endtabs %}

On large pages, narrow the scan to the elements that actually use background images by passing a selector instead:

```ts
await argosScreenshot(page, "homepage", {
  stabilize: { waitForBackgroundImages: { selector: ".hero, [data-bg]" } },
});
```

### Disabling it

To turn the scan off entirely, pass `false`:

```ts
await argosScreenshot(page, "homepage", {
  stabilize: { waitForBackgroundImages: false },
});
```

A failed background image (for example a 404) is treated as loaded, so a broken URL never blocks stabilization.

{% hint style="info" %}
The scan runs once per viewport before the wait begins, so background images added to the DOM *during* the wait window aren't picked up. For the common responsive-viewport case this is exactly what you want; for `<img>` elements added later, the default `waitForImages` already re-reads the DOM live.
{% endhint %}

See the full [`stabilize` option reference](/docs/reference/playwright) for every stabilization setting.


# Pause animated GIFs

Freeze animated GIFs on their first frame with the pauseGifs stabilization option so they no longer capture a random frame on each run.

Animated GIFs keep playing while your test runs, so each screenshot captures whatever frame happens to be on screen at capture time. That frame is non-deterministic—it depends on network timing, CPU load, and how long stabilization took—so a GIF turns an otherwise-stable page into a flaky one.

The `pauseGifs` stabilization option removes that source of noise: it freezes every animated GIF on its **first frame** before the screenshot is taken, then restores the original animation afterwards.

{% hint style="info" %}
This is on by default. Setting `stabilize: true` (the default) already pauses GIFs—there's nothing to configure unless you want to turn it off.
{% endhint %}

### How it works

For each `<img>` that resolves to a GIF, the SDK loads a fresh copy of the image and draws its first decoded frame to a canvas—an already-rendered `<img>` has been animating since it loaded, so its current frame is unpredictable. The static frame is swapped in for the screenshot and the original GIF is put back once the capture is done.

A cross-origin GIF served without the right CORS headers taints the canvas and can't be frozen; those are left animating rather than blocking the screenshot.

### Flagging GIFs that aren't detected automatically

The SDK recognizes GIFs from their URL—a `.gif` extension or a `data:image/gif` URI. A GIF served from a URL that carries neither (for example a CDN endpoint like `/media/1234`) can't be detected this way, so it keeps animating.

Flag those images explicitly with the `data-image-type="gif"` attribute and they'll be paused like any other GIF:

```html
<img src="https://cdn.example.com/media/1234" data-image-type="gif" />
```

### Disabling it

If you specifically want to capture the live animation, disable the plugin:

{% tabs %}
{% tab title="Playwright" %}

```ts
await argosScreenshot(page, "homepage", {
  stabilize: { pauseGifs: false },
});
```

{% endtab %}

{% tab title="Cypress" %}

```ts
cy.argosScreenshot("homepage", {
  stabilize: { pauseGifs: false },
});
```

{% endtab %}
{% endtabs %}

See the full [`stabilize` option reference](/docs/reference/playwright) for every stabilization setting.


# Stabilize date & time

Hide or freeze dynamic dates and times to remove a common source of flaky visual diffs in Argos.

Dates and times change between test runs, so any visible timestamp produces a diff on every build. Hide them from screenshots, or freeze them so they render the same value on every run.

### Hide the date

Add the `data-visual-test="transparent"` [Argos helper](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers) to render the element invisible in screenshots while keeping its layout space:

```html
<time data-visual-test="transparent">Oct 10, 2024</time>
```

### Freeze the date

To keep dates visible in your screenshots, make them deterministic instead:

* **Freeze the clock in your tests.** Playwright's [Clock API](https://playwright.dev/docs/clock) pins the browser time to a fixed value:

  ```js
  await page.clock.setFixedTime(new Date("2024-10-10T10:00:00"));
  ```
* **Pin dates in your test data.** If your UI renders dates from seeded data, run a script that resets those dates just before your tests, so relative labels like "2 days ago" stay constant.


# Browser glitches

Eliminate browser-induced visual discrepancies like border-radius differences with a consistent test environment.

The same page can render differently across operating systems and browser versions. Run your tests in the same environment everywhere — same OS, same browser version, locally and on CI — and smooth over the few properties that still render inconsistently.

{% hint style="info" %}
The most common cross-environment glitch is text rendering. See [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering) for the launch options that reduce text rendering differences between environments.
{% endhint %}

### Border radius

The `border-radius` property can render differently across browsers and devices. Add the `data-visual-test-no-radius` [Argos helper](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers) to remove the border radius in screenshots:

```html
<button className="rounded" data-visual-test-no-radius>My button</button>
```


# Argos helpers

Use data-visual-test attributes or SDK options to hide, remove, or mask dynamic elements for consistent, flake-free screenshots.

Every Argos SDK recognizes `data-visual-test` attributes on your elements, giving you control over how dynamic content appears in screenshots. Use them for anything you can't stabilize at the source — dates, avatars, ads, or third-party widgets.

### Helpers

* `data-visual-test="transparent"`: Makes the element invisible with `opacity: 0` and transparent text, and switches its font to monospace. Unlike `removed`, the element keeps a place in the layout.
* `data-visual-test="removed"`: Removes the element from view (`display: none`).
* `data-visual-test="blackout"`: Masks the element with a blackout effect.
* `data-visual-test-no-radius`: Strips the border radius from the element. See [Browser glitches](/docs/learn/reliability-and-flakiness/flaky-tests/browser-glitches).
* `data-visual-test-wait-bg-img`: Waits for the element's (and its descendants') CSS background images to load before capturing. See [Wait for background images](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-background-images).
* `data-image-type="gif"`: Flags an image as a GIF so it's paused even when its URL has no `.gif` extension. See [Pause animated GIFs](/docs/learn/reliability-and-flakiness/flaky-tests/pause-gifs).

For example, to hide a clock from the captured screenshot:

```html
<div id="clock" data-visual-test="transparent">...</div>
```

The WebdriverIO SDK applies the `transparent`, `removed`, `blackout`, and `no-radius` helpers, but not the rest of Argos stabilization, so `data-visual-test-wait-bg-img` and `data-image-type="gif"` have no effect there. In an Appium native app context, no helper applies.

### Apply helpers from your tests

When you can't or don't want to change the markup, pass the elements to `argosScreenshot` instead. They get the same treatment as with the attribute, for that screenshot only: Argos restores the page afterwards.

| Helper        | `argosScreenshot` option       |
| ------------- | ------------------------------ |
| `transparent` | `hide`                         |
| `removed`     | `remove`                       |
| `blackout`    | `mask` (`blackout` in Cypress) |

For example, with Playwright:

```ts
await argosScreenshot(page, "dashboard", {
  hide: ["#clock", page.getByTestId("avatar")],
  remove: [".cookie-banner"],
  mask: ["#map"],
});
```

Selectors that match nothing are ignored, and an element in both `hide` and `remove` is removed. With [viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports), the options apply to each viewport.

These options are available in every SDK: [Playwright](/docs/reference/playwright#helper-attributes-for-visual-testing), [Puppeteer](/docs/reference/puppeteer#helper-attributes-for-visual-testing), [Cypress](/docs/reference/cypress#helper-attributes-for-visual-testing), [WebdriverIO](/docs/reference/webdriverio#helper-attributes-for-visual-testing), [Vitest](/docs/reference/vitest), and [Storybook](/docs/reference/storybook). See each SDK reference for what the options accept.


# Deployments

Deploy your Storybook or static build to a unique Argos URL on every pull request to preview and share it.

A **deployment** on Argos is a static build — most commonly a Storybook — served on a unique URL that you can open, share, and review. When you run `argos deploy` in CI, Argos uploads the build, generates a URL, and posts the status back to your pull request.

Deployments pair naturally with visual testing: the same Storybook you test with Argos becomes a live preview reviewers can browse, right from the pull request — with no hosting of your own to maintain.

Use deployments to:

* Preview a Storybook for every pull request.
* Share a live link with designers, product, or stakeholders to review work in context.
* Browse the history of every deployed build across branches and commits.

<figure><img src="https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/qsX6ZxMxprXtW5m110m8/deployments%20list%20bfd87fd3af8ea82b72b991367db01f21.png" alt=""><figcaption></figcaption></figure>

*Deployments list in the Argos dashboard.*

### How it works

A deployment is created in three phases:

1. **Build** — The Argos CLI scans your local directory and computes a content hash for every file.
2. **Upload** — Argos returns pre-signed upload URLs for the files it doesn't already have. Files that match an existing hash are skipped, so re-deploying an unchanged build is near-instant.
3. **Serve** — Once the upload finishes, Argos finalizes the deployment, assigns it a URL, and reports the status to your Git provider.

Each deployment is **immutable**. Re-running the deploy command always produces a new deployment with its own URL—the previous one keeps working.

### Quickstart

The following steps deploy a Storybook. The same flow works for any static directory (Vite build, Next.js export, plain HTML, etc.).

{% stepper %}
{% step %}

### Install the Argos CLI

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cli
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cli
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cli
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cli
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Build your static site

Generate the directory you want to deploy. For Storybook:

```bash
npm run build-storybook
```

This produces a `storybook-static/` directory.
{% endstep %}

{% step %}

### Deploy

{% tabs %}
{% tab title="npm" %}

```
npm exec -- argos deploy ./storybook-static
```

{% endtab %}

{% tab title="yarn" %}

```
yarn run argos deploy ./storybook-static
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm exec -- argos deploy ./storybook-static
```

{% endtab %}

{% tab title="bun" %}

```
bun x argos deploy ./storybook-static
```

{% endtab %}
{% endtabs %}

The CLI uploads your build and prints a unique URL when the deployment is ready:

```
✔ Deployed: https://my-project-abcd123-acme.argos-ci.live
```

By default, the deployment is created in the **preview** environment. To deploy to **production**, add the `--prod` flag:

{% tabs %}
{% tab title="npm" %}

```
npm exec -- argos deploy ./storybook-static --prod
```

{% endtab %}

{% tab title="yarn" %}

```
yarn run argos deploy ./storybook-static --prod
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm exec -- argos deploy ./storybook-static --prod
```

{% endtab %}

{% tab title="bun" %}

```
bun x argos deploy ./storybook-static --prod
```

{% endtab %}
{% endtabs %}

See [Environments](/docs/learn/deployments/environments) for the rules that decide which environment a deployment lands in.
{% endstep %}
{% endstepper %}

### Authentication

The deploy command uses the same authentication as the rest of the Argos CLI. Set the `ARGOS_TOKEN` environment variable in CI:

```bash
ARGOS_TOKEN=<your-project-token>
```

You can find the project token in **Settings → Authentication → Project token**. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret.

### What's next

* [**Environments**](/docs/learn/deployments/environments) — How preview and production deployments are determined, and how to customize the production branch.
* [**URLs and domains**](/docs/learn/deployments/urls-and-domains) — The URLs Argos generates for each deployment, and how to set the production domain.
* [**Access protection**](/docs/learn/deployments/access-protection) — Restrict who can open deployment URLs.
* [**Use in CI**](/docs/learn/deployments/use-deployments-in-ci) — Deploy automatically on every pull request with GitHub Actions.
* [**Lifecycle and retention**](/docs/learn/deployments/retention) — How long deployments last, why they can't be overwritten, and how to pick a deploy cadence.


# Use deployments in CI

Run argos deploy from CI to create a deployment on every pull request, with a GitHub Actions Storybook example.

Running `argos deploy` from CI is the most common setup: a new deployment is created on every push to a pull request, and the deployment link appears in the PR comment alongside Argos visual tests.

This page walks through a GitHub Actions setup for Storybook. The same pattern works for any static build and any CI provider.

### Prerequisites

* A static build step that produces a directory (for Storybook: `npm run build-storybook` → `storybook-static/`).
* An `ARGOS_TOKEN` available as a CI secret. You can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret.

### GitHub Actions example

{% code title=".github/workflows/argos-deploy.yml" %}

```yaml
name: Deploy Storybook to Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - run: npm run build-storybook
      - run: npx --no-install argos deploy ./storybook-static
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

A few notes:

* The workflow listens to both `pull_request` and `push` to `main`. Pull request runs produce **preview** deployments; pushes to `main` produce **production** deployments (because `main` matches the default production branch pattern—see [Environments](/docs/learn/deployments/environments)).
* `actions/checkout` gives Argos the commit SHA and branch it needs to associate the deployment with the right pull request.
* The `ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**.

### Force a production deployment

If your production branch differs from your repository's default branch (or if you want to be explicit), pass the `--prod` flag:

```yaml
- run: npx --no-install argos deploy ./storybook-static --prod
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

You can keep the same workflow for both preview and production by branching on the event:

```yaml
- name: Deploy
  run: |
    if [ "${{ github.event_name }}" = "push" ]; then
      npx --no-install argos deploy ./storybook-static --prod
    else
      npx --no-install argos deploy ./storybook-static
    fi
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

### Combine with visual testing

The `deploy` and `upload` commands are independent: you can run both in the same workflow to get a deployment URL **and** Argos visual tests on the same commit.

```yaml
- run: npm run build-storybook
- run: npm run test:visual # Your usual Argos visual test job
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
- run: npx --no-install argos deploy ./storybook-static
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

Argos posts a single pull request comment that lists both the deployment URLs and the visual build results.

<figure><img src="https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/P3TZmEpwNQpR3UUgQlgf/pr%20comment%20d8856fb76179f0d92711fd7855d77906.png" alt=""><figcaption><p><em>GitHub pull request comment showing both the deployment and the Argos visual build.</em></p></figcaption></figure>

### Status checks

Each deployment registers a commit status named `argos-deploy/<project>`. The status starts as **pending** when the upload begins and becomes **success** when the deployment is ready. You can require it in your branch protection rules if a missing deployment should block a merge.

### Other CI providers

The `argos deploy` command has no GitHub-specific behavior. To run it on another CI provider:

1. Build your static directory in your pipeline.
2. Set `ARGOS_TOKEN` as a secret.
3. Run `argos deploy <directory>` (add `--prod` for production).

Argos automatically detects the commit SHA, branch, and pull request number from the most common CI environment variables.

### Related

* [Deployments overview](/docs/learn/deployments)
* [Environments](/docs/learn/deployments/environments)
* [GitHub integration](/docs/learn/integrations/github-integration)
* [Argos CLI reference](/docs/reference/argos-command-line-interface-cli)


# Environments

Understand how Argos assigns each deployment to a preview or production environment from your branch patterns.

Every deployment is created in one of two environments:

* **Preview** — A non-production deployment, typically created from a feature branch or pull request. Each preview gets its own URL and never replaces the production deployment.
* **Production** — The deployment served on your project's production domain. Only deployments from a production branch are promoted here.

The environment is decided when the deployment is created and cannot be changed afterwards.

### How the environment is determined

Argos picks the environment in this order:

1. **Explicit override.** If you pass `--prod` to the CLI (or `environment: "production"` to the SDK), the deployment is created as production.
2. **Branch match.** Otherwise, the branch name is matched against the project's **production branch pattern**. A match → production. No match → preview.

By default, the production branch pattern follows your Git repository's default branch (usually `main`). You can override it from project settings.

### Preview deployments

Preview deployments are the default. They are created when:

* You run `argos deploy <directory>` without `--prod`, **and**
* The branch does not match the production branch pattern.

Each preview deployment gets:

* A unique, immutable **deployment URL** — always points to that exact build.
* A **branch URL** — always points to the latest preview on that branch. Useful to share a link that follows a feature branch as it evolves.

See [URLs and domains](/docs/learn/deployments/urls-and-domains) for the full list of URLs Argos generates.

When a preview deployment is linked to a pull request, the deployment status appears in the [pull request comment](/docs/learn/review-workflow/pull-request-comments) and as a commit status check on the PR.

### Production deployments

A production deployment is created when:

* You run `argos deploy <directory> --prod`, **or**
* The branch matches the production branch pattern.

When a new production deployment becomes ready, it is **promoted**: the project's production domain immediately starts serving the new build. Earlier production deployments remain available at their own URLs.

The Argos dashboard shows a **Current** badge next to the deployment that is currently serving the production domain.

![Current production deployment badge](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/lrFJ8AWVYCwxF3BofwLu/current%20badge%207aa0a8c7aa6a6620e66362fdf1be7155.png)*The deployments list highlights the deployment currently promoted to production.*

### Configure the production branch

By default, Argos uses your Git repository's default branch as the production branch. You can customize this from **Settings → Deployments → Production deployment branch**.

![Production deployment branch setting](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/d5qUUZZ8MKqGhHfw3thI/prod%20branch%20a2b2abdf5548dd86026f7fd3a8e8c0e1.png)*Project Settings → Deployments → Production deployment branch.*

To customize:

1. Disable **Use GitHub repository's default branch**.
2. Enter a [glob pattern](https://github.com/isaacs/minimatch) that matches your production branches.

Examples:

* `main` — Only the `main` branch.
* `{main,production}` — Either `main` or `production`.
* `release/**` — Any branch under `release/` (for example `release/2024-q4`).

Any branch matching the pattern produces a production deployment on the next CLI run, even without `--prod`.

### Forcing an environment from the CLI

You can force a production deployment regardless of the branch with the `--prod` flag:

```bash
npm exec -- argos deploy ./storybook-static --prod
```

This is useful when you want to:

* Deploy a one-off production build from a local machine.
* Promote a deployment from a non-standard branch (for example, a release branch that doesn't match the configured pattern).

There is no flag to force a preview from a branch that matches the production pattern — a matching branch always deploys to production. If you need previews from such a branch, narrow the production branch pattern instead.

### Related

* [Deployments overview](/docs/learn/deployments)
* [URLs and domains](/docs/learn/deployments/urls-and-domains)
* [Use deployments in CI](/docs/learn/deployments/use-deployments-in-ci)


# URLs and domains

Understand the deployment, branch, and production URLs Argos generates, and serve production from a domain you own.

Every deployment is reachable through one or more URLs. The exact URLs depend on the deployment's environment and the project's configuration. Argos generates all of them under the shared root domain `argos-ci.live`, and production deployments can additionally be served from a [custom domain](#custom-domain) you own.

### Deployment URL

Every deployment has an **immutable deployment URL** that always points to that exact build. It looks like:

```
https://<project>-<random>-<account>.argos-ci.live
```

For example: `https://storybook-gdhgxamjo-acme.argos-ci.live`

The deployment URL never changes and never moves. It's the safest URL to share when you want a stable reference to a specific build—for example, in a pull request review or a bug report.

### Branch URL

In addition to the deployment URL, each deployment registers a **branch URL** that follows the latest deployment on that branch:

```
https://<project>-<branch>-<account>.argos-ci.live
```

For example, on a branch named `fix-stripe`: `https://storybook-fix-stripe-acme.argos-ci.live`

When you push a new commit on the same branch and re-deploy, the branch URL is updated to point at the new build. The previous deployment is still available at its own deployment URL.

Branch names containing characters that are not URL-safe are slugified.

{% hint style="info" %}
Branch URLs are useful in pull request templates and review checklists: a reviewer can bookmark the same link and always see the latest version of a feature.
{% endhint %}

### Production domain

Production deployments are additionally served on the project's **production domain**:

```
https://<your-slug>.argos-ci.live
```

The production domain is shared across all production deployments. When a new production deployment is promoted, the domain immediately starts serving the new build—older production deployments stay reachable on their own deployment URLs.

#### Configure the production domain

The production domain slug defaults to your project name. To change it, go to **Settings → Deployments → Domains** and click **Edit** next to the domain ending in `argos-ci.live`.

<figure><img src="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-5e646669ce8ced06a85e91dcd160132c2a70a10d%2Fdomains-settings.png?alt=media" alt=""><figcaption><p><em>Project Settings → Deployments → Domains.</em></p></figcaption></figure>

Rules for the slug:

* Lowercase, up to 48 characters.
* Must start and end with an alphanumeric character.
* Dashes are allowed in the middle.

The final domain is `<slug>.argos-ci.live`.

{% hint style="warning" %}
Changing the production domain takes effect immediately. Any existing links that used the previous domain will stop resolving.
{% endhint %}

### Custom domain

Production deployments can also be served from a domain you own, such as `storybook.acme.com`. A custom domain behaves like the production domain—it always points at the latest production deployment—but it carries your own name instead of `argos-ci.live`.

Once a custom domain is live, Argos uses it in the links it publishes back to GitHub, so the commit status and the pull request comment point at your domain rather than at the generated URL.

{% hint style="info" %}
Custom domains are included in paid plans and require a team. A personal account has no plan to upgrade—create a team and transfer the project to it first.
{% endhint %}

{% hint style="warning" %}
Custom domains serve **production deployments only**. Preview deployments keep their own deployment and branch URLs.
{% endhint %}

#### Add a custom domain

{% stepper %}
{% step %}

## Add the domain in Argos

Go to **Settings → Deployments → Domains**, click **Add domain**, and enter the fully qualified domain you want to use—for example `storybook.acme.com`. Enter the domain on its own, not a URL.
{% endstep %}

{% step %}

## Create the DNS record

Argos shows the record to create as soon as the domain is added. Add it with your DNS provider:

| Field | Value                                  |
| ----- | -------------------------------------- |
| Type  | `CNAME`                                |
| Name  | Your domain, e.g. `storybook.acme.com` |
| Value | `cname.argos-ci.live`                  |

Copy the value from the interface rather than typing it—Argos displays it with a copy button next to the domain.
{% endstep %}

{% step %}

## Wait for the certificate

Argos issues and installs a TLS certificate as soon as the record resolves. There is no second record to add and no certificate to upload.

Argos re-checks pending domains on its own. Click **Check** next to the domain if you want to verify right away instead of waiting.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
**A root domain needs an ALIAS record.** Most DNS providers cannot put a `CNAME` on a root domain such as `example.com`. Use an `ALIAS` or `ANAME` record pointing at the same value, or point a subdomain at Argos instead. This is the most common reason a domain never leaves **DNS not configured**.
{% endhint %}

#### Domain status

Each custom domain shows where it is in the process:

| Status                  | Meaning                                                                                                |
| ----------------------- | ------------------------------------------------------------------------------------------------------ |
| **DNS not configured**  | Argos cannot resolve the domain to `cname.argos-ci.live` yet. Check the record with your DNS provider. |
| **Issuing certificate** | The record resolves and the certificate is being issued. Nothing to do.                                |
| **Active**              | The domain is live and serving the latest production deployment.                                       |
| **Failed**              | Something is blocking the domain. The reason is shown underneath it.                                   |

A domain most often **fails** because the hostname is already attached to another CDN distribution. A domain can only be served by one at a time, so it has to be released there before Argos can take it.

#### Remove a custom domain

Removing a domain from **Settings → Deployments → Domains** stops serving it straight away. Delete the DNS record as well, or it keeps pointing at Argos with nothing behind it.

### Summary

| URL type          | Stability                                              | When it exists                     |
| ----------------- | ------------------------------------------------------ | ---------------------------------- |
| Deployment URL    | Immutable — always points at one build                 | Every deployment                   |
| Branch URL        | Updates when a new deployment lands on the same branch | Every deployment                   |
| Production domain | Updates when a new production deployment is promoted   | Production deployments only        |
| Custom domain     | Updates when a new production deployment is promoted   | Production deployments, paid plans |

All URLs above appear in the **Deployments** tab of your project in Argos.

<figure><img src="https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-a15a10f95782fd444970fdf8feea252744a805d9%2Fdeployment-urls.png?alt=media" alt=""><figcaption><p><em>A production deployment in the Deployments tab: custom domain first, then the production domain, the branch URL, and the deployment URL.</em></p></figcaption></figure>

### Related

* [Environments](/docs/learn/deployments/environments) — How preview vs production is decided.
* [Access protection](/docs/learn/deployments/access-protection) — Restrict who can open these URLs.


# Access protection

Require viewers to sign in before opening deployment URLs to keep your Argos previews private per project.

By default, deployment URLs are reachable by anyone who has the link. For most internal projects, that's not what you want—a Storybook can leak unreleased designs, copy, or features. Argos lets you require viewers to sign in with an Argos account before opening any deployment URL.

Access protection is configured per project, and applies to every deployment under it.

### Protection modes

Argos supports three modes:

| Mode                    | Preview URLs      | Production domain | Available on |
| ----------------------- | ----------------- | ----------------- | ------------ |
| **Public**              | No login required | No login required | All plans    |
| **Standard protection** | Login required    | No login required | All plans    |
| **All deployments**     | Login required    | Login required    | Team plans   |

When login is required, only Argos users who have access to the project can open the deployment. Everyone else sees the Argos sign-in screen.

#### Public

Everyone with the link can open the deployment. Use this if your project is fully public (for example, an open-source design system) or if the build is intentionally meant to be shared.

#### Standard protection

Login required for every URL **except** the production domain. The production domain stays public; all preview URLs and the immutable deployment URLs require sign-in.

This is the right default for most teams: production is browsable by anyone (designers, customers, stakeholders), while previews stay private to your team.

#### All deployments

Login required for **all** URLs, including the production domain. Use this when the production build itself contains sensitive material—for example, internal tools or a Storybook for unreleased features.

This option requires a Team plan.

### Configure access protection

1. Open your project in Argos.
2. Go to **Settings → Deployments**.
3. Under **Deployment authentication**, pick the protection mode.

![Deployment authentication setting](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/lmUnP0vkCqJYDqECLuYs/auth%20setting%2035a93c077ad9f57c75638510db260243.png)

*Project Settings → Deployments → Deployment authentication.*

Changes take effect immediately on the next request.

### Who can access protected deployments

When login protection is enabled, a viewer must:

1. Be signed in to Argos.
2. Have access to the project the deployment belongs to.

Project access follows the [team roles and permissions](/docs/learn/account-and-access/team-members-and-roles) rules: team members, contributors invited to the project, and account owners.

A viewer who does not have access sees a 404-style screen rather than a sign-in prompt, so the existence of the project is not disclosed.


# Lifecycle and retention

How long Argos deployments last, why they can't be overwritten, and how to pick a deploy cadence without cleaning up old builds.

Deployments are permanent, immutable records. Once a deployment is ready, it keeps working at its own URL and Argos never expires it, replaces it in place, or removes it automatically. This page explains what that means for how you deploy and how often.

### How long deployments last

A deployment has no expiration. It stays reachable at its immutable [deployment URL](/docs/learn/deployments/urls-and-domains#deployment-url) for as long as the project exists—whether it was created minutes ago or months ago.

There is no retention window to configure and nothing that ages deployments out. The only way a deployment stops existing is if you delete the whole project (see [Deleting deployments](#deleting-deployments)).

### Overwriting a deployment

You can't overwrite a deployment. Every `argos deploy` run creates a **new** deployment with its own immutable URL—the previous one keeps working untouched.

What moves instead of being overwritten are the shared URLs:

* The [branch URL](/docs/learn/deployments/urls-and-domains#branch-url) always points at the latest deployment on that branch. Re-deploying the same branch updates the branch URL to the new build; the previous deployment stays available at its own deployment URL.
* The [production domain](/docs/learn/deployments/urls-and-domains#production-domain) always serves the latest promoted production deployment. A new production deployment takes over the domain immediately; earlier production builds stay reachable on their own URLs.

So from a reviewer's point of view, a new deploy on the same branch effectively supersedes the old one—the link they use keeps showing the latest build—without any manual cleanup, and without losing the older builds' stable URLs.

### Deleting deployments

Individual deployments can't be deleted, the same way [builds are immutable records](/docs/learn/review-workflow/builds-list#faq). There's no bulk-delete and nothing you need to clear out: deployments don't age, and they don't count toward your [screenshot quota](/docs/learn/billing-and-subscription/pricing-plans) (that quota applies to visual testing, not deployments).

If you ever need to remove every deployment for a project, delete the project from **Settings → General**. This removes the project entirely, including its builds and deployments, and can't be undone.

### Choosing a deploy cadence

Because deployments never pile up in a way you have to manage, you can deploy as often—or as rarely—as fits your workflow:

* **On every pull request** — The most common setup. Each PR push creates a preview deployment and the link appears in the [pull request comment](/docs/learn/review-workflow/pull-request-comments). See [Use deployments in CI](/docs/learn/deployments/use-deployments-in-ci).
* **On a subset of pull requests** — If you don't need a preview on every PR, gate the deploy step on a label (for example, only run `argos deploy` when a `deploy-preview` label is present) or on changes to the relevant directory.
* **On releases** — Deploy to [production](/docs/learn/deployments/environments#production-deployments) from your release branch, either by matching the [production branch pattern](/docs/learn/deployments/environments#configure-the-production-branch) or by passing `--prod`.

Attaching previews to a PR label and running production deploys on releases is a perfectly reasonable setup—there's no downside to old deployments sticking around, and no cleanup step to schedule.

### FAQ

<details>

<summary>How long do deployments stick around?</summary>

Indefinitely. Deployments don't expire—each one stays reachable at its immutable deployment URL for as long as the project exists.

</details>

<details>

<summary>Can I overwrite a previous deployment?</summary>

No. Every deploy creates a new immutable deployment. The branch URL and production domain automatically move to point at the latest build, so the links people share stay current, while older builds remain available at their own URLs.

</details>

<details>

<summary>Do I have to bulk-delete old deployments to clear them out?</summary>

No. There's nothing to clean up—deployments don't age out and don't count toward your screenshot quota. Individual deployments can't be deleted; the only way to remove them all is to delete the project.

</details>

### Related

* [Deployments overview](/docs/learn/deployments)
* [URLs and domains](/docs/learn/deployments/urls-and-domains)
* [Environments](/docs/learn/deployments/environments)


# Media sharing

Upload a standalone image or video to Argos and get a shareable link with ready-to-paste Markdown, so a screenshot or a screen recording can go straight into a pull request.

**Media** on Argos is a standalone image or video: uploaded on its own, with no build and no test run behind it. Argos stores it, serves it at a share URL, and hands you the Markdown to embed it. It belongs to a project, so it inherits that project's access and moves with it when the project is transferred.

It exists because GitHub has no public API for comment attachments. Dragging an image into a pull request needs a signed-in browser session, which an agent — or a CI job — does not have. Committing the image bloats the repository, and release assets are a workaround nobody wants to maintain.

Use media sharing to:

* Put a before/after of a UI change in the pull request that made it, so the reviewer doesn't check out the branch.
* Attach a screen recording of a bug reproduction to an issue.
* Share a screenshot with your team by link, straight from the terminal.
* Point at a spot on a screenshot an agent produced and have it read your comment back — see [Reading the feedback](/docs/learn/media/standalone-media-upload#reading-the-feedback-left-on-a-media).

{% hint style="info" %}
Media is **not** visual testing. Nothing is compared to a baseline and nothing gates a build — it is a file with a link. To detect visual changes, see [Core concepts](/docs/learn/platform-fundamentals).
{% endhint %}

### How it works

A media is an **identity** — what the picture is *of* — and every upload is a **version** of it. Uploading `checkout.png` again doesn't create a second link: it adds a version under the same media, and the share URL always shows the newest one. That is what makes re-uploading after review work — the Markdown already pasted in a pull request updates itself, and the version a reviewer commented on survives underneath.

An upload takes three calls, which the CLI does in one command:

1. **Register** — Argos records the file's name, type, size and content hash, and returns a signed upload target. The share URL is allocated from this point.
2. **Upload** — the file goes straight to Argos storage, never through the API.
3. **Finalize** — Argos reads the file's first bytes back to check it really is the type it was declared as, records an image's dimensions, and makes it reachable. A file that isn't what it claims to be is rejected and its bytes are deleted.

A file Argos already holds — same contents, uploaded before — skips the last two steps entirely.

There is no processing step. Argos does not transcode, re-encode or rewrite your file: it stores the bytes you sent and serves them from its image CDN, which derives WebP and AVIF variants — and a video's poster frame — on request. **A media is fully usable the moment the upload finishes.**

### Quickstart

{% stepper %}
{% step %}

#### Install the Argos CLI

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/cli
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/cli
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/cli
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/cli
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

#### Upload a file

```bash
ARGOS_TOKEN=<your-project-token> npx argos media upload after.png
```

The CLI prints the share URL and a Markdown embed:

```
Media (1)

after.png
  ID: 4821
  staged
  image/webp · 25 KB · 1440x900 · team · ready
  Expires: 2027-08-10T09:12:00.000Z
  URL: https://app.argos-ci.com/m/kQ8vN2pXr4tYw7cD1sZ0
  File: https://files.argos-ci.com/media/42/9f86d081884c7d65…webp
  Markdown: ![after.png](https://app.argos-ci.com/m/kQ8vN2pXr4tYw7cD1sZ0)
```

The PNG went out as WebP: the CLI [compresses images before upload](/docs/learn/media/standalone-media-upload#image-compression), and the media keeps your file's name and extension regardless.
{% endstep %}

{% step %}

#### Paste the Markdown

Put the `Markdown` line in a pull request, an issue, or a chat message. Copy it verbatim rather than writing your own — for a video, the embed is a poster frame wrapped in a link, which is the only form GitHub renders.
{% endstep %}
{% endstepper %}

### Attach it to a pull request — even one that doesn't exist yet

A media can attach to a **pull request** or to a **branch**. Pass `--pr <number>` when the pull request already exists. Pass `--branch <branch>` while you are still working: the media is **staged**, and when a pull request opens for that branch, Argos publishes everything staged there and posts a single managed comment listing it — unasked, with nothing to come back and connect. See [Getting media into a pull request](/docs/learn/media/standalone-media-upload#getting-media-into-a-pull-request).

### Where to find your uploads

A media is reached by its link — there is no browsing UI. From the terminal, `argos media list` lists a project's uploads, and `--branch` narrows it to everything uploaded for the work in hand. Each media's share page shows the file with pan and zoom, its version history, its comment threads, and a copy-Markdown button.

### What's next

* [**Upload media**](/docs/learn/media/standalone-media-upload) — the CLI, the SDK, the API and the MCP server, plus getting media into a pull request and reading the feedback pinned on it.
* [**Share links, retention and limits**](/docs/learn/media/share-links-retention-and-limits) — who can open a link, how long a file lasts, accepted formats and sizes, and what an upload costs.


# Upload media

Upload images and videos to Argos from the CLI, the Node.js SDK, the REST API or an AI agent, and get them into a pull request with a managed comment.

Every surface uses the same three-step flow described in [How it works](/docs/learn/media#how-it-works). The CLI is the shortest path; the API is there when you are not in a Node.js environment.

### From the CLI

```bash
argos media upload before.png after.png
```

Pass as many files as you like. Each one is validated up front — an unsupported type, an unreadable path or a name collision fails the batch before anything is transferred — then uploaded in turn and printed with its own share URL and Markdown embed. Add `--json` when you parse the output.

| Flag                          | What it does                                                                                                                                                                                         |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--branch <branch>`           | Stage the media on a branch. Argos publishes it — and posts the pull request comment — by itself once a pull request opens for that branch.                                                          |
| `--pr <number>`               | Publish the media to an existing pull request. Detected with the GitHub CLI when neither this nor `--branch` is given.                                                                               |
| `--no-pr`                     | Skip that detection and upload the media unattached.                                                                                                                                                 |
| `--state <before\|after>`     | Which half of a before/after pair these files are. Inferred from a file name ending in `-before` or `-after`.                                                                                        |
| `--description <text>`        | Prose shown under the media in the pull request comment.                                                                                                                                             |
| `--visibility <team\|public>` | Who can open the share page. Defaults to your project's visibility — `public` for a public project, `team` for a private one. See [Share links](/docs/learn/media/share-links-retention-and-limits). |
| `--no-compress`               | Upload images exactly as they are instead of converting them to WebP.                                                                                                                                |
| `--project <owner/project>`   | Project to upload to. Required with a personal access token; also `ARGOS_PROJECT`.                                                                                                                   |

Media belongs to a **project**, so it inherits that project's access and moves with it when the project is transferred. In CI, `media upload` authenticates like every other Argos command: `ARGOS_TOKEN`, or, on GitHub Actions, OIDC or tokenless authentication once [enabled on the project](/docs/learn/integrations/github-actions-authentication#enable-oidc-or-tokenless-on-your-project). See [Tokens and permissions](#tokens-and-permissions) for the other commands.

### Getting media into a pull request

An agent produces screenshots while it is still doing the work; the pull request comes after, if it comes at all. So a media attaches to a **branch** or to a **pull request**:

* `--branch <branch>` — the media is **staged**. It is real from the start: shareable, listed, with its URL. When a pull request opens for that branch, Argos attaches everything staged there and posts its comment, unasked. Naming a branch needs no GitHub connection and no open pull request.
* `--pr <number>` — the media is **published** to that pull request immediately.

```bash
argos media upload checkout-before.png checkout-after.png --branch feat/checkout
```

A media keeps its branch after publishing, as a record of where it came from, so `argos media list --branch <branch>` finds everything uploaded for the work in hand across the moment the pull request opens.

#### When you pass neither

Argos asks the [GitHub CLI](https://cli.github.com) for the pull request of the branch you are on:

```bash
argos media upload after.png     # publishes to the pull request you are working in
```

Only when you pass neither flag. `--pr` is taken as given, and `--branch` is already an answer to the question — detecting a pull request over it would publish immediately and discard the staging you asked for.

Detection never fails an upload. `gh` not installed, not signed in, no pull request open for the branch yet, not a git repository at all — all mean the same thing, "nothing to attach to", and the media uploads with its share URL either way. Pass `--no-pr` to skip the lookup, for a screenshot that has nothing to do with the branch that happens to be checked out.

{% hint style="info" %}
CI is the case where detection usually finds nothing: `gh` is often absent, and a checkout is frequently in a detached HEAD. Pass `--pr` or `--branch` explicitly in a workflow rather than relying on it.
{% endhint %}

Two boundaries: publishing needs the project connected to a GitHub repository with [pull request comments](/docs/learn/review-workflow/pull-request-comments) enabled, and a pull request opened from a **fork** never claims a branch's staged media.

#### The managed comment

Argos maintains **one** comment per pull request — "Media uploaded by Argos" — listing every media published to it in a table, and edits it in place on each change. Attaching a media to a pull request and showing it there are the same act, not two. A [before/after pair](#before-and-after-pairs) shares a single row, descriptions appear in a Notes column, and the table lists up to 20 media.

The comment is separate from the [Argos build comment](/docs/learn/review-workflow/pull-request-comments): standalone media has no build behind it, and a media upload never rewrites a status comment reviewers rely on. If a reviewer deletes the comment, Argos takes the hint and never recreates it on that pull request. Deleting a media removes its row immediately.

### Before and after pairs

A file named `checkout-before.png` uploads as `checkout.png` labelled `before` — the suffix is lifted off the name, case-insensitively, and the extension stays. Uploading `checkout-after.png` alongside it gives the pair one identity, one row in the pull request comment, and a share page that compares the two side by side with synced pan and zoom.

`--state before` or `--state after` sets the label for files not named that way. It applies to **every** file in the invocation — passing it to an already-suffixed pair would make both files collide on one identity, which the CLI refuses rather than silently versioning:

```bash
argos media upload before/checkout.png --state before
argos media upload after/checkout.png --state after
```

### Versions: re-upload, same link

A media's identity within its pull request (or on its branch) is its **name**. Re-uploading the same name adds a **version** rather than a second media: the id, the share token and therefore the URL are unchanged, and the share page — and any Markdown already pasted in a pull request — shows the newest upload. The version a reviewer commented on survives underneath, in the share page's history.

Re-uploading a file whose bytes haven't changed does nothing: Argos recognizes the content hash, skips the transfer, and adds no version.

While a media is **staged**, `argos media update` can rename it, edit its description, or move it to another branch (`--no-branch` detaches it, so no pull request will publish it). Once **published**, name and branch are fixed — the pull request comment and its review threads are built on them:

```bash
argos media update 4821 --description "After the alignment fix"
argos media update 4821 --name checkout-v2.png     # staged media only
```

### Image compression

The CLI converts PNG and JPEG images to WebP before uploading — a 252 KB PNG screenshot typically goes out at a tenth of the size, at a quality (85) where text and 1-pixel borders survive. The media's name keeps your file's extension: `checkout.png` stays `checkout.png` even when WebP bytes are sent, so a `--no-compress` re-run adds a version instead of creating a second media.

Compression is skipped whenever it would not help: videos, already-efficient WebP and AVIF, GIFs and animated PNGs, images past WebP's 16383-pixel dimension limit (a long full-page capture reaches it), and conversions that came out no smaller. A file that can't be converted is uploaded as-is rather than failing the upload. Pass `--no-compress` to upload every image untouched.

Converting applies a photo's EXIF orientation, then drops the rest of its metadata — including GPS coordinates. A file uploaded untouched keeps its metadata in the stored original; see [What Argos does to your file](/docs/learn/media/share-links-retention-and-limits#what-argos-does-to-your-file).

### Embedding the result

Copy the `Markdown` line the command prints. It is a picture wrapped in a link to the share page — `[![name](fileUrl)](url)` — so the media shows inline, and clicking it lands on the page where it can be compared, versioned and commented on.

**Do not hand-write the embed from the share URL.** `url` is an HTML page: `![name](url)` renders as a broken image everywhere you paste it. The image part has to point at the file, which is what the `Markdown` line already does.

The picture is the file itself for an image, and the **poster frame** for a video. That difference is not cosmetic: GitHub renders an inline video player only for media it hosts itself, so a `<video>` tag or a bare `.mp4` link pointing at Argos renders as a dead link. The poster-in-a-link shows something and plays when clicked.

{% hint style="info" %}
The poster frame is derived by the CDN from the video itself, so it is available immediately — there is nothing to wait for and no second file to manage.
{% endhint %}

Uploading more than one file prints one extra block: the whole batch as a Markdown table, [pairs](#before-and-after-pairs) side by side in one row, exactly as the managed pull request comment renders them. That is the thing to paste when you want all of them at once.

```
Markdown for all of them:

| Name | Before | After |
| --- | --- | --- |
| checkout.png | [![checkout.png](…)](…) | [![checkout.png](…)](…) |
```

#### Sharing the link anywhere else

A **public** share link unfurls on its own. Argos serves OpenGraph and Twitter card tags with the page, and answers [oEmbed](https://oembed.com) at `/oembed`, so pasting one into Slack, Discord, Notion, Linear or a tweet shows the screenshot rather than a bare URL.

A **team-only** link deliberately does not: unfurl metadata is read by a crawler carrying no session, so anything in it would be public to whoever holds the link — including the file name. Team links stay opaque until somebody signs in. A public project's media is public by default, so its links already travel; from a private project, pass `--visibility public` on the upload when you mean one to.

### Reading the feedback left on a media

An uploaded media has its own comment threads, on its share page. A comment can be **pinned to a point** on the image, so a reviewer can say "this button is misaligned" about a specific pixel rather than describing where to look.

That matters most for the agent that produced the screenshot. It cannot see the image, but it can read the coordinates. Find the media uploaded for the branch, then list its open threads:

```bash
argos media list --branch feat/checkout
argos media comment list 4821
```

```
Comments (1)

#comment-xf23d [thread] Alice (@alice)
  Pinned: point 0.62,0.34
  Media version: media-version-9
  The primary button is misaligned here.
```

`Pinned` coordinates are normalized to the image's own width and height, so `0.62,0.34` is 62% across and 34% down whatever size it is displayed at. A comment also records the **version** it was written against: a pin describes a spot on the bytes its author was looking at, so once the media has been re-uploaded, resolve that version with `argos media versions 4821` and fetch *that* file rather than the newest.

Only open threads are listed — what comes back is what is left to do. Add `--all` to include threads already resolved.

Answer and close each thread as you deal with it:

```bash
argos media comment create 4821 --reply-to comment-xf23d --body "Fixed in abc1234."
argos media comment resolve 4821 comment-xf23d
```

`argos media comment` also covers `get`, `edit`, `delete`, `unresolve`, `react`, `unreact`, `subscribe` and `unsubscribe`, mirroring [build comments](/docs/learn/review-workflow/review-a-build#comment-on-exactly-what-changed). A new comment can carry its own pin with `--anchor-point <x,y>` (normalized 0–1); a reply inherits the spot its thread already points at. Every `media comment` command — reading included — needs a personal access token, because a comment has an author.

{% hint style="info" %}
Resolve only what you actually fixed. A resolved thread drops out of the next `media comment list`, so resolving something you skipped is how feedback gets silently dropped.
{% endhint %}

### Tokens and permissions

| Command                         | Token                                                                                                                                                                   |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `media upload`, `media update`  | Project token (`ARGOS_TOKEN`, or tokenless CI for upload), or a personal access token with review access.                                                               |
| `media get`, `list`, `versions` | Either token type, with access to the project.                                                                                                                          |
| `media delete`                  | Project token, or a personal access token with **administrator** rights on the project — a share URL may already be pasted somewhere, and deleting the media breaks it. |
| `media comment …`               | Personal access token, reading included.                                                                                                                                |

With a personal access token, `media upload` and `media list` need the project named: `--project <owner/project>` or `ARGOS_PROJECT`. A project token already identifies its project.

### From the Node.js SDK

```javascript
import { uploadMedia } from "@argos-ci/core";

const [media] = await uploadMedia({
  files: ["checkout-after.png"],
  branch: "feat/checkout",
});

console.log(media.url, media.markdown);
```

Options mirror the CLI: `token`, `project`, `branch`, `prNumber`, `state`, `description`, `visibility` and `compress` (`true` by default). It returns one media per file, uploaded sequentially in input order. See the [Node.js SDK reference](/docs/reference/node.js-sdk).

Pull request detection is the CLI's, not the SDK's — nothing here shells out to `gh`. Pass `prNumber` or `branch` yourself.

### From the REST API

`POST /media` declares the file and returns the media plus an `upload` target:

```bash
curl -X POST https://api.argos-ci.com/v2/media \
  -H "Authorization: Bearer $ARGOS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "after.png",
    "contentType": "image/png",
    "size": 188416,
    "hash": "<sha256-of-the-file>"
  }'
```

`state`, `description`, `visibility`, `branch` and `prNumber` are optional. With a personal access token, add `"project": "acme/web"`; a project token already identifies its project.

`POST` the file to `upload.url` as `multipart/form-data`, appending every entry of `upload.fields` **before** the `file` part — the target stays valid for 30 minutes — then call `POST /media/{mediaId}/finalize`. Finalizing checks the file is what it claims to be, records an image's dimensions, bills the upload, and makes it reachable. When `upload` comes back `null`, Argos already holds this exact file and both steps are unnecessary.

The rest of the surface:

| Endpoint                                | What it does                                                                                          |
| --------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `GET /media/{mediaId}`                  | Read one media.                                                                                       |
| `PATCH /media/{mediaId}`                | Edit `name`, `description` or `branch` — **staged media only**; `null` clears.                        |
| `DELETE /media/{mediaId}`               | Delete the media, its versions and its comments. Project administrators only.                         |
| `GET /media/{mediaId}/versions`         | The upload history, newest first — when `versionCount` says there is one.                             |
| `GET /projects/{owner}/{project}/media` | List, filtered by `branch`, `prNumber`, `stage`, `search`, `type`.                                    |
| `/media/{mediaId}/comments/…`           | The full comment surface — threads, reactions, resolution, subscriptions. Personal access token only. |

See the [API reference](https://argos-ci.com/docs/api-reference) for the full schemas.

### From an AI agent

The [MCP server](/docs/agents/mcp-server) exposes every one of these endpoints as a tool automatically — `createMedia`, `finalizeMedia`, `getMedia`, `updateMedia`, `deleteMedia`, `listMedia`, `listMediaVersions` under the `media:read` and `media:write` OAuth scopes, and the media comment tools under `comments:read` and `comments:write`.

For agents with a shell, the [`argos-upload` skill](/docs/agents/agent-skills) is the better path: it teaches *when* to attach media to a pull request, how to embed the result so it renders, and how to read back the feedback a human left on it. Install it with:

```bash
npx skills add https://argos-ci.com
```


# Share links, retention and limits

Who can open an Argos media share link, how long an uploaded file is kept, the formats and sizes accepted, and how uploads are billed.

### Who can open a share link

`visibility` controls the **share page** — the page with the file, its version history, its comment threads, and the copy-Markdown button:

| Visibility | Who can open the share page                                                      |
| ---------- | -------------------------------------------------------------------------------- |
| `team`     | Anyone signed in to Argos with access to the owning project. **Pro plans only.** |
| `public`   | Anyone holding the URL. No sign-in.                                              |

A share URL carries an unguessable token rather than the media's id, so a link cannot be found by guessing and does not reveal how much a project has uploaded. The token survives re-uploads: new versions never change the URL. Share pages are `noindex`, so they don't turn up in search results.

#### The default follows your project

An upload that doesn't choose gets **its project's** visibility: `public` for a public project, `team` for a private one. So the everyday upload needs no flag — a public project's screenshots produce links a reviewer can open, and a private project's don't become world-readable by being uploaded.

Requesting `team` on Hobby is rejected — a team-scoped link is what the paid tier sells — so on Hobby every share page is public, including a private project's.

Two things this default does **not** do:

* **It doesn't follow the project afterwards.** The visibility is fixed when the media is created, not looked up each time somebody opens the link. Making a project private closes the share pages of media uploaded from then on; the ones already pasted into pull requests keep working. To close one of those, upload it again with `--visibility team`, or delete it.
* **It doesn't override a choice you made.** Re-uploading the same name keeps the visibility the media already has unless that upload passes `--visibility` itself, so a screenshot deliberately kept team-only on a public project stays team-only.

#### Unfurling

A `public` link unfurls wherever it is pasted. Argos serves OpenGraph and Twitter card tags with the page itself, and answers [oEmbed](https://oembed.com) at `/oembed`, so Slack, Discord, Notion, Linear and the rest show the screenshot rather than a bare URL. Images answer as an oEmbed `photo`; videos answer as a `link` with a thumbnail, because a `video` response means an embedded player and Argos pages refuse to be framed.

A `team` link does not unfurl, on purpose. That metadata is read by a crawler carrying no session, so everything in it is public to whoever holds the link — the file name included, which is often the whole of what a private link was protecting.

{% hint style="warning" %}
**The file itself is always reachable without signing in**, at an unguessable URL on the Argos CDN, whatever the visibility.

That is a consequence of what the feature is for. GitHub renders an embedded image by fetching it **server-side**, through a proxy that carries no Argos session — so a file that required authentication could not appear in a pull request at all. Argos protects the bytes with an unguessable content-addressed URL rather than with a session, exactly as it already serves build screenshots.

Treat a media file as "anyone with the link", and the share page as the part that respects your project's access. If a file must never be reachable by an outsider who obtains its URL, don't upload it.
{% endhint %}

### Retention

An uploaded file is kept for a fixed window, then deleted:

| Plan  | Retention |
| ----- | --------- |
| Hobby | 30 days   |
| Pro   | 1 year    |

Retention is the plan's, not the upload's — there is no per-request setting, so "this link works for 30 days" is something you can reason about when you paste it somewhere. The countdown runs from the **upload**, not from the last view.

It applies **per version**: each upload of a media gets its own expiry, so an old version ages out of the share page's history while the media — and its share URL — live on with the newer ones. When the last version expires, the media disappears with its comment threads. Expired files are purged hourly, bytes included.

An expired link renders an "unavailable" page instead of a 404 — the same page a deleted media or a `team` page opened without access shows — so an embed in a pull request degrades visibly rather than into a broken image.

{% hint style="info" %}
Deleting a media takes effect immediately. Any share link or pull request embed pointing at it stops working, its row drops out of the managed pull request comment, and the file is removed from storage.
{% endhint %}

### Accepted formats

| Type   | Formats                    |
| ------ | -------------------------- |
| Images | PNG, JPEG, WebP, AVIF, GIF |
| Videos | MP4, WebM, MOV (QuickTime) |

Anything else is refused before the upload starts. SVG is deliberately excluded: it can carry scripts, and a share page serves media inline.

Argos verifies the bytes, not the extension: finalizing reads the file's first 64 KB and identifies the actual container. A file declared as an image that turns out not to be one — or the reverse — is rejected and its bytes are deleted, even if the name and the content type both said otherwise. Since the file URL is reachable without a session, this check is what keeps active content off an Argos domain.

### File size

| Plan  | Largest single file |
| ----- | ------------------- |
| Hobby | 50 MB               |
| Pro   | 500 MB              |

The limit is checked when the upload is registered, and enforced again by storage itself before the bytes land — so an oversized upload fails fast instead of transferring and then being rejected. A long screen recording is the usual thing that trips it; trim it before uploading.

### What Argos does to your file

Nothing, server-side. Argos stores the bytes it received and never rewrites them — there is no transcoding, no re-encoding, and no processing queue to wait on. A media is usable the moment the upload finishes.

Two things do transform your file, both outside the stored original:

* **The CLI compresses images before upload.** PNG and JPEG are converted to WebP client-side unless you pass `--no-compress` — see [Image compression](/docs/learn/media/standalone-media-upload#image-compression). What Argos stores in that case is the WebP the CLI produced.
* **The CDN works at delivery time.** Images are converted to WebP or AVIF for browsers that prefer them and resized on demand; a **video's poster frame** is derived from the video itself (a second in, where there is something to see), so it is available immediately and can never drift from the file it represents.

Two consequences worth knowing:

* **Embedded metadata survives in the stored file.** A photo's EXIF — including GPS coordinates, if the device recorded them — stays in the original and is readable by anyone with the file URL. The CDN drops it from the converted variants a browser fetches, but not from the original. The CLI's WebP conversion strips it (after applying the orientation), but a file uploaded untouched — a video, a GIF, or anything under `--no-compress` — keeps it. Strip it yourself before uploading if it matters.
* **Video plays only if the browser can decode it.** Most MP4 and WebM is fine, and so is the H.264 that screen recorders normally produce. ProRes and some HEVC exports will not play inline — the viewer gets a download instead. Export to H.264 if you need inline playback.

### Billing

Uploads draw on the **screenshot allowance you already have** — there is no second quota to track and no new line on the invoice, and the usage detail breaks media out so four screen recordings never read as a hundred unexplained screenshots.

| Upload | Screenshot units |
| ------ | ---------------- |
| Image  | 1                |
| Video  | 25               |

A video costs more because it costs more to store and to serve. An upload is billed when it finalizes; re-uploading a file whose bytes haven't changed adds no version and costs nothing.

Because uploads share the screenshot meter, they also share the existing [spend management](/docs/learn/billing-and-subscription/spend-management) thresholds and alerts — an upload is rejected when the account is over capacity or past its spend limit, exactly as a build would be.

Plans differ on retention, file size, and who can open a share page — not on volume. See [Pricing plans](/docs/learn/billing-and-subscription/pricing-plans).


# How-to guides

Task-focused guides for wiring Argos into your CI pipeline and expanding what your screenshots cover.

Task-focused guides for getting the most out of Argos in real projects — from wiring it into your CI pipeline to expanding what your screenshots cover.

* [CI pipelines](/docs/learn/how-to-guides/ci-pipelines) — run Argos efficiently in CI: monorepos, parallel and sharded runs, preview deployments, and more.
* [Visual coverage](/docs/learn/how-to-guides/visual-coverage) — capture more of your UI: responsive viewports, injected CSS, screenshot metadata, non-image files, and more.
* [Migrate to Argos](/docs/learn/how-to-guides/migrate-to-argos) — switch from Percy, Chromatic, Applitools, native Playwright or Vitest screenshots, BackstopJS, or Lost Pixel.


# CI pipelines

Run Argos in GitHub Actions, GitLab CI, CircleCI, Buildkite, or any other CI, with patterns for monorepos, sharding, preview deployments, and subset builds.

Patterns for running Argos efficiently inside your continuous integration pipeline.

* [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci) — pipeline examples for GitHub Actions, GitLab CI, CircleCI, and Buildkite, the CI providers Argos detects, and the environment variables for any other CI.
* [Monorepos setup](/docs/learn/how-to-guides/ci-pipelines/monorepos-setup) — run Argos for multiple projects in a single repository.
* [Parallel testing (sharding)](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) — collect screenshots from sharded or parallelized test runs into a single build.
* [Cached CI pipelines (Turborepo, Nx)](/docs/learn/how-to-guides/ci-pipelines/cached-pipelines) — keep builds, baselines, and required checks reliable when a task cache decides which suites run.
* [Run on preview deployments](/docs/learn/how-to-guides/ci-pipelines/run-on-preview-deployments) — trigger Argos against a deployed preview environment.
* [Skipping a build](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) — skip an Argos build when a visual comparison isn't needed.
* [Subset builds](/docs/learn/how-to-guides/ci-pipelines/subset-builds) — upload a partial run without affecting the baseline of screenshots it didn't cover.


# Run Argos in CI (GitHub Actions, GitLab CI, CircleCI, Buildkite)

Run Argos visual tests in GitHub Actions, GitLab CI, CircleCI, Buildkite, or any other CI: working pipeline examples, the CI providers Argos detects automatically, and the environment variables to set

To run Argos in CI, run your tests as usual with the `ARGOS_TOKEN` environment variable set. The Argos SDK or CLI reads the commit, the branch, and the pull request from your CI provider, uploads the screenshots, and Argos compares them with the [baseline build](/docs/learn/platform-fundamentals/baseline-build). On a CI provider that Argos doesn't detect, set `ARGOS_BRANCH` (and `ARGOS_COMMIT` if needed) yourself.

`ARGOS_TOKEN` is the project token from **Settings → Authentication → Project token**; store it as a secret in your CI. The examples below run Playwright tests: replace `npx playwright test` with your own test command, and follow the [quickstart](/docs/quickstart) for your framework to set up the SDK.

{% hint style="info" %}
Commit statuses and pull request comments come from the Argos [GitHub](/docs/learn/integrations/github-integration) or [GitLab](/docs/learn/integrations/gitlab-integration) integration, whichever CI runs your tests. Without one of them, review builds in the Argos dashboard. See [Other Git providers](/docs/learn/integrations/other-git-providers).
{% endhint %}

### Project token

Argos creates the project token when you create the project. You find it in the project's **Settings → Authentication**, on the **Project token** card. A project has one token at a time.

If the token leaks, a project admin can replace it:

1. On the **Project token** card, select **Regenerate token**.
2. Type the project's full name (`account/project`) to confirm, then select **Regenerate**.
3. Update the `ARGOS_TOKEN` secret in your CI right away.

The old token stops working immediately. Until you update the secret, uploads fail with "Project not found in Argos. If the issue persists, verify your token."

### CI providers detected automatically

Argos recognizes these CI providers from their environment variables, with no configuration:

| CI provider    | Detected with        | Commit                              | Branch                   | Pull request             |
| -------------- | -------------------- | ----------------------------------- | ------------------------ | ------------------------ |
| GitHub Actions | `GITHUB_ACTIONS`     | From the workflow event             | From the workflow event  | From the workflow event  |
| GitLab CI      | `GITLAB_CI`          | `CI_COMMIT_SHA`                     | `CI_COMMIT_REF_NAME`     | —                        |
| CircleCI       | `CIRCLECI`           | `CIRCLE_SHA1`                       | `CIRCLE_BRANCH`          | `CIRCLE_PULL_REQUEST`    |
| Buildkite      | `BUILDKITE`          | `BUILDKITE_COMMIT`, or the checkout | `BUILDKITE_BRANCH`       | `BUILDKITE_PULL_REQUEST` |
| Travis CI      | `TRAVIS`             | `TRAVIS_COMMIT`                     | `TRAVIS_BRANCH`          | `TRAVIS_PULL_REQUEST`    |
| Bitrise        | `BITRISE_IO`         | `BITRISE_GIT_COMMIT`                | `BITRISE_GIT_BRANCH`     | `BITRISE_PULL_REQUEST`   |
| Heroku CI      | `HEROKU_TEST_RUN_ID` | `HEROKU_TEST_RUN_COMMIT_VERSION`    | `HEROKU_TEST_RUN_BRANCH` | —                        |

On any other CI, Argos falls back to the Git checkout: see [Any other CI](#any-other-ci).

### GitHub Actions

{% code title=".github/workflows/argos.yml" %}

```yaml
name: Argos

on:
  pull_request:
  push:
    branches:
      - main

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

{% endcode %}

On GitHub Actions, you can also authenticate without a long-lived secret, with [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication).

### GitLab CI

{% code title=".gitlab-ci.yml" %}

```yaml
argos:
  image: node:22
  script:
    - npm ci
    - npx playwright install --with-deps chromium
    - npx playwright test
```

{% endcode %}

Add `ARGOS_TOKEN` as a masked variable in **Settings → CI/CD → Variables**. The job runs on every push, so your default branch builds the baseline and each merge request branch is compared against it. To get commit statuses on your merge requests, [connect your GitLab repository](/docs/learn/integrations/gitlab-integration) to Argos.

### CircleCI

{% code title=".circleci/config.yml" %}

```yaml
version: 2.1

jobs:
  argos:
    docker:
      - image: cimg/node:22.23
    steps:
      - checkout
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test

workflows:
  visual-tests:
    jobs:
      - argos
```

{% endcode %}

Add `ARGOS_TOKEN` as an environment variable in your CircleCI project settings, or in a context. Argos reads the pull request number from `CIRCLE_PULL_REQUEST` when CircleCI sets it.

### Buildkite

{% code title=".buildkite/pipeline.yml" %}

```yaml
steps:
  - label: "Visual tests"
    command:
      - npm ci
      - npx playwright install --with-deps chromium
      - npx playwright test
    secrets:
      - ARGOS_TOKEN
```

{% endcode %}

Buildkite runs the step on your own agents, so the agent needs Node.js 22 or later. The `secrets` key exposes a [Buildkite secret](https://buildkite.com/docs/pipelines/security/secrets/buildkite-secrets) named `ARGOS_TOKEN` as an environment variable; it requires Buildkite agent 3.106.0 or later. If your step runs inside a container, pass the `BUILDKITE_*` environment variables through so Argos can read them.

### Any other CI

On a CI provider that isn't in the table above, such as Jenkins, Azure Pipelines, or Bitbucket Pipelines, Argos reads the commit and the branch from the Git checkout. Many CI systems check out a detached `HEAD`, which has no branch name, so set `ARGOS_BRANCH` from your CI's branch variable. Outside a Git checkout, set both `ARGOS_BRANCH` and `ARGOS_COMMIT`; without them, the upload fails with "Argos requires a branch and a commit to be set".

| Environment variable                                                                     | Description                                                                                                              |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `ARGOS_TOKEN`                                                                            | Project token. Required, except with GitHub Actions OIDC or tokenless authentication.                                    |
| `ARGOS_BRANCH`                                                                           | Branch of the build.                                                                                                     |
| `ARGOS_COMMIT`                                                                           | Commit of the build, as a full 40-character SHA.                                                                         |
| `ARGOS_PR_NUMBER`                                                                        | Number of the pull request associated with the build.                                                                    |
| `ARGOS_PR_BASE_BRANCH`                                                                   | Base branch of the pull request.                                                                                         |
| `ARGOS_BUILD_NAME`                                                                       | Name of the build, to run [several builds per commit](/docs/learn/how-to-guides/ci-pipelines/monorepos-setup).           |
| `ARGOS_PARALLEL`, `ARGOS_PARALLEL_TOTAL`, `ARGOS_PARALLEL_INDEX`, `ARGOS_PARALLEL_NONCE` | Collect the uploads of [parallel jobs](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) into one build. |
| `ARGOS_REFERENCE_BRANCH`, `ARGOS_REFERENCE_COMMIT`                                       | Pick the [baseline](/docs/learn/platform-fundamentals/baseline-build#choose-a-custom-baseline-via-sdk) yourself.         |

Options passed to the SDK or the CLI take precedence over these variables, and both take precedence over the values detected from your CI provider. These variables also override detection on the providers listed above.

To find the baseline, Argos walks the commit history. When your project is connected to GitHub or GitLab, this happens server-side. Otherwise Argos reads it from the `origin` remote, or from the local history, so make sure your checkout has enough history, or pin the baseline with `ARGOS_REFERENCE_COMMIT`. See the [CLI reference](/docs/reference/argos-command-line-interface-cli#override-git-detection).

### Troubleshooting

To see the commit, branch, and pull request Argos detected, run your upload with debug logs:

```bash
DEBUG=@argos-ci/core npx playwright test
```

If a pull request build shows up as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds), Argos found no baseline yet: run the pipeline once on your default branch.


# Parallel testing (sharding)

Collect screenshots from sharded or parallelized test runs into a single Argos build across your CI nodes.

When a test suite is split across several CI jobs — Playwright shards, a test matrix, or independent suites in a monorepo — each job uploads its own screenshots. Parallel mode tells Argos to collect all these uploads into a **single build**, so you review one comparison and get one status check for the commit.

Uploads are correlated by a **nonce**, a unique identifier shared by all jobs of the same run. On most CI providers (GitHub Actions, GitLab CI, CircleCI…), Argos derives the nonce automatically from the pipeline run, so jobs of the same run join the same build without any configuration.

### Playwright

Argos integrates with Playwright out of the box, including [Playwright test sharding](https://playwright.dev/docs/test-sharding): the reporter detects the shard configuration and joins the parallel build automatically. For more details, refer to the [Argos Playwright SDK](/docs/reference/playwright).

If you use an advanced orchestration system like the excellent one from [Currents](https://currents.dev/), use [finalize mode](#finalize-mode) instead: the number of shards isn't known upfront.

### Other SDKs

For environments beyond Playwright, parallel mode is configured through environment variables:

* `ARGOS_PARALLEL`: Activate the parallel mode.
* `ARGOS_PARALLEL_TOTAL`: The number of expected uploads, or `-1` for [finalize mode](#finalize-mode).
* `ARGOS_PARALLEL_INDEX`: The index of the current parallel node. Must start from `1`.
* `ARGOS_PARALLEL_NONCE`: A unique identifier for each build. In most CI environments, Argos generates it automatically from the pipeline run.

{% hint style="info" %}
Alternatively, use the `--parallel`, `--parallel-nonce`, `--parallel-total` and `--parallel-index` flags with the CLI, or `parallel: { nonce: string, total: number, index: number }` within SDK options.
{% endhint %}

### Choosing a mode

There are two parallel modes. Pick one based on whether you know, when the pipeline starts, exactly how many uploads will happen.

#### Count mode

Set `ARGOS_PARALLEL_TOTAL` to the number of parallel jobs. Argos finalizes the build automatically once it has received that many uploads.

Use count mode when the number of uploads is **fixed and guaranteed**, typically a static CI matrix where every job always uploads — it's the simplest setup, with no extra CI step.

{% hint style="warning" %}
If fewer uploads than `ARGOS_PARALLEL_TOTAL` arrive — a job failed before uploading, was skipped by a condition or a cache — the build never completes and the commit status stays pending. If uploads can be skipped, use finalize mode.
{% endhint %}

#### Finalize mode

Set `ARGOS_PARALLEL_TOTAL` to `-1`. Each upload joins the build as a shard, and the build stays open until you explicitly close it by running [`argos finalize`](/docs/reference/argos-command-line-interface-cli#uploading-from-ci) in a step that runs after all uploads.

Use finalize mode whenever the number of uploads **isn't known upfront or isn't guaranteed**:

* **Dynamic shard counts** — the matrix size varies per run, or an orchestrator (e.g. Currents) decides how tests are distributed.
* **Conditional uploads** — some jobs only run when their part of the codebase changed, or are skipped by a task cache such as Turborepo or Nx. See [Cached CI pipelines](/docs/learn/how-to-guides/ci-pipelines/cached-pipelines).
* **Aggregating independent suites** — several packages or tools upload to the same build name at different points of the pipeline, and no single job knows how many uploads to expect.

When it's possible that **no upload happens at all** (every suite skipped or cached), pass `--skip-if-empty` to `argos finalize`: instead of doing nothing, it creates a [skipped build](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) so a required Argos status check still reports success on the commit.

### Implementing in GitHub Actions

#### Count mode, with a static shard matrix

{% code title=".github/workflows/ci.yml" %}

```yaml
jobs:
  e2e-tests:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
        shardTotal: [4]
    steps:
      # ---
      # Here you setup your repo and run your E2E tests
      # ---
      - name: Upload screenshots to Argos
        env:
          ARGOS_PARALLEL: true
          ARGOS_PARALLEL_TOTAL: ${{ matrix.shardTotal }}
          ARGOS_PARALLEL_INDEX: ${{ matrix.shardIndex }}
          # ARGOS_PARALLEL_NONCE is automatically detected
        run: npm exec -- argos upload ./screenshots
```

{% endcode %}

#### Finalize mode

A dedicated job closes the build once all test jobs have run:

{% code title=".github/workflows/ci.yml" %}

```yaml
jobs:
  e2e-tests:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shardIndex: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      # ---
      # Here you setup your repo and run your E2E tests
      # ---
      - name: Upload screenshots to Argos
        env:
          ARGOS_PARALLEL: true
          ARGOS_PARALLEL_TOTAL: -1
          ARGOS_PARALLEL_INDEX: ${{ matrix.shardIndex }}
          # ARGOS_PARALLEL_NONCE is automatically detected
        run: npm exec -- argos upload ./screenshots

  finalize:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    # Finalize the build even if some shards have failed
    if: ${{ always() }}
    needs: ["e2e-tests"]
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - name: Finalize Argos build
        # ARGOS_PARALLEL_NONCE is automatically detected
        run: npm exec -- argos finalize
```

{% endcode %}

If the upload steps are conditional and may all be skipped, use `argos finalize --skip-if-empty` so the commit still gets a successful (skipped) Argos build.

### Troubleshooting / FAQ

<details>

<summary>My build stays pending forever</summary>

In count mode, the build completes only after exactly `ARGOS_PARALLEL_TOTAL` uploads. A shard that failed, was skipped, or uploaded with a different nonce or build name leaves the build waiting. Switch to finalize mode if uploads aren't guaranteed.

</details>

<details>

<summary>Each shard created its own build instead of joining one</summary>

The shards used different nonces. On unsupported CI providers, set `ARGOS_PARALLEL_NONCE` explicitly to a value shared by all jobs of the run (e.g. the pipeline run ID). Also make sure all shards use the same build name.

</details>

<details>

<summary>What happens when a shard is retried or the workflow is re-run?</summary>

Uploads that share a nonce and a build name go to the same build:

* A second upload for a shard index (`ARGOS_PARALLEL_INDEX`) that already completed is ignored. The build keeps the screenshots from the first upload.
* Once the build is finalized, a new upload is rejected with "Build already finalized".

On GitHub Actions, the nonce includes the run attempt, so re-running a workflow starts a new build. In count mode, when you re-run only some jobs (for example with **Re-run failed jobs**), Argos fills the shards that didn't run again with the previous attempt's screenshots once the workflow run completes. This requires a project connected through the Argos GitHub App with [full access](/docs/learn/integrations/github-integration#choose-your-access-level).

</details>


# Subset builds

Run subset builds to upload a partial test run without affecting the baseline of screenshots it didn't cover.

Subset builds are designed for CI runs that **don't execute the full E2E test suite** on a branch. When a build is marked as subset, Argos **ignores removed screenshots** and only notifies you about **changed and added screenshots** from the tests you did run.

This is helpful for speeding up feature-branch validation while still getting reliable visual feedback from the relevant tests. It's a key ingredient of pipelines where a task cache decides which suites run — see [Cached CI pipelines (Turborepo, Nx)](/docs/learn/how-to-guides/ci-pipelines/cached-pipelines) for the full setup.

{% hint style="info" %}
You still need to run your full test suite on your main branch to create and update **baseline builds**. Subset builds are not eligible as baselines. See [Baseline build](/docs/learn/platform-fundamentals/baseline-build).
{% endhint %}

### Enable subset builds

You can enable subset builds in any Argos SDK or the CLI.

#### Environment variable

Set the environment variable `ARGOS_SUBSET` to `"true"` in your CI configuration.

```yaml
steps:
  - name: Run tests
    run: npm test
    env:
      ARGOS_SUBSET: "true"
```

#### CLI

Use the `--subset` flag with the CLI.

{% tabs %}
{% tab title="npm" %}

```
npm exec -- argos upload --subset ./screenshots
```

{% endtab %}

{% tab title="yarn" %}

```
yarn run argos upload --subset ./screenshots
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm exec -- argos upload --subset ./screenshots
```

{% endtab %}

{% tab title="bun" %}

```
bun x argos upload --subset ./screenshots
```

{% endtab %}
{% endtabs %}

#### SDK option

Most SDKs expose a `subset` option on the upload configuration.

{% code title="upload.js" %}

```js
import { upload } from "@argos-ci/core";

await upload({
  root: "./screenshots",
  subset: true,
});
```

{% endcode %}

### Examples

#### Playwright

For Playwright, simply set `ARGOS_SUBSET=true` in your CI job. The reporter will mark the build as a subset build.

{% code title=".github/workflows/ci.yml" %}

```yaml
steps:
  - uses: actions/checkout@v6
  - uses: actions/setup-node@v6
    with:
      node-version: 22
  - run: npm ci
  - name: Run Playwright tests
    env:
      ARGOS_SUBSET: "true"
    run: npx playwright test
```

{% endcode %}

#### Cypress

For Cypress, set `ARGOS_SUBSET=true` in your CI job that runs Cypress and uploads screenshots.

{% code title=".github/workflows/ci.yml" %}

```yaml
steps:
  - uses: actions/checkout@v6
  - uses: actions/setup-node@v6
    with:
      node-version: 22
  - run: npm ci
  - name: Run Cypress tests
    env:
      ARGOS_SUBSET: "true"
    run: npx cypress run
```

{% endcode %}

### Troubleshooting / FAQ

<details>

<summary>Why are removed screenshots ignored?</summary>

Subset builds only include a portion of your test suite, so missing screenshots may simply be from skipped tests, not actual deletions. Ignoring removals avoids false positives.

</details>

<details>

<summary>Do omitted screenshots count toward my quota?</summary>

A subset build does not copy baseline screenshots omitted from the upload into the new build. Omitted screenshots are ignored for comparison. They are neither removals nor carried-over screenshots.

Only screenshots captured and uploaded in the subset build are registered. Each registered screenshot counts toward your quota, even when it is unchanged from the baseline. Baseline screenshots omitted from the upload do not consume quota merely because they exist in the baseline. See [How usage is counted](/docs/learn/billing-and-subscription/spend-management#how-usage-is-counted).

</details>

<details>

<summary>Why can’t a subset build be a baseline?</summary>

Baselines must represent the full test suite. Subset builds are incomplete by design and would cause missing screenshots in comparisons.

</details>


# Skipping a build

Skip an Argos build to report success without screenshots when a required check shouldn't run a visual comparison.

Sometimes you want Argos to report success without running visual tests.

This is useful when Argos is configured as a required GitHub status check, but you intentionally want to skip screenshots for a commit or pull request.

A skipped build:

* uploads no screenshots
* runs no visual comparison
* immediately marks the commit status as success

### Creating a skipped build

You can skip a build in two ways.

#### Environment variable

Set the environment variable `ARGOS_SKIPPED` to `"true"` in your CI configuration.

**GitHub Actions example:**

```yaml
jobs:
  visual-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - name: Run tests
        run: npm run visual-tests # or your test command
        env:
          ARGOS_SKIPPED: "true"
```

This reports a successful Argos check without uploading screenshots.

#### Using the CLI

You can also explicitly create a skipped build using the CLI.

{% code title=".github/workflows/ci.yml" %}

```yaml
jobs:
  visual-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - name: Skip Argos build
        run: npx @argos-ci/cli skip
```

{% endcode %}

This immediately creates a successful Argos build with no visual testing.

{% hint style="info" %}
For [parallel builds in finalize mode](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding#finalize-mode) where every upload step may be skipped (e.g. by a task cache), use `argos finalize --skip-if-empty` instead: it finalizes the build when shards were uploaded, and creates a skipped build otherwise. See [Cached CI pipelines (Turborepo, Nx)](/docs/learn/how-to-guides/ci-pipelines/cached-pipelines).
{% endhint %}


# Cached CI pipelines (Turborepo, Nx)

Run Argos reliably in pipelines where a task cache like Turborepo or Nx decides which test suites actually run.

Monorepo build systems like [Turborepo](https://turborepo.com/) and [Nx](https://nx.dev/) cache test tasks: when a package and its dependencies haven't changed, the task is replayed from cache instead of executing. This is great for CI speed, but it breaks a naive visual testing setup, because **a cached test task never uploads screenshots**.

Three problems follow:

1. **You can't predict how many uploads a run will produce.** Any fixed expectation (a parallel total, a required upload step) will hang or fail on runs where suites are cached.
2. **Missing screenshots look like removed screenshots.** If a build only contains the suites that ran, Argos would report every cached suite's screenshots as removed.
3. **Baselines on your main branch can go stale.** A baseline must be a complete build. If the suite that produces a screenshot doesn't run on main, no build contains its up-to-date version.

There are two ways to handle this. The examples use Turborepo and GitHub Actions; the same patterns apply to Nx or any cache that skips tasks.

## Preferred: cache the snapshot files as task outputs

The key insight: **Argos doesn't need your tests to run — it needs their snapshot files.** Build system caches can restore task outputs without executing the task. Declare the snapshot directory as a cached output, and upload the full set in a single step after the build-system run:

* Suites that ran contribute fresh files.
* Suites replayed from cache contribute their **restored** files — identical to the run that populated the cache, which is exactly right: a cache hit means the suite's inputs are unchanged.

Every build is complete, on pull requests and on main. A suite executes at most once per content change, on any branch, and baselines are always fresh.

### 1. Write snapshots to a directory inside the package

Capture snapshots to a directory owned by the task's package — Argos SDKs default to `./screenshots`. Don't upload from inside the test task (skip the SDK reporter / `uploadToArgos` option): the upload moves to a later step, outside the cache boundary.

### 2. Declare the directory as a cached task output

{% code title="packages/my-package/turbo.json" %}

```json
{
  "extends": ["//"],
  "tasks": {
    "test": {
      "outputs": ["screenshots/**"]
    }
  }
}
```

{% endcode %}

With Nx, add the directory to the target's [`outputs`](https://nx.dev/reference/project-configuration#outputs).

### 3. Upload once after the run

{% code title=".github/workflows/tests.yml" %}

```yaml
env:
  ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}

steps:
  - name: Run tests
    run: npx turbo test

  - name: Upload snapshots to Argos
    run: >-
      npx argos upload . --build-name unit
      --files "**/screenshots/**/*"
      --ignore "**/node_modules/**" "**/*.argos.json"
```

{% endcode %}

That's the whole setup: no parallel mode, no subset builds, no finalize step, and nothing special on your main branch.

{% hint style="warning" %}
Scope the `--files` globs so they match only the snapshot directories your cached tasks produce — a repo-root glob can accidentally sweep up unrelated screenshot folders (a Storybook suite from another workflow, stray local artifacts). Always ignore `**/node_modules/**` and the `**/*.argos.json` metadata sidecars.
{% endhint %}

### Caveats

* **Snapshots must be deterministic and machine-independent.** Cached files are reused across runners and branches; anything environment-dependent in them (timestamps, absolute paths, font rendering differences) will produce diffs that depend on which machine populated the cache. Text snapshots and stabilized screenshots are fine.
* **You extend the cache's trust model to baselines.** With a remote cache, files produced on a pull-request runner can end up in a main-branch build. That's the same trust you already place in the cache for test *results* — but it's worth stating.
* **Screenshot names become upload-root-relative.** Uploading from the repository root names snapshots by their full path (`packages/app/screenshots/home.png`), which also prevents name collisions between packages sharing a build.

## Alternative: subset builds + finalize

When the snapshot files can't be treated as cacheable outputs — uploads happen deep inside the test process, or suites are skipped by change detection rather than an artifact-restoring cache — use [parallel finalize mode](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding#finalize-mode) with [subset builds](/docs/learn/how-to-guides/ci-pipelines/subset-builds):

1. **Aggregate uploads into one build with finalize mode.** Each suite uploads as a shard (`ARGOS_PARALLEL=true`, `ARGOS_PARALLEL_TOTAL=-1`, one shared `ARGOS_BUILD_NAME`); the number of uploads may vary per run.
2. **Mark pull request builds as subset** (`ARGOS_SUBSET=true` on non-main runs), so screenshots missing from skipped suites are ignored instead of reported as removed.
3. **Guarantee complete builds on your main branch.** Subset builds are not eligible as baselines, so on main the Argos-enabled suites must actually run. Declare a cache-busting variable in the task's **hashed** `env` (not the passthrough list — passthrough doesn't invalidate the cache) and give it a fresh value on main only:

{% code title=".github/workflows/tests.yml" %}

```yaml
env:
  ARGOS_SUBSET: ${{ github.ref != 'refs/heads/main' }}
  # Not read by Argos — hashed into the Argos-enabled test tasks
  # (turbo.json `env`) so a fresh value on every main commit forces
  # them to re-run and produce complete builds.
  CACHE_BUST_KEY: ${{ github.ref == 'refs/heads/main' && github.sha || '' }}
```

{% endcode %}

4. **Finalize, and keep a required check green.** After the run, `argos finalize --skip-if-empty` closes the build — and creates a [skipped build](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) when every upload was skipped, so a required Argos check still reports success. Run it only when the tests succeeded, so a partial run can never become a baseline.

{% code title=".github/workflows/tests.yml" %}

```yaml
- name: Run tests
  run: npx turbo test

- name: Finalize Argos build
  run: npx argos finalize --skip-if-empty
```

{% endcode %}

This costs a re-run of the Argos-enabled suites on every main commit, and uploads happen inside cached tasks — see the FAQ below for a logging gotcha that comes with that.

## Troubleshooting / FAQ

<details>

<summary>How do PR builds compare against the right screenshots when suites are cached?</summary>

Argos resolves the baseline by walking the ancestor commits of the pull request's merge base until it finds an eligible build. As long as main builds are complete — automatic with the outputs-caching pattern, forced in the subset + finalize pattern — the nearest ancestor build contains an up-to-date version of every screenshot.

</details>

<details>

<summary>Turborepo strips my ARGOS_* variables inside tasks</summary>

Turborepo runs tasks in [strict environment mode](https://turborepo.com/docs/reference/configuration#envmode) by default: undeclared variables never reach the task. This only matters when uploading from **inside** tasks (the subset + finalize pattern) — pass the variables through without hashing them:

{% code title="turbo.json" %}

```json
{
  "globalPassThroughEnv": ["ARGOS_*", "GITHUB_*"]
}
```

{% endcode %}

With the outputs-caching pattern the upload runs outside the build system, so no passthrough is needed.

</details>

<details>

<summary>The logs show "Argos build created" but the build isn't in my finalized build</summary>

When uploading from inside cached tasks, a cache hit replays the cached task's **logs**, including the "Argos build created" line from the run that populated the cache — but no upload actually happens, and the replayed URL points to the old run's build. Trust the output of the `argos finalize` step: it lists the builds that were really uploaded and finalized in the current run. (The outputs-caching pattern doesn't have this problem: the only upload log line is the real one.)

</details>

<details>

<summary>What about re-runs of a failed workflow?</summary>

With the outputs-caching pattern, the upload step simply runs again with the same files. In finalize mode, the parallel nonce includes the run attempt on GitHub Actions, so a re-run starts a fresh build; Argos detects partial re-runs of parallel builds and fills the missing shards from the previous attempt automatically.

</details>


# Monorepos setup

Use build splitting to run separate Argos visual tests for each package or app in your monorepo within one commit.

In a monorepo, several packages or apps produce screenshots on the same commit. **Build splitting** keeps them apart: give each test suite its own build name, and Argos creates a separate build — with its own baseline and its own status check — for each name.

Build splitting works across all SDKs and the CLI. Set a unique build name per suite with the `--build-name` CLI flag or the `buildName` SDK option.

### Example

Say your monorepo has a component library and an app with end-to-end (E2E) tests. Give each suite its own build name:

**For components:**

{% tabs %}
{% tab title="npm" %}

```
npm exec -- argos upload --build-name components ./screenshots/components
```

{% endtab %}

{% tab title="yarn" %}

```
yarn run argos upload --build-name components ./screenshots/components
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm exec -- argos upload --build-name components ./screenshots/components
```

{% endtab %}

{% tab title="bun" %}

```
bun x argos upload --build-name components ./screenshots/components
```

{% endtab %}
{% endtabs %}

**For E2E tests with Playwright:**

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  reporter: [
    process.env.CI ? ["dot"] : ["list"],
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
        buildName: "e2e",
      }),
    ],
  ],
  // Other config
});
```

{% endcode %}

Each build appears separately in Argos — `components` and `e2e` are compared against their own baselines and report their own status checks, so a visual change in one suite never blocks the other.

{% hint style="info" %}
If your monorepo uses a task cache like Turborepo or Nx that decides which test suites actually run, see [Cached CI pipelines (Turborepo, Nx)](/docs/learn/how-to-guides/ci-pipelines/cached-pipelines) — it covers aggregating packages into a single build and keeping baselines and required checks reliable when suites are skipped.
{% endhint %}


# Run on preview deployments

Run Argos visual tests against Vercel, Netlify, or Cloudflare preview deployments to catch regressions before merging.

Catch visual regressions **before merging** by running automated tests on every preview deployment.

Argos integrates with GitHub Actions and works with providers like **Vercel, Netlify, and Cloudflare**.

With this setup:

* Each deployment preview is tested automatically.
* Regressions are surfaced directly in your pull requests.
* Your production baseline stays reliable and up to date.

### Setup with Vercel repository dispatch events

Vercel can notify GitHub on every deployment via a [`repository_dispatch` event](https://vercel.com/docs/git/vercel-for-github#repository-dispatch-events).

Argos can use this payload to run visual tests against the preview URL.

{% code title=".github/workflows/ci.yml" %}

```yaml
name: Playwright + Argos Tests

on:
  repository_dispatch:
    types:
      - "vercel.deployment.success"

permissions:
  contents: read
  # Required to access pull request metadata for Argos with GITHUB_TOKEN
  pull-requests: read

jobs:
  run-e2es:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: 22

      - name: Install dependencies
        run: npm ci && npx playwright install --with-deps

      - name: Print context (optional)
        run: |
          echo "URL: $BASE_URL"

      - name: Run Playwright tests with Argos reporter
        run: npx playwright test
        env:
          # URL of the preview deployment used by your test as the base URL.
          BASE_URL: ${{ github.event.client_payload.deployment.url }}
          # Provided by GitHub used by Argos to link builds to branches and pull requests
          # Optional, if not provided Argos will not link builds to PRs
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

{% endcode %}

### Setup with GitHub deployment status events

If your hosting provider emits GitHub Deployments events (Vercel, Netlify, Cloudflare), you can trigger tests from the [`deployment_status` event](https://docs.github.com/en/webhooks/webhook-events-and-payloads#deployment_status).

The event carries the preview URL when a deployment becomes successful.

{% code title=".github/workflows/ci.yml" %}

```yaml
name: Playwright + Argos Tests

on:
  deployment_status:

permissions:
  contents: read
  # Required to access pull request metadata for Argos with GITHUB_TOKEN
  pull-requests: read

jobs:
  run-e2es:
    # Run tests only if the deployment is successful
    if: github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success'

    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6

      - uses: actions/setup-node@v6
        with:
          node-version: 22

      - name: Install dependencies
        run: npm ci && npx playwright install --with-deps

      - name: Print context (optional)
        run: |
          echo "URL: $BASE_URL"
          echo "Branch: $ARGOS_BRANCH"

      - name: Run Playwright tests with Argos reporter
        run: npx playwright test
        env:
          # URL of the preview deployment, used by your tests as the base URL.
          BASE_URL: ${{ github.event.deployment_status.environment_url }}
          # Set only for production deployments to keep baselines stable (usually `main`).
          ARGOS_BRANCH: ${{ github.event.deployment_status.environment == 'Production' && 'main' || '' }}
          # Provided by GitHub used by Argos to link builds to branches and pull requests
          # Optional, if not provided Argos will not link builds to PRs
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

{% endcode %}

### Disable the Vercel Toolbar

By default, Vercel may inject a toolbar overlay into preview deployments. This can interfere with automated screenshots.

To avoid false diffs, disable the toolbar in your tests by sending the [`x-vercel-skip-toolbar` header](https://vercel.com/docs/vercel-toolbar/managing-toolbar#disable-toolbar-for-automation) with every request.

#### Playwright

Add the header in your `playwright.config.ts` so all test requests skip the toolbar:

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
  use: {
    extraHTTPHeaders: {
      "x-vercel-skip-toolbar": "1",
    },
  },
});
```

{% endcode %}

#### Cypress

Inject the header for all network requests in your test suite:

{% code title="support/index.ts" %}

```ts
beforeEach(() => {
  cy.intercept(`${Cypress.config("baseUrl")}**`, (req) => {
    req.headers["x-vercel-skip-toolbar"] = "1";
  });
});
```

{% endcode %}

### Running without GITHUB\_TOKEN

You can run Argos tests without exposing `GITHUB_TOKEN`. This is useful if you want to limit token scope in deployment workflows.

However, some features will not be available:

* Builds are only associated with a branch (no pull request metadata).
* No pull request link will appear in Argos.

{% hint style="info" %}
Argos SDKs warn if `GITHUB_TOKEN` is missing. To silence this, set `DISABLE_GITHUB_TOKEN_WARNING=true`.
{% endhint %}


# Visual coverage

Techniques to capture more of your interface, from responsive viewports and injected CSS to non-image file diffs.

Techniques to capture more of your interface and make every screenshot count.

* [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports) – Capture the same screen at multiple viewport widths
* [Capture user journeys](/docs/learn/how-to-guides/visual-coverage/capture-user-journeys) – Capture a flow such as a signup or an onboarding, screen by screen, as a journey
* [Storybook story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes) – Capture stories across multiple modes and themes
* [Capture screenshots from URLs](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls) – Screenshot a list of URLs without writing tests
* [Injecting CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css) – Apply custom CSS at capture time to hide or restyle elements
* [Fallback baselines](/docs/learn/how-to-guides/visual-coverage/fallback-baselines) – Compare a variant against an existing screenshot instead of reporting it as added
* [Adding ARIA snapshots manually](/docs/learn/how-to-guides/visual-coverage/adding-aria-snapshots-manually) – Attach accessibility snapshots alongside your screenshots
* [Compare non-image files](/docs/learn/how-to-guides/visual-coverage/compare-non-image-files) – Diff text, JSON, and other non-image files in Argos


# Responsive viewports

Capture the same page at multiple breakpoints in one test with Argos using Playwright, Cypress, or Puppeteer.

Argos lets you capture the same page at multiple breakpoints with a single test. Configure viewports once and get consistent responsive coverage across Playwright, Cypress and Puppeteer.

### Prerequisites

This feature works with [Playwright](/docs/reference/playwright), [Cypress](/docs/reference/cypress) and [Puppeteer](/docs/reference/puppeteer).

{% hint style="info" %}
If you use Storybook, see the dedicated guide on [Storybook modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes).
{% endhint %}

### Viewport configuration

Pass a viewports array to `argosScreenshot()` to generate screenshots for each dimension or preset you define. You can mix explicit sizes and device presets.

```js
await argosScreenshot(..., {
  viewports: [
    "iphone-4",
    { width: 800, height: 600 },
    { preset: "ipad-2", orientation: "landscape" },
  ],
});
```

### Available presets

| Preset            | Width (px) | Height (px) |
| ----------------- | ---------- | ----------- |
| pro-display       | 3008       | 1962        |
| studio-display    | 2560       | 1440        |
| imac-24           | 2240       | 1260        |
| macbook-16        | 1536       | 960         |
| macbook-15        | 1440       | 900         |
| macbook-13        | 1280       | 800         |
| macbook-neo       | 1204       | 753         |
| macbook-11        | 1366       | 768         |
| ipad-13-pro       | 1032       | 1376        |
| ipad-11-pro-m5    | 834        | 1210        |
| ipad-13-air       | 1024       | 1366        |
| ipad-11-air       | 820        | 1180        |
| ipad-11           | 820        | 1180        |
| ipad-12-pro       | 1024       | 1366        |
| ipad-11-pro       | 834        | 1194        |
| ipad-10           | 810        | 1080        |
| ipad-10-pro       | 834        | 1112        |
| ipad-9-pro        | 768        | 1024        |
| ipad-2            | 768        | 1024        |
| ipad-mini-a17     | 744        | 1133        |
| ipad-mini         | 768        | 1024        |
| iphone-air        | 420        | 912         |
| iphone-17         | 402        | 874         |
| iphone-17-pro     | 402        | 873         |
| iphone-17-pro-max | 440        | 956         |
| iphone-17e        | 390        | 844         |
| iphone-16         | 393        | 852         |
| iphone-16e        | 390        | 844         |
| iphone-16-plus    | 430        | 932         |
| iphone-16-pro     | 402        | 874         |
| iphone-16-pro-max | 440        | 956         |
| iphone-15         | 393        | 852         |
| iphone-15-plus    | 430        | 932         |
| iphone-15-pro     | 393        | 852         |
| iphone-15-pro-max | 430        | 932         |
| iphone-14         | 390        | 844         |
| iphone-14-plus    | 428        | 926         |
| iphone-14-pro     | 393        | 852         |
| iphone-14-pro-max | 490        | 932         |
| iphone-13         | 390        | 844         |
| iphone-13-mini    | 360        | 780         |
| iphone-13-pro     | 390        | 844         |
| iphone-13-pro-max | 428        | 926         |
| iphone-12         | 390        | 844         |
| iphone-12-mini    | 360        | 780         |
| iphone-12-pro     | 390        | 844         |
| iphone-12-pro-max | 428        | 926         |
| iphone-11         | 414        | 896         |
| iphone-11-pro     | 375        | 812         |
| iphone-11-pro-max | 414        | 896         |
| iphone-xr         | 414        | 896         |
| iphone-x          | 375        | 812         |
| iphone-6+         | 414        | 736         |
| iphone-se2        | 375        | 667         |
| iphone-8          | 375        | 667         |
| iphone-7          | 375        | 667         |
| iphone-6          | 375        | 667         |
| iphone-5          | 320        | 568         |
| iphone-4          | 320        | 480         |
| iphone-3          | 320        | 480         |
| samsung-s10       | 360        | 760         |
| samsung-note9     | 414        | 846         |

### Troubleshooting and best practices

Many sites compute layout at load time and will not adapt cleanly if the viewport changes later. If you notice issues, you may want to run your test suite entirely for each viewport instead of changing the viewport before taking each screenshot.

For example, in Playwright you can create a separate browser context for each viewport size.

```ts
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  projects: [
    {
      name: "chromium-desktop",
      use: {
        ...devices["Desktop Chrome"],
        channel: "chrome",
      },
    },
    {
      name: "chromium-mobile",
      use: {
        ...devices["Desktop Chrome"],
        channel: "chrome",
        viewport: { width: 402, height: 874 }, // iphone-16-pro preset size
      },
    },
  ],
});
```

You can also run `page.setViewportSize()` before navigating to the page to ensure the layout is computed correctly.

```ts
await page.setViewportSize({
  width: 640,
  height: 480,
});
await page.goto("https://example.com");
```


# Capture user journeys

Capture a signup, a checkout or an onboarding as a journey with Argos, so each change is reviewed next to the screens before and after it.

A test that walks a flow, such as a signup or an onboarding, and captures each screen along the way gives a **journey**: in the build review, Argos shows those screens as a strip of steps, in the order the test captured them. See [Follow a journey step by step](/docs/learn/review-workflow/review-a-build#follow-a-journey-step-by-step).

There is nothing to configure. Walk the flow in a test and capture each screen along the way:

{% tabs %}
{% tab title="Playwright" %}
{% code title="onboarding.spec.ts" %}

```ts
import { test, expect } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("onboarding", async ({ page }) => {
  await page.goto("/signup");
  await argosScreenshot(page, "signup");

  await page.getByLabel("Email").fill("jane@example.com");
  await page.getByRole("button", { name: "Continue" }).click();
  await expect(
    page.getByRole("heading", { name: "Create your workspace" }),
  ).toBeVisible();
  await argosScreenshot(page, "create-workspace");

  await page.getByLabel("Workspace name").fill("Acme");
  await page.getByRole("button", { name: "Create" }).click();
  await expect(
    page.getByRole("heading", { name: "Invite your team" }),
  ).toBeVisible();
  await argosScreenshot(page, "invite-team");
});
```

{% endcode %}
{% endtab %}

{% tab title="Cypress" %}
{% code title="onboarding.cy.ts" %}

```ts
it("onboarding", () => {
  cy.visit("/signup");
  cy.argosScreenshot("signup");

  cy.get("input[name=email]").type("jane@example.com");
  cy.contains("button", "Continue").click();
  cy.contains("h1", "Create your workspace");
  cy.argosScreenshot("create-workspace");

  cy.get("input[name=workspace]").type("Acme");
  cy.contains("button", "Create").click();
  cy.contains("h1", "Invite your team");
  cy.argosScreenshot("invite-team");
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

* **One test per path.** A signup with several options gets one test per option, not one test that walks them all.
* **Unique names.** A screen captured by several tests needs one name per test: put the path in it, such as `free-signup` and `team-signup`. When that screen changes, Argos [groups the identical changes](/docs/learn/review-workflow/review-a-build#review-similar-changes-together) in one row, so you review it once. The order comes from the calls, so there is nothing to number.

{% hint style="info" %}
Journeys work with Playwright, Cypress and Vitest. The order of the steps is recorded by `@argos-ci/playwright` 7.5.1, `@argos-ci/cypress` 7.2.1 and `@argos-ci/vitest` 0.6.1 and above: with an earlier version, the steps fall back to alphabetical order.
{% endhint %}


# Storybook story modes

Capture separate Storybook snapshots for each mode, combining theme, viewport, and locale globals in Argos.

Argos can capture multiple versions of your stories by applying different “modes,” which are essentially combinations of global Storybook settings (such as theme, viewport, locale, etc.). With modes, you can automatically generate a separate snapshot for each unique configuration.

{% hint style="info" %}
If you already have `parameters.chromatic.modes`, Argos will handle those settings by default. Prefer `parameters.argos.modes` in new work.

Argos stops reading `parameters.chromatic` as soon as a story has an `argos` parameter. For example, if you add `argos: { fitToContent: false }` to `.storybook/preview.ts`, your Chromatic modes are ignored for every story. Move them to `parameters.argos.modes` at the same time.
{% endhint %}

### What are modes?

A mode is a preset that configures various Storybook globals. For instance, you can have a “dark” mode for your UI theme, a “mobile” mode for smaller screens, or a combined “dark-mobile-spanish” mode that configures multiple globals at once.

**Key features of modes:**

* Each mode is named (e.g., "dark desktop" or "mobile").
* Each mode sets specific values for the Storybook globals (e.g., viewport size, background color, locale).
* Argos creates a separate visual baseline for each mode name.

### Setting up globals & addons

Before you define any modes, make sure you’ve configured the relevant Storybook features and addons in your `.storybook/preview.ts` (or `.js`) file. Examples include:

* [Viewports](https://storybook.js.org/docs/essentials/viewport) and [backgrounds](https://storybook.js.org/docs/essentials/backgrounds), built into Storybook 9 and later (on Storybook 8, install [`@storybook/addon-viewport`](https://www.npmjs.com/package/@storybook/addon-viewport) and [`@storybook/addon-backgrounds`](https://www.npmjs.com/package/@storybook/addon-backgrounds))
* [`@storybook/addon-themes`](https://www.npmjs.com/package/@storybook/addon-themes) for light/dark themes
* [`storybook-i18n`](https://www.npmjs.com/package/storybook-i18n) for locales

These features rely on Storybook “globals” and “decorators” under the hood. Argos modes simply set those globals at test time to generate multiple snapshots of the same story.

{% code title=".storybook/preview\.ts" %}

```ts
import { withThemeByClassName } from "@storybook/addon-themes";
import "../src/styles.css";

const preview = {
  parameters: {
    viewport: {
      options: {
        compact: {
          name: "Compact",
          styles: { width: "600px", height: "900px" },
        },
        widescreen: {
          name: "Widescreen",
          styles: { width: "1440px", height: "900px" },
        },
      },
    },
    backgrounds: {
      options: {
        light: { name: "Light", value: "#ffffff" },
        dark: { name: "Dark", value: "#1A1A1A" },
      },
    },
  },
  decorators: [
    withThemeByClassName({
      themes: {
        light: "light",
        dark: "dark",
      },
      defaultTheme: "light",
    }),
  ],
};

export default preview;
```

{% endcode %}

{% hint style="info" %}
On Storybook 8, viewports are defined under `viewport.viewports` and backgrounds under `backgrounds.values` (an array of `{ name, value }`), and a mode selects a background by its color, for example `backgrounds: { value: "#1A1A1A" }`. Argos resolves a mode’s `viewport` against either format.
{% endhint %}

### Defining modes

Create a `.storybook/modes.ts` (or `.js`) file that exports an object where each key is a mode name and each value is a set of overrides for the Storybook globals. For example:

{% code title=".storybook/modes.ts" %}

```ts
export const allModes = {
  dark: {
    backgrounds: { value: "dark" },
    theme: "dark",
  },
  mobile: {
    viewport: "compact",
  },
  "dark widescreen": {
    backgrounds: { value: "dark" },
    theme: "dark",
    viewport: "widescreen",
  },
  "light mobile": {
    backgrounds: { value: "light" },
    theme: "light",
    viewport: "compact",
  },
};
```

{% endcode %}

Each object can include as many or as few globals as you need. If a mode doesn’t specify a particular global, that global simply won’t be changed in that mode.

A mode’s `viewport` is a key of your `viewport.options` map: Argos resizes the browser to that viewport before capturing the story. Storybook’s own global format works too, so `viewport: { value: "compact", isRotated: true }` captures the viewport in landscape orientation. Likewise, `backgrounds.value` is a key of `backgrounds.options`, and `theme` is the global read by `withThemeByClassName`.

### Applying modes

Attach modes to any level of your Storybook: globally in `.storybook/preview.ts` (or `.js`), at the component (default export) level, or in an individual story’s parameters. Argos merges all modes defined up the chain.

#### Basic usage in a story file

{% code title="ProductCard.stories.ts" %}

```ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from "@storybook/your-framework";

import { ProductCard } from "./ProductCard";
import { allModes } from "../../../.storybook/modes";

const meta = {
  title: "Components/ProductCard",
  component: ProductCard,
  parameters: {
    // Use Argos for new projects; Chromatic is recognized too
    argos: {
      modes: {
        mobile: allModes.mobile,
        dark: allModes.dark,
      },
    },
  },
} satisfies Meta<typeof ProductCard>;

export default meta;
type Story = StoryObj<typeof meta>;

export const DefaultView: Story = {
  args: {
    productName: "Coffee Beans",
    price: 9.99,
  },
};

export const SoldOutView: Story = {
  args: {
    productName: "Coffee Beans",
    price: 9.99,
    isSoldOut: true,
  },
};
```

{% endcode %}

In this example, Argos will generate two snapshots for each story (DefaultView and SoldOutView): one in “mobile” mode and another in “dark” mode.

### Combining modes from multiple levels

You can add modes in your `.storybook/preview.ts` (or `.js`) at the project level, then define additional modes in a story file. Argos merges the modes defined at every level and captures one snapshot per mode name.

#### Project-level modes

{% code title=".storybook/preview\.ts" %}

```ts
import { allModes } from "./modes";

const preview = {
  parameters: {
    argos: {
      modes: {
        "light mobile": allModes["light mobile"],
      },
    },
  },
};

export default preview;
```

{% endcode %}

#### Component-level modes

{% code title="ProductCard.stories.ts" %}

```ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from "@storybook/your-framework";
import { ProductCard } from "./ProductCard";
import { allModes } from "../../../.storybook/modes";

const meta = {
  title: "Components/ProductCard",
  component: ProductCard,
  parameters: {
    argos: {
      modes: {
        "dark widescreen": allModes["dark widescreen"],
      },
    },
  },
} satisfies Meta<typeof ProductCard>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Basic: Story = {
  args: {
    /* ... */
  },
};
```

{% endcode %}

When Argos runs, it will generate snapshots for each mode defined at the project level and the component level. So for Basic, you get “light mobile” (from `preview.ts`) plus “dark widescreen” (from the component’s parameter).

### Excluding or disabling modes

Sometimes you want to turn off a certain higher-level mode for a specific story. You can do this by passing a `disabled` property:

{% code title="ProductCard.stories.ts" %}

```ts
// ...

export const SpecialCard: Story = {
  args: {
    /* ... */
  },
  parameters: {
    argos: {
      modes: {
        "light mobile": { disabled: true }, // turns off this inherited mode
      },
    },
  },
};
```

{% endcode %}

That story will now ignore light mobile mode but still apply any other inherited modes.

If every mode that applies to a story is disabled, Argos captures nothing for that story, including the screenshots taken with `argosScreenshot` in its play function. The story's test still runs. To leave out stories that don't use modes, see [Choose which stories are captured](/docs/reference/storybook#choose-which-stories-are-captured).

### Working with baselines

Each mode name corresponds to a separate baseline in Argos. If you rename a mode, it’s treated as entirely new. If you alter the internals of a mode (like changing the viewport from “compact” to “ultra-compact”) without renaming it, Argos still compares the new screenshot against the old baselines for that mode name.

{% hint style="info" %}
If you have an original single baseline from before you introduced modes, and you want to keep it around, just add a mode like `"baseline"` that reproduces the same environment as the original story. That way, your old baseline is preserved while you experiment with new modes.
{% endhint %}

### FAQ

<details>

<summary>Can modes be applied if I'm still using <code>parameters.chromatic</code>?</summary>

Yes. Argos reads your `chromatic.modes` settings if present. However, for new users or updated setups, prefer using `argos.modes` to avoid any confusion in the future.

Argos stops reading `parameters.chromatic` as soon as a story has an `argos` parameter, even one that only sets `fitToContent`. If you add one, move your modes to `argos.modes` at the same time.

</details>

<details>

<summary>Do all Storybook addons work with Argos modes?</summary>

Any feature or addon that leverages Storybook globals should work, including Storybook’s built-in viewports and backgrounds, [@storybook/addon-themes](https://storybook.js.org/addons/@storybook/addon-themes), or [storybook-i18n](https://storybook.js.org/addons/storybook-i18n). Modes just provide different values for those globals.

</details>

<details>

<summary>What happens if I delete or rename a mode?</summary>

If you remove a mode from your code, Argos will stop capturing new snapshots for that mode, and its baseline history won’t be updated anymore. Renaming a mode effectively creates a new baseline, much like renaming a story.

</details>

By setting up modes for dark vs. light, mobile vs. desktop, and everything in between, you verify all key variants of your UI without writing extra stories.


# Capture screenshots from URLs

Capture screenshots of a list of web pages with Argos using Playwright, Cypress, or Puppeteer.

Cover many pages with little code: loop over a list of URLs and capture a screenshot of each. The same pattern works in Playwright, Cypress, and Puppeteer.

### Using Playwright

Generate one test per page, each capturing a screenshot with the Argos Playwright SDK:

{% code title="screenshot-pages.spec.ts" %}

```js
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

const pages = [
  { name: "homepage", path: "/" },
  { name: "integrations", path: "/integrations" },
  { name: "contact", path: "/contact-us" },
  { name: "pricing", path: "/pricing" },
];

for (const { name, path } of pages) {
  test(`Run Argos on ${name} (${path})`, async ({ page }) => {
    await page.goto(path);
    await argosScreenshot(page, name);
  });
}
```

{% endcode %}

### Using Cypress

Generate one test per page with the Argos Cypress command:

{% code title="screenshot-pages.cy.js" %}

```js
const pages = [
  { name: "homepage", path: "/" },
  { name: "integrations", path: "/integrations" },
  { name: "contact", path: "/contact-us" },
  { name: "pricing", path: "/pricing" },
];

for (const { name, path } of pages) {
  it(`Run Argos on ${name} (${path})`, () => {
    cy.visit(path);
    cy.argosScreenshot(name);
  });
}
```

{% endcode %}

### Using Puppeteer

Loop over the pages in a standalone script and capture each with the Argos Puppeteer SDK:

{% code title="screenshot-pages.mjs" %}

```js
import puppeteer from "puppeteer";
import { argosScreenshot } from "@argos-ci/puppeteer";

const baseUrl = "http://localhost:3000";
const pages = [
  { name: "homepage", path: "/" },
  { name: "integrations", path: "/integrations" },
  { name: "contact", path: "/contact-us" },
  { name: "pricing", path: "/pricing" },
];

const browser = await puppeteer.launch();
const page = await browser.newPage();

for (const { name, path } of pages) {
  await page.goto(`${baseUrl}${path}`);
  await argosScreenshot(page, name);
}

await browser.close();
```

{% endcode %}


# Injecting CSS

Apply custom CSS at capture time to hide or restyle elements and fix flaky visual diffs in your Argos screenshots.

Argos can apply custom CSS while a screenshot is taken — useful to hide or restyle elements that the [built-in helpers](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers#helpers) don't cover, such as third-party iframes or complex dynamic regions.

### Add CSS while taking the screenshot

All our SDKs support an `argosCSS` option that allows you to specify custom CSS evaluated during the screenshot process. The style will be removed from the page after the screenshot is taken.

```ts
// Usage in Playwright or Puppeteer
await argosScreenshot(page, "my-screenshot", {
  argosCSS: `iframe { display: none; }`,
});

// Usage in Cypress
cy.argosScreenshot("my-screenshot", {
  argosCSS: `iframe { display: none; }`,
});
```

### Add CSS in your code

Argos adds a `__argos__` class to the HTML element during screenshots. You can target this class in your CSS for Argos-specific styling.

```css
.__argos__ iframe {
  display: none;
}
```

Both approaches only affect screenshots — your app's rendering outside Argos is untouched.


# Fallback baselines

Compare a new screenshot against an existing one so variants and A/B tests show real changes instead of appearing as brand new screenshots.

Argos compares a screenshot with the screenshot of the same name in the baseline build. When a name has no match, the screenshot is reported as **added** — there is nothing to compare it with.

That's the right default, but it gets in the way when a screenshot is really a variant of an existing one. The `baseName` option lets you list the names to compare against instead, in priority order.

### The problem

Say you run an A/B test on your home page and capture each variant under its own name:

```
home.png             # variant A, the page you already ship
home-variant-b.png   # variant B, the one being tested
```

The first time variant B runs, `home-variant-b.png` doesn't exist in the baseline, so Argos reports it as added and you see no diff — even though what you actually want to review is how variant B differs from the page you ship.

### Set fallback baselines

Pass an array to `baseName`. Argos tries each name in order and compares against the first one that exists in the baseline build:

{% tabs %}
{% tab title="Playwright" %}

```ts
await argosScreenshot(page, "home-variant-b", {
  baseName: ["home-variant-b", "home"],
});
```

{% endtab %}

{% tab title="Cypress" %}

```ts
cy.argosScreenshot("home-variant-b", {
  baseName: ["home-variant-b", "home"],
});
```

{% endtab %}

{% tab title="Puppeteer" %}

```ts
await argosScreenshot(page, "home-variant-b", {
  baseName: ["home-variant-b", "home"],
});
```

{% endtab %}

{% tab title="Vitest" %}

```ts
await argosScreenshot("home-variant-b", {
  baseName: ["home-variant-b", "home"],
});
```

{% endtab %}
{% endtabs %}

With that in place:

* Once variant B has a baseline of its own, `home-variant-b.png` is used — you review it against its own history.
* Until then, Argos falls back to `home.png`, so the first build shows variant B as a change from variant A.

{% hint style="info" %}
The screenshot's own name is **not** added implicitly. List it first, as above, so the screenshot keeps comparing against itself once it has a baseline. Passing only `baseName: ["home"]` would always compare against `home.png`, ignoring variant B's own history.
{% endhint %}

### Other uses

Fallback baselines fit any case where a screenshot is a derivative of another one:

* **Feature flags** — compare a flagged variant against the unflagged page.
* **Localizations** — bootstrap a new locale from the default one.
* **Renames** — keep history across a rename by falling back to the old name.

### How the comparison is reported

A screenshot compared against a fallback shows the mapping in the snapshot metadata on the build page, for example `home-variant-b.png → home.png`, so it's clear which baseline was used.

A baseline reached through a fallback is not reported as removed, even when no screenshot in the build carries its name.

### Single-name override

`baseName` also accepts a single string, which always compares against that name:

```ts
await argosScreenshot(page, "home-variant-b", { baseName: "home" });
```

### Without an SDK

If you generate [screenshot metadata](/docs/reference/screenshot-metadata) yourself, set `transient.baseName`:

```json
{
  "transient": {
    "baseName": ["home-variant-b.png", "home.png"]
  }
}
```

Names include the file extension here, and the project prefix if you use Playwright projects (e.g. `chromium/home.png`).


# Adding ARIA snapshots manually

Include ARIA accessibility snapshots alongside screenshots when uploading files to Argos manually with the CLI.

If you upload files to Argos manually, you can include ARIA snapshots alongside screenshots.

This setup is mainly useful if you are using Playwright for Python or another tool that can generate ARIA snapshots but does not have direct Argos support for them.

If you use Playwright with Node.js, use the official [Argos Playwright SDK](/docs/reference/playwright), which supports ARIA snapshots directly.

{% hint style="info" %}
Each ARIA snapshot counts as an additional screenshot for billing.
{% endhint %}

### Configure uploaded files

When you upload a folder with the Argos CLI, pass the `--files` flag so Argos picks up both screenshot files and ARIA snapshot files. For example, if your snapshots are stored in `./screenshots`, upload that directory with:

{% tabs %}
{% tab title="npm" %}

```
npm exec -- argos upload --files "**/*.png" "**/*.aria.yml" ./screenshots
```

{% endtab %}

{% tab title="yarn" %}

```
yarn run argos upload --files "**/*.png" "**/*.aria.yml" ./screenshots
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm exec -- argos upload --files "**/*.png" "**/*.aria.yml" ./screenshots
```

{% endtab %}

{% tab title="bun" %}

```
bun x argos upload --files "**/*.png" "**/*.aria.yml" ./screenshots
```

{% endtab %}
{% endtabs %}

Argos will then detect every `.png` screenshot and every `.aria.yml` ARIA snapshot found in that folder tree.

### Add an ARIA snapshot

Create an ARIA snapshot file with the `.aria.yml` extension:

```
my-snapshot-name.aria.yml
```

You can also add metadata in a companion file using the `.argos.json` suffix:

```
my-snapshot-name.aria.yml.argos.json
```

For general metadata conventions, see [Screenshot metadata](/docs/reference/screenshot-metadata). ARIA snapshot companion metadata follows the same conventions and can additionally define ARIA-specific transient fields, such as `transient.parentName`.

### Attach an ARIA snapshot to a screenshot

To associate an ARIA snapshot with a screenshot, set `transient.parentName` to the exact screenshot identifier used by Argos. This value must match the uploaded screenshot path and filename exactly, including subfolders and the file extension.

Example file structure:

```
folder/
  my-snapshot.png
  my-snapshot.png.argos.json
  my-aria-snapshot.aria.yml
  my-aria-snapshot.aria.yml.argos.json
```

{% code title="my-aria-snapshot.aria.yml.argos.json" %}

```json
{
  "transient": {
    "parentName": "folder/my-snapshot.png"
  }
}
```

{% endcode %}

When `parentName` exactly matches the screenshot identifier, Argos displays the ARIA snapshot as attached to that screenshot.


# Compare non-image files

Upload and diff text-based artifacts like JSON, HTML, Markdown, and CSS in Argos using the CLI files option.

Argos can compare more than screenshots. With the CLI, you can upload text-based artifacts such as API snapshots, generated HTML, Markdown files, CSS output, JavaScript bundles, XML documents, YAML files, or JSON fixtures and review their changes in Argos.

![Compare non-image files in Argos](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/KCZ7I73JUXmNDitNYHWA/json%20comparison%20d95543132bde0693cbb86877d840113a.png)

### Upload non-image files

Use the `-f` or `--files` option to tell the CLI which files to upload:

{% tabs %}
{% tab title="npm" %}

```
npm exec -- argos upload -f "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}

{% tab title="yarn" %}

```
yarn run argos upload -f "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm exec -- argos upload -f "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}

{% tab title="bun" %}

```
bun x argos upload -f "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}
{% endtabs %}

The `-f` option replaces the default screenshot glob. If you want to upload screenshots and non-image files in the same build, include both patterns:

{% tabs %}
{% tab title="npm" %}

```
npm exec -- argos upload -f "**/*.{png,jpg,jpeg,webp,avif,gif}" "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}

{% tab title="yarn" %}

```
yarn run argos upload -f "**/*.{png,jpg,jpeg,webp,avif,gif}" "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm exec -- argos upload -f "**/*.{png,jpg,jpeg,webp,avif,gif}" "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}

{% tab title="bun" %}

```
bun x argos upload -f "**/*.{png,jpg,jpeg,webp,avif,gif}" "**/*.{txt,json,yaml,yml,xml,html,md,css,js}" ./snapshots
```

{% endtab %}
{% endtabs %}

Use stable file names and paths so Argos can match each uploaded file with its baseline on future builds.

### Supported content types

Argos currently supports these non-image content types:

* `text/plain`
* `application/json`
* `application/yaml`
* `text/yaml`
* `application/xml`
* `text/xml`
* `text/html`
* `text/markdown`
* `text/css`
* `application/javascript`
* `text/javascript`

Image snapshots can be PNG, JPEG, WebP, AVIF or GIF (`image/png`, `image/jpeg`, `image/webp`, `image/avif`, `image/gif`).

Video snapshots are not supported: an upload that includes a video file fails with an "Unsupported content type" error. To share a screen recording on a pull request without diffing it, use [media sharing](/docs/learn/media).

### Framework support

Besides the CLI, the [Vitest SDK](/docs/reference/vitest#capturing-snapshots) uploads non-image snapshots with `argosSnapshot`, from browser or Node tests. [Contact us](https://argos-ci.com/contact) if you are interested in using this feature from another test framework.


# Migrate to Argos from another visual testing tool

Step-by-step guides for switching to Argos from Percy, Chromatic, Applitools, native Playwright or Vitest screenshots, BackstopJS, or Lost Pixel.

Already running visual tests with another tool? These guides walk you through moving to Argos without rewriting your test suite. In most cases you keep your existing tests and only swap the SDK, the snapshot call, and the CI step.

Argos is built around three ideas that shape every migration:

* **Keep your tests.** Argos plugs into Playwright, Vitest, Storybook, Cypress, WebdriverIO, and Puppeteer, or takes screenshots from any pipeline through the [CLI](/docs/reference/argos-command-line-interface-cli). You don't rewrite tests to switch.
* **Baselines come from your Git history, not from files in your repo.** Argos selects the [baseline build](/docs/learn/platform-fundamentals/baseline-build) automatically from your Git history, so there are no reference images to commit, update, or resolve in merge conflicts.
* **Review happens on the pull request.** Diffs are approved or rejected in the Argos app and reported back as a [commit status](/docs/learn/platform-fundamentals/build-modes) and [PR comment](/docs/learn/review-workflow/pull-request-comments).

### Pick your current tool

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Percy (BrowserStack)</strong></td><td>Replace <code>@percy/*</code> SDKs and <code>percy exec</code> with the Argos reporter.</td><td><a href="/docs/learn/how-to-guides/migrate-to-argos/from-percy">From Percy</a></td></tr><tr><td><strong>Chromatic</strong></td><td>Move Storybook, Playwright, or Cypress visual tests off the <code>chromatic</code> CLI.</td><td><a href="/docs/learn/how-to-guides/migrate-to-argos/from-chromatic">From Chromatic</a></td></tr><tr><td><strong>Applitools Eyes</strong></td><td>Replace <code>eyes.check()</code> and the Ultrafast Grid with Argos screenshots.</td><td><a href="/docs/learn/how-to-guides/migrate-to-argos/from-applitools">From Applitools</a></td></tr><tr><td><strong>Playwright screenshots</strong></td><td>Move off committed <code>toHaveScreenshot()</code> snapshots to hosted review.</td><td><a href="/docs/learn/how-to-guides/migrate-to-argos/from-playwright-native-screenshots">From Playwright</a></td></tr><tr><td><strong>Vitest screenshots</strong></td><td>Move off committed <code>toMatchScreenshot()</code> references to hosted review.</td><td><a href="/docs/learn/how-to-guides/migrate-to-argos/from-vitest-native-screenshots">From Vitest</a></td></tr><tr><td><strong>BackstopJS</strong></td><td>Move from self-hosted <code>backstop.json</code> scenarios to a hosted workflow.</td><td><a href="/docs/learn/how-to-guides/migrate-to-argos/from-backstopjs">From BackstopJS</a></td></tr><tr><td><strong>Lost Pixel</strong></td><td>Map <code>lostpixel.config.ts</code> shot modes to Argos before Lost Pixel is sunset.</td><td><a href="/docs/learn/how-to-guides/migrate-to-argos/from-lost-pixel">From Lost Pixel</a></td></tr></tbody></table>

### Don't see your tool?

If you're coming from a tool that isn't listed, the [Any test framework](/docs/quickstart/any-test-framework) quickstart covers the generic path: capture screenshots however you do today, then upload them with `argos upload`. [Contact support](https://argos-ci.com/contact) if you'd like help planning a migration.


# Migrate from Percy to Argos

Migrate visual testing from Percy (BrowserStack) to Argos. Replace the Percy SDK and percy exec with the Argos reporter for Playwright, Cypress, or Storybook.

This guide shows how to move a project from [Percy](https://percy.io/) (now part of BrowserStack) to Argos. You keep your existing tests. The migration is three edits: swap the SDK package, rename the snapshot call, and drop `percy exec` from your CI command.

### How Percy and Argos differ

Both tools catch visual regressions on every pull request, but the capture model is different, and it changes a few steps below:

* **Percy** captures the **DOM, CSS, and assets** in your test, uploads them, and re-renders each snapshot **in Percy's cloud** across the widths and browsers you configure in `.percy.yml`.
* **Argos** captures the **rendered screenshot in the real browser your test already runs** and uploads the image. Extra viewports and browsers come from your test framework (for example [Playwright projects](/docs/learn/how-to-guides/visual-coverage/responsive-viewports)), not from a server-side re-render.

The practical upshot: with Argos, what you see in your test is exactly what gets diffed, and there is no separate rendering configuration to maintain. Baselines are selected automatically from your [Git history](/docs/learn/platform-fundamentals/baseline-build) — there is no equivalent of managing them per branch in a dashboard.

### Why teams switch

Beyond the capture model, the reasons teams pick Argos over Percy:

* **Review where you work** — a [pull request comment](/docs/learn/review-workflow/pull-request-comments) summarizes every build, and reviews support [comments pinned to the exact pixel that changed](/docs/learn/review-workflow/review-a-build#comment-on-exactly-what-changed), threads, and real-time collaboration.
* **Flakiness under control** — [flaky test detection](/docs/learn/reliability-and-flakiness/flaky-test-detection) scores unstable tests, and recurring flaky changes can be ignored once or [auto-ignored](/docs/learn/reliability-and-flakiness/flaky-test-detection#automatically-ignore-recurring-flaky-changes).
* **Built for agents** — an [MCP server](/docs/agents/mcp-server), a scriptable [CLI](/docs/reference/argos-command-line-interface-cli), and [AI agent skills](/docs/learn/review-workflow/review-builds-with-ai-agents) let AI tools review builds with you.
* **Open source** — the whole platform, diff engine included, is MIT-licensed on [GitHub](https://github.com/argos-ci/argos).

### Concept mapping

| Percy                                   | Argos                                                                                  |
| --------------------------------------- | -------------------------------------------------------------------------------------- |
| `@percy/cli` + `@percy/playwright`      | `@argos-ci/playwright`                                                                 |
| `percySnapshot(page, "Name")`           | `argosScreenshot(page, "Name")`                                                        |
| `cy.percySnapshot("Name")` (Cypress)    | `cy.argosScreenshot("Name")`                                                           |
| `percy exec -- <test command>`          | `<test command>` (the reporter uploads automatically)                                  |
| `PERCY_TOKEN`                           | `ARGOS_TOKEN`                                                                          |
| `.percy.yml` → `snapshot.widths`        | [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports) |
| `.percy.yml` → `percy-css` / `percyCSS` | [Injecting CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css)               |
| Percy project + browsers dashboard      | Argos project ([Settings → Authentication → Project token](https://app.argos-ci.com/)) |
| Percy build review                      | [Review a build](/docs/learn/review-workflow/review-a-build) + PR comment              |

## Migrate a Playwright project

{% stepper %}
{% step %}

### Remove the Percy packages

```bash
npm uninstall @percy/cli @percy/playwright
```

{% endstep %}

{% step %}

### Install the Argos Playwright SDK

{% tabs %}
{% tab title="npm" %}

```bash
npm i --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add --dev @argos-ci/playwright
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev @argos-ci/playwright
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add the Argos reporter to your Playwright config

Percy hooks in by wrapping your test command with `percy exec`. Argos hooks in through a Playwright reporter instead, so there is no wrapper command to run.

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  reporter: [
    process.env.CI ? ["dot"] : ["list"],
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
      }),
    ],
  ],
  use: {
    // Reduce text rendering differences between machines.
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },
});
```

{% endcode %}
{% endstep %}

{% step %}

### Replace `percySnapshot` with `argosScreenshot`

The call signature is nearly identical — swap the import and the function name.

**Before (Percy)**

```ts
import { test } from "@playwright/test";
import { percySnapshot } from "@percy/playwright";

test("homepage", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await percySnapshot(page, "Homepage");
});
```

**After (Argos)**

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("homepage", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await argosScreenshot(page, "Homepage");
});
```

{% endstep %}

{% step %}

### Update the CI command

Drop `percy exec --` and run your tests directly. Rename the token secret from `PERCY_TOKEN` to `ARGOS_TOKEN`.

**Before (Percy)**

```yaml
- run: npx percy exec -- npx playwright test
  env:
    PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
```

**After (Argos)**

```yaml
- run: npx playwright test
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

`ARGOS_TOKEN` comes from **Settings → Authentication → Project token** in your Argos project. On GitHub Actions you can skip the secret entirely with [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication).
{% endstep %}

{% step %}

### Delete Percy configuration

Remove `.percy.yml` / `percy.config.js` and the `PERCY_TOKEN` secret. See the notes below for where its options map in Argos.
{% endstep %}
{% endstepper %}

## Migrate a Cypress project

Cypress follows the same pattern. Replace `@percy/cypress` with [`@argos-ci/cypress`](/docs/quickstart/cypress-quickstart), swap the import in `cypress/support/e2e.js`, register the Argos task in `cypress.config.js`, and rename `cy.percySnapshot("Name")` to `cy.argosScreenshot("Name")`. Then remove `percy exec` from your test command. Full steps are in the [Cypress quickstart](/docs/quickstart/cypress-quickstart).

## Migrate a Storybook project

If you used `@percy/storybook` (`percy storybook`), Argos captures stories with the [Storybook SDK](/docs/quickstart/storybook-quickstart) and can also deploy a live Storybook URL on every PR. Follow the [Storybook quickstart](/docs/quickstart/storybook-quickstart).

## Migrating Percy configuration

<details>

<summary>Responsive widths (<code>snapshot.widths</code>)</summary>

Percy re-renders each width in the cloud. In Argos you capture each viewport in the browser. The cleanest approach is a Playwright project per viewport, or the responsive-viewport helper. See [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).

</details>

<details>

<summary>Snapshot-time CSS (<code>percy-css</code> / <code>percyCSS</code>)</summary>

Percy lets you inject CSS to hide dynamic content at render time. Argos supports the same idea — hide or neutralize dynamic elements with injected CSS. See [Injecting CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css).

</details>

<details>

<summary>Cross-browser rendering</summary>

Percy renders one DOM snapshot across multiple browsers server-side. Argos screenshots the real browser your test runs in, so to cover multiple browsers you add [Playwright projects](https://playwright.dev/docs/test-projects) for `chromium`, `firefox`, and `webkit`. Each becomes its own set of screenshots in the build.

</details>

<details>

<summary>Ignoring regions</summary>

Percy uses `data-percy-hide` / percy-specific CSS. In Argos, mark elements you want excluded or stabilized before the screenshot. See the [flaky test playbook](/docs/learn/reliability-and-flakiness/flaky-tests) and [Argos helpers](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers).

</details>

## Frequently asked questions

<details>

<summary>Do I have to rewrite my tests?</summary>

No. Your test files, selectors, and navigation stay the same. You only change the import, the snapshot function name, and the CI command.

</details>

<details>

<summary>What happens to my Percy baselines?</summary>

They don't transfer, and they don't need to. The first Argos build on your reference branch becomes the baseline automatically. Until that build exists, pull request builds stay [orphan](/docs/learn/platform-fundamentals/baseline-build) — so run Argos on your default branch first.

</details>

<details>

<summary>Do I still need a separate <code>percy exec</code> step?</summary>

No. The Argos reporter uploads screenshots as part of your normal test run when it detects CI. There is no wrapper command.

</details>

<details>

<summary>How does pull request feedback differ from Percy?</summary>

Percy reports results through a GitHub status check that links to its dashboard. Argos posts a [commit status](/docs/learn/integrations/github-integration#commit-status-names) too, and additionally a [pull request comment](/docs/learn/review-workflow/pull-request-comments) summarizing the build — screenshots changed, ignored, and links to the review. During the review itself, Argos supports [comments pinned to the exact pixel or line that changed](/docs/learn/review-workflow/review-a-build#comment-on-exactly-what-changed), with threads, mentions, and reactions — feedback lives on the snapshot, not in a separate tool.

</details>

<details>

<summary>Is Argos open-source friendly?</summary>

Yes. Argos has a [free plan for open-source projects](/docs/learn/billing-and-subscription/open-source).

</details>

## Next steps

* [Keep your screenshots stable](/docs/learn/reliability-and-flakiness/flaky-tests) — avoid false positives after you switch.
* [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports) — reproduce Percy's multi-width coverage.
* [Playwright SDK reference](/docs/reference/playwright)
* [Argos vs Percy](https://argos-ci.com/compare/percy) — a side-by-side comparison.


# Migrate from Chromatic to Argos

Migrate visual testing from Chromatic to Argos. Move your Storybook, Playwright, or Cypress visual tests off the chromatic CLI to the Argos SDK.

This guide shows how to move a project from [Chromatic](https://www.chromatic.com/) to Argos. Chromatic is built around a hosted `chromatic` CLI that builds and uploads your Storybook (or an archive of your E2E runs). Argos plugs into the test runner you already use and, for Storybook, can also publish a live Storybook URL on every pull request.

### How Chromatic and Argos differ

* **Chromatic** builds your Storybook, uploads it, and captures every story **on Chromatic's infrastructure**. Cross-browser and multi-viewport coverage come from Chromatic's cloud and story **modes**.
* **Argos** captures your stories **locally with the Storybook Vitest addon** (a real Playwright browser) and uploads the images. It then deploys the built Storybook to an Argos URL so reviewers can open the live component next to the diff.

Both select baselines automatically and gate the pull request, so the review workflow feels similar. The main change is *where* rendering happens and how you scope large suites.

### Concept mapping

| Chromatic                                            | Argos                                                                                                                                                |
| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chromatic` CLI + `@chromatic-com/storybook`         | `@argos-ci/storybook` + `argos deploy`                                                                                                               |
| `npx chromatic --project-token=<token>`              | `npx vitest run --project=storybook` + `npx argos deploy ./storybook-static`                                                                         |
| `CHROMATIC_PROJECT_TOKEN`                            | `ARGOS_TOKEN`                                                                                                                                        |
| TurboSnap (`--only-changed`)                         | [Subset builds](/docs/learn/how-to-guides/ci-pipelines/subset-builds) / [sharding](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) |
| Modes (viewports, themes, globals)                   | [Storybook story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes)                                                             |
| Publish Storybook (`chromatic` hosting + permalinks) | `argos deploy ./storybook-static` → [Deployments](/docs/learn/deployments)                                                                           |
| `@chromatic-com/playwright` + `--playwright`         | `@argos-ci/playwright` reporter + `argosScreenshot`                                                                                                  |
| `@chromatic-com/cypress` + `--cypress`               | `@argos-ci/cypress` + `cy.argosScreenshot`                                                                                                           |
| Chromatic web review                                 | [Review a build](/docs/learn/review-workflow/review-a-build) + PR comment                                                                            |

## Migrate a Storybook project

{% stepper %}
{% step %}

### Remove the Chromatic packages

```bash
npm uninstall chromatic @chromatic-com/storybook
```

{% endstep %}

{% step %}

### Install the Argos Storybook SDK

{% tabs %}
{% tab title="npm" %}

```bash
npm i --save-dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add --dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add --save-dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev @argos-ci/storybook @argos-ci/cli
```

{% endtab %}
{% endtabs %}

Argos uses the [Storybook Vitest addon](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon) to run your stories. The addon requires Storybook 9 or later; if you don't have it yet, install it before continuing. On Storybook 8, use the [Test Runner quickstart](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart) instead, and on older versions the [legacy quickstart](/docs/quickstart/storybook-quickstart/storybook-legacy-less-than-v8-quickstart).
{% endstep %}

{% step %}

### Add the Argos plugin to your Vitest config

The Argos Vitest plugin captures a screenshot of every story and uploads it. Add it alongside the Storybook test plugin:

{% code title="vitest.config.ts" %}

```ts
import { argosVitestPlugin } from "@argos-ci/storybook/vitest-plugin";
// ...alongside storybookTest({ ... }) in your plugins array:

argosVitestPlugin({
  uploadToArgos: !!process.env.CI,
}),
```

{% endcode %}

See the [Storybook quickstart](/docs/quickstart/storybook-quickstart) for the complete `vitest.config.ts`, including the browser provider and text-rendering flags.
{% endstep %}

{% step %}

### Replace the CI step

Swap the single `chromatic` command for two steps: run the visual tests, then deploy the built Storybook.

**Before (Chromatic)**

```yaml
- run: npm ci
- run: npx chromatic
    --project-token=${{ secrets.CHROMATIC_PROJECT_TOKEN }}
```

**After (Argos)**

```yaml
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx vitest --project=storybook
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
- run: npm run build-storybook
- run: npx --no-install argos deploy ./storybook-static
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

`ARGOS_TOKEN` comes from **Settings → Authentication → Project token**. On GitHub Actions you can use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) instead of a secret.
{% endstep %}

{% step %}

### Remove Chromatic config

Delete `chromatic.config.json`, the `@chromatic-com/storybook` addon entry in `.storybook/main.ts`, and the `CHROMATIC_PROJECT_TOKEN` secret.
{% endstep %}
{% endstepper %}

## Migrate the published Storybook

Chromatic doesn't only run visual tests — it also **publishes your Storybook** and serves it at a hosted permalink (for example `main--<appid>.chromatic.com`), which you share with designers and stakeholders. Argos covers this with [Deployments](/docs/learn/deployments): the `argos deploy` step you added above uploads the built Storybook and serves it on its own URL, so you can drop Chromatic's publish entirely.

### How the hosting maps

| Chromatic                                             | Argos                                                                                                             |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `npx chromatic` publishes the built Storybook         | `npx argos deploy ./storybook-static` uploads and serves it                                                       |
| Per-build permalink                                   | Immutable [deployment URL](/docs/learn/deployments/urls-and-domains) (`<project>-<hash>-<account>.argos-ci.live`) |
| Branch permalink (e.g. `main--<appid>.chromatic.com`) | [Branch URL](/docs/learn/deployments/urls-and-domains) that follows the latest deploy on that branch              |
| "Latest published" / production Storybook             | [Production deployment](/docs/learn/deployments/environments) (via `--prod` or your production branch)            |
| Account-gated access to the published Storybook       | [Access protection](/docs/learn/deployments/access-protection) (require sign-in per project)                      |

### What to change

{% stepper %}
{% step %}

### Keep the `argos deploy` step

The CI step you added when migrating visual tests already publishes your Storybook:

```yaml
- run: npm run build-storybook
- run: npx --no-install argos deploy ./storybook-static
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

By default this creates a **preview** deployment. Deployments from your production branch (usually `main`) are promoted to **production** automatically — or force it with `--prod`. See [Environments](/docs/learn/deployments/environments).
{% endstep %}

{% step %}

### Point shared links at the Argos URL

Replace any `*.chromatic.com` links in your README, PR templates, or design docs with the Argos deployment URL. Argos posts the deployment status and URL back to the pull request, so reviewers get the link automatically — no separate publish command to run.
{% endstep %}

{% step %}

### Match your access setting

If your Chromatic Storybook required a login, turn on [access protection](/docs/learn/deployments/access-protection) in Argos so deployment URLs require an Argos sign-in. If it was public, no change is needed.
{% endstep %}
{% endstepper %}

{% hint style="info" %}
If you only used Chromatic to **publish** Storybook (not for visual tests), you can adopt just the deploy step. See the [Deployments quickstart](/docs/learn/deployments) and [Use deployments in CI](/docs/learn/deployments/use-deployments-in-ci) for non-GitHub providers.
{% endhint %}

## Migrate Playwright or Cypress E2E visual tests

Chromatic's E2E integrations (`@chromatic-com/playwright`, `@chromatic-com/cypress`) capture an **archive** during your test run, which you then upload with `npx chromatic --playwright` or `--cypress`. Argos captures screenshots inline instead — no archive step.

* **Playwright:** install `@argos-ci/playwright`, add the reporter to `playwright.config.ts`, and call `argosScreenshot(page, "Name")` where you want a snapshot. Then run `npx playwright test` directly (no `chromatic` command). See the [Playwright quickstart](/docs/quickstart/playwright-quickstart).
* **Cypress:** install `@argos-ci/cypress`, register the Argos task, and call `cy.argosScreenshot("Name")`. See the [Cypress quickstart](/docs/quickstart/cypress-quickstart).

## Migrating Chromatic features

<details>

<summary>TurboSnap (only test changed stories)</summary>

Argos doesn't use TurboSnap's git-diff story selection. Instead it runs your test runner, so you scope work with the tools you already have: [subset builds](/docs/learn/how-to-guides/ci-pipelines/subset-builds) to run part of the suite, and [parallel testing and sharding](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) to spread it across CI machines.

</details>

<details>

<summary>Modes (viewports, themes, locales)</summary>

Chromatic modes map to [Storybook story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes) in Argos, which capture the same story under multiple viewports, themes, or globals.

</details>

<details>

<summary>Interaction tests (play functions)</summary>

Your `play` functions keep working. Call `argosScreenshot(ctx, "name")` inside a play function to capture a specific interaction state. See the [Storybook quickstart](/docs/quickstart/storybook-quickstart).

</details>

## Frequently asked questions

<details>

<summary>Do I keep writing stories the same way?</summary>

Yes. Your stories, args, and play functions are unchanged. Argos runs them through Vitest and screenshots each one.

</details>

<details>

<summary>What replaces Chromatic's hosted Storybook?</summary>

`argos deploy ./storybook-static` publishes your built Storybook to a unique Argos [deployment URL](/docs/learn/deployments) on every pull request, so reviewers can open the live component.

</details>

<details>

<summary>What happens to my Chromatic baselines?</summary>

They don't transfer. The first Argos build on your reference branch establishes the baseline automatically. Run Argos on your default branch first so pull request builds have something to compare against.

</details>

<details>

<summary>Is there an open-source plan?</summary>

Yes — see [Open source](/docs/learn/billing-and-subscription/open-source).

</details>

## Next steps

* [Storybook quickstart](/docs/quickstart/storybook-quickstart) — the full setup.
* [Storybook story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes) — replace Chromatic modes.
* [Deployments](/docs/learn/deployments) — live Storybook URLs on every PR.
* [Argos vs Chromatic](https://argos-ci.com/compare/chromatic) — a side-by-side comparison.


# Migrate from Applitools to Argos

Migrate visual testing from Applitools Eyes to Argos. Replace the eyes.check() lifecycle and the Ultrafast Grid with Argos screenshots for Playwright or Cypress.

This guide shows how to move a project from [Applitools Eyes](https://applitools.com/) to Argos. The biggest change is the API shape: Applitools uses an `open` / `check` / `close` lifecycle per test, while Argos captures a screenshot with a single call.

### How Applitools and Argos differ

* **Applitools** captures the DOM in your test and renders it in the **Ultrafast Grid** across the browsers and viewports you configure, then compares with **Visual AI** using match levels (Strict, Layout, Content, Exact).
* **Argos** captures the **rendered screenshot in the browser your test runs** and compares it with a [pixel diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) tuned to ignore anti-aliasing and sub-pixel noise. Cross-browser and multi-viewport coverage comes from your test framework (for example [Playwright projects](/docs/learn/how-to-guides/visual-coverage/responsive-viewports)).

If you leaned heavily on Applitools' Layout match level and cloud grid, plan for two things in Argos: cover extra browsers/viewports by running your tests in them, and [stabilize dynamic content](/docs/learn/reliability-and-flakiness/flaky-tests) so the pixel diff stays clean.

### Concept mapping

| Applitools                                            | Argos                                                                                                                                                                    |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `@applitools/eyes-playwright`                         | `@argos-ci/playwright`                                                                                                                                                   |
| `eyes.open()` / `eyes.check()` / `eyes.close()`       | `argosScreenshot(page, "Name")`                                                                                                                                          |
| `cy.eyesOpen` / `cy.eyesCheckWindow` / `cy.eyesClose` | `cy.argosScreenshot("Name")`                                                                                                                                             |
| `APPLITOOLS_API_KEY`                                  | `ARGOS_TOKEN`                                                                                                                                                            |
| Ultrafast Grid (browsers & viewports)                 | [Playwright projects](https://playwright.dev/docs/test-projects) / [responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports)                |
| Match levels (Strict, Layout, Content)                | [Diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) + [flaky-test stabilization](/docs/learn/reliability-and-flakiness/flaky-tests) |
| Batches                                               | [Builds](/docs/learn/platform-fundamentals/build-modes)                                                                                                                  |
| `applitools.config.js`                                | Argos reporter options in `playwright.config.ts`                                                                                                                         |
| Eyes dashboard                                        | [Review a build](/docs/learn/review-workflow/review-a-build) + PR comment                                                                                                |

## Migrate a Playwright project

{% stepper %}
{% step %}

### Remove the Applitools packages

```bash
npm uninstall @applitools/eyes-playwright
```

{% endstep %}

{% step %}

### Install the Argos Playwright SDK

{% tabs %}
{% tab title="npm" %}

```bash
npm i --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add --dev @argos-ci/playwright
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev @argos-ci/playwright
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add the Argos reporter

Applitools is configured through `applitools.config.js` and custom fixtures. In Argos, configuration lives in the Playwright reporter:

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  reporter: [
    process.env.CI ? ["dot"] : ["list"],
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
      }),
    ],
  ],
  use: {
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },
});
```

{% endcode %}
{% endstep %}

{% step %}

### Collapse the Eyes lifecycle into one call

Replace each `open` → `check` → `close` sequence with a single `argosScreenshot` call. There is no per-test session to open or close.

**Before (Applitools)**

```ts
import { Target } from "@applitools/eyes-playwright";
import { test } from "@applitools/eyes-playwright/fixture";

test("homepage", async ({ page, eyes }) => {
  await page.goto("http://localhost:3000");
  await eyes.check("Homepage", Target.window().fully());
  // eyes.open()/close() handled by the fixture
});
```

**After (Argos)**

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("homepage", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await argosScreenshot(page, "Homepage");
});
```

If you used the plain (non-fixture) API — `const eyes = new Eyes(runner); await eyes.open(...); await eyes.check(...); await eyes.close();` — remove all three lifecycle calls and keep one `argosScreenshot` per checkpoint.
{% endstep %}

{% step %}

### Update the CI command

Rename the secret from `APPLITOOLS_API_KEY` to `ARGOS_TOKEN` and run your tests directly.

```yaml
- run: npx playwright test
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

On GitHub Actions you can use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) instead of a secret.
{% endstep %}

{% step %}

### Delete Applitools config

Remove `applitools.config.js`, any Eyes fixtures/imports, and the `APPLITOOLS_API_KEY` secret.
{% endstep %}
{% endstepper %}

## Migrate a Cypress project

Replace `@applitools/eyes-cypress` with [`@argos-ci/cypress`](/docs/quickstart/cypress-quickstart). The three-call lifecycle collapses the same way:

{% columns %}
{% column %}
**Before (Applitools)**

```js
cy.eyesOpen({ appName: "App", testName: "Homepage" });
cy.eyesCheckWindow("Homepage");
cy.eyesClose();
```

{% endcolumn %}

{% column %}
**After (Argos)**

```js
cy.argosScreenshot("Homepage");
```

{% endcolumn %}
{% endcolumns %}

Full setup is in the [Cypress quickstart](/docs/quickstart/cypress-quickstart).

## Migrating Applitools features

<details>

<summary>Match levels (Layout / Content / Strict)</summary>

Argos uses a single [pixel diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) with tolerance for anti-aliasing and rendering noise, rather than selectable match levels. To handle content that legitimately changes (dates, avatars, ads), stabilize it before the screenshot — see the [flaky test playbook](/docs/learn/reliability-and-flakiness/flaky-tests) and [Argos helpers](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers).

</details>

<details>

<summary>Ultrafast Grid (many browsers &#x26; viewports)</summary>

Argos captures the real browser your test runs in. Reproduce grid coverage by adding [Playwright projects](https://playwright.dev/docs/test-projects) for `chromium`, `firefox`, and `webkit`, and by capturing multiple [responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).

</details>

<details>

<summary>Regions to ignore / floating regions</summary>

Instead of ignore regions defined in the check call, hide or neutralize dynamic elements before the screenshot with [injected CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css) or the stabilization helpers.

</details>

## Frequently asked questions

<details>

<summary>Does Argos use AI to compare screenshots?</summary>

Argos compares with a pixel diff algorithm tuned to ignore anti-aliasing and sub-pixel differences. It also offers [AI-assisted review](/docs/learn/review-workflow/review-builds-with-ai-agents) to help triage changes, but the diff itself is deterministic. See [how Argos detects visual differences](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences).

</details>

<details>

<summary>Do I need to open and close a session per test?</summary>

No. There is no session lifecycle. Call `argosScreenshot` wherever you want a checkpoint and the reporter handles the upload.

</details>

<details>

<summary>What happens to my Applitools baselines?</summary>

They don't transfer. The first Argos build on your reference branch becomes the baseline automatically, so run Argos on your default branch first.

</details>

## Next steps

* [How Argos detects visual differences](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences)
* [Keep your screenshots stable](/docs/learn/reliability-and-flakiness/flaky-tests)
* [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports) — reproduce Ultrafast Grid coverage.
* [Argos vs Applitools](https://argos-ci.com/compare/applitools) — a side-by-side comparison.


# Migrate from Playwright toHaveScreenshot to Argos

Move from Playwright's built-in toHaveScreenshot() snapshots to Argos. Stop committing PNG baselines to Git and review visual changes on the pull request.

If you use Playwright's built-in [`toHaveScreenshot()`](https://playwright.dev/docs/test-snapshots), you already have visual tests — you're just storing the baselines in your repository and diffing them on the CI machine. This guide moves those tests to Argos so baselines live in the cloud, review happens on the pull request, and you stop fighting cross-platform PNG mismatches.

### Why teams move off native snapshots

Playwright's snapshots work, but they push three problems onto your team:

* **Baselines are committed PNGs.** Every screenshot lives in a `*-snapshots/` folder in Git. They bloat the repo, clutter diffs, and cause merge conflicts.
* **Snapshots are platform-specific.** A baseline captured on macOS won't match Linux CI (`-darwin` vs `-linux` suffixes), so teams end up running snapshots only in Docker or CI to stay consistent.
* **There is no review UI.** A failing `toHaveScreenshot()` is a red test. To accept an intended change you re-run with `--update-snapshots` and commit new PNGs — there's no place to *see* the before/after or have a teammate approve it.

Argos keeps your Playwright tests but removes all three: baselines are selected from your [Git history](/docs/learn/platform-fundamentals/baseline-build) in the cloud, rendering happens the same way every run, and changes are reviewed and approved on the pull request.

### What changes semantically

With native snapshots, a visual difference **fails the test**. With Argos, `argosScreenshot` uploads the image and the comparison runs in Argos — the visual result becomes a [commit status and PR check](/docs/learn/platform-fundamentals/build-modes) you review and approve, decoupled from whether the test itself passed. This is what lets you approve intended changes without editing files.

### Concept mapping

| Playwright native                           | Argos                                                                                    |
| ------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `expect(page).toHaveScreenshot("name.png")` | `argosScreenshot(page, "name")`                                                          |
| `expect(locator).toHaveScreenshot()`        | `argosScreenshot(page, "name", { element: locator })`                                    |
| Committed `*-snapshots/` PNG folders        | Cloud baselines from [Git history](/docs/learn/platform-fundamentals/baseline-build)     |
| `--update-snapshots`                        | Approve in the [Argos review UI](/docs/learn/review-workflow/review-a-build)             |
| `maxDiffPixels` / `threshold`               | [Diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) |
| Diff fails the test                         | Diff becomes a PR check to review + approve                                              |

## Migrate the project

{% stepper %}
{% step %}

### Install the Argos Playwright SDK

{% tabs %}
{% tab title="npm" %}

```bash
npm i --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add --dev @argos-ci/playwright
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add --save-dev @argos-ci/playwright
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev @argos-ci/playwright
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add the Argos reporter

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  reporter: [
    process.env.CI ? ["dot"] : ["list"],
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
      }),
    ],
  ],
  use: {
    // No longer needed to match CI, but still good for stable text.
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },
});
```

{% endcode %}
{% endstep %}

{% step %}

### Replace `toHaveScreenshot()` with `argosScreenshot()`

**Before (native)**

```ts
import { test, expect } from "@playwright/test";

test("homepage", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await expect(page).toHaveScreenshot("homepage.png");
});
```

**After (Argos)**

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("homepage", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await argosScreenshot(page, "homepage");
});
```

To screenshot a single element, pass the page and a locator or selector in the `element` option: `await argosScreenshot(page, "dialog", { element: page.getByRole("dialog") })`. The first argument must be a `Page` or a `Frame`, not a locator.
{% endstep %}

{% step %}

### Delete committed baselines

Remove the snapshot folders Playwright generated and stop tracking them:

```bash
git rm -r "**/*-snapshots"
```

If you set a custom `snapshotPathTemplate`, remove it too. You no longer commit baseline images — Argos stores them.
{% endstep %}

{% step %}

### Seed the baseline and wire up CI

Run your tests in CI with `ARGOS_TOKEN` set. **Run on your default branch first** so Argos has a baseline; until then, pull request builds stay [orphan](/docs/learn/platform-fundamentals/baseline-build).

```yaml
- run: npx playwright install --with-deps chromium
- run: npx playwright test
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

`ARGOS_TOKEN` comes from **Settings → Authentication → Project token**. On GitHub Actions you can use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) instead.
{% endstep %}
{% endstepper %}

## Frequently asked questions

<details>

<summary>Can I keep some <code>toHaveScreenshot()</code> assertions?</summary>

Yes — the two can coexist during migration. But the whole point is to stop committing and diffing PNGs locally, so we recommend converting each `toHaveScreenshot()` to `argosScreenshot` and deleting the committed baselines.

</details>

<details>

<summary>Do I still need Docker to keep screenshots consistent?</summary>

No. Because comparison happens in Argos against a baseline captured the same way, you don't need to render locally in the same OS as CI just to match committed PNGs. You still want [stable rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering), which the `launchOptions` flags above handle.

</details>

<details>

<summary>How do I accept an intended visual change now?</summary>

Open the build in Argos and approve it in the [review UI](/docs/learn/review-workflow/review-a-build). No `--update-snapshots`, no committing new images.

</details>

<details>

<summary>What about <code>maxDiffPixels</code> and <code>threshold</code>?</summary>

Argos applies its own [diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) with tolerance for anti-aliasing and sub-pixel noise, so you rarely need per-assertion tuning. If a specific screenshot needs a different sensitivity, pass the [`threshold` option](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences#sensitivity-threshold) to `argosScreenshot` (between 0 and 1).

</details>

## Next steps

* [Playwright quickstart](/docs/quickstart/playwright-quickstart)
* [Keep your screenshots stable](/docs/learn/reliability-and-flakiness/flaky-tests)
* [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports)
* [Argos vs Playwright screenshots](https://argos-ci.com/compare/playwright) — a side-by-side comparison with `toHaveScreenshot()`.


# Migrate from Vitest toMatchScreenshot to Argos

Move from Vitest's built-in toMatchScreenshot() to Argos. Stop committing \_\_screenshots\_\_ reference images and review visual changes on the pull request.

To migrate from Vitest's built-in [`toMatchScreenshot()`](https://vitest.dev/api/browser/assertions#tomatchscreenshot) to Argos, install `@argos-ci/vitest`, add `argosVitestPlugin()` to your Vitest config, replace each `toMatchScreenshot()` assertion with `await argosScreenshot("name")`, and delete the committed `__screenshots__` folders. Your browser mode tests stay the same; Argos picks baselines from your Git history and reports visual changes as a check on your pull request.

### Why teams move off `toMatchScreenshot()`

`toMatchScreenshot()` works, but it puts three problems on your team:

* **References are committed images.** Vitest stores reference screenshots in `__screenshots__` folders next to your tests, and you commit them. Screenshots of deleted or renamed tests aren't removed automatically.
* **References are platform-specific.** File names include the browser and the platform, for example `button-chromium-darwin.png`, so references captured on macOS are not used on Linux CI. Vitest recommends a controlled environment, such as Docker or CI-only runs, to keep them consistent.
* **There is no review step.** A mismatch fails the test. To accept an intended change, you re-run with `--update` and commit the new images, and the diff image is written to `.vitest/attachments/` on the machine that ran the test.

Argos keeps your Vitest tests but removes all three: baselines are selected from your [Git history](/docs/learn/platform-fundamentals/baseline-build), screenshots are captured on CI, and changes are reviewed and approved on the pull request.

### What changes semantically

With `toMatchScreenshot()`, a visual difference **fails the test**. With Argos, `argosScreenshot` saves the screenshot and the comparison runs in Argos once the files are uploaded: the visual result becomes a [commit status](/docs/learn/platform-fundamentals/build-modes) that you review and approve, independent of whether the test passed.

### Concept mapping

| Vitest `toMatchScreenshot()`                                         | Argos                                                                                                                                         |
| -------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `await expect(page.getByRole("button")).toMatchScreenshot("button")` | `await argosScreenshot("button")`                                                                                                             |
| A screenshot of one element                                          | `await argosScreenshot("button", { element: "button" })` (a CSS selector)                                                                     |
| `__screenshots__/` folders committed to Git                          | Baselines from [Git history](/docs/learn/platform-fundamentals/baseline-build)                                                                |
| `vitest --update`                                                    | Approve in the [Argos review UI](/docs/learn/review-workflow/review-a-build)                                                                  |
| `comparatorOptions` (`threshold`, `allowedMismatchedPixelRatio`, …)  | The `threshold` option of `argosScreenshot`, and the [diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) |
| `screenshotOptions.mask`                                             | The `argosCSS` option, for example `visibility: hidden` on dynamic elements                                                                   |
| `timeout` (retries until the screenshot is stable)                   | Argos stabilization: waits for fonts, images, and `aria-busy` elements                                                                        |
| Diff images in `.vitest/attachments/`                                | The [build page](/docs/learn/review-workflow/review-a-build) and the pull request comment                                                     |
| A mismatch fails the test                                            | A pull request check to review and approve                                                                                                    |

## Migrate the project

{% stepper %}
{% step %}

### Install the Argos Vitest SDK

{% tabs %}
{% tab title="npm" %}

```bash
npm i --save-dev @argos-ci/vitest
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add --dev @argos-ci/vitest
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add --save-dev @argos-ci/vitest
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev @argos-ci/vitest
```

{% endtab %}
{% endtabs %}

You keep `vitest`, `@vitest/browser`, `@vitest/browser-playwright`, and `playwright`: the SDK requires Vitest 4 or later and captures screenshots with the Playwright provider. If your browser mode uses another provider, switch to [`@vitest/browser-playwright`](https://vitest.dev/guide/browser/playwright).
{% endstep %}

{% step %}

### Add the Argos plugin

Add `argosVitestPlugin()` to your Vitest config, next to your existing browser mode settings:

{% code title="vitest.config.ts" %}

```ts
import { defineConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";
import { argosVitestPlugin } from "@argos-ci/vitest/plugin";

export default defineConfig({
  plugins: [
    argosVitestPlugin({
      // Upload to Argos on CI only.
      uploadToArgos: !!process.env.CI,
    }),
  ],
  test: {
    browser: {
      enabled: true,
      headless: true,
      provider: playwright({
        // Reduce text rendering differences between machines.
        launchOptions: {
          args: ["--disable-lcd-text", "--font-render-hinting=none"],
        },
      }),
      instances: [{ browser: "chromium" }],
    },
  },
});
```

{% endcode %}

You can remove the `browser.expect.toMatchScreenshot` settings once every assertion is migrated.
{% endstep %}

{% step %}

### Replace `toMatchScreenshot()` with `argosScreenshot()`

**Before (Vitest)**

```tsx
import { expect, test } from "vitest";
import { page } from "vitest/browser";
import { render } from "vitest-browser-react";
import { Button } from "./Button";

test("button", async () => {
  render(<Button>Click me</Button>);
  await expect(page.getByRole("button")).toMatchScreenshot("button");
});
```

**After (Argos)**

```tsx
import { test } from "vitest";
import { render } from "vitest-browser-react";
import { argosScreenshot } from "@argos-ci/vitest";
import { Button } from "./Button";

test("button", async () => {
  render(<Button>Click me</Button>);
  await argosScreenshot("button");
});
```

By default, `argosScreenshot` captures the rendered output of the test. To capture a single element, pass a CSS selector: `await argosScreenshot("button", { element: "button" })`. Per-call options must be serializable, so pass a selector string, not a locator. The name is optional: omit it and Argos derives one from the current test.
{% endstep %}

{% step %}

### Delete the committed references

Remove the reference folders and stop tracking them:

```bash
git rm -r "**/__screenshots__"
```

If you set a custom `screenshotDirectory` or `resolveScreenshotPath`, remove it too. You no longer commit reference images: Argos stores the baselines.
{% endstep %}

{% step %}

### Seed the baseline and wire up CI

Run your tests in CI with `ARGOS_TOKEN` set. **Run on your default branch first** so Argos has a baseline; until then, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds).

```yaml
- run: npx playwright install --with-deps chromium
- run: npx vitest run
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

`ARGOS_TOKEN` comes from **Settings → Authentication → Project token**. On GitHub Actions you can use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) instead of a secret.
{% endstep %}
{% endstepper %}

## Frequently asked questions

<details>

<summary>Can I keep some <code>toMatchScreenshot()</code> assertions?</summary>

Yes. `argosScreenshot` is a separate command and doesn't change `toMatchScreenshot()`, so both can run in the same suite while you migrate. Once every assertion is converted, delete the `__screenshots__` folders.

</details>

<details>

<summary>Do I still need Docker to keep screenshots consistent?</summary>

No. Argos compares each screenshot with a baseline captured on CI, so you don't need to render locally in the same environment as CI to match committed images. Keep the text-rendering `launchOptions` above: they make glyphs render the same way on every machine. See [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering).

</details>

<details>

<summary>How do I accept an intended visual change now?</summary>

Open the build in Argos and approve it in the [review UI](/docs/learn/review-workflow/review-a-build). There is no `--update` run and no image to commit.

</details>

<details>

<summary>What about <code>threshold</code> and <code>allowedMismatchedPixelRatio</code>?</summary>

Argos applies its own [diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences), so you rarely need per-assertion tuning. If a screenshot needs a different sensitivity, pass the `threshold` option to `argosScreenshot`: a value between 0 and 1, where higher is less sensitive. It defaults to 0.5 and doesn't use the same scale as the pixelmatch `threshold`.

</details>

<details>

<summary>Can I diff things that aren't screenshots?</summary>

Yes. `argosSnapshot()` captures any value — an API response, generated HTML, Markdown — in browser or Node tests, and Argos diffs it as text. See the [Vitest SDK reference](/docs/reference/vitest#capturing-snapshots).

</details>

## Next steps

* [Vitest quickstart](/docs/quickstart/vitest-quickstart) — the full setup, including `argosSnapshot`.
* [Vitest SDK reference](/docs/reference/vitest) — every option of `argosScreenshot` and the plugin.
* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) — keep your screenshots free of flaky diffs.
* [Vitest visual testing](https://argos-ci.com/blog/vitest-visual-testing) — a guide to browser mode screenshots, on the Argos blog.


# Migrate from BackstopJS to Argos

Migrate visual testing from self-hosted BackstopJS to Argos. Turn backstop.json scenarios into Playwright screenshots with cloud baselines and pull request review.

[BackstopJS](https://github.com/garris/BackstopJS) runs visual tests locally: you describe scenarios in `backstop.json`, capture baselines to a folder, and diff against them on your machine or CI. This guide moves that setup to Argos so baselines live in the cloud, comparisons are consistent across machines, and changes are reviewed on the pull request instead of in a local HTML report.

### Why teams move off BackstopJS

BackstopJS is a solid self-hosted tool, but running it as a team surfaces gaps:

* **Baselines are local files.** `bitmaps_reference/` is committed to Git or regenerated per machine — bloating the repo and drifting between environments.
* **Approval is manual and local.** `backstop approve` promotes images on one machine; there's no shared review, no history, and no per-change approval.
* **No pull request integration.** BackstopJS doesn't post a status or comment on your PR. You wire up reporting yourself.
* **You maintain the infrastructure.** Browsers, parallelization, storage, and flaky-content handling are all on you.

Argos keeps the same idea — visit a page, screenshot it, diff it — but selects baselines from your [Git history](/docs/learn/platform-fundamentals/baseline-build), runs the comparison in the cloud, and turns each change into a [reviewable PR check](/docs/learn/review-workflow/review-a-build).

### Concept mapping

| BackstopJS                                          | Argos                                                                                    |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `backstop.json` `scenarios` (URL + selectors)       | A Playwright test that visits URLs and calls `argosScreenshot`                           |
| `viewports`                                         | [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports)   |
| `backstop reference`                                | Run on your default branch (baseline is automatic)                                       |
| `backstop test`                                     | Run on your pull request branch                                                          |
| `backstop approve`                                  | Approve in the [Argos review UI](/docs/learn/review-workflow/review-a-build)             |
| `bitmaps_reference/` (local baselines)              | Cloud baselines from [Git history](/docs/learn/platform-fundamentals/baseline-build)     |
| Local HTML report                                   | [Build review](/docs/learn/review-workflow/review-a-build) + PR comment                  |
| `hideSelectors` / `removeSelectors`                 | [Injected CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css) / stabilization  |
| `misMatchThreshold`                                 | [Diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) |
| `clickSelector` / `hoverSelector` / `onReadyScript` | Playwright interactions before the screenshot                                            |

## Migrate the project

Argos captures screenshots from a test framework rather than a JSON scenario file. Playwright is the closest match to BackstopJS's browser-driving model, so this guide translates scenarios into a small Playwright test. (If you drive a fully custom pipeline, you can also capture images yourself and upload them with [`argos upload`](/docs/quickstart/any-test-framework).)

{% stepper %}
{% step %}

### Install Playwright and the Argos SDK

{% tabs %}
{% tab title="npm" %}

```bash
npm i --save-dev @playwright/test @argos-ci/playwright
```

{% endtab %}

{% tab title="yarn" %}

```bash
yarn add --dev @playwright/test @argos-ci/playwright
```

{% endtab %}

{% tab title="pnpm" %}

```bash
pnpm add --save-dev @playwright/test @argos-ci/playwright
```

{% endtab %}

{% tab title="bun" %}

```bash
bun add --dev @playwright/test @argos-ci/playwright
```

{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Add the Argos reporter

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  reporter: [
    process.env.CI ? ["dot"] : ["list"],
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
      }),
    ],
  ],
  use: {
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },
});
```

{% endcode %}
{% endstep %}

{% step %}

### Translate scenarios into a test

Each BackstopJS scenario becomes a `goto` + `argosScreenshot`. Interaction fields map to Playwright actions before the screenshot.

**Before (`backstop.json`)**

```json
{
  "scenarios": [
    {
      "label": "Homepage",
      "url": "http://localhost:3000/"
    },
    {
      "label": "Menu open",
      "url": "http://localhost:3000/",
      "clickSelector": ".menu-toggle"
    }
  ],
  "viewports": [
    { "label": "mobile", "width": 375, "height": 667 },
    { "label": "desktop", "width": 1280, "height": 800 }
  ]
}
```

**After (`tests/visual.spec.ts`)**

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("Homepage", async ({ page }) => {
  await page.goto("http://localhost:3000/");
  await argosScreenshot(page, "Homepage");
});

test("Menu open", async ({ page }) => {
  await page.goto("http://localhost:3000/");
  await page.click(".menu-toggle");
  await argosScreenshot(page, "Menu open");
});
```

To reproduce `viewports`, add a [Playwright project per viewport](/docs/learn/how-to-guides/visual-coverage/responsive-viewports). If your scenarios are just a list of URLs with no interactions, the [Capture screenshots from URLs](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls) guide shows a compact loop.
{% endstep %}

{% step %}

### Seed the baseline and wire up CI

There's no `backstop reference` step — run the tests on your default branch and Argos captures the baseline automatically. Until that build exists, pull request builds stay [orphan](/docs/learn/platform-fundamentals/baseline-build).

```yaml
- run: npx playwright install --with-deps chromium
- run: npx playwright test
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

`ARGOS_TOKEN` comes from **Settings → Authentication → Project token**. On GitHub Actions you can use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) instead of a secret.
{% endstep %}

{% step %}

### Remove BackstopJS

Delete `backstop.json`, the `backstop_data/` directory (`bitmaps_reference/`, `bitmaps_test/`, `html_report/`), and the `backstopjs` dependency. Baselines and reports now live in Argos.
{% endstep %}
{% endstepper %}

## Migrating BackstopJS options

<details>

<summary>Hiding or removing elements (<code>hideSelectors</code> / <code>removeSelectors</code>)</summary>

Neutralize dynamic content before the screenshot with [injected CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css) (for example `visibility: hidden`) or the [stabilization helpers](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers), rather than per-scenario selector lists.

</details>

<details>

<summary>Ready scripts and delays (<code>onReadyScript</code> / <code>delay</code>)</summary>

Replace these with normal Playwright waits — `await page.waitForSelector(...)`, `await expect(locator).toBeVisible()` — before calling `argosScreenshot`. See [Wait for loading](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-loading).

</details>

<details>

<summary>Mismatch threshold (<code>misMatchThreshold</code>)</summary>

Argos uses its own [diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences) with anti-aliasing tolerance, so you don't set a per-scenario threshold.

</details>

## Frequently asked questions

<details>

<summary>Do I have to use Playwright?</summary>

No. Playwright is the closest match to how BackstopJS drives a browser, but Argos also works with [Cypress, WebdriverIO, Puppeteer](/docs/quickstart), or [any pipeline](/docs/quickstart/any-test-framework) via `argos upload`. If you already capture images, you can skip the framework and just upload them.

</details>

<details>

<summary>Where do baselines and reports live now?</summary>

In Argos. Baselines are selected from your [Git history](/docs/learn/platform-fundamentals/baseline-build), and every run produces a [reviewable build](/docs/learn/review-workflow/review-a-build) with a PR status and comment — no committed `bitmaps_reference/` or local HTML report.

</details>

<details>

<summary>How do I approve changes without <code>backstop approve</code>?</summary>

Open the build in the [Argos review UI](/docs/learn/review-workflow/review-a-build) and approve or request changes. The PR check updates automatically.

</details>

## Next steps

* [Playwright quickstart](/docs/quickstart/playwright-quickstart)
* [Capture screenshots from URLs](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls) — closest to a scenario list.
* [Keep your screenshots stable](/docs/learn/reliability-and-flakiness/flaky-tests)
* [Argos vs BackstopJS](https://argos-ci.com/compare/backstopjs) — a side-by-side comparison.


# Migrate from Lost Pixel to Argos

Migrate visual testing from Lost Pixel to Argos. Map the Storybook, Ladle, Histoire, page, and custom shots of lostpixel.config.ts to the Argos Storybook SDK, Playwright, or argos upload.

To migrate from [Lost Pixel](https://lost-pixel.com/) to Argos, replace each shot mode of `lostpixel.config.ts` with its Argos equivalent: `@argos-ci/storybook` for `storybookShots`, a Playwright test with `@argos-ci/playwright` for `pageShots`, `ladleShots`, and `histoireShots`, and `argos upload` for `customShots`. Then delete the `.lostpixel/` folder and the Lost Pixel GitHub Action. Argos picks baselines from your Git history and reports visual changes as a check on your pull request.

{% hint style="info" %}
On April 22, 2026, the Lost Pixel team [announced](https://lost-pixel.com/blog/lost-pixel-team-is-joining-figma) that it is joining Figma and sunsetting Lost Pixel, and the [lost-pixel repository](https://github.com/lost-pixel/lost-pixel) was archived. The announcement gives no shutdown date for the hosted Lost Pixel Platform.
{% endhint %}

### How Lost Pixel and Argos differ

* **Lost Pixel** is a separate runner: it opens your built Storybook, Ladle, or Histoire, or a list of pages, in its own browser and captures every shot. In open-source mode, baselines are images committed under `.lostpixel/baseline/` and refreshed with `npx lost-pixel docker update`. In Platform mode, shots go to the Lost Pixel Platform for review.
* **Argos** captures screenshots in the test runner you already use, such as the Storybook Vitest addon or Playwright, or uploads a folder of images you captured yourself. Baselines are selected from your [Git history](/docs/learn/platform-fundamentals/baseline-build), and changes are approved or rejected on the pull request.

### Concept mapping

| Lost Pixel                                  | Argos                                                                                                                                     |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `storybookShots` (`storybookUrl`)           | `@argos-ci/storybook` — see the [Storybook quickstart](/docs/quickstart/storybook-quickstart)                                             |
| `pageShots` (`pages`, `baseUrl`)            | A Playwright test per page — see [Capture screenshots from URLs](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls) |
| `ladleShots` / `histoireShots`              | A Playwright test that visits each story — see [Ladle and Histoire](#ladle-and-histoire)                                                  |
| `customShots` (`currentShotsPath`)          | `npm exec -- argos upload <folder>` — see [Any test framework](/docs/quickstart/any-test-framework)                                       |
| `lostPixelProjectId` + `LOST_PIXEL_API_KEY` | `ARGOS_TOKEN`                                                                                                                             |
| `.lostpixel/baseline/` (open-source mode)   | Baselines from [Git history](/docs/learn/platform-fundamentals/baseline-build)                                                            |
| `npx lost-pixel docker update`              | Approve in the [Argos review UI](/docs/learn/review-workflow/review-a-build)                                                              |
| `failOnDifference`                          | The Argos commit status on your pull request                                                                                              |
| `breakpoints`                               | The `viewports` option — see [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports)                       |
| `mask`                                      | [`data-visual-test` helpers](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers), or Playwright's `mask` screenshot option   |
| `threshold`                                 | The `threshold` option of `argosScreenshot` (a sensitivity between 0 and 1)                                                               |
| `waitBeforeScreenshot` / `waitForSelector`  | Argos [stabilization](/docs/learn/reliability-and-flakiness/flaky-tests), plus Playwright waits before `argosScreenshot`                  |
| `lost-pixel/lost-pixel` GitHub Action       | Your test command in CI, with `ARGOS_TOKEN` set                                                                                           |

## Migrate the project

{% stepper %}
{% step %}

### Pick the Argos setup for each shot mode

Look at the shot modes enabled in your `lostpixel.config.ts` and set up the matching Argos integration:

* **`storybookShots`**: follow the [Storybook quickstart](/docs/quickstart/storybook-quickstart) (Storybook 9 or later, with the Vitest addon). On Storybook 8, use the [Test Runner quickstart](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart); on older versions, the [legacy quickstart](/docs/quickstart/storybook-quickstart/storybook-legacy-less-than-v8-quickstart). Every story is captured, as with Lost Pixel.
* **`pageShots`**: follow the [Playwright quickstart](/docs/quickstart/playwright-quickstart), then turn your `pages` list into tests as shown in [Capture screenshots from URLs](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls).
* **`ladleShots`** or **`histoireShots`**: follow the [Playwright quickstart](/docs/quickstart/playwright-quickstart) and add the test from [Ladle and Histoire](#ladle-and-histoire).
* **`customShots`**: keep capturing screenshots as you do today, and upload the folder you passed as `currentShotsPath` with the [Argos CLI](/docs/quickstart/any-test-framework).
  {% endstep %}

{% step %}

### Translate page shots into a Playwright test

Each entry of `pageShots.pages` becomes a `page.goto` followed by `argosScreenshot`:

**Before (`lostpixel.config.ts`)**

```ts
import { CustomProjectConfig } from "lost-pixel";

export const config: CustomProjectConfig = {
  pageShots: {
    pages: [
      { path: "/", name: "home" },
      { path: "/pricing", name: "pricing" },
    ],
    baseUrl: "http://localhost:3000",
  },
  generateOnly: true,
  failOnDifference: true,
};
```

**After (`tests/pages.spec.ts`)**

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

const pages = [
  { path: "/", name: "home" },
  { path: "/pricing", name: "pricing" },
];

for (const { path, name } of pages) {
  test(name, async ({ page }) => {
    await page.goto(path);
    await argosScreenshot(page, name);
  });
}
```

Set `baseURL` and `webServer` in `playwright.config.ts` so `page.goto(path)` reaches your app, as in the [Playwright quickstart](/docs/quickstart/playwright-quickstart). To keep your `breakpoints`, pass them as [viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).
{% endstep %}

{% step %}

### Replace the CI step

Swap the Lost Pixel action for your test command, with `ARGOS_TOKEN` set:

**Before (Lost Pixel)**

```yaml
- run: npm ci
- uses: lost-pixel/lost-pixel@v3.22.0
  env:
    LOST_PIXEL_API_KEY: ${{ secrets.LOST_PIXEL_API_KEY }}
```

**After (Argos, Playwright)**

```yaml
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test
  env:
    ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
```

For Storybook, use the workflow from the [Storybook quickstart](/docs/quickstart/storybook-quickstart). For custom shots, run `npm exec -- argos upload <folder>` after your tests. `ARGOS_TOKEN` comes from **Settings → Authentication → Project token**; on GitHub Actions you can use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) instead of a secret.
{% endstep %}

{% step %}

### Seed the baseline

Run the new workflow on your default branch first. That build becomes the baseline; until it exists, pull request builds are marked as [orphan](/docs/learn/platform-fundamentals/baseline-build#orphan-builds).
{% endstep %}

{% step %}

### Remove Lost Pixel

Delete `lostpixel.config.ts`, the `.lostpixel/` folder (`baseline/`, `current/`, `difference/`), the `lost-pixel` dependency, and the `LOST_PIXEL_API_KEY` secret.
{% endstep %}
{% endstepper %}

## Ladle and Histoire

Argos has no dedicated Ladle or Histoire SDK. A short Playwright test reproduces what Lost Pixel did: read the list of stories that Ladle or Histoire publishes, open each one, and capture it with `argosScreenshot`. Serve your Ladle or Histoire build first, for example from the `webServer` option of `playwright.config.ts`.

{% tabs %}
{% tab title="Ladle" %}
{% code title="tests/ladle.spec.ts" %}

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

// URL of your running Ladle instance.
const LADLE_URL = "http://localhost:61000";

test("Ladle stories", async ({ page, request }) => {
  // Ladle lists every story in meta.json, the file Lost Pixel read.
  const response = await request.get(`${LADLE_URL}/meta.json`);
  const { stories } = await response.json();

  for (const id of Object.keys(stories)) {
    await page.goto(`${LADLE_URL}/?story=${id}&mode=preview`);
    // Lost Pixel waited for this attribute before each Ladle shot.
    await page.waitForSelector("[data-storyloaded]");
    await argosScreenshot(page, id);
  }
});
```

{% endcode %}
{% endtab %}

{% tab title="Histoire" %}
{% code title="tests/histoire.spec.ts" %}

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

// URL where your Histoire build is served.
const HISTOIRE_URL = "http://localhost:6006";

test("Histoire stories", async ({ page, request }) => {
  // Histoire lists stories and variants in histoire.json, the file Lost Pixel read.
  const response = await request.get(`${HISTOIRE_URL}/histoire.json`);
  const { stories } = await response.json();

  for (const story of stories) {
    for (const variant of story.variants ?? [story]) {
      await page.goto(
        `${HISTOIRE_URL}/__sandbox.html?storyId=${story.id}&variantId=${variant.id}`,
      );
      await argosScreenshot(page, `${story.id}_${variant.id}`);
    }
  }
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

## Migrating Lost Pixel options

<details>

<summary>Breakpoints (<code>breakpoints</code>)</summary>

Pass the widths to the `viewports` option of `argosScreenshot`, or add one Playwright project per viewport. See [Responsive viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports). For Storybook, use [story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes).

</details>

<details>

<summary>Masks (<code>mask</code>)</summary>

Add a [`data-visual-test` helper](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers) to the element: `data-visual-test="blackout"` masks it (Storybook, Playwright, and Cypress SDKs), `transparent` hides it and keeps its space, and `removed` takes it out of the layout. In Playwright tests, you can also pass Playwright's `mask` option, which `argosScreenshot` forwards to the screenshot call.

</details>

<details>

<summary>Waits (<code>waitBeforeScreenshot</code> / <code>waitForSelector</code>)</summary>

`argosScreenshot` already waits for fonts, images, and `aria-busy` elements before capturing. For anything else, wait in your test before the screenshot, for example with `await page.waitForSelector(...)`. See [Wait for loading](/docs/learn/reliability-and-flakiness/flaky-tests/wait-for-loading).

</details>

<details>

<summary>Threshold (<code>threshold</code>)</summary>

Lost Pixel's `threshold` is the share or number of pixels allowed to differ. Argos's `threshold` option is different: it sets the sensitivity of the [diff algorithm](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences), between 0 and 1, and defaults to 0.5. Start without it, and only raise it for a screenshot that needs it.

</details>

<details>

<summary>Story parameters (<code>parameters.lostpixel</code>)</summary>

Argos doesn't read `parameters.lostpixel`. Remove those parameters, and use [story modes](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes) and the [Argos helpers](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers) for the per-story settings you need.

</details>

## Frequently asked questions

<details>

<summary>What happens to my Lost Pixel baselines?</summary>

They don't transfer. Lost Pixel and Argos capture screenshots differently, so Argos starts from its own baseline: the first build on your default branch. Delete `.lostpixel/baseline/` once the migration is merged.

</details>

<details>

<summary>Is Argos open source, like Lost Pixel?</summary>

Yes. The whole platform, diff engine included, is MIT-licensed in the [argos-ci/argos](https://github.com/argos-ci/argos) repository. Argos runs as a managed cloud service; self-hosting is not officially supported.

</details>

<details>

<summary>How do I approve changes without <code>lost-pixel update</code>?</summary>

Open the build in the [Argos review UI](/docs/learn/review-workflow/review-a-build) and approve or reject the changes. The pull request check updates automatically, and there are no images to commit.

</details>

<details>

<summary>Is there a free plan?</summary>

Yes, the Hobby plan is free. See [Argos pricing plans](/docs/learn/billing-and-subscription/pricing-plans) for what each plan includes.

</details>

## Next steps

* [Storybook quickstart](/docs/quickstart/storybook-quickstart) — replaces `storybookShots`.
* [Capture screenshots from URLs](/docs/learn/how-to-guides/visual-coverage/capture-screenshots-from-urls) — replaces `pageShots`.
* [Stabilize screenshots](/docs/learn/reliability-and-flakiness/flaky-tests) — keep your new suite free of flaky diffs.
* [Lost Pixel alternatives in 2026](https://argos-ci.com/blog/lost-pixel-alternatives) — where Lost Pixel users can migrate, on the Argos blog.


# Integrations

Connect Argos to your Git provider for pull request statuses, authenticate your CI uploads, and get notified in Slack.

Argos integrates with your Git provider to post build statuses on pull requests, and with Slack, Microsoft Teams, or Discord to notify your team. Which notifications go where is decided by [automations](/docs/learn/review-workflow/automations).

### Git providers

* [GitHub integration](/docs/learn/integrations/github-integration) – Install the Argos GitHub App for statuses, comments, and merge queue support
* [GitLab integration](/docs/learn/integrations/gitlab-integration) – Connect GitLab to get statuses on merge requests
* [Other Git providers](/docs/learn/integrations/other-git-providers) – Bitbucket and Azure DevOps status, and how to use Argos without a native integration

### CI authentication

* [GitHub Actions authentication](/docs/learn/integrations/github-actions-authentication) – Authenticate uploads with a token, OIDC, or tokenless authentication

{% hint style="info" %}
Looking for how to sign in to Argos itself? See [Account & access](/docs/learn/account-and-access) for GitHub and SAML single sign-on.
{% endhint %}

### Notifications

* [Slack integration](/docs/learn/integrations/slack-integration) – Send build notifications to Slack channels with automations
* [Microsoft Teams integration](/docs/learn/integrations/microsoft-teams-integration) – Send build notifications to a Teams channel with automations
* [Discord integration](/docs/learn/integrations/discord-integration) – Send build notifications to a Discord channel with automations

### Webhooks

Argos does not offer user-configurable outgoing webhooks. To react to build events, use [Slack](/docs/learn/integrations/slack-integration), [Microsoft Teams](/docs/learn/integrations/microsoft-teams-integration), or [Discord](/docs/learn/integrations/discord-integration) notifications, the commit statuses posted on your Git provider, or poll builds from the [REST API](https://argos-ci.com/docs/api-reference) or the [CLI](/docs/reference/argos-command-line-interface-cli#inspecting-builds-and-tests). If webhooks would unblock your workflow, [tell us about your use case](https://argos-ci.com/contact).


# GitHub integration

Connect Argos to GitHub for automated visual testing, commit and pull request checks, and baseline selection.

Connect Argos to GitHub for automated visual testing on every pull request and merge queue run. Argos reads commit history to pick the right baseline build and reports results back to GitHub so you can ship with confidence.

### What Argos does on GitHub

* Adds commit and pull request checks, so Argos results can block merges when required in GitHub.
* Posts [pull request comments](/docs/learn/review-workflow/pull-request-comments) with the latest build results and links back to Argos.
* Analyzes commit history to find the right merge base and select the correct [baseline build](/docs/learn/platform-fundamentals/baseline-build) for visual comparisons.

This is why Argos asks for repository access: it reads commits to find merge bases, and writes statuses and comments to report results.

### Commit status names

Argos reports results as [commit statuses](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) on the pull request's head commit, not as check runs. The status context depends on your setup:

| Context                             | When it is used                                                                                                                                                 |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `argos`                             | The default — one Argos project on the repository, default build name.                                                                                          |
| `argos/<build-name>`                | The build has a custom name (for example `argos/e2e` with `--build-name e2e`), as in [monorepo setups](/docs/learn/how-to-guides/ci-pipelines/monorepos-setup). |
| `argos/<project-name>`              | Several Argos projects share the same repository — the project name disambiguates them.                                                                         |
| `argos/<project-name>/<build-name>` | Several projects share the repository and the build has a custom name.                                                                                          |
| `argos/summary`                     | The [summary check](/docs/learn/review-workflow/summary-checks) that combines all Argos builds on a commit.                                                     |

Use these exact context names when you configure [required status checks](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging) in branch protection.

#### Which commit receives the status

On GitHub Actions `pull_request` events, `GITHUB_SHA` points to an ephemeral merge commit, not to your branch's head. Argos records that merge commit as the build commit — what you tested is the merged result — but detects the pull request from the event payload and **posts the status on the pull request's head commit**, so the check shows up on your PR as expected.

Special cases:

* On `pull_request_target` events, `GITHUB_SHA` points to the base branch, so Argos uses the pull request head commit as the build commit instead.
* Outside a pull request (a push to a branch), the status is posted on the commit the screenshots were captured from.
* In a [merge queue](#merge-queue-support), the status is posted on the merge group's commit, where the queue reads it, and Argos doesn't comment on the pull request.

To override detection — for example in a custom pipeline — set the `ARGOS_COMMIT` (full 40-character SHA) and `ARGOS_BRANCH` environment variables.

### Commit status states

Each Argos status moves through these GitHub states as the build progresses:

| Build                                                              | GitHub state | Description                                  |
| ------------------------------------------------------------------ | ------------ | -------------------------------------------- |
| Queued                                                             | `pending`    | "Build is queued"                            |
| In progress                                                        | `pending`    | "Build in progress..."                       |
| No changes detected                                                | `success`    | "Everything's good!" or "… no changes found" |
| Changes detected                                                   | `failure`    | "… waiting for your decision"                |
| Changes approved                                                   | `success`    | "… approved"                                 |
| Changes rejected                                                   | `failure`    | "… rejected"                                 |
| Changes detected on an auto-approved build                         | `success`    | "…, automatically approved"                  |
| [Skipped](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) | `success`    | "Build skipped"                              |

Once the comparison is done, the description starts with the screenshot counts, such as "2 changed, 1 added".

A build on an [auto-approved branch](/docs/learn/platform-fundamentals/baseline-build#auto-approved-branches) outside a pull request is approved automatically, so its status passes even when it has changes.

Whether a `failure` blocks merging is your choice in GitHub. To block, mark the Argos context as a required status check in a [branch protection rule](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging) or a [ruleset](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets#require-status-checks-to-pass-before-merging). Otherwise the status is informational. Argos posts the same states either way.

### Choose your access level

* **Full access (recommended)** — Required for Merge Queue and baseline selection based on commit history.
* **Limited access (no content)** — Works for basic checks without reading repository content; Merge Queue is not supported. See [GitHub integration without content permission](#github-integration-without-content-permission).

Use GitHub Connect to log in with your GitHub account, then install the Argos GitHub App to link repositories.

### Install the Argos GitHub App

Argos provides a dedicated GitHub App that connects directly to your repositories, enabling real-time visual testing feedback on pull requests.

1. Visit the [Argos app page on GitHub](https://github.com/apps/argos-ci).
2. Select **Configure** and pick the organization where you want to install Argos.
3. Follow the prompts to complete the installation.

#### Import a GitHub repository to Argos

1. Sign in to Argos and select **Create a new project**.
2. Choose GitHub as your provider, then select **Import your repository**.

#### Update the repositories shared with Argos

1. Go to the [Argos app page on GitHub](https://github.com/apps/argos-ci) and select **Configure**.
2. Select the organization where you want to manage repository access.
3. Under **Repository access**, choose **Only select repositories** and pick the repositories you want to share with Argos.

![Repository access settings for the Argos GitHub App](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/DgSTCg66b8P2VFZ44ns6/repository%20access%209f17c9ae0a701fcacc914b10dea40a5e.jpg)

### Required GitHub App permissions

Argos needs the following permissions to operate:

* **Contents** — used to find a common commit ancestor between branches
* **Statuses** — used to add statuses to commits
* **Pull requests** — used to add comments in pull requests
* **Actions** — used to re-run an individual GitHub Actions workflow run when a check is retried from Argos, and to look up workflow runs for [tokenless authentication](/docs/learn/integrations/github-actions-authentication#tokenless-authentication)

We take your security and privacy seriously. If you have any concerns or questions, please [contact us](https://argos-ci.com/contact).

### Merge Queue support

Argos supports [GitHub's native Merge Queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue) out of the box, and can be configured for custom merge queue systems as well.

#### How Argos compares a merge queue build

A merge queue build takes its baseline from your base branch, then applies the changes already approved on the queued pull request (or on each pull request listed in `ARGOS_MERGE_QUEUE_PRS`). Changes you approved on the pull request don't show up again in the queue.

A merge queue build can't be reviewed: Argos shows "This build was triggered in a merge queue and doesn't require a review." If it finds changes that weren't approved on the pull request, its status is `failure` ("waiting for your decision"), so the queue check fails when Argos is a [required status check](#commit-status-states).

Once GitHub merges the group, your base branch points to the commit the merge queue build tested, so that build can serve as the [baseline](/docs/learn/platform-fundamentals/baseline-build) for later builds, and a separate `push` build on the base branch is optional. Like any baseline, the build must be complete, its tests must have passed, it must not be a subset, and it must not be rejected. Pull request builds also [need an approved baseline](/docs/learn/platform-fundamentals/baseline-build#when-the-baseline-must-be-approved). A merge queue build counts as approved once its pull request is merged, as long as the build is linked to that pull request (see `GITHUB_TOKEN` below).

#### GitHub Actions setup for GitHub Merge Queue

Listen to both `pull_request` and `merge_group` events so Argos uploads run for PR reviews and for queued merges:

{% code title=".github/workflows/ci.yml" %}

```yaml
name: Visual tests

on:
  pull_request:
  merge_group:

jobs:
  argos:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - run: npm run test:e2e # Run E2E tests with Argos SDK integration
        env:
          ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} # Argos token stored in GitHub Secrets
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Lets the SDK find the pull request of a merge group
```

{% endcode %}

On `merge_group` events, the SDK reads the pull request from the GitHub API with `GITHUB_TOKEN`. Without it, the build isn't linked to its pull request, and Argos can't apply the changes approved there. If your workflow restricts `GITHUB_TOKEN` permissions, grant `pull-requests: read`.

#### Custom merge queue systems

When you are using a merge queue system other than GitHub’s built-in Merge Queue (like [Mergify](https://mergify.com/)), you can set `ARGOS_MERGE_QUEUE_PRS` to the comma-separated pull request numbers included in the queued build. This tells Argos to treat the upload as a merge queue build and use the right baseline for visual comparisons.

When the queued build contains a single pull request, Argos finds the builds approved on it by branch, so upload under that pull request's branch: set `ARGOS_BRANCH` if your merge queue runs on a branch of its own.

```yaml
steps:
  - name: Upload screenshots to Argos
    env:
      ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
      ARGOS_MERGE_QUEUE_PRS: "101,102"
    run: npm exec -- argos upload ./screenshots
```

### GitHub integration without content permission

If you prefer to use Argos without granting full content access to your repositories, you can integrate via a more restricted setup.

#### Set up Argos with limited GitHub access

1. From the dashboard, select your team from the scope selector.
2. Select the **Settings** tab and go to the **Integrations** section.
3. Scroll to **GitHub without content access**.
4. Select **Install GitHub App**.

![GitHub without content access settings in Argos](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/L6FXO6nnmQiYAceS52Fm/github%20light%20settings%20f92cce07ed0ba7ddcd67bdaf4d189c51.jpg)

5. On GitHub, choose the specific repositories where you want to install the Argos app.

![Argos GitHub app without content access](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/c8R92RerSYyUdZWu3NMl/github%20light%20app%202fab2975617916eb69fa710a113d9882.jpg)

6. Back in Argos, go to the **Projects** tab.
7. Select **Create a new project** at the top right.
8. Select **Continue with GitHub (no-content access)**.

![Argos GitHub app without content access](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/H0le3fZYi73qd80foBvu/create%20project%2097d56ce76dfd351b29e727076e01021c.jpg)

9. Choose the repository you want to connect.

### GitHub Enterprise

#### GitHub Enterprise Cloud

GitHub Enterprise Cloud works out of the box. Follow the same GitHub App setup described above — no extra configuration is needed in Argos.

#### GitHub Enterprise Server (self-hosted)

Self-hosted GitHub Enterprise Server deployments are supported on the Argos Enterprise plan, which also includes SAML SSO and other advanced features. To upgrade to Enterprise, [contact sales](https://argos-ci.com/contact).

### Authenticating Argos in CI

Uploads from GitHub Actions can authenticate in three ways:

* **`ARGOS_TOKEN`** — a project token stored as a GitHub secret (used in the examples above).
* [**GitHub OIDC**](/docs/learn/integrations/github-actions-authentication#oidc) — short-lived signed tokens with no secret to manage. Preferred where available.
* [**Tokenless authentication**](/docs/learn/integrations/github-actions-authentication#tokenless-authentication) — the automatic fallback for pull requests from forked repositories.

OIDC and tokenless authentication are per-project settings: a project admin [enables them](/docs/learn/integrations/github-actions-authentication#enable-oidc-or-tokenless-on-your-project) in **Project Settings → Authentication**.


# GitHub Actions authentication

Authenticate Argos uploads from GitHub Actions with a project token, OIDC, or tokenless authentication.

Argos supports three ways to authenticate uploads from GitHub Actions: a project token, OpenID Connect (OIDC), and tokenless authentication. The SDK picks one automatically based on what is available in the workflow. OIDC and tokenless authentication must also be [enabled on the Argos project](#enable-oidc-or-tokenless-on-your-project).

### Choose a method

The SDK selects an authentication method in this order:

1. **`ARGOS_TOKEN`** is set → use the token.
2. **The job can request a GitHub OIDC token** (the `id-token: write` permission is granted) → use OIDC.
3. **Neither is available** → use tokenless authentication.

The SDK doesn't read your project settings. As soon as the job has `id-token: write`, it uses OIDC, even if OIDC is disabled on the project. In that case the upload fails; the SDK does not fall back to tokenless.

* **Project token (`ARGOS_TOKEN`)**: A long-lived secret stored in your repository. Works on every CI provider, but needs to be provisioned and rotated. This is the only option outside GitHub Actions.
* **OIDC**: A short-lived identity token signed by GitHub, verified by Argos on every upload. The most secure option — prefer it when your workflow can use it.
* **Tokenless**: Argos verifies the upload by looking up the workflow run on GitHub. Nothing to add to the workflow once it is enabled on the project, and the only method that works on pull requests from forks.

A single workflow can combine OIDC and tokenless: enable both on the project, grant `id-token: write` and leave `ARGOS_TOKEN` unset. The SDK uses OIDC when GitHub issues a token and automatically falls back to tokenless on fork PRs, where GitHub doesn't.

### Enable OIDC or tokenless on your project

OIDC and tokenless authentication are per-project settings. A project admin turns them on once the project is connected to a GitHub repository:

1. Open your project in Argos and go to **Project Settings → Authentication**.
2. Turn on **Enable GitHub Actions OIDC**, **Enable tokenless authentication**, or both.
3. Select **Save**.

Both are off by default for new projects. Tokenless authentication became a setting in May 2026, and Argos turned it on for every project that existed then, so their workflows kept working.

### Private repositories

OIDC and tokenless authentication both work with private repositories:

* **OIDC** doesn't use the GitHub API. Argos matches the repository ID in the signed token against the repository linked to the project.
* **Tokenless** looks up the workflow run through the Argos GitHub App installation, so the app must stay installed on the repository.

## OIDC

With [OpenID Connect (OIDC)](https://docs.github.com/en/actions/reference/security/oidc), each workflow run receives a short-lived identity token signed by GitHub. Argos verifies that token against the repository you have connected, then issues a temporary upload credential scoped to your project. No secret is ever stored in GitHub.

### Why use OIDC

* **No long-lived secret.** Nothing to rotate, leak, or copy between repositories.
* **Per-run identity.** GitHub issues the token for a single job. The credential Argos returns only uploads builds for the commit named in the token.
* **Least privilege.** The credential Argos issues from the OIDC exchange is scoped to a single project.

### How OIDC works

1. The job has the `id-token: write` permission, so it can request an identity token from GitHub's OIDC provider.
2. The Argos SDK detects that no `ARGOS_TOKEN` is set and requests the token for the audience `https://api.argos-ci.com`.
3. The SDK exchanges the token with the Argos API.
4. Argos verifies the token's signature, its issuer (`https://token.actions.githubusercontent.com`) and its audience (`https://api.argos-ci.com`). It then finds the Argos project linked to the repository identified in the token, and checks that the token's repository and commit match the ones the SDK reports.
5. If OIDC is enabled on that project, Argos returns a short-lived token scoped to the project, and the SDK uses it to upload.

Argos only accepts tokens issued for the `https://api.argos-ci.com` audience. A token requested for another service is rejected.

### Set up OIDC

1. [Enable **GitHub Actions OIDC**](#enable-oidc-or-tokenless-on-your-project) in your project settings.
2. Grant the workflow permission to mint an OIDC token by adding `id-token: write` to the job's `permissions` block.
3. **Remove** `ARGOS_TOKEN` from the job environment. If `ARGOS_TOKEN` is set, it takes precedence and OIDC is skipped.

```yaml
name: Visual tests

on:
  pull_request:
  merge_group:

jobs:
  argos:
    runs-on: ubuntu-latest
    permissions:
      # Required to check out the repository and read its metadata
      contents: read
      # Required for Argos to authenticate via OIDC
      id-token: write
      # Lets the SDK read the pull request of merge queue runs with GITHUB_TOKEN
      pull-requests: read
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 22
      - run: npm ci
      - run: npm run test:e2e
        # No ARGOS_TOKEN: the SDK uses OIDC automatically
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

{% hint style="info" %}
`id-token: write` must be set on the job (or at the workflow level). Adding it only to the `permissions` block of another job does not grant it to your Argos job. See [GitHub's documentation on `id-token` permissions](https://docs.github.com/en/actions/reference/security/oidc#adding-permissions-settings).
{% endhint %}

## Tokenless authentication

Tokenless authentication lets the Argos SDK upload builds without an `ARGOS_TOKEN` secret and without OIDC. Argos verifies the run by looking it up on GitHub.

It is the method the SDK uses when neither a token nor OIDC is available. Most importantly, it works on **pull requests from forked repositories**, where GitHub does not issue OIDC tokens or expose secrets.

### How tokenless works

1. The Argos SDK detects that no `ARGOS_TOKEN` is set and no OIDC token is available.
2. The SDK sends Argos the GitHub-provided context: repository, job ID, and workflow run ID, along with the commit and branch of the build.
3. Argos finds the project linked to the repository, checks that tokenless authentication is enabled on it, and looks up the workflow run on GitHub through the Argos GitHub App installation.
4. If the run is **in progress** and its commit and branch match the ones the SDK reported, Argos issues a **short-lived token** scoped to the project.
5. The SDK uses that short-lived token to complete the upload.

The verification rests on a simple invariant: only the actual workflow run on GitHub can produce an upload whose context matches a run GitHub reports as in progress. If the run is not found, is not in progress, or its commit or branch doesn't match, the upload is rejected.

### When to use tokenless

* **Forked pull requests.** GitHub blocks OIDC and secrets on workflows triggered from forks. Tokenless authentication still works because it relies on the GitHub API rather than on a token injected into the runner.
* **Public repositories without configured secrets.** New contributors can open a PR and have visual tests run end-to-end without a maintainer needing to provision a token.
* **Quick setup.** Once tokenless is enabled on the project, uploads from GitHub Actions work without adding anything to the workflow.

To use tokenless, [enable it on the project](#enable-oidc-or-tokenless-on-your-project), leave `ARGOS_TOKEN` unset, and do not grant `id-token: write`. To pair it with OIDC in the same workflow, enable both on the project and grant `id-token: write`: internal PRs use OIDC and fork PRs fall back to tokenless.

### Specify a project when several share a repository

Tokenless authentication identifies your Argos project from the GitHub repository running the workflow. If you have **multiple Argos projects linked to the same repository**, Argos cannot tell which one the upload belongs to, and the upload is rejected.

To disambiguate, pass the project slug (`account/project-name`) so Argos knows which project to use. You can set it three ways, all equivalent:

{% tabs %}
{% tab title="Environment variable" %}

```yaml
- run: npm run test:e2e
  env:
    ARGOS_PROJECT: my-account/my-project
```

{% endtab %}

{% tab title="CLI flag" %}

```bash
argos upload ./screenshots --project my-account/my-project
```

{% endtab %}

{% tab title="SDK option" %}

```js
import { upload } from "@argos-ci/core";

await upload({
  files: ["screenshots/**/*.png"],
  project: "my-account/my-project",
});
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
You only need this when more than one Argos project is connected to the same GitHub repository. With a single linked project, tokenless resolves it automatically.

OIDC has no such option: with several projects linked to the repository, the OIDC exchange fails. Use tokenless with a project slug, or a project token, for these projects.
{% endhint %}

### Tokenless limitations

* **GitHub Actions only.** Tokenless authentication relies on the GitHub workflow run lookup. It does not work outside GitHub Actions — for other CI providers, use `ARGOS_TOKEN`.
* **The Argos GitHub App must be installed on the repository.** Argos looks up the run with the app's access, for public and private repositories alike.
* **The workflow run must be visible to GitHub.** If the SDK runs in a context where GitHub does not report the run (for example, a self-hosted action that runs outside the standard workflow runner), tokenless cannot verify it.
* **Weaker proof than OIDC.** Tokenless verifies that the workflow run is in progress on the reported commit and branch; OIDC verifies a signed token from GitHub. Prefer OIDC where it is available.

## Troubleshooting

To see which method the SDK used, run the upload with `DEBUG=@argos-ci/core`: the log says "Authenticated with ARGOS\_TOKEN", "Authenticated with GitHub Actions OIDC", or "Authenticated with GitHub Actions tokenless exchange".

**The SDK uses tokenless instead of OIDC.** The job is missing `id-token: write`, so GitHub doesn't give it an OIDC token. Add the permission to the job's `permissions` block. On pull requests from forks this is expected: GitHub never issues OIDC tokens there.

**Argos still uses `ARGOS_TOKEN`.** A secret is being injected into the job. Search your workflow, environments, and reusable workflows for `ARGOS_TOKEN` and remove it. You can confirm by printing `echo "${ARGOS_TOKEN:+set}${ARGOS_TOKEN:-unset}"` in a step (the value itself is masked).

**`GitHub Actions OIDC authentication is not enabled for this project. Enable it in the project settings.`** The job has `id-token: write`, so the SDK chose OIDC, but OIDC is off on the project. Turn on **Enable GitHub Actions OIDC** in **Project Settings → Authentication**, or remove `id-token: write` from the job to use tokenless authentication.

**`Tokenless authentication is disabled for this project. Set the ARGOS_TOKEN environment variable to authenticate.`** Tokenless authentication is off on the project, which is the default for new projects. Turn on **Enable tokenless authentication** in **Project Settings → Authentication**, or authenticate with OIDC or `ARGOS_TOKEN`.

**`No Argos project is linked to this GitHub repository.`** (OIDC) or **`No project found. Tokenless authentication requires an Argos project to be linked to your GitHub repository.`** (tokenless) No Argos project is connected to the repository that ran the workflow. Connect the repository in your project settings, or set `ARGOS_TOKEN`.

**`The Argos GitHub App is no longer installed on this repository. Reinstall the app or use a project token.`** Tokenless authentication looks up the workflow run through the Argos GitHub App. [Install the app](/docs/learn/integrations/github-integration#install-the-argos-github-app) on the repository again, or set `ARGOS_TOKEN`.

**`GitHub run not found`** or **`GitHub job is not in progress`**. Argos could not find the workflow run the SDK reported in progress on GitHub. Upload while the workflow is still running, from the repository linked to the Argos project.

**`GitHub Actions workflow run does not match commit.`**, **`GitHub Actions workflow run does not match branch.`**, **`GitHub Actions OIDC token does not match repository.`**, or **`GitHub Actions OIDC token does not match commit.`** The repository, commit, or branch the SDK reported differs from the workflow run or the OIDC token, for example because `ARGOS_COMMIT` or `ARGOS_BRANCH` overrides them. Remove the override, or authenticate with `ARGOS_TOKEN`.

**`Multiple projects found for GitHub repository`** (tokenless) or **`Multiple Argos projects are linked to this GitHub repository.`** (OIDC) More than one Argos project is connected to the repository, so Argos cannot pick one automatically. With tokenless, specify the project slug: see [Specify a project when several share a repository](#specify-a-project-when-several-share-a-repository). With OIDC, switch to tokenless with a project slug, or to `ARGOS_TOKEN`.

**`Too many failed tokenless authentication attempts for this GitHub installation.`** Argos limits how many tokenless lookups can fail for a GitHub App installation in a short time. Retry later, or set `ARGOS_TOKEN`.

**Merge queue builds aren't linked to their pull request.** On `merge_group` events, the SDK fetches the pull request from the GitHub API, which needs `GITHUB_TOKEN` in the job environment, whatever the authentication method. See [Merge Queue support](/docs/learn/integrations/github-integration#merge-queue-support).

## Further reading

* [GitHub: About security hardening with OpenID Connect](https://docs.github.com/en/actions/reference/security/oidc)
* [GitHub integration](/docs/learn/integrations/github-integration) — connect Argos to your GitHub repositories.


# GitLab integration

Connect Argos to GitLab for visual testing, baseline selection, and direct feedback on your merge requests.

Connect Argos to GitLab to get commit statuses on your merge requests and accurate baseline selection from your commit history.

Argos reads commit history to select the correct [baseline build](/docs/learn/platform-fundamentals/baseline-build) and reports statuses back to GitLab. The integration also lets you sign in with GitLab and import your GitLab repositories.

### Connect a GitLab repository

Argos communicates with GitLab through a Personal Access Token, acting as a dedicated bot user on your merge requests.

{% stepper %}
{% step %}

#### Generate a Personal Access Token in GitLab

1. Go to [GitLab tokens settings](https://gitlab.com/-/profile/personal_access_tokens?name=argos2\&scopes=api,read_user).
2. Select **Add new token**.
3. Set an expiration date 12 months ahead (the maximum allowed).
4. Select **Create personal access token** and copy the generated token.

![Generate a Personal Access Token in GitLab](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/nDuBLwZ8tNzwL2Njb4VA/gitlab%20create%20token%2001eecee687ee76bd683c82d45c88df99.png)

{% hint style="info" %}
You can also use a [Project Access Token](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html) if you want to restrict access to a single project. If you choose this option, be sure to set the role of the token to **Developer**.
{% endhint %}
{% endstep %}

{% step %}

#### Configure the token in Argos

1. From the dashboard, select your team or your personal account from the scope selector.
2. Select the **Settings** tab and go to the **Integrations** section.
3. Scroll to the **GitLab** section.
4. Enter the generated token and select **Save**.

![Configure GitLab in Argos](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/JS2n9MNfxoJs5gPL3tge/gitlab%20configuration%20argos%206715b7d493508d93af13bd1d4cb58563.png)
{% endstep %}

{% step %}

#### Link a GitLab project to Argos

1. From the dashboard, select your team or your personal account from the scope selector.
2. Select the **Projects** tab.
3. Select **Create a new Project** at the top right.
4. Select **Continue with GitLab**.
5. Pick your GitLab organization and the desired repository. The new project appears in your projects list.

![Configure GitLab in Argos](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/xPVPwz5xzDBNbXTddc4L/argos%20create%20new%20project%20ab30905e08e499062fcc221cdb79f779.png)
{% endstep %}
{% endstepper %}

### Run Argos in GitLab CI

The integration reports statuses; your pipeline uploads the screenshots. For a `.gitlab-ci.yml` example, see [Run Argos in CI](/docs/learn/how-to-guides/ci-pipelines/run-argos-in-ci#gitlab-ci).

### Commit statuses

Argos names its commit status as on GitHub (`argos`, or `argos/<build-name>`; see [commit status names](/docs/learn/integrations/github-integration#commit-status-names)). The status moves through these GitLab states as the build progresses:

| Build                                                              | GitLab state |
| ------------------------------------------------------------------ | ------------ |
| Queued                                                             | `pending`    |
| In progress                                                        | `running`    |
| No changes detected                                                | `success`    |
| Changes detected                                                   | `failed`     |
| Changes approved                                                   | `success`    |
| Changes rejected                                                   | `failed`     |
| Changes detected on an auto-approved build                         | `success`    |
| [Skipped](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) | `success`    |

GitLab adds the status to the commit's pipeline as an external job. Whether a `failed` status blocks merging is your choice: turn on **Pipelines must succeed** in your GitLab project's **Settings → Merge requests → Merge checks** to prevent merging while the pipeline has failed. Argos posts the same statuses either way.

### Connect an existing Argos project to GitLab

To link a GitLab repository to a project that already exists in Argos, go to **Project Settings → Connect Git Repository** and select the repository. The [token](#connect-a-gitlab-repository) must be configured first.

### GitLab Self-Managed

GitLab Self-Managed deployments are supported on the Argos Enterprise plan. These setups often require tailored configuration alongside SAML SSO and other enterprise features. To discuss enabling GitLab Self-Managed, [contact sales](https://argos-ci.com/contact).


# Other Git providers

Bitbucket and Azure DevOps support in Argos, and how to use Argos with an unsupported Git provider.

Argos integrates natively with [GitHub](/docs/learn/integrations/github-integration) and [GitLab](/docs/learn/integrations/gitlab-integration). Other providers are not supported yet.

### Bitbucket

Argos does not support Bitbucket Cloud or Bitbucket Data Center. If a Bitbucket integration would be valuable to your team and you are open to partnering with us on its development, [contact us](https://argos-ci.com/contact).

### Azure DevOps

Argos does not support Azure DevOps. If an Azure DevOps integration would be valuable to your team and you are open to partnering with us on its development, [contact us](https://argos-ci.com/contact).

### Use Argos without a native integration

You can still run visual tests from any CI system: upload screenshots with `ARGOS_TOKEN` and review builds in the Argos dashboard. Without a native integration, Argos cannot post commit statuses or pull request comments on your provider, so you review changes in Argos directly.

Follow the [Quickstart](/docs/quickstart) to set up your project, and see the [CLI reference](/docs/reference/argos-command-line-interface-cli) for upload options.


# Slack integration

Connect Argos to Slack for build notifications and rich previews when you paste an Argos build URL.

Connect Argos to Slack to notify your team about visual changes and get rich previews when an Argos build URL is shared in Slack.

{% hint style="info" %}
The Slack integration is available on Pro and Enterprise plans.
{% endhint %}

### What you get

* Automatic Slack notifications when builds are created, reviewed, approved, or rejected.
* Rich previews when pasting an Argos build URL in Slack.
* Fine-grained control over notifications using automation conditions.

### Slack notifications

Notifications are driven by [**automations**](/docs/learn/review-workflow/automations): rules that post messages to the right channels at the right time. Typical signals you may want to route to Slack:

* A build is ready for review.
* Changes are requested.
* A build is auto-approved on main.
* Only failed or blocking builds.

Notifications include the build status, branch, author, and a direct link to the build.

![Slack notification sent by Argos](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/5kWJLJ7tSF1AGxAqOsgQ/slack%20notification%20example%20937c6480622562c1b7b2641fd364e254.png)

*Example of a Slack notification sent by Argos*

### URL unfurling in Slack

When you paste an Argos build URL into Slack, Argos automatically unfurls it.

The preview includes:

* Build status and outcome
* Project and branch
* Direct shortcut to open the build in Argos

This works in messages, threads, and shared links, which makes reviews and handoffs easier without extra clicks.

### Connect Slack to your team

Connecting your Slack workspace enables URL unfurling and makes Slack available as an automation action.

1. From the dashboard, select your team from the scope selector.
2. Go to the team's **Settings** tab.
3. Select the **Integrations** section.
4. Select **Connect to Slack** and follow the connection flow.

### Set up Slack notifications

Create a notification rule with Argos automations:

1. Select a project in your Argos team.
2. Go to the **Automations** tab and select **New Automation**.
3. Name your automation, e.g., "Notify Slack on build completion".
4. Under **WHEN**, select one or several events that trigger the notification.
5. (Optional) Under **IF**, add conditions such as "Build type is check".

   ![Automation conditions example](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/KribhFwJank8LlcwBLWe/automation%20conditions%206bc66f9d8c99db7e8601ac368a9fda4c.png)
6. Under **THEN**, choose the action **Post in Slack channel**. If your workspace isn't connected yet, select **Connect to Slack** and follow the connection flow.
7. Select the Slack channel and optionally provide the channel ID.

   ![Locate Slack channel ID](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/u2iR1sKQWwb4XNlG46qJ/slack%20channel%20id%207e607e0f5800c51bc38f17bb856ac88c.png)

   *Finding the ID of a Slack channel*
8. Select **Send test notification** to verify the connection. A test message is sent to the selected channel.
9. Select **Create Rule** to activate it.

### Troubleshooting and tips

* Make sure the Argos app is authorized in your Slack workspace.
* For private channels, manually invite the bot with `/invite @Argos`.
* Only Argos team admins can configure Slack integrations.
* You can test notifications anytime using **Send test notification**.

Need help setting up Slack integration? Reach out via [Discord](https://argos-ci.com/discord) or [contact support](https://argos-ci.com/contact).


# Microsoft Teams integration

Send Argos build notifications to a Microsoft Teams channel using an incoming webhook.

Connect Argos to Microsoft Teams to notify your team about visual changes directly in a channel.

{% hint style="info" %}
The Microsoft Teams integration is configured at team level and is available on Pro and Enterprise plans.
{% endhint %}

### What you get

* Automatic Teams notifications when builds are created, reviewed, approved, or rejected.
* Fine-grained control over notifications using automation conditions.
* Notifications rendered as adaptive cards, with the build status, project, screenshot counts, commit, branch, and a direct link to the build.

![Microsoft Teams notification sent by Argos](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-1643c3621eb2e1f4ae5a953c3e4b9a85f47adf30%2Fmsteams-notification-example.png?alt=media)

*Example of a Microsoft Teams notification sent by Argos*

### How it works

Unlike Slack, Argos does not install an app in your Microsoft 365 tenant. You create a **Workflows** flow in Teams that listens for an incoming webhook, and paste the URL it gives you into Argos. Argos then posts adaptive cards to that URL.

This has two consequences worth knowing before you start:

* **One webhook per channel.** Each channel you want to notify needs its own flow and its own URL in Argos.
* **No URL unfurling.** Pasting an Argos build URL in Teams does not produce a build preview. The connection is outbound only, so Argos never sees messages posted in Teams.

{% hint style="warning" %}
Microsoft Teams for personal use (`teams.live.com`) does not support Workflows. You need a Microsoft 365 work or school account.
{% endhint %}

### Create the webhook in Microsoft Teams

{% stepper %}
{% step %}

#### Open the Workflows dialog

In Teams, hover the channel you want to notify, open its **More options** menu, and select **Workflows**.

![Opening Workflows from a channel menu](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-ad6aecd14c35844583b2a869221b9f03ce714d3b%2Fmsteams-channel-workflows-menu.png?alt=media)
{% endstep %}

{% step %}

#### Choose the right template

Search for `webhook` and select **Send webhook alerts to a channel**.

![The four webhook templates offered by Workflows](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-97030f730a0cfaecbecae4ea1cdf97bd541e15b5%2Fmsteams-webhook-templates.png?alt=media)

{% hint style="warning" %}
Four similar templates exist, and picking the wrong one breaks every notification. Do **not** use **Send webhook alerts from specific people to a channel** or **Send webhook alerts from people in an org to a channel**: they authenticate the caller through Microsoft Entra ID, and Argos posts server-to-server without a user identity. **Send webhook alerts to a chat** targets a chat instead of a channel.
{% endhint %}
{% endstep %}

{% step %}

#### Select the team and channel

Confirm the target team and channel, then select **Save**.
{% endstep %}

{% step %}

#### Copy the webhook URL

Once the flow is created, select **Copy webhook link** on the flow details page.

The URL looks like `https://<tenant>.<region>.environment.api.powerplatform.com/powerautomate/automations/direct/...`.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
The webhook URL contains an access signature. Anyone holding it can post messages to that channel. Treat it like a secret and avoid sharing it in tickets or chat.

Argos lists each connected channel with its webhook URL underneath, so you can tell two flows apart. Only team admins see the whole URL; for everybody else the signature is masked as `***`.
{% endhint %}

### Connect the channel to your team

{% stepper %}
{% step %}

#### Open your team integrations

From the dashboard, select your team from the scope selector, go to the team's **Settings** tab, then the **Integrations** section.
{% endstep %}

{% step %}

#### Add the channel

In the **Microsoft Teams** card, enter a **Name** to identify the channel in Argos, paste the **Webhook URL**, and select **Add channel**.

The name is only a label: a webhook URL never reveals which channel it points to, so pick something recognizable such as `#engineering`.
{% endstep %}

{% step %}

#### Send a test message

Open the channel's actions menu (**⋯**) and select **Send a test message** to post a confirmation card. If it arrives in Teams, the connection works.

The same menu holds **Copy webhook URL** and **Remove channel**.
{% endstep %}
{% endstepper %}

### Set up Microsoft Teams notifications

Create a notification rule with Argos [automations](/docs/learn/review-workflow/automations):

1. Select a project in your Argos team.
2. Go to the **Automations** tab and select **New Automation**.
3. Name your automation, e.g., "Notify Teams on build completion".
4. Under **WHEN**, select one or several events that trigger the notification.
5. (Optional) Under **IF**, add conditions such as "Build type is check".
6. Under **THEN**, choose the action **Post in Microsoft Teams channel**. If no channel is connected yet, select **Connect Microsoft Teams** and follow the steps above.
7. Select the channel to notify.
8. Select **Send Test Notification** to verify the rule. A card built from your project's latest build is sent to the selected channel.
9. Select **Create Rule** to activate it.

### Troubleshooting and tips

* **"This does not look like a Microsoft Teams webhook URL".** Argos only accepts URLs served by Microsoft: `powerplatform.com`, `logic.azure.com`, and `webhook.office.com`. Copy the URL from the flow details page rather than retyping it.
* **Nothing arrives in the channel.** Check that the flow is still **Active** in Teams. Deleting or disabling the flow silently stops delivery; use **Send a test message** in Argos to surface the error.
* **Notifications stopped after a while.** The webhook signature can be rotated or revoked on the Microsoft side. Recreate the flow and replace the URL in Argos.
* **Send Test Notification does nothing.** The automation form must be valid first, including the rule name.
* Only Argos team admins can add, test, or remove channels.

Need help setting up the Microsoft Teams integration? Reach out via [Discord](https://argos-ci.com/discord) or [contact support](https://argos-ci.com/contact).


# Discord integration

Send Argos build notifications to a Discord channel using an incoming webhook.

Connect Argos to Discord to notify your team about visual changes directly in a channel.

{% hint style="info" %}
The Discord integration is configured at team level and is available on Pro and Enterprise plans.
{% endhint %}

### What you get

* Automatic Discord notifications when builds are created, reviewed, approved, or rejected.
* Fine-grained control over notifications using automation conditions.
* Notifications rendered as embeds, with the build status, project, commit, branch, pull request, and a direct link to the build.

### How it works

Like Microsoft Teams and unlike Slack, Argos does not install a bot in your Discord server. You create a **webhook** in the channel you want to notify and paste the URL it gives you into Argos. Argos then posts embeds to that URL.

This has two consequences worth knowing before you start:

* **One webhook per channel.** Each channel you want to notify needs its own webhook and its own URL in Argos.
* **No URL unfurling.** Pasting an Argos build URL in Discord does not produce a build preview. The connection is outbound only, so Argos never sees messages posted in Discord.

{% hint style="info" %}
You need the **Manage Webhooks** permission on the channel to create one. If you don't see the **Integrations** tab, ask a server admin.
{% endhint %}

### Create the webhook in Discord

{% stepper %}
{% step %}

#### Open the channel integrations

In Discord, hover the channel you want to notify, select the gear icon to **Edit Channel**, then open the **Integrations** tab.

You can also reach it from **Server Settings** › **Integrations** › **Webhooks**.
{% endstep %}

{% step %}

#### Create the webhook

Select **Webhooks**, then **New Webhook**. Discord creates one targeting the current channel.

The name and avatar you give the webhook here are cosmetic: Argos posts under the name **Argos**, whatever the webhook is called in Discord.
{% endstep %}

{% step %}

#### Copy the webhook URL

Select **Copy Webhook URL**.

The URL looks like `https://discord.com/api/webhooks/<id>/<token>`.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
The webhook URL ends with a token. Anyone holding it can post messages to that channel. Treat it like a secret and avoid sharing it in tickets or chat.

Argos lists each connected channel with its webhook URL underneath, so you can tell two webhooks apart. Only team admins see the whole URL; for everybody else the token is masked as `***`.
{% endhint %}

### Connect the channel to your team

{% stepper %}
{% step %}

#### Open your team integrations

From the dashboard, select your team from the scope selector, go to the team's **Settings** tab, then the **Integrations** section.
{% endstep %}

{% step %}

#### Add the channel

In the **Discord** card, enter a **Name** to identify the channel in Argos, paste the **Webhook URL**, and select **Add channel**.

The name is only a label: a webhook URL never reveals which channel it points to, so pick something recognizable such as `#engineering`.
{% endstep %}

{% step %}

#### Send a test message

Open the channel's actions menu (**⋯**) and select **Send a test message** to post a confirmation embed. If it arrives in Discord, the connection works.

The same menu holds **Copy webhook URL** and **Remove channel**.
{% endstep %}
{% endstepper %}

### Set up Discord notifications

Create a notification rule with Argos [automations](/docs/learn/review-workflow/automations):

1. Select a project in your Argos team.
2. Go to the **Automations** tab and select **New Automation**.
3. Name your automation, e.g., "Notify Discord on build completion".
4. Under **WHEN**, select one or several events that trigger the notification.
5. (Optional) Under **IF**, add conditions such as "Build type is check".
6. Under **THEN**, choose the action **Post in Discord channel**. If no channel is connected yet, select **Connect Discord** and follow the steps above.
7. Select the channel to notify.
8. Select **Send Test Notification** to verify the rule. An embed built from your project's latest build is sent to the selected channel.
9. Select **Create Rule** to activate it.

### Troubleshooting and tips

* **"This does not look like a Discord webhook URL".** Argos only accepts webhook URLs served by Discord: `discord.com`, `discordapp.com`, and the `canary` and `ptb` release channels. Copy the URL with **Copy Webhook URL** rather than retyping it, and keep the whole `/api/webhooks/<id>/<token>` path.
* **Nothing arrives in the channel.** Check that the webhook still exists in the channel's **Integrations** tab. Deleting it silently stops delivery; use **Send a test message** in Argos to surface the error.
* **Notifications stopped after a while.** Deleting and recreating a webhook in Discord issues a new token. Copy the new URL and replace it in Argos.
* **The channel moved or was deleted.** A webhook is bound to the channel it was created in. Create a new webhook in the new channel and replace the URL in Argos.
* **Send Test Notification does nothing.** The automation form must be valid first, including the rule name.
* Only Argos team admins can add, test, or remove channels.

Need help setting up the Discord integration? Reach out on our [Discord community](https://argos-ci.com/discord) or [contact support](https://argos-ci.com/contact).


# Account & access

Manage your Argos account, configure how your team signs in, and control who can access which projects.

Manage your Argos account, how you sign in, and who on your team can access what.

* [Account management](/docs/learn/account-and-access/account-management) – Sign up, create projects and teams, and manage login methods and emails
* [Analytics](/docs/learn/account-and-access/analytics) – Track builds, screenshots, change rate, and approval rate across your projects
* [Team members & roles](/docs/learn/account-and-access/team-members-and-roles) – Invite members and control their permissions with roles
* [Passkeys](/docs/learn/account-and-access/passkeys) – Sign in with your fingerprint, face, screen lock, or a hardware security key
* [GitHub single sign-on](/docs/learn/account-and-access/github-single-sign-on) – Sync your Argos team with a GitHub organization
* [SAML single sign-on](/docs/learn/account-and-access/saml-single-sign-on) – Enforce single sign-on through your SAML identity provider
* [SCIM provisioning](/docs/learn/account-and-access/scim-provisioning) – Let your identity provider add, remove and assign roles to team members
* [Security and compliance](/docs/learn/account-and-access/security-and-compliance) – Data hosting, SOC 2, GDPR and the DPA, data retention, export and deletion


# Account management

Create and manage your Argos account, sign up with Google, GitHub, or GitLab, and pick the plan that fits.

When you sign up for Argos, you create an account that manages your projects and subscriptions. You can sign up with **Google**, **GitHub**, or **GitLab** — see [Pricing plans](/docs/learn/billing-and-subscription/pricing-plans) for what each plan includes.

### Sign up with a Git provider

Argos supports [GitHub](/docs/learn/integrations/github-integration) and [GitLab](/docs/learn/integrations/gitlab-integration) as Git providers. When you sign up with one, you authorize Argos to access it, and that connection becomes your default login method. After signing up, you can manage login methods in your **Personal Settings**.

### Login methods and connections

To manage login connections:

1. Select your profile picture in the top-right corner of the dashboard.
2. Choose **Settings**.
3. Open the **Authentication** section.

![The Authentication section of your account settings.](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/vI8gtM8skEWsH0CSVteD/authentication%20005df39b1abdc82a91a54c5c055a9dcd.png)

#### Logging in with a passkey

A passkey lets you sign in with your fingerprint, face, screen lock, or a hardware security key instead of a code or a Git provider. You can register several and use any of them.

For setup and login instructions, see [Passkeys](/docs/learn/account-and-access/passkeys).

#### Logging in with SAML Single Sign-On (SSO)

SAML SSO allows team members to log in with their organization’s identity provider.

For setup and login instructions, see [SAML Single Sign-On](/docs/learn/account-and-access/saml-single-sign-on).

### Creating a project

You can create a project in two ways from the **New Project** page — import an existing Git repository, or start with just a name and connect a repository later.

{% tabs %}
{% tab title="Import a Git repository" %}
Choose **Import a Git repository** and select a Git provider (GitHub or GitLab) to import a repository. Argos links the project to that repository, which enables [pull request comments](/docs/learn/review-workflow/pull-request-comments) and status checks.
{% endtab %}

{% tab title="Create without Git" %}
Choose **Create a project without Git**, enter a **Project name**, and select **Create project**.

A project without a connected repository still receives builds through the [Argos CLI](/docs/reference/argos-command-line-interface-cli) using its project token. Features that depend on a repository — such as pull request comments — become available once you link one.
{% endtab %}
{% endtabs %}

![The New Project page with two paths: import a Git repository or create a project without Git](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-13487a4ba22c756ddec483a5bb5fc7b04969aafe%2Fnew-project-page.png?alt=media)

#### Connect a repository later

To link a repository to a project you created without Git:

1. Open the project's **Settings**.
2. Go to the **Connected Git Repository** section.
3. Select a Git provider and choose the repository to connect.

You can disconnect or change the repository from the same section at any time.

### Delete a project

Only project admins can delete a project. To keep it under another account instead, [transfer it](/docs/learn/billing-and-subscription/how-to-subscribe#transfer-your-projects).

1. Open the project's **Settings**, in the **General** section.
2. On the **Delete Project** card, select **Delete**.
3. Type the project's full name (`account/project`), then `delete my project`, and select **Delete**.

Argos warns that this can't be undone. Once the project is deleted, uploads with its project token fail and Argos stops updating its commit statuses. If the Argos check is a required status check in your Git provider, remove it there, or open pull requests keep waiting for it.

### Uninstall the GitHub App

You uninstall the Argos GitHub App from GitHub, not from Argos:

* **Personal account:** in GitHub's **Settings → Applications → Installed GitHub Apps**, select **Configure** next to Argos, then **Uninstall**.
* **Organization:** in the organization's **Settings → GitHub Apps** (under "Third-party Access"), select **Configure** next to Argos, then **Uninstall**.

When GitHub notifies Argos, Argos disconnects each repository it can no longer access from its Argos projects. Their builds no longer get commit statuses or pull request comments, and uploads that use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) fail. Uploads with a project token are still accepted.

Removing a repository from the app's **Repository access** has the same effect on that repository. To undo it, [install the app](/docs/learn/integrations/github-integration#install-the-argos-github-app) again and reconnect the repository from the project's settings.

### Teams

Teams let you collaborate on projects and share resources.

#### Creating a team

1. Select the **scope selector** in the top-left of the navbar.
2. Choose **Create new team**.
3. Enter a team name.
4. By default, new teams start on the **Pro plan**. To switch plans, [contact us](https://argos-ci.com/contact).

Team settings and members can be managed from the team’s settings page.

#### Free Pro trial

Your first team automatically starts with a **14-day free trial of Argos Pro**.

The trial includes unlimited users and **35,000 free screenshots**.

#### Team membership

Owners can invite new members using an invitation link found in **Team Settings**.

See [Roles and permissions](/docs/learn/account-and-access/team-members-and-roles#owner-role) for details.

**Inviting teammates**

1. From the dashboard, select your team from the scope selector.
2. Open the **Settings** tab and go to the **Members** section.
3. Select **Invite Link** and copy the generated link.
4. Share the link with anyone you want to join the team.

{% hint style="info" %}
Public repositories on Argos are accessible to everyone.
{% endhint %}

#### Leaving a team

You cannot leave a team if you are the **last owner** or the **last member**.

To leave a team:

1. If necessary, assign another owner first.
2. Go to your team's **Settings** tab and go to the **Members** section.
3. Scroll to **Leave Team** and select **Leave Team**.
4. Confirm the action.
5. If you are the last member, [delete the team](#deleting-a-team) instead.

#### Deleting a team

Only team [owners](/docs/learn/account-and-access/team-members-and-roles#owner-role) can delete a team. Deleting a team also deletes its projects, so you don't need to remove them first. To keep a project, [transfer it](/docs/learn/billing-and-subscription/how-to-subscribe#transfer-your-projects) to another account before you delete the team.

1. If the team has an active subscription, cancel it first in **Settings → Billing → Manage subscription**. The **Delete** button stays disabled until you do.
2. Open the team's **Settings** tab, in the **General** section.
3. On the **Delete Team** card, select **Delete**.
4. Type the team slug, then `delete my team`, and select **Delete**.

### Managing emails

To access email settings:

1. Select your avatar in the top-right corner.
2. Choose **Settings**.
3. Scroll to the **Emails** section.
4. From here, you can [add](#adding-a-new-email-address), [remove](#removing-an-email-address), or [change](#changing-your-primary-email-address) your primary email.

### Adding a new email address

1. In the **Emails** section, select **Add Another**.
2. Verify the new address using the link sent to your inbox.
3. Once verified, any email address on your account can be used to log in.

![Your account email addresses.](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/2m2pYwW1xNvx1A6GDdym/email%20management%202ddc061d8706486091f2cff3c1784d19.png)

### Changing your primary email address

Your primary email is used for Argos notifications.

To change it:

* Add and verify a new email.
* Open the dot menu next to the address and choose **Set as Primary**.

![Setting your primary email address.](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/PiEZzeTCL9cKFsl5in4j/set%20email%20as%20primary%208aa94036efe6d5e3efd5a3c1b77f6504.png)

### Removing an email address

To remove an address, use the **Delete** option in the dot menu.

> Note: You must set a new primary email before removing the current one.

### Resolving "Account already attached" issues

If you see the error message:

> **Your&#x20;*****GitHub|GitLab|Google*****&#x20;account is already linked to another Argos account**

it means the provider account you are trying to connect is already associated with a different Argos account.

#### Steps to fix

{% stepper %}
{% step %}

### Log out of Argos

Select your avatar in the top-right corner, then select **Log out**.
{% endstep %}

{% step %}

### Log back in with your provider

From the login page, choose **Continue with&#x20;*****GitHub|GitLab|Google***.

This will sign you into the Argos account that currently owns your provider link.
{% endstep %}

{% step %}

### Disconnect or delete the account

Once logged in, select your avatar in the top-right corner, choose **Settings**.

Select **Authentication** from the sidebar.

Find the *GitHub|GitLab|Google* connection, select the vertical ellipsis (⋮), and select **Disconnect**.

Alternatively, if the account is no longer needed, you can delete it entirely from **Account Settings → General**.
{% endstep %}

{% step %}

### Log out again

Return to the login page.
{% endstep %}

{% step %}

### Reconnect with the right account

Log back in with the account you want to use.

From the **Authentication** section, link your provider account.
{% endstep %}
{% endstepper %}

After completing these steps, your provider account will be linked to the correct Argos account.


# Analytics

Track builds, screenshots, change rate, and approval rate across every project in your Argos account — from the dashboard, the REST API, or the CLI.

The **Analytics** dashboard gives you an account-wide view of your visual testing activity: how many builds and screenshots you run, how often Argos detects changes, and how your team reviews them — across every project you can access.

The same metrics are available programmatically through the [REST API](#rest-api) and the [CLI](#cli).

### Open the dashboard

1. Select your account or team in Argos.
2. Select the **Analytics** tab.

Use the filters at the top to scope the whole page to specific projects, and to pick a time period — **last 7, 30, 90, or 365 days**, or a custom range. Data points are grouped by day, week, or month depending on the period.

### Key metrics

The summary band at the top answers the big questions at a glance:

* **Builds**: Total builds created over the period, with a trend sparkline and a per-period average.
* **Screenshots**: Total screenshots captured over the period, with a trend sparkline and a per-period average.
* **Change rate**: The share of concluded builds where Argos detected visual changes.
* **Approval rate**: The share of reviewed builds that were approved rather than rejected.

### Sections

Below the summary, themed sections let you explore where activity concentrates:

* **Activity**: Builds created and screenshots captured over time, broken down by project.
* **Build outcomes**: Changes detected vs. no changes, and approved vs. rejected builds over time, using the same status colors as builds.
* **Breakdown**: Screenshots by project, and screenshots per build.

### Export to CSV

Every chart can export its underlying data as CSV. The builds export includes changes detected, no changes, approved, and rejected columns, so you can build your own reports on top of it.

### REST API

The [`GET /accounts/{accountSlug}/analytics`](https://argos-ci.com/docs/api-reference/reference/analytics) endpoint returns the build and screenshot metrics that power the dashboard: totals and a per-period series, broken down by project. You can filter by project name, set a custom date range (up to 365 days), and group data points by day, week, or month.

The endpoint requires a **personal access token** scoped to the account — project tokens won't work. See the [API reference](https://argos-ci.com/docs/api-reference/reference/analytics) for details.

### CLI

The [`argos analytics`](/docs/reference/argos-command-line-interface-cli#account-commands) command wraps the REST endpoint in a single command:

```bash
argos analytics --account my-team --from 2026-01-01 --group-by week
```

It prints a human-readable summary with a per-project breakdown, or machine-readable JSON with `--json` — handy when a script, a dashboard, or an AI agent needs to parse the result. See the [CLI reference](/docs/reference/argos-command-line-interface-cli#account-commands) for all options.


# Team members & roles

Invite team members and assign team and project roles to control permissions with role-based access control.

Teams are made up of members, and each member has a role. Roles define what a member can and cannot do within a team on Argos — assign them so everyone has the right permissions as your team grows.

Argos distinguishes two groups of roles: **team-level roles** apply to the entire team and all its projects, while **project-level roles** are confined to individual projects.

![Team and project roles relationship diagram](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/G8fxnmqGn6H8YZxNC3gu/permissions%20diagram%20634397797bd522b3c4f79256e500f104.png)

### Roles at a glance

| Role                                            | Scope   | Availability       |
| ----------------------------------------------- | ------- | ------------------ |
| [Owner](#owner-role)                            | Team    | Pro and Enterprise |
| [Member](#member-role)                          | Team    | Pro and Enterprise |
| [Contributor](#contributor-role)                | Team    | Enterprise         |
| [Project Administrator](#project-administrator) | Project | Enterprise         |
| [Project Reviewer](#project-reviewer)           | Project | Enterprise         |
| [Project Viewer](#project-viewer)               | Project | Enterprise         |

Only contributors can have configurable project roles — Owners and Members always have access to every project in the team.

### Team-level roles

Team-level roles apply to all projects within the team. With [SCIM provisioning](/docs/learn/account-and-access/scim-provisioning#map-roles-from-your-identity-provider), your identity provider can assign them.

#### Owner role

The Owner role is the highest level of authority within a team, with full access and control over all team and project settings.

**Key responsibilities**

* Oversee and manage all team resources and projects.
* Modify team settings, including billing.
* Grant or revoke access to team projects and assign project roles.
* Access and modify all projects, including their settings.

**Access and permissions**

Owners have unrestricted access to all team functionality, can modify all settings, and change other members' roles. They inherently act as project administrators for every project in the team.

Teams can have more than one owner — for continuity, we recommend at least two. Role changes, including assigning and revoking roles, are exclusive to owners.

#### Member role

Members are the most common role in the team. They have access to all projects but not to team management settings.

**Key responsibilities**

* See and review builds.
* Access and modify all projects, including their settings.

**Access and permissions**

Members have full autonomy to review projects and edit project settings. They can't edit team settings or invite new users to the team — only owners can.

#### Contributor role

Contributors give you fine-grained access control at the project level. A contributor has **no access to any project unless explicitly assigned** a [project role](#project-level-roles) on it.

**Key responsibilities**

* Typically assigned to specific projects based on expertise and needs.
* Review builds and edit project settings, depending on their assigned [project role](#project-level-roles).

**Access and permissions**

On each project, a contributor can be granted the [Project Administrator](#project-administrator), [Project Reviewer](#project-reviewer), or [Project Viewer](#project-viewer) role — or none, which denies access to that project.

### Project-level roles

Project-level roles restrict a contributor's access to specific projects:

* [**Project Administrator**](#project-administrator): Reviews builds and manages all project settings.
* [**Project Reviewer**](#project-reviewer): Reviews builds.
* [**Project Viewer**](#project-viewer): Has read-only access to the project.

#### Project Administrator

Project administrators are the project-level counterparts of team owners and members.

**Key responsibilities**

* Review builds.
* Manage project settings.
* Add contributors to the project.

**Access and permissions**

Their authority doesn't extend across the team: project administrators are restricted to the projects they're assigned to.

#### Project Reviewer

Project reviewers mirror the review capabilities of team members, with a narrowed project focus.

**Key responsibilities**

* Review builds.

#### Project Viewer

Project viewers have read-only access to the project.

**Key responsibilities**

* View and inspect all builds and screenshots.

### Default project roles

When you set a default role on a project, all of your team's contributors automatically inherit that role when granted access to the project — keeping permissions consistent without per-person configuration.

To set a default role on a project:

1. Go to the project's **Settings** page.
2. Scroll to the **Access management** section.
3. Select **Set default contributor level**.
4. Select the desired default role (Viewer, Reviewer, or Admin).
5. Select **Save**.

Every contributor then accesses this project with the default role. You can still override it by assigning a custom role to individual contributors.


# Passkeys

Add passkeys to your Argos account and sign in with your fingerprint, face, screen lock, or a hardware security key.

A passkey replaces a password with the unlock method you already use on your device — Touch ID, Face ID, Windows Hello, your phone's screen lock, or a hardware security key such as a YubiKey.

Argos always asks the authenticator to verify it is really you, so the passkey is protected by your biometric, PIN, or screen lock. A security key with no PIN set cannot be registered.

Because the key is bound to `argos-ci.com` and never leaves your device or password manager, a passkey cannot be phished, reused on a lookalike site, or leaked in a database breach.

You can register as many passkeys as you like — one per laptop, one on your phone, one on a security key — and sign in with any of them.

{% hint style="info" %}
Passkeys are available to every account, on every plan. They sit alongside your other login methods rather than replacing them: adding a passkey does not disable email, Google, GitHub, GitLab, or SAML sign-in.
{% endhint %}

### Adding a passkey

{% stepper %}
{% step %}

### Open your authentication settings

Select your avatar in the top-right corner of the dashboard, choose **Settings**, then open the **Authentication** section.
{% endstep %}

{% step %}

### Start the registration

In the **Passkeys** row, select **Add**. A **Create Passkey** dialog explains what happens next — select **Continue**.
{% endstep %}

{% step %}

### Confirm on your device

Your browser or password manager takes over and asks you to confirm with your fingerprint, face, screen lock, or security key.

If you use a password manager such as 1Password, Bitwarden, or iCloud Keychain, it may offer to store the passkey for you so it syncs to your other devices.
{% endstep %}

{% step %}

### Done

The passkey appears in the **Passkeys** row, named after wherever it was stored — for example **1Password** or **iCloud Keychain**. Expand the row to see it.
{% endstep %}
{% endstepper %}

{% hint style="warning" %}
If the dialog reports that the prompt was "canceled or timed out", the device prompt was dismissed or ran out of time. Select **Retry** to start over.
{% endhint %}

### Signing in with a passkey

On the [login page](https://app.argos-ci.com/login), select **Continue with Passkey**.

You are not asked for your email first. Your device lists the Argos accounts it holds a passkey for, you pick one and confirm, and you are signed in.

{% hint style="info" %}
A passkey signs you into an account that already exists — it is a login method, not a way to sign up. [Create your account](/docs/learn/account-and-access/account-management) first, then add a passkey to it.
{% endhint %}

#### Using a passkey from another device

A passkey stored on your phone can sign you in on a computer that has none. Choose **Continue with Passkey**, then pick the option to use a phone or tablet. Your browser shows a QR code; scan it with your phone and confirm there.

### Managing your passkeys

Expand the **Passkeys** row in **Settings -> Authentication** to see every passkey on your account, when each was created, and when it was last used.

* **Rename** — select the pencil icon and give the passkey a name you will recognise later, such as "Work laptop". The default name reflects where the passkey is stored, which is not always enough to tell two apart.
* **Delete** — select the trash icon and confirm. That device can no longer be used to sign in.

{% hint style="warning" %}
Deleting a passkey only removes it from Argos. Your password manager or device keychain may still hold an entry for `argos-ci.com` that you will want to clean up separately.
{% endhint %}

### Passkeys and SAML SSO

If one of your teams [enforces SAML SSO](/docs/learn/account-and-access/saml-single-sign-on#enforcing-saml), a passkey signs you into Argos but does not satisfy that requirement. You keep access to your personal account and to teams without enforcement, and Argos prompts you to re-authenticate with SAML when you open a team that enforces it.

### Troubleshooting

<details>

<summary>"Continue with Passkey" is missing from the login page</summary>

Your browser does not support passkeys. Argos hides the option rather than offering something that cannot work. Passkeys need a current version of Chrome, Safari, Edge, or Firefox; update your browser, or sign in with another method.

</details>

<details>

<summary>Nothing happens, or my device says it has no passkey for this site</summary>

The device you are on holds no Argos passkey. Either sign in another way and [add a passkey](#adding-a-passkey) on this device, or use the [QR-code flow](#using-a-passkey-from-another-device) to sign in with a passkey stored on your phone.

Passkeys are also bound to the domain they were created for, so only a passkey created on `app.argos-ci.com` works there.

</details>

<details>

<summary>"This passkey is already registered"</summary>

That device or password manager already holds a passkey for Argos — possibly on a different Argos account. A passkey belongs to exactly one account. Check the **Passkeys** row on the account you expect it on, or delete the existing entry from your password manager before registering again.

</details>

<details>

<summary>"The passkey request expired"</summary>

Registration and sign-in must be completed within a few minutes of starting. Start again from **Add** or **Continue with Passkey**.

</details>

<details>

<summary>I lost the device holding my only passkey</summary>

Sign in with another method — an email code, or Google, GitHub, GitLab, or SAML — then delete the passkey for the lost device from **Settings -> Authentication** and register a new one. This is why it is worth keeping a second passkey, or at least one other login method, on your account.

</details>


# GitHub single sign-on

Enable GitHub single sign-on to sync your Argos team with a GitHub organization and let members sign in with GitHub.

GitHub single sign-on (SSO) syncs your Argos team with a GitHub organization: members of the organization can join your Argos team automatically by signing in with GitHub.

{% hint style="info" %}
GitHub SSO is a paid add-on ($50 / month) available to teams on a Stripe-based subscription.
{% endhint %}

### Enabling the feature

{% stepper %}
{% step %}
Go to your team settings.
{% endstep %}

{% step %}
Select **Enable and Pay** under the **GitHub Single Sign-On** section.
{% endstep %}

{% step %}
Select your team and authorize Argos on GitHub. The feature is activated immediately.
{% endstep %}
{% endstepper %}

![Enable GitHub SSO](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/jxEJI96aiJGXTRpvUP4l/enable%20github%20sso%200543329dbe51da6773d39f061606e923.png)

### Disabling the feature

In team settings, select **Disable** under the **GitHub Single Sign-On** section and confirm. The owners remain as members of the team.

![GitHub SSO enabled](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/TpbGm3yfmoxs2AyXgfxY/github%20sso%20enabled%203b6b2bfec185891f6a3ca6909e1f721a.png)

### Billing

When you enable GitHub SSO, the add-on appears as an "Add-on: GitHub SSO" line on your monthly invoice. You can disable it at any time.


# SAML single sign-on

Configure SAML single sign-on with providers like Okta or Auth0 to authenticate your Argos team members.

{% hint style="info" %}
SAML SSO is a paid add-on ($200 / month) available to teams on a Stripe-based subscription. It is included in the [Enterprise](/docs/learn/billing-and-subscription/pricing-plans#enterprise-plan) plan, and comes with [SCIM provisioning](/docs/learn/account-and-access/scim-provisioning).

Only team members with the [owner role](/docs/learn/account-and-access/team-members-and-roles#owner-role) can configure SAML SSO.
{% endhint %}

To manage your [team members](/docs/learn/account-and-access/team-members-and-roles) through a third-party identity provider like [Okta](https://www.okta.com/) or [Auth0](https://auth0.com/), configure Security Assertion Markup Language (SAML) from **Team Settings -> Authentication**.

Once enabled, team members can authenticate with your configured identity provider, and new users signing in with SAML will be added to your team.

![The SAML SSO settings for a Team.](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/qhufiLVkPuv86miOxCcb/saml%20settings%204a875c027de3b7cc49da61c3b7cbfcbd.png)

### Enabling the add-on

Unless your plan includes SAML SSO, enable it as an add-on first:

{% stepper %}
{% step %}
Go to your team settings and open the **Billing** tab.
{% endstep %}

{% step %}
In the **Add-ons** section, select **Enable** next to **SAML Single Sign-On**.
{% endstep %}

{% step %}
Select **Confirm and Pay**. The feature is activated immediately.
{% endstep %}
{% endstepper %}

The add-on appears as a "SAML SSO" line on your monthly invoice. You can disable it at any time from the **Add-ons** section; team members will then no longer be able to sign in with SAML.

### Configuring SAML SSO

1. Ensure you are an [owner](/docs/learn/account-and-access/team-members-and-roles#owner-role) of the team.
2. From the dashboard, select the team in the scope selector.
3. Open the **Settings** tab, then go to **Authentication**.
4. In **SAML Single Sign-On**, click **Configure** and follow the setup flow for your identity provider.
5. Optionally enforce SAML SSO for all team members after confirming authentication works.

### Enforcing SAML

For additional security, you can enforce SAML so team members can only access team resources when their current session is authenticated with SAML.

1. Ensure you are an [owner](/docs/learn/account-and-access/team-members-and-roles#owner-role) and currently authenticated with SAML.
2. Go to **Team Settings -> Authentication -> SAML Single Sign-On**.
3. Turn on **Require Team Members to login with SAML to access this Team**.

![SAML SSO configured and enforced.](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/SsSDoqqpifDZBDCgDWMs/saml%20enforced%200b9acb80b05d917ee70c452b00b6a254.png)

When you modify your SAML configuration, enforcement is automatically disabled. Re-authenticate with SAML and verify the new configuration before re-enabling enforcement.

### Authenticating with SAML SSO

After SAML is configured, team members can sign in using SAML SSO:

1. On the login page, click **Continue with SAML SSO** and enter your team slug.
2. Click **Continue with SAML SSO** again to be redirected to your identity provider.
3. Complete authentication to access Argos.

#### Customizing the login page

You can share an Argos login URL that only displays the SAML SSO option for a specific team.

```
https://app.argos-ci.com/login?saml=team_slug
```

Replace `team_slug` with your team identifier in Argos URLs.

![Argos's login page showing only the SAML SSO login button.](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/Wow3ioE4YdShlZtc8bEt/saml%20login%209c431d652c0bef06bc59ca1366e15142.png)

### Managing team members

With SAML SSO, users authenticate through your identity provider, but team membership can still be managed from Argos team settings.

Members are added to your team when they first sign in with SAML, but you can also pre-provision members from the **Team Settings -> Members** page.

{% hint style="success" %}
**Let your identity provider manage the team.** With [SCIM provisioning](/docs/learn/account-and-access/scim-provisioning), included with SAML SSO, your identity provider adds people to the team when they are assigned to Argos, removes them as soon as they are unassigned or deactivated, and can set their roles.
{% endhint %}

Without SCIM, a user is provisioned on their first SAML sign-in. Argos creates their account if they don't have one and adds them to the team with the team's default access role.

To offboard someone without SCIM, remove them from the team: in **Team Settings -> Members**, open the member's actions menu and select **Remove from Team**. Also remove them from the Argos app in your identity provider. Otherwise, signing in with SAML again adds them back to the team.

When SAML SSO is enforced, team members must have an active SAML session to access team resources. If a member's SAML session expires, they will be prompted to re-authenticate with SAML to regain access.

### SAML providers

Argos supports the following SAML providers:

* [Okta](https://www.okta.com/)
* [Auth0](https://auth0.com/)
* [Google](https://accounts.google.com/)
* [Microsoft Entra (formerly Azure Active Directory)](https://www.microsoft.com/en-in/security/business/identity-access/microsoft-entra-single-sign-on)
* [Microsoft ADFS](https://docs.microsoft.com/en-us/windows-server/identity/active-directory-federation-services)
* [OneLogin](https://onelogin.com/)
* [Duo](https://duo.com/product/single-sign-on-sso/)
* [JumpCloud](https://jumpcloud.com/)
* [PingFederate](https://www.pingidentity.com/en/platform/capabilities/single-sign-on.html)
* [ADP](https://apps.adp.com/en-US/home)
* [Keycloak](https://www.keycloak.org/)
* [Cyberark](https://www.cyberark.com/products/single-sign-on/)
* [OpenID](https://openid.net/)
* [VMware](https://kb.vmware.com/s/article/2034918)
* [LastPass](https://www.lastpass.com/)
* [miniOrange](https://www.miniorange.com/products/single-sign-on-sso)
* [NetIQ](https://www.microfocus.com/en-us/cyberres/identity-access-management/secure-login)
* [Oracle Cloud](https://docs.oracle.com/en/cloud/paas/content-cloud/administer/enable-single-sign-sso.html)
* [Salesforce](https://help.salesforce.com/s/articleView?id=sf.sso_about.htm\&type=5)
* [CAS](https://www.apereo.org/projects/cas)
* [ClassLink](https://www.classlink.com/)
* [Cloudflare](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/dash-sso-apps/)
* [SimpleSAMLphp](https://simplesamlphp.org/)


# SCIM provisioning

Provision and deprovision Argos team members from Okta, Microsoft Entra ID or any SCIM 2.0 identity provider, and map their roles.

{% hint style="info" %}
SCIM provisioning is part of [SAML SSO](/docs/learn/account-and-access/saml-single-sign-on): it is included in the [Enterprise](/docs/learn/billing-and-subscription/pricing-plans#enterprise-plan) plan, and available with the [SAML SSO add-on](/docs/learn/account-and-access/saml-single-sign-on#enabling-the-add-on).

Only team members with the [owner role](/docs/learn/account-and-access/team-members-and-roles#owner-role) can set it up.
{% endhint %}

SCIM (System for Cross-domain Identity Management) lets your identity provider manage your Argos team for you. When someone is assigned to Argos in Okta, Microsoft Entra ID or any SCIM 2.0 identity provider, they become a member of your team. When they are unassigned or deactivated, they lose access right away — no one has to remember to remove them from Argos.

Without SCIM, [SAML SSO](/docs/learn/account-and-access/saml-single-sign-on) adds people on their first sign-in and offboarding is manual. With SCIM:

* **Onboarding is automatic** — people are members before they ever sign in.
* **Offboarding is immediate** — deactivating someone in your identity provider removes them from the team.
* **Roles come from your identity provider** — map who is an owner, a member or a contributor.
* **Your identity provider stays the source of truth** — assignments, deactivations and role changes reach Argos on every sync.

![The SCIM Provisioning card in Team Settings -> Authentication.](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-c5cdc09e1b3be44f3f678dfee40d3f3ad7cced8d%2Fscim-provisioning-card.png?alt=media)

## Set up SCIM provisioning

### Before you start

* Your team has access to SAML SSO, through the Enterprise plan or the add-on.
* You are an [owner](/docs/learn/account-and-access/team-members-and-roles#owner-role) of the team.
* We recommend configuring [SAML SSO](/docs/learn/account-and-access/saml-single-sign-on) in the same identity provider app first, so the people SCIM provisions can sign in with it.
* We recommend having at least two owners, or [mapping roles](#map-roles-from-your-identity-provider) — see [When the last owner leaves](#when-the-last-owner-leaves).

### Generate the SCIM token

{% stepper %}
{% step %}

### Open the SCIM settings

From the dashboard, select your team, open the **Settings** tab, then go to **Authentication**. Scroll to **SCIM Provisioning**.
{% endstep %}

{% step %}

### Copy the SCIM base URL

Your identity provider asks for it as the **SCIM connector base URL** (Okta) or the **Tenant URL** (Microsoft Entra ID):

```
https://api.argos-ci.com/scim/v2
```

{% endstep %}

{% step %}

### Generate a token

Click **Generate token**, then **Generate token** again to confirm. Copy the token right away: it starts with `argos_scim_`, and Argos only shows it once.

![The SCIM token, shown once after it is generated.](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-5c257072068b789abc392bba4287ddbe13c65510%2Fscim-token-generated.png?alt=media)

Treat it like a password: anyone holding it can add and remove members of your team.
{% endstep %}

{% step %}

### Hand them to your identity provider

Give your identity provider the base URL and the token, as described below. The token is sent as a bearer token in the `Authorization` header.
{% endstep %}
{% endstepper %}

Once the token exists, the card shows when it was generated and when your identity provider last used it.

![The SCIM Provisioning card once a token is generated.](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-646649234590e70b4b0a225773c287002e64bd09%2Fscim-provisioning-token.png?alt=media)

### Configure your identity provider

{% tabs %}
{% tab title="Okta" %}
Enable SCIM on the Argos app integration you use for SAML:

1. In the Okta Admin Console, open **Applications -> Applications** and select your Argos app.
2. On the **General** tab, in **App Settings**, click **Edit**, set **Provisioning** to **SCIM**, and save.
3. Open the new **Provisioning** tab, then **Integration**, and click **Edit**:
   * **SCIM connector base URL**: `https://api.argos-ci.com/scim/v2`
   * **Unique identifier field for users**: `userName`
   * **Supported provisioning actions**: **Import New Users and Profile Updates**, **Push New Users** and **Push Profile Updates**. Leave the group actions off: Argos does not support group push.
   * **Authentication Mode**: **HTTP Header**, and paste the SCIM token as the **Bearer** token.
4. Click **Test Connector Configuration**, then **Save**.
5. On **Provisioning -> To App**, click **Edit** and enable **Create Users**, **Update User Attributes** and **Deactivate Users**. Save.
6. On the **Assignments** tab, assign the people or groups who should be members of your Argos team.

Okta provisions assigned users right away, and pushes profile changes as they happen. Unassigning someone, or deactivating them in Okta, removes them from your Argos team.
{% endtab %}

{% tab title="Microsoft Entra ID" %}
Enable provisioning on the enterprise application you use for SAML:

1. In the Microsoft Entra admin center, open **Enterprise applications** and select your Argos application.
2. Open **Provisioning**, and start a new configuration (**Get started** or **New configuration**).
3. Set **Provisioning Mode** to **Automatic**, and fill in **Admin Credentials**:
   * **Tenant URL**: `https://api.argos-ci.com/scim/v2`
   * **Secret Token**: the SCIM token
4. Click **Test Connection**, then **Save**.
5. In **Mappings**, set **Provision Microsoft Entra ID Groups** to **No**: Argos does not support group provisioning. Keep **Provision Microsoft Entra ID Users** on. Its default mappings work as is:
   * `userPrincipalName` → `userName`
   * `mail` → `emails[type eq "work"].value`
   * `givenName`, `surname` and `displayName` → `name.givenName`, `name.familyName` and `displayName`
   * the `IsSoftDeleted` switch → `active`
6. In **Settings**, set **Scope** to **Sync only assigned users and groups**, and turn **Provisioning Status** **On**. Save.
7. In **Users and groups**, assign the people or groups who should be members of your Argos team.

Entra ID runs a provisioning cycle about every 40 minutes. To provision one person right away, for example to try your setup, use **Provision on demand**.

Unassigning someone, or disabling them in Entra ID, removes them from your Argos team. When a user is deleted in Entra ID, Argos also forgets them.
{% endtab %}

{% tab title="Other identity providers" %}
Argos works with any identity provider that speaks SCIM 2.0 (OneLogin, JumpCloud, Rippling, …). Configure a SCIM app with:

* **Base URL**: `https://api.argos-ci.com/scim/v2`
* **Authentication**: bearer token, with the SCIM token
* **Unique identifier**: `userName`
* **Operations**: create, update and deactivate users. Argos does not support groups.

The [reference](#reference) lists the endpoints and attributes Argos supports.
{% endtab %}
{% endtabs %}

## What happens when…

### Someone is assigned to Argos

Your identity provider creates the user, and Argos makes them a member of your team at the team's default access role — **member**, unless you changed it in **Team Settings -> Members** — or at the role your identity provider [sends](#map-roles-from-your-identity-provider).

Argos links the user to an Argos account by email. It reads the primary email, then the work email, then the first one, and falls back to `userName` when it is an email address:

* **Someone with an Argos account** that holds the email as a **verified** address gets linked to it. They keep their account, their other teams and their sign-in methods.
* **Someone new to Argos** gets an account, created with the email already verified and the name from your identity provider. They receive a welcome email, and can sign in with SAML or with the email.
* **An unverified email** never links: anyone can add an unverified address to their account, so Argos creates a new account for the email's real owner, and the unverified address is released.
* **Someone already in your team**, invited or added before SCIM, gets linked too and keeps their role, unless your identity provider sends one.

A user created as inactive is recorded, but not added to the team until your identity provider activates them.

### Someone's profile changes

Changes to the name, email, `userName` or `externalId` are synced and shown back to your identity provider. They don't change the person's Argos account, which belongs to them: their Argos name and sign-in emails stay theirs to manage.

### Someone is unassigned or deactivated

They are removed from your team at once: they lose access to its projects and builds, and personal access tokens scoped to the team stop working for it. Their Argos account is kept, with their other teams.

Argos still knows the user, so your identity provider can reactivate them later.

### Someone is reactivated

They are added back to the team, at the team's default access role or at the role your identity provider sends. The role they had before being deactivated is not restored on its own.

### Someone is deleted in your identity provider

They are removed from the team, and Argos forgets the SCIM user. Their Argos account is kept. If they are assigned again later, they are provisioned like a new user and linked to their account again.

### When the last owner leaves

Deprovisioning always goes through, even for the last owner of a team: blocking it would let someone who left your company keep owner access. If other members remain, the team is left without an owner, and nobody can manage its settings, billing or SCIM token.

To prevent it:

* Keep **at least two owners**. The SCIM card warns you when your team has a single owner.
* Or [map roles](#map-roles-from-your-identity-provider), so your identity provider can make someone else an owner, even once the team has none.

Argos is notified when SCIM leaves a team without an owner. [Contact us](https://argos-ci.com/contact) if you need help assigning a new one.

A team always keeps at least one member: deactivating its only member fails, and your identity provider reports the error.

### You change a member in Argos

SCIM-provisioned members are best managed from your identity provider:

* **Changing a role in Argos** works, and the role stays until your identity provider sends a different one.
* **Removing a member in Argos** works too, but your identity provider still sees them as active. It adds them back only after it deactivates and reactivates them.

Members who joined another way — invites, a verified email domain, SAML sign-in — are untouched by SCIM until your identity provider provisions them.

### Someone deletes their Argos account

Argos removes the link between their account and your identity provider. If your identity provider provisions them again, it gets a new account.

### Your team loses SAML SSO

If the SAML SSO add-on is disabled or your plan no longer includes it, every SCIM request is refused until it is back. The token is kept and starts working again then; members stay in the team.

## Map roles from your identity provider

Your identity provider can set each member's [team role](/docs/learn/account-and-access/team-members-and-roles#team-level-roles) through the standard SCIM `roles` attribute. Argos reads these values, in any case:

| Value         | Role                                                                                               |
| ------------- | -------------------------------------------------------------------------------------------------- |
| `owner`       | [Owner](/docs/learn/account-and-access/team-members-and-roles#owner-role)                          |
| `member`      | [Member](/docs/learn/account-and-access/team-members-and-roles#member-role)                        |
| `contributor` | [Contributor](/docs/learn/account-and-access/team-members-and-roles#contributor-role) (Enterprise) |

{% tabs %}
{% tab title="Microsoft Entra ID" %}

1. In **App registrations**, open your Argos application, then **App roles**, and create one app role per value: `owner`, `member` and `contributor`. Allow **Users/Groups** as member types.
2. In your enterprise application, under **Users and groups**, assign each user or group one of these roles.
3. In **Provisioning -> Mappings -> Provision Microsoft Entra ID Users**, click **Add New Mapping**:

   * **Mapping type**: **Expression**
   * **Expression**: `SingleAppRoleAssignment([appRoleAssignments])`
   * **Target attribute**: `roles[primary eq "True"].value`

   If the target attribute is missing from the list, add it under **Show advanced options -> Edit attribute list**.
   {% endtab %}

{% tab title="Okta" %}

1. In **Directory -> Profile Editor**, open the profile of your Argos app and click **Add Attribute**:
   * **Data type**: **string array**
   * **Variable name** and **External name**: `roles`
   * **External namespace**: `urn:ietf:params:scim:schemas:core:2.0:User`
2. Set the role when you assign a person or a group to the app, or map it from a user attribute in the profile mappings.
   {% endtab %}
   {% endtabs %}

How roles apply:

* **Roles apply when a user is provisioned, reactivated, or gets a different role.** Without a role, a new member gets the team's default access role, and an existing member keeps theirs.
* **When several roles are sent**, the primary one wins, then the first one.
* **An unknown value** is ignored, as if no role were sent. A mapping mistake never blocks provisioning or deprovisioning.
* **The last owner is never demoted.** When a role would demote the team's only owner, the rest of the update goes through and the owner stays an owner. Make someone else an owner first; the demotion applies at the next role change.

## SCIM and SAML together

SCIM decides who is in your team; [SAML SSO](/docs/learn/account-and-access/saml-single-sign-on) decides how they sign in. Used together:

* People get access before their first sign-in, instead of on it.
* SAML sign-in finds the account SCIM linked, by email. Map the same email in both, which the default Okta and Entra ID mappings do.
* Deactivating someone in your identity provider both stops their SAML sign-in and removes them from the team.
* [Enforcing SAML](/docs/learn/account-and-access/saml-single-sign-on#enforcing-saml) still requires members to sign in with SAML to reach the team.

## Rotate or revoke the token

* **Regenerate** replaces the token. The current one stops working at once, and provisioning fails until you paste the new one into your identity provider.
* **Revoke** deletes the token: your identity provider can no longer add or remove members. Members it already provisioned stay in the team.

![Regenerating the SCIM token.](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-3a9336bea83cb7525a0c883969305460aa4a9cd3%2Fscim-regenerate-token.png?alt=media)

![Revoking the SCIM token.](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-9b154fc26466141ab57a840a80002dc59600359f%2Fscim-revoke-token.png?alt=media)

## Reference

### Endpoints

All endpoints live under `https://api.argos-ci.com/scim/v2`, and answer `application/scim+json`. Requests accept `application/scim+json` and `application/json` bodies.

| Endpoint                 | Methods                         | Authentication |
| ------------------------ | ------------------------------- | -------------- |
| `/Users`                 | `GET`, `POST`                   | SCIM token     |
| `/Users/{id}`            | `GET`, `PUT`, `PATCH`, `DELETE` | SCIM token     |
| `/ServiceProviderConfig` | `GET`                           | None           |
| `/ResourceTypes`         | `GET`                           | None           |
| `/Schemas`               | `GET`                           | None           |

Groups, bulk operations, sorting, ETags and password changes are not supported.

### User attributes

| Attribute                           | Notes                                                                                                      |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `userName`                          | Required. Unique in your team, compared case-insensitively.                                                |
| `externalId`                        | Optional. Stored and returned as is.                                                                       |
| `name.givenName`, `name.familyName` | Optional.                                                                                                  |
| `displayName`                       | Optional. Names the Argos account when SCIM creates it.                                                    |
| `emails`                            | The primary email, else the `work` one, else the first, is used to link the Argos account.                 |
| `active`                            | Defaults to `true`. `false` removes the member from the team. `"True"` and `"False"` strings are accepted. |
| `roles`                             | Optional. See [Map roles](#map-roles-from-your-identity-provider).                                         |

Other attributes, such as phone numbers or the enterprise user extension, are accepted and ignored.

### Filtering and pagination

`GET /Users` supports a single `eq` filter on `userName`, `externalId`, `id` or `emails.value`:

```
GET /scim/v2/Users?filter=userName eq "jane@acme.com"
```

Results are paginated with `startIndex` (1-based) and `count` (100 at most).

### Updates

`PATCH` accepts `add`, `replace` and `remove` operations, with or without a `path`, including filtered paths such as `emails[type eq "work"].value` and `roles[primary eq "True"].value`. `PUT` replaces the user; when it omits `active`, the user keeps their current state.

### Limits

* **3,000 requests per team every 5 minutes.** Beyond that, Argos answers `429` and your identity provider retries later.
* Requests with an invalid token are limited per address, so a client guessing tokens is stopped quickly.

### Errors

Errors follow the SCIM error format, with a `detail` explaining the problem:

| Status | When                                                                                 |
| ------ | ------------------------------------------------------------------------------------ |
| `400`  | Invalid value, filter or patch — for example a user without a valid email.           |
| `401`  | Missing or invalid SCIM token.                                                       |
| `403`  | The team no longer has SAML SSO, or the request would remove the team's last member. |
| `404`  | The user does not exist in your team.                                                |
| `409`  | A user with the same `userName` or email already exists in your team.                |
| `429`  | Too many requests.                                                                   |

## Troubleshooting

<details>

<summary>Testing the connection fails with 401</summary>

The token is missing, mistyped or revoked, or it was regenerated since. Generate a new token in **Team Settings -> Authentication**, and paste it again in your identity provider.

</details>

<details>

<summary>Requests fail with 403 "SCIM provisioning is part of SAML SSO"</summary>

Your team no longer has access to SAML SSO. Enable the [SAML SSO add-on](/docs/learn/account-and-access/saml-single-sign-on#enabling-the-add-on) or contact us about the Enterprise plan: the existing token works again as soon as access is back.

</details>

<details>

<summary>A user fails with "The user needs a valid email"</summary>

Argos links people by email. Map a valid email to `emails`, or use email addresses as `userName`.

</details>

<details>

<summary>A user fails with 409</summary>

Another SCIM user of your team has the same `userName`, or is linked to the same person through the same email. Check for duplicate identities in your identity provider.

</details>

<details>

<summary>A role is not applied</summary>

* Check the value is exactly `owner`, `member` or `contributor`: other values are ignored.
* Roles apply when they change: re-sending the same role does not overwrite a change made in Argos.
* The team's last owner is never demoted: make someone else an owner first.

</details>

<details>

<summary>Someone deactivated in the identity provider can still sign in to Argos</summary>

That is expected: SCIM removes them from your team, not from Argos. They keep their own account and their other teams, but can no longer reach yours.

</details>


# Security and compliance

Where Argos hosts your data, its SOC 2 Type II and GDPR compliance, the DPA, and how long your data is kept, how to export it, and how to delete it.

This page answers the questions security and procurement teams ask most often. For the controls Argos applies, such as encrypted access tokens and least-privilege access, see the [Argos security page](https://argos-ci.com/security).

### Where your data is hosted

Argos runs on Amazon Web Services. Your data, screenshots included, is stored in the United States and replicated to the European Union. It is encrypted at rest and in transit.

There is no data residency option: you can't choose the region where your data is stored.

### SOC 2 Type II

Argos is SOC 2 Type II compliant, audited by Prescient Security and continuously monitored with Vanta. Request the report from the [Argos trust center](https://argos-ci.com/trust-center).

### GDPR and the DPA

Argos is GDPR compliant. It is operated by Smooth Code SAS, a company established in France.

* **Data Processing Agreement**: The [Argos DPA](https://argos-ci.com/dpa) applies automatically, without a signature, wherever Argos processes personal data on your behalf. If your procurement process needs a signed copy, request one at <contact@argos-ci.com>.
* **International transfers**: When personal data goes to a subprocessor outside the European Economic Area, in a country without an EU adequacy decision, the transfer relies on the European Commission's Standard Contractual Clauses, or on the EU-U.S. Data Privacy Framework for a certified US subprocessor.
* **Subprocessors**: The current list is published in the [trust center](https://app.eu.vanta.com/argos/trust/8z3w834xz9a4snga4obms/subprocessors) and summarized in the [privacy policy](https://argos-ci.com/privacy). Argos tells customers before a new subprocessor starts processing their data, as set out in the DPA.

### Data retention

Argos keeps builds, screenshots and diffs with no expiry, on every plan.

Media sharing files have their own retention, which depends on your plan: see [Share links, retention and limits](/docs/learn/media/share-links-retention-and-limits#retention). For deployments, see [Lifecycle and retention](/docs/learn/deployments/retention).

### Export your data

There is no one-click export, but the [Argos CLI](/docs/reference/argos-command-line-interface-cli#downloading-screenshots-and-baselines) downloads screenshots with a project token. `argos build list` lists a project's builds, `argos build download` saves the screenshots of one build, and `argos baseline download` saves the screenshots of the project's latest baseline:

```bash
export ARGOS_TOKEN=<project-token>
argos build download 72652   # into ./argos-build-72652
argos baseline download      # into ./argos-baseline
```

Files are saved under their snapshot names, so a directory uploaded with `argos upload` comes back with the same layout.

### Delete your data

You can delete a project, a team or your personal account, from the **General** section of its **Settings**. Deleting a team or your personal account deletes all of its projects. Deletion can't be undone.

You can't delete individual builds or screenshots.

### Self-hosting

Argos is open source. For what that means if you want to run it yourself, see [Open source and self-hosting](/docs#open-source-and-self-hosting).


# Billing & subscription

Understand Argos plans, payment, and usage so you can keep your team's billing under control.

Everything about plans, payment, and keeping your usage under control.

* [Pricing plans](/docs/learn/billing-and-subscription/pricing-plans) – Compare the available plans and what each one includes
* [How to subscribe](/docs/learn/billing-and-subscription/how-to-subscribe) – Start a paid subscription and manage it over time
* [Usage & spend management](/docs/learn/billing-and-subscription/spend-management) – Track screenshot usage and set spend limits
* [Open source](/docs/learn/billing-and-subscription/open-source) – Free Argos access for qualifying open-source projects


# Argos pricing plans

Compare the Hobby, Pro, and Enterprise plans to find the right Argos pricing for individuals, teams, or companies.

Argos offers plans for individuals, teams, and enterprises — from the free Hobby plan to fully custom Enterprise options. For current details, see the [pricing page](https://argos-ci.com/pricing).

### Hobby plan

The Hobby plan is free and belongs to your personal account. You can use it for commercial work, and connect repositories from any GitHub organization, private repositories included.

* **Price**: Free
* **Screenshots included**: 5,000 / month
* **Extra screenshots**: Not available. When you reach the limit, uploads pause until the next billing period.
* **Members**: Only you
* **Media sharing**: public share pages, 30-day retention, 50 MB per file

### Pro plan

Designed for teams, with usage-based pricing. Pro adds two things to Hobby:

* **No hard screenshot limit.** Screenshots beyond the included volume are billed at the per-screenshot rate, so uploads keep going instead of pausing. You can cap the extra cost with [spend management](/docs/learn/billing-and-subscription/spend-management#manage-your-spend).
* **Team collaboration.** Invite members to your team, so several people review and approve builds.

Plan details:

* **Starting price**: $100 / month
* **Screenshots included**: 35,000 / month
* **Extra screenshots**:
  * Standard: $0.004 per screenshot
  * Storybook: $0.0015 per screenshot (see [Storybook screenshots](#storybook-screenshots))
* **Optional add-ons** (Stripe subscriptions only):
  * [GitHub SSO](/docs/learn/account-and-access/github-single-sign-on): $50 / month
  * [SAML SSO](/docs/learn/account-and-access/saml-single-sign-on), with [SCIM provisioning](/docs/learn/account-and-access/scim-provisioning): $200 / month
* **Media sharing**: team-scoped or public share pages, 1-year retention, 500 MB per file

A Pro plan belongs to a team, not to a personal account. You can subscribe with a credit card through [Stripe](/docs/learn/billing-and-subscription/how-to-subscribe), or through GitHub Marketplace to pay via your GitHub invoice.

#### Pro plans on GitHub Marketplace

GitHub Marketplace plans come in fixed sizes, for GitHub organizations only. They have no extra screenshots: when you reach the plan's limit, uploads pause until the next billing period. The sizes, as listed on [Argos's GitHub Marketplace page](https://github.com/marketplace/argos-ci), which is the reference for these prices:

* **Pro S**: $100 / month, 35,000 screenshots / month
* **Pro M**: $300 / month, 100,000 screenshots / month
* **Pro L**: $500 / month, 150,000 screenshots / month
* **Pro XL**: $1,000 / month, 300,000 screenshots / month

See [Usage & spend management](/docs/learn/billing-and-subscription/spend-management) for how each billing model handles the limit.

### What counts as a screenshot

Every snapshot Argos stores for a build counts as one screenshot, whether it is an image or a non-image file such as Markdown, JSON or HTML. Each [ARIA snapshot](/docs/learn/how-to-guides/visual-coverage/adding-aria-snapshots-manually) (`.aria.yml` file) also counts as one screenshot.

[Media uploads](/docs/learn/media) draw on the same allowance rather than a separate quota: one uploaded image counts as 1 screenshot, and one uploaded video as 25 — a video costs more to store and to serve. Uploading the same file twice is free.

The monthly allowance belongs to the account, your personal account or your team, and covers all of its projects together. See [How usage is counted](/docs/learn/billing-and-subscription/spend-management#how-usage-is-counted).

#### Storybook screenshots

A screenshot is billed at the Storybook rate when it was captured by:

* `@argos-ci/storybook`, with the Storybook Test Runner or the Vitest plugin.
* `@argos-ci/vitest`, in a test that renders a Storybook story, for example through `@storybook/addon-vitest` or `composeStories()`.

Every other screenshot is billed at the standard rate. That includes screenshots taken with `@argos-ci/playwright`, even of a Storybook and even inside `@storybook/test-runner`, and images you upload with the CLI that no Argos SDK captured, such as Storycap screenshots of a [legacy Storybook](/docs/quickstart/storybook-quickstart/storybook-legacy-less-than-v8-quickstart).

Your plan's included screenshots go to standard screenshots first. Storybook screenshots use what is left, and each screenshot beyond the included volume is billed at its own rate.

### Data retention

Argos keeps your builds, screenshots and diffs with no expiry, on every plan.

Media sharing files have their own retention, which depends on your plan: see [Share links, retention and limits](/docs/learn/media/share-links-retention-and-limits#retention).

### Open-source plan

Argos sponsors selected [open-source projects](/docs/learn/billing-and-subscription/open-source) with free usage, evaluated case by case.

### Enterprise plan

For large organizations with custom requirements: custom screenshot volumes, [SAML single sign-on](/docs/learn/account-and-access/saml-single-sign-on) with [SCIM provisioning](/docs/learn/account-and-access/scim-provisioning), and [fine-grained access control](/docs/learn/account-and-access/team-members-and-roles).

[Contact us](https://argos-ci.com/contact) to design the right plan for your business.


# How to subscribe

Move from the free Hobby plan to a Pro team, with a 14-day trial, Stripe checkout, or GitHub Marketplace — and manage the subscription afterwards.

Paid plans belong to **teams**, not to personal accounts. A personal account stays on the free [Hobby plan](/docs/learn/billing-and-subscription/pricing-plans) forever, so upgrading means creating a team on the Pro plan and transferring your projects to it.

{% hint style="info" %}
Your first team starts on a **14-day Pro trial**, with no credit card required. If you have already used your trial, you go straight to Stripe Checkout instead.
{% endhint %}

### Upgrade a Hobby account to a Pro team

{% stepper %}
{% step %}

### Create a team

From your personal account, go to **Settings → Billing** and select **Create a Team**.

![The Plan card of a personal account on the Hobby plan](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-3b5281824d406e8aa07cf27bb452ece6421bd6a8%2Fhobby-plan-card.png?alt=media)
{% endstep %}

{% step %}

### Name the team and continue

Enter a team name, then select **Continue**. The line above the button tells you what happens next.

If you have never used the Pro trial, the team is created immediately on a 14-day trial — no payment details are asked for.

![The Create a Team form starting a 14-day Pro plan trial](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-65288fd6c9308974ff52f31a31222348f2f7a902%2Fcreate-team-trial.png?alt=media)

If you have already used your trial, you are redirected to Stripe Checkout to complete the subscription before the team becomes usable.

![The Create a Team form redirecting to Stripe](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-b9c49d7d6cf7284045474f05dbb416099c5c0b70%2Fcreate-team-checkout.png?alt=media)

{% hint style="info" %}
The optional **Let @your-domain emails join this team** checkbox only appears if you have a verified company email address. It can be changed later in the team settings.
{% endhint %}
{% endstep %}

{% step %}

### Transfer your projects

Your existing projects stay on your personal account until you move them. For each project, go to **Settings → Transfer Project**, select **Transfer**, then pick the new team.

![Selecting the destination team in the Transfer Project dialog](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-b70528ce5accee37b39be1c3b000e5019218c6a0%2Ftransfer-project-select-team.png?alt=media)

The next screen lists the builds and screenshots that will move, and confirms the plan change, before you select **Transfer**.
{% endstep %}

{% step %}

### Add a payment method

During the trial, the team's **Settings → Billing** page shows how many days are left. Select **Add a payment method** to open the Stripe portal and enter your card — without it, the team loses access to team features when the trial ends.

![The Plan card of a team on the Pro trial](https://2221810533-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0slCNmeVzY3qdh0I85HX%2Fuploads%2Fgit-blob-ab649ac2cd3201b58325f8b423b249bcf9a67670%2Fteam-pro-trial-plan-card.png?alt=media)
{% endstep %}
{% endstepper %}

{% hint style="info" %}
The Pro plan's base price is charged at the start of each billing period. Extra screenshots beyond the included volume are billed at the end of the period, based on actual usage. See [Usage & spend management](/docs/learn/billing-and-subscription/spend-management).
{% endhint %}

### Subscribe through GitHub Marketplace

To pay through your GitHub invoice instead of a card, start the subscription from the [Argos page on GitHub Marketplace](https://github.com/marketplace/argos-ci). Make sure you upgrade the correct organization.

{% hint style="warning" %}
GitHub does not allow **invoiced GitHub accounts** to purchase paid plans on the Marketplace. If you see the error "Unfortunately, invoiced customers cannot purchase paid plans on the GitHub Marketplace", [subscribe through Stripe](#subscribe-through-stripe) instead.
{% endhint %}

### Subscribe a team whose trial has expired

If a team's trial has expired or its subscription was canceled, a banner at the top of the team pages offers a **Subscribe** button, which opens Stripe Checkout. A team that has never extended its trial also gets an **Extend trial** button there.

### Manage your subscription

To view invoices, update your payment method, or cancel your plan, go to the team's **Settings → Billing** and select **Manage subscription**.

You are redirected to your payment provider — the Stripe customer portal, or GitHub Marketplace if you subscribed there.

### Next steps

* [Pricing plans](/docs/learn/billing-and-subscription/pricing-plans) – Compare what each plan includes
* [Usage & spend management](/docs/learn/billing-and-subscription/spend-management) – Track usage and set spend limits


# Usage & spend management

Track your team's screenshot usage, understand what happens at your plan limit, and set spend thresholds to get notified or pause builds.

Argos bills on screenshot usage, so keeping an eye on it is how you keep costs under control. This page covers where to find your usage, what happens when you reach your plan's limit, and how to cap spending with spend management.

### How usage is counted

Argos counts every screenshot registered in a build during the billing period. A few rules worth knowing:

* **Usage is counted per account.** The screenshots of every project on your personal account or team add up against one allowance, whether the projects are public or private.
* **Unchanged screenshots count.** Every screenshot in a build counts toward your quota, whether or not it differs from the baseline — comparing is the work Argos does on each one.
* **Failed builds count.** Screenshots count once their upload completes, including builds whose test suite failed or that errored during comparison. A build that never finishes uploading contributes nothing.
* **Omitted screenshots don't count.** In a [subset build](/docs/learn/how-to-guides/ci-pipelines/subset-builds), only the screenshots you upload are counted, not the baseline screenshots you leave out.

See [What counts as a screenshot](/docs/learn/billing-and-subscription/pricing-plans#what-counts-as-a-screenshot) for ARIA snapshots, media uploads and the Storybook rate.

### Monitor your usage

To see your usage details:

1. From the dashboard, select your team or personal account from the scope selector.
2. Select the **Settings** tab and go to the **Billing** section.

The plan card shows the screenshots counted in the current period against your plan's included screenshots, for all your projects together. When the total mixes several kinds of usage, it is broken down into Storybook screenshots, classic screenshots and media uploads. Select **Show usage detail** to see the usage of each project.

### What happens at your plan's limit

When you approach your plan's included screenshots, Argos alerts you. What happens when you exceed them depends on your plan:

* **Hobby plan**: Uploads are paused until the next billing period. To keep uploading, upgrade to the [Pro plan](/docs/learn/billing-and-subscription/pricing-plans) — you can transfer your project to a newly created team.
* **Pro plan (Stripe, usage-based)**: Extra screenshots are billed at the [per-screenshot rate](/docs/learn/billing-and-subscription/pricing-plans). Uploads continue uninterrupted — use [spend management](#manage-your-spend) to cap the extra cost.
* **Pro plan (GitHub Marketplace, fixed size)**: Uploads are paused until the next billing period. To keep uploading, [upgrade your plan](https://github.com/marketplace/argos-ci) to a larger size (S, M, L, XL).

### Manage your spend

{% hint style="info" %}
Spend management is available on Pro and Enterprise plans with usage-based pricing.
{% endhint %}

Spend management lets you act automatically when your team reaches a spend amount you set:

* **Get notified** when you reach thresholds of your spend amount.
* **Pause builds** on all your projects when the amount is reached.

The spend amount covers additional screenshots beyond your plan's included usage, across all projects on your team. It does not include separate **add-ons**, which Argos charges per billing period. The amount is set per billing cycle, and Argos checks your usage against it at every build.

{% hint style="warning" %}
Setting a spend amount does not stop usage by itself. To stop builds at the amount, keep the [Pause builds](#pause-builds-at-the-spend-amount) option enabled.
{% endhint %}

#### Set a spend amount

To configure spend management, you need the **Owner** role on your team.

{% stepper %}
{% step %}

### Open your team's billing settings

From the dashboard, select your team from the scope selector, then open the **Settings** tab and go to the **Billing** section.
{% endstep %}

{% step %}

### Enable Spend Management

Scroll to **Spend Management** and enable the switch.

![The Spend Management section enabled in Billing settings](https://content.gitbook.com/content/0slCNmeVzY3qdh0I85HX/blobs/L0mFHIbP5HqcVAtjI53v/spend%20management%20section%209076213ed403afcd2136d0f06cc85c8b.jpg)
{% endstep %}

{% step %}

### Set your spend amount

Enter the amount at which you want to be notified or take action. Setting the amount halfway through a billing cycle takes your current spend into account — if you set it below what you have already spent, the configured actions (including pausing projects) trigger immediately.
{% endstep %}
{% endstepper %}

#### Threshold notifications

Once a spend amount is set, Argos emails all team owners when spending reaches **50%**, **75%**, and **100%** of the amount. The thresholds are not customizable.

#### Pause builds at the spend amount

When spend management is enabled, the **Pause builds** option is on by default: once your team reaches the spend amount, Argos pauses builds for all projects on the team. To confirm the option, enter the team slug and select **Continue**.

When builds are paused, creating a new build fails with an error explaining that the spend amount was reached. **Your CI builds will fail until you increase the spend amount or disable the Pause builds option.**


# Open-source sponsorship

Find out how qualifying open-source projects can get sponsored, free access to Argos visual testing.

Argos sponsors selected open-source projects with free usage. Sponsorships are evaluated case by case.

### Qualification for sponsorship

To qualify for sponsorship, your project must meet all of the following conditions:

* The project is open source and not for commercial use.
* The project's usage of Argos falls within reasonable use limits.
* The repository's `README.md` includes the Argos banner below, pointing to `https://argos-ci.com`.
* The project includes a dofollow link to `https://argos-ci.com` on at least one page.
* Banner links use a UTM tag in the format `?utm_source=[team-name]&utm_campaign=oss`.

![Argos badge](https://argos-ci.com/badge-large.svg)

### How to apply

1. Make sure your project meets all the conditions above.
2. Check that you actually need sponsorship — the [Hobby plan](/docs/learn/billing-and-subscription/pricing-plans) is often sufficient for smaller projects.
3. Email <contact@argos-ci.com> confirming that your project meets the criteria and explaining why the Hobby plan doesn't cover your needs. Include your Argos team slug and a link to your open-source repository.


# Argos for AI agents

Everything Argos offers to AI agents: an MCP server, a CLI, installable agent skills, and AI-ready documentation.

Argos is built to work with AI agents. Whether your assistant reviews visual changes in a pull request, inspects builds from your terminal, or answers questions about Argos, these are the features it can use.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>MCP server</strong></td><td>Connect AI assistants like Claude, Cursor, or Copilot to Argos: list builds, inspect diffs, review changes, and manage projects through the Model Context Protocol.</td><td><a href="/docs/agents/mcp-server">MCP server</a></td></tr><tr><td><strong>CLI</strong></td><td>Upload screenshots, inspect builds, and submit reviews from scripts and local workflows — the tool agents use when they work in your terminal.</td><td><a href="/docs/reference/argos-command-line-interface-cli">CLI</a></td></tr><tr><td><strong>Agent skills</strong></td><td>Installable skills that teach your assistant the Argos CLI and a complete pull-request visual review workflow.</td><td><a href="/docs/agents/agent-skills">Agent skills</a></td></tr><tr><td><strong>AI-ready docs</strong></td><td>This documentation is optimized for LLMs: Markdown for every page, <code>llms.txt</code> indexes, and its own docs MCP server.</td><td><a href="/docs/agents/ai-ready-docs">AI-ready docs</a></td></tr></tbody></table>

### Where to start

* To review builds with an AI agent in a pull request, follow the [Review builds with AI agents](/docs/learn/review-workflow/review-builds-with-ai-agents) guide — it combines the CLI and agent skills.
* To let an agent track down what makes a screenshot unstable, follow [Fix flaky tests with AI agents](/docs/learn/reliability-and-flakiness/fix-flaky-tests-with-ai-agents).
* To give a general-purpose assistant access to your Argos account, connect it to the [MCP server](/docs/agents/mcp-server).
* To let an agent answer questions about Argos itself, point it at the [AI-ready docs](/docs/agents/ai-ready-docs).


# Argos MCP server

Connect AI assistants to Argos with the official MCP server: inspect builds, review visual changes, and manage projects straight from your agent.

Connect your AI tools to Argos using the [Model Context Protocol (MCP)](https://modelcontextprotocol.io), an open standard that lets AI assistants interact with your Argos projects.

MCP defines a common way for an AI assistant to discover the tools a service exposes and call them on your behalf. Instead of a bespoke plugin for each assistant, a service publishes one MCP server, and every MCP-compatible client — Claude, Cursor, VS Code, and others — can use it.

## What is the Argos MCP server?

The Argos MCP server is the official remote MCP server for Argos, available at:

```
https://mcp.argos-ci.com
```

It integrates with popular AI assistants like Claude, enabling them to:

* List builds and inspect their screenshot diffs
* Approve or reject builds, dismiss reviews, and request reviewers
* Read and post comments on builds and tests
* Investigate flaky tests and ignore the changes behind them
* Read and update project settings, contributors, deployments, and automation rules
* Manage a team's members, invites, and email domains
* Retrieve project metadata, account analytics, plan, and usage
* Upload standalone images and videos and get shareable links for them

The server is remote and uses the **streamable HTTP** transport — there is no package to install or process to run locally. Configure your client with the transport `http` (sometimes labeled `streamable-http`) and the URL above. It implements the latest [MCP Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) and [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http) specifications.

## Available tools

Tools are generated directly from the [Argos REST API](https://argos-ci.com/docs/api-reference): every API operation that a user can call is exposed as an MCP tool with the same name, parameters, and permissions. When the API gains an endpoint, the MCP server gains the matching tool — with no client update on your side.

Agents can list builds and inspect their screenshot diffs, approve or reject changes, request reviewers, read and post comments, diagnose flaky tests and ignore their recurring changes, configure projects and their contributors, manage automation rules, administer a team's members and invites, upload and share [standalone media](/docs/learn/media) and read the comments left on it, and retrieve account analytics and usage. Ask your client to list the server's tools, or see the [API reference](https://argos-ci.com/docs/api-reference) for the underlying operations.

What an agent may actually do is decided by the scopes you grant it, not by the tool list: `projects:read` for inspecting projects, builds and tests, `reviews:write` for reviews, review requests and ignoring changes, `comments:write` for comments and notification subscriptions, `projects:write` for project configuration and automation rules, `media:read` and `media:write` for uploading and managing shared media, and `account:admin` for team administration.

## Authentication

The MCP server supports two authentication methods:

### OAuth (recommended)

Most MCP clients handle OAuth automatically: add the server URL, and your client opens a browser window where you sign in to Argos, choose which organizations to share, and select the scopes to grant. Tokens are scoped to the organizations and permissions you approve, and you can revoke an authorization at any time from your Argos settings under **Authorized applications**.

### Personal access token

For clients or scripts where OAuth is impractical, authenticate with a personal access token sent as a bearer token. To create one, go to your personal account settings, open **Tokens**, then select **Generate new token**.

```
Authorization: Bearer <personal-access-token>
```

{% hint style="info" %}
Project tokens (the ones used by CI to upload screenshots) are not accepted by the MCP server — they identify a project, not a user.
{% endhint %}

### Cloud and headless agents

Agents that run in the cloud can use the Argos MCP server too. Hosted agents that support MCP OAuth, such as Cursor's cloud agents, go through the same sign-in: you authorize them once in your browser.

When an agent can't complete the browser sign-in, for example in CI or another headless environment, pass a personal access token in the `headers` of its MCP server configuration. In Cursor's `mcp.json`:

```json
{
  "mcpServers": {
    "argos": {
      "url": "https://mcp.argos-ci.com",
      "headers": {
        "Authorization": "Bearer ${env:ARGOS_PERSONAL_ACCESS_TOKEN}"
      }
    }
  }
}
```

Cursor replaces `${env:ARGOS_PERSONAL_ACCESS_TOKEN}` with that environment variable, so the token stays out of the file. For Cursor's cloud agents, set the same header when you add the server from [cursor.com/agents](https://cursor.com/agents). See the [Cursor MCP documentation](https://cursor.com/docs/mcp) for details.

## Setup

### Claude Code

```bash
# Add the Argos MCP server
claude mcp add --transport http argos https://mcp.argos-ci.com

# Start Claude Code
claude

# Authenticate by typing /mcp
/mcp
```

### Claude.ai and Claude for desktop

1. Open **Settings** in the sidebar
2. Navigate to **Connectors** and select **Add custom connector**
3. Configure the connector:
   * Name: `Argos`
   * URL: `https://mcp.argos-ci.com`

### Cursor

Add the snippet below to your project-specific or global `.cursor/mcp.json` file. See the [Cursor documentation](https://cursor.com/docs/mcp) for details.

```json
{
  "mcpServers": {
    "argos": {
      "url": "https://mcp.argos-ci.com"
    }
  }
}
```

Once the server is added, Cursor displays a `Needs login` prompt. Click it to authorize Cursor to access your Argos account.

### VS Code with Copilot

1. Open the Command Palette
2. Run **MCP: Add Server**
3. Select **HTTP**
4. Enter the following details:
   * **URL:** `https://mcp.argos-ci.com`
   * **Name:** `Argos`
5. Select **Global** or **Workspace** depending on your needs
6. Click **Add**, then start the server and complete the sign-in flow when prompted

### Codex CLI

```bash
codex mcp add argos --url https://mcp.argos-ci.com
```

When adding the server, Codex detects OAuth support and opens your browser to authorize the connection.

### Windsurf

Add the snippet below to your `mcp_config.json` file. See the [Windsurf documentation](https://docs.windsurf.com/windsurf/cascade/mcp#adding-a-new-mcp-plugin) for details.

```json
{
  "mcpServers": {
    "argos": {
      "serverUrl": "https://mcp.argos-ci.com"
    }
  }
}
```

{% hint style="info" %}
Setup steps may vary based on your MCP client version. Always check your client's documentation for the latest instructions.
{% endhint %}

## Security best practices

* **Verify the official endpoint.** Always confirm you're connecting to Argos's official MCP endpoint: `https://mcp.argos-ci.com`.
* **Grant only what you need.** During the OAuth consent, share only the organizations the agent needs, and prefer read-only scopes when the agent only inspects builds.
* **Review authorized applications.** Audit and revoke authorizations from your Argos settings under **Authorized applications**.
* **Enable human confirmation.** Keep your agent's approval prompts on for write actions like approving builds or posting comments, so you review each change before it happens.


# Agent skills

Installable agent skills that teach your AI assistant the Argos CLI, a complete pull-request visual review workflow, and how to share screenshots and recordings by link.

Argos publishes [agent skills](https://skills.sh) — reusable instruction packs your AI assistant loads to perform Argos tasks reliably, without you writing long prompts. They work with assistants that support skills, such as Claude Code, Codex, and Cursor.

## Available skills

| Skill             | What it teaches the agent                                                                                                                                                        |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `argos-cli`       | The [Argos CLI](/docs/reference/argos-command-line-interface-cli): commands, flags, authentication rules, and output formats.                                                    |
| `argos-pr-review` | A complete pull-request visual review workflow: find the Argos build, inspect snapshots, summarize changes, and approve or request changes.                                      |
| `argos-upload`    | When and how to share a screenshot or a screen recording by link, how to embed it so a pull request actually renders it, and how to read back the comments a human pinned on it. |

## Installation

```bash
npx skills add https://argos-ci.com
```

This installs every skill Argos publishes. The skill content lives in the [argos-javascript](https://github.com/argos-ci/argos-javascript) repository.

## Usage

Reference a skill in your prompt to activate it:

```
Use $argos-pr-review to review this pull request with its Argos build.
https://github.com/acme/app/pull/123
```

For the full workflow — tokens to set, what the agent does, and how to phrase prompts when your assistant does not support skills — see [Review builds with AI agents](/docs/learn/review-workflow/review-builds-with-ai-agents).

The `argos-cli` skill also covers the flakiness commands, so an agent can investigate an unstable test and silence a change it can't fix — see [Fix flaky tests with AI agents](/docs/learn/reliability-and-flakiness/fix-flaky-tests-with-ai-agents).

{% hint style="info" %}
Skills drive the CLI, so they run wherever the agent has a terminal. If your assistant connects to remote services instead, use the [MCP server](/docs/agents/mcp-server).
{% endhint %}


# AI-ready docs

The Argos documentation is optimized for LLMs: Markdown for every page, llms.txt indexes, and a docs MCP server.

This documentation is published with [GitBook](https://gitbook.com/docs/ai-and-search/llm-ready-docs), which exposes AI-friendly outputs for every page. Point your agent at these endpoints instead of letting it scrape HTML.

## Markdown for every page

Append `.md` to any page URL to get its raw Markdown:

```
https://argos-ci.com/docs/agents/mcp-server.md
```

## Site indexes: llms.txt and llms-full.txt

* [`https://argos-ci.com/docs/llms.txt`](https://argos-ci.com/docs/llms.txt) — an AI-friendly index of the whole documentation, listing every page with its Markdown URL. Give this to an agent so it can discover and fetch only the pages it needs.
* [`https://argos-ci.com/docs/llms-full.txt`](https://argos-ci.com/docs/llms-full.txt) — a full snapshot of the published documentation in one file, for tools that ingest everything at once.

## Search from the Argos CLI

An agent that already runs shell commands can search these docs with the [Argos CLI](/docs/reference/argos-command-line-interface-cli#searching-the-documentation), with no MCP setup and no token:

```bash
argos docs search "parallel builds" --json
```

Each result carries its page URL. Append `.md` to it to read the page.

## Docs MCP server

The documentation also exposes its own read-only MCP server, so MCP-compatible tools can search and read these docs directly:

```
https://argos-ci.com/docs/~gitbook/mcp
```

For example, with Claude Code:

```bash
claude mcp add --transport http argos-docs https://argos-ci.com/docs/~gitbook/mcp
```

{% hint style="info" %}
This docs MCP server answers questions about Argos from the documentation. To let an agent act on your Argos account — list builds, review changes, post comments — connect it to the [Argos MCP server](/docs/agents/mcp-server) instead.
{% endhint %}


# Playwright SDK reference (@argos-ci/playwright)

Reference for @argos-ci/playwright: the Argos reporter, argosScreenshot options and stabilization, ARIA snapshots, sharding, and trace uploads for Playwright visual tests.

### Get started

Please refer to our [Quickstart guide](/docs/quickstart/playwright-quickstart) to get started with Argos and Playwright.

### Compatibility

* **Playwright**: `@argos-ci/playwright` declares no peer dependency on Playwright; it runs with the `@playwright/test` installed in your project.
* **Node.js** 22 or later.

### Setup Visual Testing

Argos presents a significant advantage over traditional Playwright visual tests with its streamlined approach to managing and reviewing test results:

* **Visual Testing on CI**: With Argos, there's no need to run tests locally or commit screenshots to your repository. This not only saves time but also keeps your repository clean and focused on code rather than binary assets.
* **Fluid UI for Comparison**: Argos intuitive interface makes it easy to spot discrepancies and understand visual changes without the need for cumbersome manual checks.
* **Enhanced Stabilization**: The Argos integration with Playwright takes visual testing to the next level by ensuring stability and consistency in the screenshots captured. Fonts, images, animations, loaders, everything is just stable.

To enable Visual Testing with Playwright, first you have to setup the Argos reporter in your Playwright config

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  // ... other configurations

  use: {
    // Reduce text rendering differences between machines.
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },

  // Setup Argos reporter to send screenshots and traces to Argos.
  reporter: [
    // Use "dot" reporter on CI, "list" otherwise (Playwright default).
    process.env.CI ? ["dot"] : ["list"],

    // Add Argos reporter.
    [
      "@argos-ci/playwright/reporter",
      // Upload only on CI.
      createArgosReporterOptions({ uploadToArgos: !!process.env.CI }),
    ],
  ],
});
```

{% endcode %}

{% hint style="success" %}
The `launchOptions` above disable subpixel text and font hinting, which reduces text rendering differences between your machine and CI. This prevents one of the most common causes of flaky screenshots. The Argos reporter warns you at startup if these flags are missing from a Chromium project — learn why in [Stabilize text rendering](/docs/learn/reliability-and-flakiness/flaky-tests/stabilize-text-rendering).
{% endhint %}

And use `argosScreenshot` helper to capture stable screenshots in your E2E tests:

{% code title="tests/example.spec.ts" %}

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("screenshot homepage", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await argosScreenshot(page, "homepage");
});
```

{% endcode %}

### Setup Tests Debugging

The Argos Playwright reporter automatically reports failure screenshots and playwright traces. You can access them directly in Argos UI. If you are tired to download traces from artifact and run a command to see them, it is the solution for you.

* **Failure Screenshots**: View failure screenshots from your CI tests directly in Argos. No extra steps, just immediate clarity where you need it most.
* **Playwright Traces**: “Time travel” through your failing tests with remote Playwright traces. Gain a complete, step-by-step visual journey to the heart of any test issue.

{% hint style="info" %}
Each uploaded snapshot — including screenshots and Playwright traces — is limited to 50 MB. See [Snapshot size limit](/docs/reference/argos-command-line-interface-cli#snapshot-size-limit).
{% endhint %}

To enable Test Debugging, you have to add Argos reporter and to turn on [Playwright test use options](https://playwright.dev/docs/test-use-options) in your Playwright config:

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  // ... other configurations

  // Setup Argos reporter to send screenshots and traces to Argos.
  reporter: [
    // Use "dot" reporter on CI, "list" otherwise (Playwright default).
    process.env.CI ? ["dot"] : ["list"],

    // Add Argos reporter.
    [
      "@argos-ci/playwright/reporter",
      // Upload only on CI.
      createArgosReporterOptions({ uploadToArgos: !!process.env.CI }),
    ],
  ],

  // Setup recording option to enable test debugging features.
  use: {
    // Collect trace when retrying the failed test.
    trace: "on-first-retry",

    // Capture screenshot after each test failure.
    screenshot: "only-on-failure",
  },
});
```

{% endcode %}

### Tests Sharding

Argos integrates with [Playwright test sharding](https://playwright.dev/docs/test-sharding), enabling efficient test distribution without the need for manual configuration. [Argos Sharding/Parallel mode](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) is automatically configured for you.

Argos also supports [Currents orchestration](https://currents.dev/) using [Argos parallel + finalize](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding).

### Where screenshots are saved

* **With the Argos reporter**, `argosScreenshot` writes each screenshot to an `argos` folder inside the test's Playwright output directory (`test-results/` by default). At the end of the run, the reporter uploads them to Argos, unless `uploadToArgos` is `false`.
* **Without the reporter**, screenshots are written to `./screenshots`, or to the folder set with the `root` option. Upload that folder with the [Argos CLI](/docs/reference/argos-command-line-interface-cli): `argos upload ./screenshots`.

Taking screenshots never needs an Argos token. Only the upload authenticates, with `ARGOS_TOKEN` or, on GitHub Actions, with [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication).

### Helper Attributes for Visual Testing

For tailored visual testing, the `data-visual-test` attributes provide control over how elements appear in Argos screenshots. This can be especially useful for obscuring or modifying elements with dynamic content, like dates.

* `[data-visual-test="transparent"]`: Renders the element transparent (`opacity: 0`), keeping its layout space.
* `[data-visual-test="removed"]`: Removes the element from view (`display: none`).
* `[data-visual-test="blackout"]`: Covers the element with Playwright's screenshot mask. See [Mask elements](#mask-elements).
* `[data-visual-test-no-radius]`: Strips the border radius from the element.

**Example: Using a helper attribute to hide a div from the captured screenshot:**

```html
<div id="clock" data-visual-test="transparent">...</div>
```

**Example: Using the `hide` and `remove` options, without changing the markup:**

```ts
await argosScreenshot(page, "dashboard", {
  hide: ["#clock", page.getByTestId("avatar")],
  remove: [".cookie-banner"],
});
```

`hide` works like `transparent` and `remove` like `removed`. Selectors that match nothing are ignored, and an element in both lists is removed. Argos restores the elements after the screenshot and applies the options again for each viewport. To apply `blackout` from the test, use [`mask`](#mask-elements).

### Mask elements

`argosScreenshot` supports Playwright's [`mask` and `maskColor`](https://playwright.dev/docs/api/class-page#page-screenshot-option-mask) screenshot options, and `mask` also accepts string selectors. Each masked element is covered with a solid box:

```ts
await argosScreenshot(page, "dashboard", {
  mask: ["#map", page.getByTestId("live-chart")],
  maskColor: "#000000",
});
```

The `data-visual-test="blackout"` helper uses the same `mask` option:

* Blacked-out elements take the `maskColor`, which is Playwright's pink (`#FF00FF`) unless you set it.
* The elements you pass in `mask` are masked in addition to the ones with the `blackout` helper.
* Selectors that match nothing are ignored.

### Animations

`argosScreenshot` takes the screenshot with Playwright's [`animations: "disabled"`](https://playwright.dev/docs/api/class-page#page-screenshot-option-animations) option. Playwright stops CSS animations, CSS transitions and Web Animations while the screenshot is taken: finite ones are fast-forwarded to their end, infinite ones are reset to their initial state. Pass `animations: "allow"` to turn this off.

Argos stabilization covers two kinds of motion that this option doesn't reach:

* **SVG animations that repeat forever**, such as a spinning loader (`repeatCount="indefinite"` or `repeatDur="indefinite"`), are paused on their first frame. SVG animations that end are left alone. Disable this with `stabilize: { pauseSvgAnimations: false }`.
* **Animated GIFs** are paused on their first frame. See [Pause animated GIFs](/docs/learn/reliability-and-flakiness/flaky-tests/pause-gifs).

Argos has no specific handling for scroll-driven animations and view transitions. Neutralize them with [`argosCSS`](/docs/learn/how-to-guides/visual-coverage/injecting-css):

```ts
await argosScreenshot(page, "homepage", {
  argosCSS: `
    /* Your elements animated with animation-timeline: scroll() or view() */
    .reveal-on-scroll { animation: none !important; }
    /* View transition pseudo-elements */
    ::view-transition-group(*),
    ::view-transition-old(*),
    ::view-transition-new(*) { animation: none !important; }
  `,
});
```

### Screenshots inside iframes

Pass a Playwright [`Frame`](https://playwright.dev/docs/api/class-frame) as the handler to work inside an iframe's document:

```ts
const frame = page.frame({ url: /\/embed\// });
if (!frame) throw new Error("iframe not found");
await argosScreenshot(frame, "embedded-widget");
```

With a `Frame`:

* Stabilization, `argosCSS` and the `data-visual-test` helpers run inside the frame's document.
* The screenshot captures the frame's `<body>`, or the `element` you pass.
* The `viewports` option isn't supported and throws an error. Use one [Playwright project per viewport](/docs/learn/how-to-guides/visual-coverage/responsive-viewports#troubleshooting-and-best-practices) instead.

If you pass the `page` with an `element` that lives inside an iframe, such as `page.frameLocator("iframe").locator(".widget")`, Argos captures that element, but stabilization, `argosCSS` and the helpers only apply to the top-level document.

### Create multiple Argos builds from a single suite of tests

To generate multiple Argos builds from a single suite of Playwright tests, use a dynamic `buildName`. This object contains two properties:

* `values`: An array of possible values returned by the `get` function.
* `get`: A function that takes a [test case](https://playwright.dev/docs/api/class-testcase) as an argument and returns the build name.

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  // ... other configurations

  reporter: [
    // Use "dot" reporter on CI, "list" otherwise (Playwright default).
    process.env.CI ? ["dot"] : ["list"],

    // Argos reporter
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
        buildName: {
          values: ["app", "website"],
          // Split Argos build based on Playwright tags
          // Learn more about Playwright tags: https://playwright.dev/docs/test-annotations#tag-tests
          get: (test) => (test.tags.includes("@website") ? "website" : "app"),
        },
      }),
    ],
  ],
});
```

{% endcode %}

### Capture light and dark mode

To capture every page in both color schemes, run your tests in two [Playwright projects](https://playwright.dev/docs/test-projects) that set [`colorScheme`](https://playwright.dev/docs/api/class-testoptions#test-options-color-scheme):

{% code title="playwright.config.ts" %}

```ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
  // ... other configurations

  projects: [
    { name: "light", use: { colorScheme: "light" } },
    { name: "dark", use: { colorScheme: "dark" } },
  ],
});
```

{% endcode %}

`argosScreenshot` prefixes each screenshot name with the project name, so `argosScreenshot(page, "homepage")` produces `light/homepage` and `dark/homepage`, each with its own baseline. Without projects, names get no prefix.

To switch schemes inside a single test instead, call `page.emulateMedia({ colorScheme: "dark" })` and give the second screenshot a different name.

### Debug flaky tests

To debug flaky tests, Argos supports using the [Playwright --repeat-each option](https://playwright.dev/docs/test-cli#reference). This runs each test multiple times to detect discrepancies.

```bash
npm exec -- playwright test --repeat-each 5
```

### Configure Content-Security-Policy (CSP)

To stabilize tests, Argos injects a script before taking screenshots. This script may conflict with your CSP settings. To resolve this issue, you have two options:

#### 1. Allow the Argos script in your CSP

The `@argos-ci/playwright` package provides a `getCSPScriptHash` method to retrieve the hash of the script injected by Argos. Additionally, Argos requires the `'unsafe-eval'` directive to function properly.

To use this method, you need to be able to inject CSP headers into your server. Here's an example in a Playwright configuration:

```ts
import { defineConfig } from "@playwright/test";
import { getCSPScriptHash } from "@argos-ci/playwright";

export default defineConfig({
  webServer: {
    command: "node my-app.js",
    port: 3000,
    env: {
      CSP_SCRIPT_SRC: `${getCSPScriptHash()},'unsafe-eval'`,
    },
  },
});
```

#### 2. Configure Playwright to bypass CSP

If you do not have the flexibility to add custom CSP headers for Playwright test execution, you can [configure Playwright to bypass CSP](https://playwright.dev/docs/api/class-testoptions#test-options-bypass-csp) in your Playwright configuration:

```ts
import { defineConfig } from "@playwright/test";

export default defineConfig({
  use: {
    bypassCSP: true,
  },
});
```

### Set a Preview URL

Argos displays the URL of the page when a screenshot is taken, helping you understand the screenshot’s context in the Argos UI. If you run tests locally and deploy your pull requests (PRs) to a preview URL, you can link the two by setting the `ARGOS_PREVIEW_BASE_URL` environment variable or configuring the `previewUrl` option in the Argos reporter.

#### Example Configuration

```ts
import { defineConfig } from "@playwright/test";
import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  reporter: [
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        previewUrl: {
          baseUrl: "https://my-site.com", // Use a dynamic value here for different environments if needed.
        },
      }),
    ],
  ],
});
```

### Test annotations

Argos supports [annotations](https://playwright.dev/docs/test-annotations) added to your tests, providing additional context and information. All annotations are displayed in Argos Build UI, except those where type starts with \_ symbol.

#### Example Usage

```ts
import { test, expect } from "@playwright/test";

test(
  "test login page",
  {
    annotation: {
      type: "visual-test",
      description:
        "Critical UI path – ensures login form fields and CTA button align correctly on desktop viewport. Related to regression #1423.",
    },
  },
  async ({ page }) => {
    // ...
  },
);
```

### ARIA Snapshots

Argos allows you to capture ARIA snapshots alongside your screenshots, enhancing accessibility testing. ARIA snapshots provide a structured representation of the page's accessible elements, which can be invaluable for identifying accessibility issues.

#### Capture alongside screenshots

You can set `ariaSnapshot: true` in the `argosScreenshot` options to capture an ARIA snapshot along with the screenshot.

{% code title="tests/example.spec.ts" %}

```ts
import { test } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("screenshot homepage with ARIA snapshot", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await argosScreenshot(page, "homepage", { ariaSnapshot: true });
});
```

{% endcode %}

{% hint style="info" %}
When using the `viewports` option, an ARIA snapshot is captured for each viewport.
{% endhint %}

#### Capture only ARIA snapshot

It is also possible to capture only an ARIA snapshot without a screenshot using the `argosAriaSnapshot` function:

<pre class="language-ts" data-title="tests/example.spec.ts"><code class="lang-ts"><strong>import { test } from "@playwright/test";
</strong>import { argosAriaSnapshot } from "@argos-ci/playwright";

test("capture ARIA snapshot of homepage", async ({ page }) => {
  await page.goto("http://localhost:3000");
  await argosAriaSnapshot(page, "homepage-aria-snapshot");
});
</code></pre>

#### Billing

Each ARIA snapshot counts as an additional screenshot for billing.

### API Overview

#### argosScreenshot(handler, name\[, options])

* `handler`: Instance of the [Playwright Page](https://playwright.dev/docs/api/class-page) or [Playwright Frame](https://playwright.dev/docs/api/class-frame). More on [screenshots inside iframes](#screenshots-inside-iframes).
* `name`: Unique name for the screenshot.
* `options`: Explore [`Page.screenshot` command options](https://playwright.dev/docs/api/class-page#page-screenshot) for details.
* `options.element`: Use a [`Locator`](https://playwright.dev/docs/api/class-locator) or a string selector to capture a specific element's screenshot.
* `options.viewports`: Define specific viewports for capturing screenshots. More on [viewports configuration](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).
* `options.ariaSnapshot`: Capture an ARIA snapshot along with the screenshot. More on [ARIA snapshots](#aria-snapshots).
* `options.argosCSS`: Specific CSS applied during the screenshot process. More on [injecting CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css)
* `options.hide`: Array of [`Locator`](https://playwright.dev/docs/api/class-locator)s or string selectors of elements to hide. Hidden elements keep their space in the layout, like with `data-visual-test="transparent"`. Selectors that match nothing are ignored. More on [helpers](#helper-attributes-for-visual-testing).
* `options.remove`: Array of `Locator`s or string selectors of elements to remove. Removed elements no longer take space in the layout, like with `data-visual-test="removed"`. Selectors that match nothing are ignored. An element in both `hide` and `remove` is removed.
* `options.mask`: Array of `Locator`s or string selectors of elements to cover with a box, in addition to the elements with `data-visual-test="blackout"`. Selectors that match nothing are ignored. More on [masking elements](#mask-elements).
* `options.maskColor`: Color of the box covering masked elements, in CSS color format. Applies to `mask` and to `data-visual-test="blackout"`. Defaults to `#FF00FF`.
* `options.disableHover`: Disable hover effects by moving the mouse to the top-left corner of the page. Defaults to `true`.
* `options.threshold`: Sensitivity threshold between 0 and 1. The higher the threshold, the less sensitive the diff will be. Defaults to `0.5`. More on [how the threshold works](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences#sensitivity-threshold).
* `options.baseName`: Name, or list of names, to compare this screenshot against instead of its own name. Useful to compare a variant against an existing screenshot. More on [fallback baselines](/docs/learn/how-to-guides/visual-coverage/fallback-baselines).
* `options.root`: Folder where the screenshots will be saved if not using the Argos reporter. Defaults to `./screenshots`.
* `options.stabilize`: Wait for the UI to stabilize before taking the screenshot. Set to `false` to disable stabilization. Pass an object to customize the stabilization. Defaults to `true`.
* `options.stabilize.disableSpellCheck`: Disable spell check before taking the screenshot. Defaults to `true`.
* `options.stabilize.fontAntialiasing`: Force font antialiasing. Defaults to `true`.
* `options.stabilize.hideCarets`: Hide text carets before taking the screenshot. Defaults to `true`.
* `options.stabilize.hideScrollbars`: Hide scrollbars before taking the screenshot. Defaults to `true`.
* `options.stabilize.loadImageSrcset`: Force the loading of images with `srcset` attributes when the viewport changes. Defaults to `true`.
* `options.stabilize.pauseGifs`: Pause animated GIFs on their first frame so they don't capture a random frame on each run. Defaults to `true`. Flag GIFs served from extension-less URLs with `data-image-type="gif"` so they're detected too.
* `options.stabilize.pauseSvgAnimations`: Pause SVG animations that repeat forever (`repeatCount="indefinite"` or `repeatDur="indefinite"`), such as spinners, on their first frame. Defaults to `true`.
* `options.stabilize.roundImageSize`: Round image sizes to the nearest integer. Defaults to `true`.
* `options.stabilize.stabilizeSticky`: Stabilize sticky and fixed elements by switching to `position: absolute`. Defaults to `true`.
* `options.stabilize.waitForAriaBusy`: Wait for the `aria-busy` attribute to be removed from the document. Defaults to `true`.
* `options.stabilize.waitForFonts`: Wait for fonts to be loaded. Defaults to `true`.
* `options.stabilize.waitForImages`: Wait for images to be loaded. Defaults to `true`.
* `options.stabilize.waitForBackgroundImages`: Wait for CSS background images (including `::before`/`::after`) to load before taking the screenshot. Enabled by default, scoped to elements flagged with the `data-visual-test-wait-bg-img` attribute (and their descendants). Pass `true` to scan the whole document, `{ selector: string }` to target a custom selector, or `false` to disable it. A failed image (e.g. a 404) is treated as loaded so it never blocks stabilization.
* `options.beforeScreenshot`: Run a function before taking the screenshot. When using viewports, this function will run before taking screenshots on each viewport.
* `options.afterScreenshot`: Run a function after taking the screenshot. When using viewports, this function will run after taking screenshots on each viewport.
* `options.tag`: Tag or array of tags to attach to the screenshot for filtering in Argos.

Playwright test tags (from `test.describe` or `test` annotations) are automatically captured in the metadata.

Unlike [Playwright's `screenshot` method](https://playwright.dev/docs/api/class-page#page-screenshot), set `fullPage` option to `true` by default. Feel free to override this option if you prefer partial screenshots of your pages.

#### argosAriaSnapshot(handler, name\[, options])

* `handler`: Instance of the [Playwright Page](https://playwright.dev/docs/api/class-page) or [Playwright Frame](https://playwright.dev/docs/api/class-frame).
* `name`: Unique name for the screenshot.
* `options.element`: Use a [`Locator`](https://playwright.dev/docs/api/class-locator) or a string selector to capture a specific element's screenshot.
* `options.root`: Folder where the screenshots will be saved if not using the Argos reporter. Defaults to `./screenshots`.
* `options.timeout`: Maximum time in milliseconds. Defaults to `0` - no timeout.
* `options.stabilize`: Wait for the UI to stabilize before taking the screenshot. Set to `false` to disable stabilization. Pass an object to customize the stabilization. Defaults to `true`.
* `options.stabilize.disableSpellCheck`: Disable spell check before taking the screenshot. Defaults to `true`.
* `options.stabilize.fontAntialiasing`: Force font antialiasing. Defaults to `true`.
* `options.stabilize.hideCarets`: Hide text carets before taking the screenshot. Defaults to `true`.
* `options.stabilize.hideScrollbars`: Hide scrollbars before taking the screenshot. Defaults to `true`.
* `options.stabilize.loadImageSrcset`: Force the loading of images with `srcset` attributes when the viewport changes. Defaults to `true`.
* `options.stabilize.pauseGifs`: Pause animated GIFs on their first frame so they don't capture a random frame on each run. Defaults to `true`. Flag GIFs served from extension-less URLs with `data-image-type="gif"` so they're detected too.
* `options.stabilize.pauseSvgAnimations`: Pause SVG animations that repeat forever (`repeatCount="indefinite"` or `repeatDur="indefinite"`), such as spinners, on their first frame. Defaults to `true`.
* `options.stabilize.roundImageSize`: Round image sizes to the nearest integer. Defaults to `true`.
* `options.stabilize.stabilizeSticky`: Stabilize sticky and fixed elements by switching to `position: absolute`. Defaults to `true`.
* `options.stabilize.waitForAriaBusy`: Wait for the `aria-busy` attribute to be removed from the document. Defaults to `true`.
* `options.stabilize.waitForFonts`: Wait for fonts to be loaded. Defaults to `true`.
* `options.stabilize.waitForImages`: Wait for images to be loaded. Defaults to `true`.
* `options.stabilize.waitForBackgroundImages`: Wait for CSS background images (including `::before`/`::after`) to load before taking the screenshot. Enabled by default, scoped to elements flagged with the `data-visual-test-wait-bg-img` attribute (and their descendants). Pass `true` to scan the whole document, `{ selector: string }` to target a custom selector, or `false` to disable it. A failed image (e.g. a 404) is treated as loaded so it never blocks stabilization.

#### getCSPScriptHash()

Returns the Content-Security-Policy script hash used by Argos (ex: `'sha256-xaC9wWpMVRiAXSfhxhP+Wyqkw0mgO+MIrHuzmMPIxEI='`).

#### Playwright reporter

The Argos reporter offers extensive configuration options. Specifically, all [upload parameters](https://js-sdk-reference.argos-ci.com/interfaces/UploadParameters.html) are available for customizing the reporter.

The reporter also has its own options:

* `uploadToArgos`: Upload the screenshots to Argos at the end of the run. Defaults to `true`.
* `ignoreUploadFailures`: When `true`, an upload error is logged and doesn't fail the test run. Defaults to `false`: a failed upload marks the run as failed.
* `buildName`: Name of the Argos build, or a dynamic name to [create multiple builds](#create-multiple-argos-builds-from-a-single-suite-of-tests).

The `createArgosReporterOptions` ensures that your options are correctly typed.

<pre class="language-ts" data-title="playwright.config.ts"><code class="lang-ts"><strong>import { defineConfig } from "@playwright/test";
</strong>import { createArgosReporterOptions } from "@argos-ci/playwright/reporter";

export default defineConfig({
  // ... other configurations

  reporter: [
    // Use "dot" reporter on CI, "list" otherwise (Playwright default).
    process.env.CI ? ["dot"] : ["list"],

    // Argos reporter
    [
      "@argos-ci/playwright/reporter",
      createArgosReporterOptions({
        uploadToArgos: !!process.env.CI,
        buildName: "custom-build-name",
      }),
    ],
  ],
});
</code></pre>

### Frequently asked questions

<details>

<summary>Can I add padding around an element screenshot?</summary>

No, there is no padding option. Capture a wrapper element that includes the space you want instead:

```ts
await argosScreenshot(page, "card", {
  element: page.getByTestId("card-wrapper"),
});
```

</details>

<details>

<summary>Can I take screenshots of an Electron app?</summary>

Yes. Pass the window returned by Playwright's `_electron.launch()` to `argosScreenshot`, as you would a page:

```ts
import { test, _electron as electron } from "@playwright/test";
import { argosScreenshot } from "@argos-ci/playwright";

test("main window", async () => {
  const app = await electron.launch({ args: ["main.js"] });
  const window = await app.firstWindow();
  await argosScreenshot(window, "main-window");
  await app.close();
});
```

An Electron window has no Playwright viewport: the window itself sets the size of the page.

* Argos records the window's content size as the viewport. Create your `BrowserWindow` with `useContentSize: true` so its `width` and `height` set that size exactly, on every operating system.
* The `viewports` option isn't supported and throws an error.

Argos shows Electron as the browser, with the version read from the window's user agent. If your app sets its own user agent, the screenshot is recorded without a browser.

</details>

### Additional Resources

* [Quickstart with Argos + Playwright](/docs/quickstart/playwright-quickstart)
* [Argos + Playwright example](https://github.com/argos-ci/argos-javascript/tree/main/examples/playwright)
* [@argos-ci/playwright on GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/playwright)
* [@argos-ci/playwright on npm](https://www.npmjs.com/package/@argos-ci/playwright)


# Vitest SDK reference (@argos-ci/vitest)

Reference for @argos-ci/vitest: the Vitest plugin, argosScreenshot for browser mode screenshots, and argosSnapshot for diffing any value from browser or Node tests.

The `@argos-ci/vitest` SDK captures Argos screenshots directly from your [Vitest browser tests](https://vitest.dev/guide/browser/), and snapshots of any serializable value from either browser or Node tests.

### Get started

To get started with Argos and Vitest, follow our [Vitest Quickstart](/docs/quickstart/vitest-quickstart).

{% hint style="info" %}
Using **Storybook**? The [Storybook SDK](/docs/reference/storybook) builds on this same Vitest integration. Follow the [Storybook Quickstart](/docs/quickstart/storybook-quickstart) instead.
{% endhint %}

### Compatibility

* **Vitest** 4 or later.
* **`@vitest/browser`** and **`@vitest/browser-playwright`** 4 or later, and **`playwright`** 1 or later, for screenshots in browser mode. Snapshots run in any Vitest test, with or without a browser.
* **Node.js** 22 or later.

### Requirements

Install `@argos-ci/vitest`:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev @argos-ci/vitest
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev @argos-ci/vitest
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev @argos-ci/vitest
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev @argos-ci/vitest
```

{% endtab %}
{% endtabs %}

Capturing **screenshots** requires [Vitest browser mode](https://vitest.dev/guide/browser/) with the [Playwright provider](https://vitest.dev/guide/browser/playwright). Install these peer dependencies as well:

{% tabs %}
{% tab title="npm" %}

```
npm i --save-dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}

{% tab title="yarn" %}

```
yarn add --dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}

{% tab title="pnpm" %}

```
pnpm add --save-dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}

{% tab title="bun" %}

```
bun add --dev vitest @vitest/browser @vitest/browser-playwright playwright
```

{% endtab %}
{% endtabs %}

**Snapshots** (`argosSnapshot`) run in any Vitest test—browser or Node—and require none of the browser-mode dependencies.

### Capturing screenshots

Use the `argosScreenshot` function to capture a screenshot during a browser test. It requires the Argos Vitest plugin to be registered in your config (see [`argosVitestPlugin`](#argosvitestpluginoptions) below).

```ts
import { test } from "vitest";
import { render } from "vitest-browser-react";
import { argosScreenshot } from "@argos-ci/vitest";
import { Button } from "./Button";

test("Button", async () => {
  render(<Button>Click me</Button>);
  await argosScreenshot("button");
});
```

The name is optional. When omitted, Argos derives one from the current test—mimicking [Vitest's snapshot naming](https://vitest.dev/guide/snapshot)—with a per-test counter so several unnamed captures in the same test stay unique:

```ts
test("Button", async () => {
  render(<Button>Click me</Button>);
  await argosScreenshot(); // -> "src/Button.test.tsx > Button 1"
  await argosScreenshot(); // -> "src/Button.test.tsx > Button 2"
});
```

Unlike Vitest—which keeps a per-file `.snap`, so its keys only need to be unique within a file—Argos names are **global** across the build. The generated name therefore includes the test file path, so two tests with the same title in different files never collide. It is also truncated when needed so the resulting filename stays within the filesystem's 255-character limit.

Screenshots are written to the `./snapshots` directory by default and uploaded by the plugin when `uploadToArgos` is enabled.

### Capturing snapshots

`argosSnapshot` captures a snapshot of **any value**—not just a screenshot—and uploads it to Argos to diff across builds, mimicking [Vitest snapshots](https://vitest.dev/guide/snapshot). Unlike `argosScreenshot`, it does **not** need a browser and works in **both** browser and Node tests.

Strings are written verbatim; any other value is serialized with [`@vitest/pretty-format`](https://www.npmjs.com/package/@vitest/pretty-format) (the serializer Vitest itself uses).

The value comes first; the name is optional. Omit it to auto-name the snapshot from the current test (like screenshots above), or pass `options.name` to set it explicitly:

```ts
import { test } from "vitest";
import { argosSnapshot } from "@argos-ci/vitest";

test("API response", async () => {
  const user = await fetchUser();
  // Objects are serialized automatically.
  await argosSnapshot(user); // -> "src/user.test.ts > API response 1"
  await argosSnapshot(user, { name: "user" }); // explicit name
});
```

Use the `extension` option to control how Argos renders and diffs the snapshot, and `tag` to attach tags:

```ts
await argosSnapshot(JSON.stringify(config, null, 2), {
  name: "config",
  extension: ".json",
  tag: "config",
});
```

Snapshots are written to the same `./snapshots` folder as screenshots and uploaded by the plugin when `uploadToArgos` is enabled.

### Tests Sharding

Argos integrates with [Vitest sharding](https://vitest.dev/guide/improving-performance#sharding) (`vitest --shard=<index>/<count>`). When a shard is detected, [Argos Sharding/Parallel mode](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) is configured automatically — you only need to set the `ARGOS_PARALLEL_NONCE` environment variable to a value shared across the shards.

```bash
# Each machine runs a shard; they upload into a single Argos build.
ARGOS_PARALLEL_NONCE=$CI_RUN_ID vitest run --shard=1/4
ARGOS_PARALLEL_NONCE=$CI_RUN_ID vitest run --shard=2/4
# …
```

The parallel `total` and `index` default to the shard's `count` and `index`, and can be overridden with `ARGOS_PARALLEL_TOTAL` and `ARGOS_PARALLEL_INDEX` if needed.

### API Overview

#### `argosVitestPlugin(options)`

Registers the `argosScreenshot`/`argosSnapshot` browser commands and, when `uploadToArgos` is enabled, the reporter that uploads captured files to Argos. Import it from `@argos-ci/vitest/plugin` and add it to your Vitest config.

```ts
import { defineConfig } from "vitest/config";
import { playwright } from "@vitest/browser-playwright";
import { argosVitestPlugin } from "@argos-ci/vitest/plugin";

export default defineConfig({
  plugins: [
    argosVitestPlugin({
      // Upload the captured files to Argos at the end of the run.
      uploadToArgos: process.env.CI === "true",
    }),
  ],
  test: {
    browser: {
      enabled: true,
      headless: true,
      provider: playwright(),
      instances: [{ browser: "chromium" }],
    },
  },
});
```

* **`uploadToArgos`**: Upload the captured files to Argos at the end of the run (default: `false`).
* **`ignoreUploadFailures`**: When `true`, an upload error is logged and doesn't fail the run (default: `false`, a failed upload sets the exit code to `1`).
* **`root`**: Folder where screenshots and snapshots are written (default: `"./snapshots"`).

The plugin also accepts every option supported by the [Playwright `argosScreenshot` function](/docs/reference/playwright#argosscreenshothandler-name-options)—including non-serializable ones like `beforeScreenshot` and `afterScreenshot`—and all [upload parameters](https://js-sdk-reference.argos-ci.com/interfaces/UploadParameters.html). These act as defaults for every screenshot and can be overridden per call. For example, `hide: ["#clock"]` on the plugin hides the clock in every screenshot, and a per-call `hide` replaces that list instead of adding to it.

#### `argosScreenshot(name?, options?)`

Take a screenshot in a Vitest browser test. Import it from `@argos-ci/vitest`.

```ts
import { argosScreenshot } from "@argos-ci/vitest";

await argosScreenshot("button"); // explicit name
await argosScreenshot(); // automatic name, derived from the current test
await argosScreenshot({ fullPage: true }); // automatic name, with options
```

* **`name`**: A unique name for the screenshot. When omitted, Argos derives one from the current test (including the test file path, so names stay unique across files).
* **`options`**: Serializable screenshot options (see below).

{% hint style="info" %}
Per-call options cross the Vitest browser/Node boundary, so they must be JSON-serializable. Non-serializable options (`beforeScreenshot`, `afterScreenshot`, a `Locator`/`ElementHandle` `element`, …) can only be set on the plugin.
{% endhint %}

Available options:

* **`element`**: String selector of the element to screenshot.
* **`viewports`**: Array of [viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports) to capture.
* **`fullPage`**: Capture the full page instead of fitting the screenshot to the content (default: `false`).
* **`argosCSS`**: Custom CSS evaluated during the screenshot process.
* **`hide`**: Array of selectors of elements to hide. Hidden elements keep their space in the layout, like with the [`data-visual-test="transparent"` helper](/docs/learn/reliability-and-flakiness/flaky-tests/argos-helpers). Selectors that match nothing are ignored.
* **`remove`**: Array of selectors of elements to remove. Removed elements no longer take space in the layout, like with `data-visual-test="removed"`. Selectors that match nothing are ignored. An element in both `hide` and `remove` is removed.
* **`mask`**: Array of selectors of elements to cover with a box, in addition to the elements with `data-visual-test="blackout"`. Selectors that match nothing are ignored.
* **`maskColor`**: Color of the box covering masked elements, in CSS color format (default: `"#FF00FF"`).
* **`threshold`**: Sensitivity threshold between `0` and `1`. The higher the threshold, the less sensitive the diff (default: `0.5`). More on [how the threshold works](/docs/learn/platform-fundamentals/how-argos-detects-visual-differences#sensitivity-threshold).
* **`baseName`**: Name, or list of names, to compare this screenshot against instead of its own name. More on [fallback baselines](/docs/learn/how-to-guides/visual-coverage/fallback-baselines).
* **`tag`**: A [tag](/docs/learn/review-workflow/tags) or array of tags to attach to the screenshot.
* **`ariaSnapshot`**: Capture an [ARIA snapshot](/docs/learn/how-to-guides/visual-coverage/adding-aria-snapshots-manually) along with the screenshot (default: `false`). Each ARIA snapshot counts as an additional screenshot for billing.
* **`disableHover`**: Disable hover effects by moving the mouse to the top-left corner (default: `true`).
* **`stabilize`**: Wait for the UI to stabilize before taking the screenshot. Set to `false` to disable or pass an object to customize it (default: `true`).

#### `argosSnapshot(content, options?)`

Take a snapshot of any serializable value. Works in both browser and Node tests. Import it from `@argos-ci/vitest`.

```ts
import { argosSnapshot } from "@argos-ci/vitest";

await argosSnapshot(user); // automatic name, derived from the current test
await argosSnapshot(user, { name: "user" }); // explicit name
```

* **`content`**: The value to snapshot. Strings are written as-is; any other value is serialized.
* **`options`**: Snapshot options (see below).

Available options:

* **`name`**: A unique name for the snapshot. When omitted, Argos derives one from the current test (including the test file path, so names stay unique across files).
* **`root`**: Folder where the snapshot is written. In Node tests it defaults to `"./snapshots"`; in browser tests it defaults to the plugin `root`.
* **`extension`**: Extension of the snapshot file. It also determines how Argos renders and diffs the snapshot, e.g. `.txt`, `.json`, `.yml`, `.html`, `.md` (default: `".txt"`).
* **`tag`**: A [tag](/docs/learn/review-workflow/tags) or array of tags to attach to the snapshot.
* **`serialize`**: Custom serializer used when `content` is not already a string. Defaults to `@vitest/pretty-format`.

### Additional Resources

* [Vitest Quickstart](/docs/quickstart/vitest-quickstart)
* [@argos-ci/vitest on npm](https://www.npmjs.com/package/@argos-ci/vitest)
* [@argos-ci/vitest on GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/vitest)
* [Vitest browser mode documentation](https://vitest.dev/guide/browser/)




---

[Next Page](https://argos-ci.com/docs/llms-full.txt/1)

