# Overview

Learn how Argos works and where to start.

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 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.

### How it works

<figure><img src="/files/Km0nvDsufzKaRJIwZsz2" 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.

### 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


# 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="/files/8uP2vzb72SMkgxCocoRE">/files/8uP2vzb72SMkgxCocoRE</a></td><td><a href="/pages/25354052bab4dc611cb5faacd5e902830b5e20d6">/pages/25354052bab4dc611cb5faacd5e902830b5e20d6</a></td></tr><tr><td>Vitest</td><td data-object-fit="contain"><a href="/files/M2fnYJLs2eXygeb2sD2U">/files/M2fnYJLs2eXygeb2sD2U</a></td><td><a href="/pages/ZI4PCrfNWmYN9a0lsMCU">/pages/ZI4PCrfNWmYN9a0lsMCU</a></td></tr><tr><td>Storybook</td><td data-object-fit="contain"><a href="/files/LB8iJlPkGS6hzlqohrpg">/files/LB8iJlPkGS6hzlqohrpg</a></td><td><a href="/pages/398bf497453faea82bf41b6c44fe359820cc533e">/pages/398bf497453faea82bf41b6c44fe359820cc533e</a></td></tr><tr><td>Cypress</td><td data-object-fit="contain"><a href="/files/BXGcR9ixY9GeicISqpQX">/files/BXGcR9ixY9GeicISqpQX</a></td><td><a href="/pages/3fe9fc7f0a0709a80874856b5d0e92776a649bdc">/pages/3fe9fc7f0a0709a80874856b5d0e92776a649bdc</a></td></tr><tr><td>WebdriverIO</td><td data-object-fit="contain"><a href="/files/lo3PAT8JboVAHCX6tFE1">/files/lo3PAT8JboVAHCX6tFE1</a></td><td><a href="/pages/52440c01ea316fa4a0e9a9e160e40503200a6dbb">/pages/52440c01ea316fa4a0e9a9e160e40503200a6dbb</a></td></tr><tr><td>Puppeteer</td><td data-object-fit="contain"><a href="/files/PXG5gTOHwBm6V92ibbNX">/files/PXG5gTOHwBm6V92ibbNX</a></td><td><a href="/pages/9abc4dc0e9a7fbac7e1a346bb9519b7ae7381a09">/pages/9abc4dc0e9a7fbac7e1a346bb9519b7ae7381a09</a></td></tr><tr><td>Any test framework</td><td data-object-fit="contain"><a href="/files/koasZc7THNpnvmFU4NJ5">/files/koasZc7THNpnvmFU4NJ5</a></td><td><a href="/pages/cc795ab1ff1a6699e450df594f792b682d516064">/pages/cc795ab1ff1a6699e450df594f792b682d516064</a></td></tr></tbody></table>


# Playwright Quickstart

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

Set up Argos with [Playwright](https://playwright.dev/) to run visual tests on every pull request: install the SDK, add the reporter, capture screenshots, and run it in CI.

### Prerequisites

* [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,
      }),
    ],
  ],

  // 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",

    // Stabilize text rendering so screenshots match across macOS and CI.
    launchOptions: {
      args: ["--disable-lcd-text", "--font-render-hinting=none"],
    },
  },
});
```

{% endcode %}

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, so glyphs render identically on your machine and on 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("http://localhost:3000");
  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) or [capture multiple viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).
{% 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
      - 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 → General → 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.
{% 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 %}

### 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

***

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 Quickstart

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

Set up Argos with [Vitest](https://vitest.dev/) to run visual tests on every pull request. The Argos Vitest SDK adds visual testing to Vitest in two ways:

* **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.

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

### Prerequisites

* [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 (skip this if you only need snapshots):

{% 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 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({
        // Stabilize text rendering so screenshots match across macOS and CI.
        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, so glyphs render identically on your machine and on 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) or [add ARIA snapshots](/docs/learn/how-to-guides/visual-coverage/adding-aria-snapshots-manually).
{% 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
      - 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 → General → 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.
{% 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 %}

### 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

***

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 Quickstart

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

Set up Argos with [Storybook](https://storybook.js.org/) to get **visual testing** and **live deployment URLs** on every 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.

{% hint style="info" %}
If you use Test Runner instead of Vitest, follow the [Storybook Test Runner Quickstart](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart).

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

* [Storybook v8+](https://storybook.js.org/docs/get-started/install) set up in your project
* [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:

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

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

{% endtab %}

{% tab title="yarn" %}

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

{% endtab %}

{% tab title="pnpm" %}

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

{% endtab %}

{% tab title="bun" %}

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

{% 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({
              // Stabilize text rendering so screenshots match across macOS and CI.
              launchOptions: {
                args: ["--disable-lcd-text", "--font-render-hinting=none"],
              },
            }),
            instances: [{ browser: "chromium" }],
          },
          setupFiles: [".storybook/vitest.setup.ts"],
        },
      },
    ],
  },
});
```

{% endcode %}

{% hint style="success" %}
The `launchOptions` above disable subpixel text and font hinting, so glyphs render identically on your machine and on 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
      - 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 → General → 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, follow [Use deployments in CI](/docs/learn/deployments/use-deployments-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, 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 %}

### 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

***

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 Quickstart

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

Set up Argos with [Storybook Test Runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner) to run visual tests on every pull request: capture a screenshot of each story during the test run, then upload them with the Argos CLI.

{% hint style="info" %}
If you use Vitest instead of Test Runner, follow the [Storybook Quickstart](/docs/quickstart/storybook-quickstart).

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

* [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 accordingly:

{% 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

      - 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 → General → 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 %}

### 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).


# Storybook Legacy (\<v8) Quickstart

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

Set up Argos with a legacy version of Storybook (\<v8) using [Storycap](https://github.com/reg-viz/storycap): Storycap crawls your Storybook and captures a screenshot of each story, then the Argos CLI uploads them.

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

### Prerequisites

* [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 accordingly:

{% 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
      - 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 → General → 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 %}

### 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 Quickstart

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

Set up Argos with [Cypress](https://www.cypress.io/) to run visual tests on every pull request: install the SDK, register the command and task, capture screenshots, and run it in CI.

### Prerequisites

* [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) or [capture multiple viewports](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).
{% 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
      - 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 → General → 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.
{% 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 %}

### 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

***

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 Quickstart

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

Set up Argos with [WebdriverIO](https://webdriver.io/), the Node.js test automation framework for web and mobile applications, to run visual tests on every pull request: capture screenshots with the SDK, then upload them with the Argos CLI.

### Prerequisites

* [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
      - 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 → General → 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 %}

### 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 Quickstart

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

Set up Argos with [Puppeteer](https://pptr.dev/) to run visual tests on every pull request: capture screenshots with the SDK, then upload them with the Argos CLI.

### Prerequisites

* [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
      - 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 → General → 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 %}

### 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).


# Any test framework

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

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.

### Prerequisites

* 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
      - 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 → General → 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.

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

### 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 comparison runs in four stages:

1. **Image normalization**: Resolution, color space, and alpha channels are aligned.
2. **Multiple diff passes**: Each pass uses different thresholds to detect both strict and subtle changes.
3. **Pixel clustering**: Random noise is separated from meaningful visual changes.
4. **Final diff output**: A diff mask and score are produced.

Running multiple passes lets Argos stay strict while remaining resilient to minor, explainable 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.

### 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 most recent build 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.
* **It is approved.** It is an auto-approved build, an [orphan](#orphan-builds), or a check build with an approved review or a merged pull request.

**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 an ancestor of the **merge base** — 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.

### 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](/files/m3SPj4DEikqjwFX0Q0cR)

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](/files/HEJSl1Pi5DRoOz0FiW8R)

{% 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.
{% 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`).

{% hint style="info" %}
By default, Argos auto-approves your default baseline branch. You can configure auto-approved branch patterns in the Argos project settings.
{% 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](/files/d3d6d44598bcc094df6da74edac9654a21754583)

### 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.

### 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, 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. 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](/files/bffe5cc07035373fc717d5e4973f4f5cc53d05c4)

*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](/files/bCA1WkqddO4kL7H1LWgI)

### 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.

### 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 |
| `S`       | Toggle side-by-side view                  |
| `D`       | Toggle the changes overlay                |
| `H`       | Highlight the changed regions             |
| `J` / `K` | Go to the previous / next change          |
| `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](/files/UaRZ5XRqihyGCUeswGqW)

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](/files/6EUMPbz5Ys1nefiS7njn)

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.

### 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](/files/UrI60uZxsH8FzLKmu1sK)

### 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](/files/oXYyqVfKe7YMLeCJIGec)

### 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](/files/BFxiyzSa9RyLDvCRfdgg)

### 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. The build must also be in a reviewable state (changes detected, approved, or rejected).
* 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.

### 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](/files/768b12d931538b93cf0af42a090919c87cc841b7)

### 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](/files/fb4acba4f0ae2ecca31d4466c76b6a0c6e3dbedb)

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](/files/b87bbd4cb5e920f6fea5d2709fb859776d134af2)

### 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](/files/4c0e603ed775d51224ba76ba7fd134449adaf918)


# 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](/files/252d3d1fb2e5f00037d2d7db03988775a48a7101)

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", {
      tags: ["@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 `tags` 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", { tags: ["@desktop", "@homepage"] });
});
```

{% endcode %}

#### Puppeteer

In Puppeteer, you can add tags to screenshots through the `tags` 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", {
  tags: ["@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](/files/c125950c0832cffbd468a956b1b0c610182a81a1)

*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](/files/ad28850a723cf42e6531a6a715adc1af282c259d)

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](/files/fe55f04cd76aaa8a4fd28f8643b81d6bca045ce5)

***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](/files/71e19ef36f0c6e07dc139edd7a967dad203c6454)

*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

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`, 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)
* **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)
* **Force consistent text rendering.** Disable subpixel text and font hinting so glyphs look identical on every machine. → [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 Chromium so glyphs render identically across macOS, Linux, and CI.

Force consistent glyph rendering across operating systems: disable subpixel (LCD) text and font hinting so the same text looks identical on macOS, Linux, and CI—eliminating one of the most common sources of screenshot flakiness.

### 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 make text rendering deterministic:

* `--disable-lcd-text` — forces **grayscale antialiasing** instead of subpixel rendering, removing the red/blue edge fringing.
* `--font-render-hinting=none` — disables font hinting so glyph rasterization is **platform-independent**.

{% 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 make text rendering consistent **across** environments, so screenshots captured on your machine match those captured on CI.
{% 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, improving screenshot consistency.

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 element with `aria-busy` remains on the page, so the screenshot always shows the fully loaded state. Make sure your app removes the attribute (or the loader) once loading completes.

{% hint style="info" %}
Waiting for `aria-busy` is part of the SDK's default stabilization (the `stabilize.waitForAriaBusy` option) — there's nothing to enable, you only need to mark your loaders.
{% endhint %}


# 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 make glyphs render identically everywhere.
{% 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 to hide, remove, or mask dynamic elements for consistent, flake-free screenshots.

Argos SDKs recognize `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"`: Renders the element transparent (`visibility: hidden`), keeping its layout space.
* `data-visual-test="removed"`: Removes the element from view (`display: none`).
* `data-visual-test="blackout"`: Masks the element with a blackout effect (Storybook, Playwright, and Cypress SDKs only).
* `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>
```


# 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="/files/94aa617a534a9d32bdd7244792a08faa1ecc6983" 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 → General → 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
      - 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 → General → 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="/files/1c58f2730680577bba3c1fbd6fc7ebf36c94dad2" 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](/files/8450c8c3069acb689473ca6f029dd08aea7f54ea)*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](/files/e67f42fb6178fd133204a571153eae7c890b1925)*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 when to share each one.

Every deployment is reachable through one or more URLs. The exact URLs depend on the deployment's environment and the project's configuration. All deployment URLs are served from the shared root domain `argos-ci.live`.

### 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. You can change it in **Settings → Deployments → Production domain**.

<figure><img src="/files/72b7b0fa5c9e527bb96ba8d2fdbd3fff2dd34761" alt=""><figcaption><p><em>Project Settings → Deployments → Production domain.</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 %}

### 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 |

All URLs above appear in the **Deployments** tab of your project in Argos.

<figure><img src="/files/fc6296c12b4612a4c4db47938ca657d485c34ed7" alt=""><figcaption><p><em>The Deployments tab shows the deployment URL, branch URL, and—for production—the production domain.</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](/files/3dbfd65e28f3c8f65441a70400532b3854be6573)

*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 tokenless GitHub Actions authentication with no token at all. 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 screenshots, or BackstopJS.


# CI pipelines

Patterns for running Argos efficiently in CI, from monorepos and sharding to preview deployments and subset builds.

Patterns for running Argos efficiently inside your continuous integration pipeline.

* [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.


# 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
      - 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
      - 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>


# 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
  - 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
  - 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>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
      - 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
      - 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

      - 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

      - 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
* [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");
```


# 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.
{% 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 addons in your `.storybook/preview.ts` (or `.js`) file. Examples include:

* [`@storybook/addon-viewport`](https://www.npmjs.com/package/@storybook/addon-viewport) for screen sizes
* [`@storybook/addon-themes`](https://www.npmjs.com/package/@storybook/addon-themes) for light/dark themes
* [`@storybook/addon-backgrounds`](https://www.npmjs.com/package/@storybook/addon-backgrounds) for backgrounds
* [`storybook-i18n`](https://www.npmjs.com/package/storybook-i18n) for locales

These addons utilize Storybook “globals” and “decorators” under the hood. Argos modes simply manipulate 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: {
      viewports: {
        compact: {
          name: "Compact",
          styles: { width: "600px", height: "900px" },
        },
        widescreen: {
          name: "Widescreen",
          styles: { width: "1440px", height: "900px" },
        },
      },
    },
    backgrounds: {
      values: [
        { name: "Light", value: "#ffffff" },
        { name: "Dark", value: "#1A1A1A" },
      ],
    },
  },
  decorators: [
    withThemeByClassName({
      themes: {
        light: "light",
        dark: "dark",
      },
      defaultTheme: "light",
    }),
  ],
};

export default preview;
```

{% endcode %}

### 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: "#1A1A1A" },
    theme: "dark",
  },
  mobile: {
    viewport: "compact",
  },
  "dark widescreen": {
    backgrounds: { value: "#1A1A1A" },
    theme: "dark",
    viewport: "widescreen",
  },
  "light mobile": {
    backgrounds: { value: "#ffffff" },
    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.

### 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 disable property:

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

```ts
// ...

export const SpecialCard: Story = {
  args: {
    /* ... */
  },
  parameters: {
    argos: {
      modes: {
        "light mobile": { disable: true }, // turns off this inherited mode
      },
    },
  },
};
```

{% endcode %}

That story will now ignore light mobile mode but still apply any other inherited modes.

### 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.

</details>

<details>

<summary>Do all Storybook addons work with Argos modes?</summary>

Any addon that leverages Storybook globals should work, including [@storybook/addon-themes](https://storybook.js.org/addons/@storybook/addon-themes), [@storybook/addon-viewport](https://storybook.js.org/addons/@storybook/addon-viewport), [@storybook/addon-backgrounds](https://storybook.js.org/addons/@storybook/addon-backgrounds), 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](/files/5a57b725e54d4a5010746d76df222eb4bc6bb473)

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

### Framework support

Non-image comparisons are only supported through the CLI for now. [Contact us](https://argos-ci.com/contact) if you are interested in using this feature from a test framework like Vitest.


# Migrate to Argos

Step-by-step guides for switching to Argos from Percy, Chromatic, Applitools, native Playwright screenshots, or BackstopJS.

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, Cypress, Storybook, 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 live in Git, not 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="/pages/RbONVLqReRF2XuqeJkYZ">/pages/RbONVLqReRF2XuqeJkYZ</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="/pages/D7zfyd3fb1YwRnc3UH6k">/pages/D7zfyd3fb1YwRnc3UH6k</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="/pages/hQLThpMNGXLRQsjlt96r">/pages/hQLThpMNGXLRQsjlt96r</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="/pages/EYrJDbz74M20TV06fbYF">/pages/EYrJDbz74M20TV06fbYF</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="/pages/qFLkUqWr94JvauxSKPcn">/pages/qFLkUqWr94JvauxSKPcn</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

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 → General → 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: {
    // Stabilize text rendering so screenshots match across macOS and CI.
    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 → General → 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)


# Migrate from Chromatic

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. If you're on Storybook v8+ and don't have it yet, install it before continuing. (On older Storybook, use the [Test Runner quickstart](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart) or 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 → General → 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.


# Migrate from Applitools

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.


# Migrate from Playwright screenshots

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(locator, "name")`                                                       |
| 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 a locator: `await argosScreenshot(page.getByRole("dialog"), "dialog")`.
{% 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 → General → 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 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)


# Migrate from BackstopJS

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 → General → 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)


# 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.

Two 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.

To override detection — for example in a custom pipeline — set the `ARGOS_COMMIT` (full 40-character SHA) and `ARGOS_BRANCH` environment variables.

### 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](/files/ce64491dd225f415224b6f02c87bb9bd17d8c7e7)

### 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

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

When Argos runs inside the merge queue, it compares the queued changes against the merge base of all pull requests in the queue, ensuring that visual tests reflect the final merged state. This is supported out of the box with [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), and can be configured for custom merge queue systems as well.

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

{% endcode %}

#### 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.

```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](/files/9350ccd345be129222c75f6f5d7949db0984bd79)

5. On GitHub, choose the specific repositories where you want to install the Argos app.

![Argos GitHub app without content access](/files/5100dd66497e4570a33028213c175a2f8fa90a00)

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](/files/5fb0e114c4770054ce4a18ce3de7709534290777)

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.


# 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.

### Choose a method

The SDK selects an authentication method in this order:

1. **`ARGOS_TOKEN`** is set → use the token.
2. **OIDC is available** (`id-token: write` permission granted and OIDC enabled in your Argos project) → use OIDC.
3. **Neither is available** → fall back to tokenless authentication.

* **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. No configuration at all, and the only method that works on pull requests from forks.

A single workflow can combine OIDC and tokenless: 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.

## 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 that build. 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.** Each token is tied to a specific repository, workflow, branch, and run. Compromise of one run cannot be replayed elsewhere.
* **Least privilege.** The credential Argos issues from the OIDC exchange is scoped to uploading to a single project.
* **Auditable.** GitHub signs the identity token with claims (repository, ref, workflow, actor) that Argos records on the resulting build.

### How OIDC works

1. GitHub Actions requests an OIDC identity token from GitHub's OIDC provider for the current job. This requires the `id-token: write` workflow permission.
2. The Argos SDK detects that no `ARGOS_TOKEN` is set and fetches the OIDC token from the runner.
3. The SDK sends the token to Argos with the upload request.
4. Argos verifies the token's signature, issuer, audience, and claims (`repository` and `commit`) against the project's **Connected Git Repository**.
5. If the token is valid and the repository matches the Argos project, the upload is accepted.

The OIDC token is short-lived (minutes) and never persisted by Argos beyond what is needed to attribute the build.

### Set up OIDC

To enable OIDC in your Argos project:

1. Open your project in Argos and go to **Project Settings → Authentication**.
2. Enable **GitHub OIDC**.
3. Save your changes.

Then configure your workflow. Two things are required:

1. Grant the workflow permission to mint an OIDC token by adding `id-token: write` to the job's `permissions` block.
2. **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 Argos link the build to its pull request
      pull-requests: read
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
      - run: npm ci
      - run: npm run test:e2e
        # Note: no ARGOS_TOKEN — the SDK uses OIDC automatically
```

{% 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 from the commit and branch the SDK reports.

It is the fallback the SDK uses when neither a token nor OIDC is available — most importantly, 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 an upload request to Argos with the GitHub-provided context: repository, commit SHA, branch, and workflow run ID.
3. Argos calls the GitHub API to look up workflow runs for that repository at that commit and branch.
4. If Argos finds a workflow run that is **in progress** and matches the context the SDK reported, it issues a **short-lived token** scoped to that run and 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 doesn't match the repository linked to the Argos project, 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.** You can connect a repository to Argos and have uploads work from GitHub Actions without configuring anything in the workflow.

Tokenless requires no configuration — leave `ARGOS_TOKEN` unset and do not grant `id-token: write`. To pair it with OIDC in the same workflow, 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.
{% 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 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 a workflow with the same commit and branch is in progress; OIDC verifies a signed token from GitHub. Prefer OIDC where it is available.

## Troubleshooting

**`Unable to get OIDC token` / 403 from the OIDC endpoint.** The job is missing `id-token: write`. Add it to the `permissions` block.

**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).

**`Repository does not match the Argos project`.** The repository that ran the workflow is not the one linked to the Argos project. Run the workflow from the linked repository, or update the project's connected repository in Argos.

**`No matching workflow run found`.** Argos could not find an in-progress workflow run on GitHub matching the commit and branch the SDK reported. Check that the repository linked to the Argos project matches the one running the workflow, and that the workflow is still in progress when the upload runs.

**`Multiple projects are linked to this repository`.** More than one Argos project is connected to the repository, so tokenless cannot pick one automatically. Specify which project to use with the project slug — see [Specify a project when several share a repository](#specify-a-project-when-several-share-a-repository).

**Uploads work but are missing pull request metadata.** Tokenless authenticates the upload but does not by itself link the build to a pull request. Pass `GITHUB_TOKEN` in the job environment so the SDK can resolve the PR.

## 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](/files/77e46b7b227ebe26ff1c3a9856999cba6a01c709)

{% 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](/files/b70b5aaa3463a14ad12d259ce58ec07d7e9ee72c)
{% 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](/files/d14da22355b95096636c09a0f07792148dbf088d)
{% endstep %}
{% endstepper %}

### 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](/files/997eda32874de8f5ed75771e5a72928ed8f9537e)

*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](/files/1497994cbc5651b74d13450af9bd27e5a8eaead7)
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](/files/990d849cd23027cf3053641e14d251d17f3e239a)

   *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](/files/23EI2kbY3p9KDLVSxWfd)

*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](/files/1MASsCv6WfYW3qAb6mEN)
{% endstep %}

{% step %}

#### Choose the right template

Search for `webhook` and select **Send webhook alerts to a channel**.

![The four webhook templates offered by Workflows](/files/p9mCkyOS5BbccH048xHQ)

{% 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


# 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.](/files/f6c58e7e69044a5ed18b1ee66ea602d2224c86eb)

#### 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](/files/eviGGZz7K0QErilHR2yI)

#### 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.

### 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

1. Remove all team projects.
2. Open the team's **Settings** tab.
3. Scroll to **Delete Team** and select **Delete Team**.
4. Confirm the action.

### 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.](/files/5ca12431c84b2aefa0b57e872c4a14c8bc28f457)

### 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.](/files/210068fed77fc8daa132e24ee9de44911350e1d8)

### 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](/files/63014f3ba0789f27c6d68fbec3635fda3f4f6b35)

### 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.

#### 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 one registered on a self-hosted Argos instance will not work on `app.argos-ci.com`.

</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](/files/851d6d91738891d48068562f15d4c758d6086eac)

### 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](/files/9a46a12ac972b32211683a3d43c140fdfe42537d)

### 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.

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 -> Security and Privacy**.

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.](/files/664423fad0e389e1b7f4996941b198a12d446706)

### 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 **Security and Privacy**.
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 -> Security and Privacy -> SAML Single Sign-On**.
3. Enable **Require team members to log in with SAML**.

![SAML SSO configured and enforced.](/files/9698a4236966d56b7a5591c182f39bae909dae41)

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.](/files/3bf69d57702d7f263d29bb1c798455e5e4999266)

### 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.

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/)


# 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


# 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

Best for individuals and small projects.

* **Price**: Free
* **Screenshots included**: 5,000 / month
* **Extra screenshots**: Not available
* **Media sharing**: public share pages, 30-day retention, 50 MB per file

### Pro plan

Designed for teams, with usage-based pricing.

* **Starting price**: $100 / month
* **Screenshots included**: 35,000 / month
* **Extra screenshots**:
  * Standard: $0.004 per screenshot
  * Storybook: $0.0015 per screenshot
* **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): $200 / month
* **Media sharing**: team-scoped or public share pages, 1-year retention, 500 MB per file

You can subscribe to the Pro plan with a credit card through [Stripe](/docs/learn/billing-and-subscription/how-to-subscribe), or through [GitHub Marketplace](https://github.com/marketplace/argos-ci) to pay via your GitHub invoice. Marketplace plans come in fixed sizes (S, M, L, XL) with increasing screenshot limits instead of usage-based billing — see [Usage & spend management](/docs/learn/billing-and-subscription/spend-management) for how each model handles going over 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.

[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.

### 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), 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](/files/M1KQBYm0iPIPPl7hGNV8)
{% 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](/files/JaVzJLwgeOkh0GGBz0Du)

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](/files/BC3YlLt1rCUR3pe0srRM)

{% 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](/files/1SqgQOk4boXlxh9rO9PO)

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](/files/NzylFhFRLG31f6q5AKVV)
{% 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:

* **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.

### Monitor your usage

To see your team's usage details:

1. From the dashboard, select your team from the scope selector.
2. Select the **Settings** tab and go to the **Billing** section.

![The plan card showing screenshot usage in the Billing settings](/files/be6a20226326a6f90c90ad27aa4ad2b5011f5ecf)

The plan card breaks down your consumption:

* **Private project**: Screenshots taken from private projects. These count toward your plan limit and are subject to charges.
* **Public project**: Screenshots taken from open-source projects.

### 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](/files/92a565ced94cf1c72fc61f15b8b4830c966b2c25)
{% 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

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.


# Overview

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="/pages/0axIlbxcGWsTpLc1S4qq">/pages/0axIlbxcGWsTpLc1S4qq</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="/pages/ce4ff41dcaf17a29ff520ad83b29a2afab15bfa4">/pages/ce4ff41dcaf17a29ff520ad83b29a2afab15bfa4</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="/pages/TATuQgbXKE4CEGUazUvD">/pages/TATuQgbXKE4CEGUazUvD</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="/pages/juJ8a2yOgrosLCxNrOWJ">/pages/juJ8a2yOgrosLCxNrOWJ</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).


# 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](https://app.argos-ci.com/settings/personal-access-tokens) sent as a bearer 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 %}

## 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://docs.cursor.com/en/context/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.

## 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

Improve test debugging and boost your visual testing capabilities by combining Argos with your Playwright tests.

### Get started

Please refer to our [Quickstart guide](/docs/quickstart/playwright-quickstart) to get started with Argos and Playwright.

### 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: {
    // Stabilize text rendering so screenshots match across macOS and CI.
    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, so glyphs render identically on your machine and on 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).

### 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 (`visibility: hidden`).
* `[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.

**Example: Using a helper attribute to hide a div from the captured screenshot:**

```html
<div id="clock" data-visual-test="transparent">...</div>
```

### 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 %}

### 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).
* `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.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`.
* `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.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.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 `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>

### 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

Integrate visual testing into your Vitest browser tests with Argos. Capture screenshots and snapshots and review visual changes directly within your CI.

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

### 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`).
* **`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.

#### `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.
* **`threshold`**: Sensitivity threshold between `0` and `1`. The higher the threshold, the less sensitive the diff (default: `0.5`).
* **`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/)


# Storybook

Integrate visual testing with your Storybook using Argos. This SDK allows you to capture and review visual changes in your Storybook components directly within your CI.

### Get started

To get started with Argos and Storybook, check out our Quickstart guides:

* [Storybook + Vitest](/docs/quickstart/storybook-quickstart)
* [Storybook + Test Runner](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart)
* [Storybook Legacy (\<v8)](/docs/quickstart/storybook-quickstart/storybook-legacy-less-than-v8-quickstart)

### Comparing Argos and Chromatic

While both Argos and Chromatic provide visual testing for Storybook, they take different approaches:

* **Argos** captures screenshots of your Storybook components **in your CI using Playwright**.
* **Chromatic** captures screenshots of your Storybook components **in the cloud**.

For a deeper comparison, see our [Argos vs Chromatic guide](https://argos-ci.com/compare/chromatic).

### Vitest vs Test Runner

Argos supports both [Vitest](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon) and [Test Runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner) for visual testing in Storybook.

Storybook recommends using Vitest for testing and Argos experience is also better if you use it. To learn more about the differences, see [Vitest vs Test Runner comparison guide](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon#comparison-to-the-test-runner).

### Interactions using the play function

The `play` function in Storybook lets you define interactions for your components, ensuring they are in a specific state before capturing a screenshot.

When running tests, either [Vitest](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon) or [Test Runner](https://storybook.js.org/docs/writing-tests/integrations/test-runner) **automatically executes the `play` function** before taking a screenshot.

If you use Vitest, you can also take custom screenshots in your tests using the `argosScreenshot` function:

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

export const FormStory: Story = {
  play: async (ctx) => {
    const { canvasElement } = ctx;

    // Take a screenshot before filling the form
    await argosScreenshot(ctx, "before-fill");

    const canvas = within(canvasElement);

    await userEvent.type(
      canvas.getByLabelText("Email", { selector: "input" }),
      "example-email@email.com",
      { delay: 100 },
    );

    // Take a screenshot after filling the form
    await argosScreenshot(ctx, "after-fill");

    await userEvent.click(canvas.getByRole("button"));
  },
};
```

### Story Modes

Argos supports Story modes to capture different states of your components. Read our [Story modes guide](/docs/learn/how-to-guides/visual-coverage/storybook-story-modes) for more details.

### Fit to Content vs Page

By default, Argos screenshots are cropped to fit the rendered component (`fitToContent: true`). You can capture the entire page instead by setting `argos.parameters.fitToContent` to `false`.

#### Per Story

`src/components/ProductPage/ProductPage.stories.js`

```ts
import { ProductPage } from "./ProductPage";

export default {
  title: "Pages/ProductPage",
  component: ProductPage,
  parameters: {
    argos: {
      fitToContent: false,
    },
  },
};
```

#### Project-Wide Setting

`.storybook/preview.js`

```ts
const preview = {
  parameters: {
    argos: {
      fitToContent: false,
    },
  },
};

export default preview;
```

#### Options

* **`fitToContent`**: Adjusts the screenshot to the content size (default: `true`).
* **`fitToContent.padding`**: Sets padding around the content in pixels (default: `16`).
* **`fitToContent.zoom`**: Specifies the zoom level (default: `2`).

### Troubleshooting

#### My addon is not working with Vitest

To be sure that your addons are properly loaded when running Vitest, be sure to specify your addon annotations in `setProjectAnnotations`:

**Example for `storybook-addon-pseudo-states`**:

```ts
import { setProjectAnnotations } from "@storybook/react-vite";
import * as projectAnnotations from "./preview";
import * as addonAnnotations from "storybook-addon-pseudo-states/preview";

setProjectAnnotations([projectAnnotations, addonAnnotations]);
```

Read more in the [Storybook documentation](https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations).

### API Overview

#### `@argos-ci/storybook/vitest-plugin`

Exposes the Vitest plugin to capture screenshots of your Storybook stories during tests.

```ts
import { argosVitestPlugin } from "@argos-ci/storybook/vitest-plugin";
import { defineConfig } from "vitest/config";
export default defineConfig({
  plugins: [
    argosVitestPlugin({
      uploadToArgos: true, // Set to false to disable uploading
      buildName: "My Build Name", // Optional build name
    }),
  ],
});
```

* **`uploadToArgos`**: Set to `true` to upload screenshots to Argos.

Also supports all options from the [Playwright `argosScreenshot` function](/docs/reference/playwright#argosscreenshothandler-name-options) and [upload parameters](https://js-sdk-reference.argos-ci.com/interfaces/UploadParameters.html).

#### `@argos-ci/storybook/vitest`

Take a screenshot of the Story inside the `play` function of a Storybook story (only available when using Vitest).

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

export const Example: Story = {
  play: async (ctx) => {
    // Take a screenshot of the story
    await argosScreenshot(ctx, "example-screenshot");
  },
};
```

* **`ctx`**: The Storybook context provided to the `play` function.
* **`name`**: A name for the screenshot.

#### `@argos-ci/storybook/test-runner`

#### argosScreenshot(page, context\[, options])

Take a screenshot of the Story inside the `postVisit` hook of the Storybook Test Runner.

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

* **`page`**: The [Playwright Page](https://playwright.dev/docs/api/class-page) instance.
* **`context`**: The test context provided by the Storybook test runner.
* **`options`**: Customizable options for `argosScreenshot`. Explore [available options](/docs/reference/playwright#argosscreenshothandler-name-options).

### Additional Resources

* [Quickstart with Argos + Storybook + Vitest](/docs/quickstart/storybook-quickstart)
* [Quickstart with Argos + Storybook Test Runner](/docs/quickstart/storybook-quickstart/storybook-test-runner-quickstart)
* [Quickstart with Argos + Storybook Legacy (\<v8)](/docs/quickstart/storybook-quickstart/storybook-legacy-less-than-v8-quickstart)
* [Argos + Storybook Test Runner example](https://github.com/argos-ci/argos-javascript/tree/main/examples/storybook-test-runner)
* [Argos + Storybook Vitest example](https://github.com/argos-ci/argos-javascript/tree/main/examples/storybook-vitest)
* [@argos-ci/storybook on GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/storybook)
* [@argos-ci/storybook on npm](https://www.npmjs.com/package/@argos-ci/storybook)


# Cypress

Combine Argos with Cypress to stabilize screenshots, wait for fonts and images, and surface test failures.

Boost your visual testing capabilities by combining Argos with your [Cypress](https://www.cypress.io/) tests.

While Cypress inherently provides screenshot functionality, the Argos Cypress integration enhances this by:

* Ensuring all images are fully loaded.
* Ensuring all fonts are rendered.
* Confirming the absence of any `aria-busy` (loading) elements on the page.
* Concealing scrollbars.
* Obscuring text cursors or carets.
* Providing CSS utilities to simplify content hiding.
* Gives you visibility on test failures.

### Get started

Please refer to our [Quickstart guide](/docs/quickstart/cypress-quickstart) to get started with Argos and Cypress.

### 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 Cypress configuration.

#### Example Configuration

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

```ts
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,
        previewUrl: {
          baseUrl: "https://my-site.com", // Use a dynamic value here for different environments if needed.
        },
      });
    },
  },
});
```

{% endcode %}

### Setup individual Cypress events

Cypress only supports one handler per event. If you need to set up other handlers for the same event, you can call the individual functions provided by the SDK.

{% code title="" %}

```ts
const { defineConfig } = require("cypress");
const {
  argosAfterScreenshot,
  argosAfterRun,
} = require("@argos-ci/cypress/task");

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    async setupNodeEvents(on, config) {
      const argosConfig = {
        uploadToArgos: !!process.env.CI,
      };

      on("after:screenshot", async (details) => {
        // Your custom logic...

        return argosAfterScreenshot(config, details, argosConfig);
      });

      on("after:run", async (results) => {
        // Your custom logic...

        return argosAfterRun(config, results, argosConfig);
      });
    },
  },
});
```

{% endcode %}

### API Overview

#### cy.argosScreenshot(\[name]\[, options])

* `name`: Unique name for the screenshot.
* `options`: Explore [cy.screenshot command options](https://docs.cypress.io/api/commands/screenshot) for details.
* `options.element`: Use an ElementHandle or 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.argosCSS`: Specific CSS applied during the screenshot process. More on [injecting CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css)
* `options.threshold`: Sensitivity threshold between 0 and 1. The higher the threshold, the less sensitive the diff will be. Defaults to `0.5`.
* `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.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.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.tag`: Tag or array of tags to attach to the screenshot for filtering in Argos.

### 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 (`visibility: hidden`).
* `[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.

**Example: Using a helper attribute to hide a div from the captured screenshot:**

```html
<div id="clock" data-visual-test="transparent">...</div>
```

#### registerArgosTask(on, config\[, options])

* `on`: Cypress plugin events.
* `config`: Cypress config.
* `options`: All [upload parameters](https://js-sdk-reference.argos-ci.com/interfaces/UploadParameters.html).
* `options.uploadToArgos`: Upload results and create a build on Argos, `true` by default.

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

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

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    async setupNodeEvents(on, config) {
      registerArgosTask(on, config, {
        uploadToArgos: !!process.env.CI,
      });

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

{% endcode %}

#### argosAfterScreenshot(config, details\[, options])

Cypress "after:screenshot" event handler.

* `config`: Cypress config.
* `details`: Screenshot details provided by Cypress.
* `options`: All [upload parameters](https://js-sdk-reference.argos-ci.com/interfaces/UploadParameters.html).
* `options.uploadToArgos`: Upload results and create a build on Argos, `true` by default.

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

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

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    async setupNodeEvents(on, config) {
      on("after:screenshot", async (details) => {
        // Your custom logic...

        return argosAfterScreenshot(config, details, {
          uploadToArgos: !!process.env.CI,
        });
      });

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

{% endcode %}

#### argosAfterRun(config, results\[, options])

Cypress "after:run" event handler.

* `config`: Cypress config.
* `results`: Run results provided by Cypress.
* `options`: All [upload parameters](https://js-sdk-reference.argos-ci.com/interfaces/UploadParameters.html).
* `options.uploadToArgos`: Upload results and create a build on Argos, `true` by default.

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

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

module.exports = defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    async setupNodeEvents(on, config) {
      on("after:run", async (results) => {
        // Your custom logic...

        await argosAfterRun(config, results, {
          uploadToArgos: !!process.env.CI,
        });
      });

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

{% endcode %}

### Troubleshooting

#### Error while importing `@argos-ci/cypress/task` in `cypress.config.ts`

To address the `ts(1479)` error when importing `@argos-ci/cypress/task` in your `cypress.config.ts`, you have two main strategies:

{% stepper %}
{% step %}

### Update your `tsconfig.json`

Set `moduleResolution` to `"Bundler"`. This method leverages TypeScript's support for newer module resolution strategies, aligning with Node.js's `exports` feature used by Argos to distribute optimized SDKs.
{% endstep %}

{% step %}

### Suppress the error in `cypress.config.ts`

Add the line `// @ts-expect-error moduleResolution` right above the import statement. This tells TypeScript to expect an error at this line and ignore it, offering a quick workaround without adjusting your project's module resolution strategy.
{% endstep %}
{% endstepper %}

The first option is a more comprehensive solution, dealing with the TypeScript bug through adopting the new `moduleResolution: "Bundler"` setting, which is designed for such cases. The second option is simpler and quicker but bypasses the issue rather than solving it at its core.

#### Viewports option not working

When running Cypress in headless mode, the [Cypress.viewport](https://docs.cypress.io/api/commands/viewport) command (used internally by `@argos-ci/cypress`) may not behave as expected. This is because headless browsers don’t render a visible viewport, which can result in incorrect or inconsistent screenshots.

To ensure a consistent viewport size, configure it via `setupNodeEvents` in your `cypress.config.js`. This approach sets the viewport before the browser launches, avoiding visual regressions.

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

```ts
import { defineConfig } from "cypress";

export default defineConfig({
  // setupNodeEvents can be defined in either
  // the e2e or component configuration
  e2e: {
    setupNodeEvents(on, config) {
      on("before:browser:launch", (browser, launchOptions) => {
        if (browser.name === "chrome" && browser.isHeadless) {
          // fullPage screenshot size is 1400x1200 on non-retina screens
          // and 2800x2400 on retina screens
          launchOptions.args.push("--window-size=1400,1200");

          // force screen to be non-retina (1400x1200 size)
          launchOptions.args.push("--force-device-scale-factor=1");

          // force screen to be retina (2800x2400 size)
          // launchOptions.args.push('--force-device-scale-factor=2')
        }

        if (browser.name === "electron" && browser.isHeadless) {
          // fullPage screenshot size is 1400x1200
          launchOptions.preferences.width = 1400;
          launchOptions.preferences.height = 1200;
        }

        if (browser.name === "firefox" && browser.isHeadless) {
          // menubars take up height on the screen
          // so fullPage screenshot size is 1400x1126
          launchOptions.args.push("--width=1400");
          launchOptions.args.push("--height=1200");
        }

        return launchOptions;
      });
    },
  },
});
```

{% endcode %}

Reference: [Cypress Docs – Set screen size when running headless](https://docs.cypress.io/api/node-events/browser-launch-api#Set-screen-size-when-running-headless)

### Additional Resources

* [Quickstart with Argos + Cypress](/docs/quickstart/cypress-quickstart)
* [Example of Argos + Cypress](https://github.com/argos-ci/argos-javascript/tree/main/examples/cypress)
* [@argos-ci/cypress on GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/cypress)
* [@argos-ci/cypress on npm](https://www.npmjs.com/package/@argos-ci/cypress)


# WebdriverIO

Integrating Argos with your WebdriverIO tests to enable visual testing on your application.

[WebdriverIO](https://webdriver.io/) is a Node.js test automation framework for web and mobile applications, built on the WebDriver and WebDriver BiDi protocols. The `@argos-ci/webdriverio` SDK captures screenshots from your WebdriverIO tests so Argos can detect visual changes on every pull request.

### Get started

Follow the [WebdriverIO Quickstart](/docs/quickstart/webdriverio-quickstart) to set up Argos with WebdriverIO.

### API overview

#### argosScreenshot(browser, name\[, options])

* `browser` - A `WebdriverIO.Browser` instance.
* `name` - The screenshot name; must be unique. If it ends with `.png`, it is treated as a path.
* `options.mask` - Areas to mask when the screenshot is taken, as an array of rectangles (`{ x, y, width, height }`). Masked areas are overlaid with a box that completely covers them.
* `options.maskColor` - The color of the overlay box for masked areas, in CSS color format. Defaults to `#FF00FF`.

{% hint style="info" %}
Unlike the Playwright, Cypress, and Puppeteer SDKs, the WebdriverIO SDK captures the screenshot as-is: it does not apply Argos stabilization (waiting for loading, pausing GIFs, `data-visual-test` helper attributes). Stabilize the page in your test before calling `argosScreenshot`.
{% endhint %}

### Additional resources

* [WebdriverIO Quickstart](/docs/quickstart/webdriverio-quickstart)
* [@argos-ci/webdriverio on GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/webdriverio)
* [@argos-ci/webdriverio on npm](https://www.npmjs.com/package/@argos-ci/webdriverio)


# Puppeteer

Integrating Argos with your Puppeteer tests to enable visual testing on your application.

Puppeteer already offers a command to take screenshots. The official Argos Puppeteer integration uses it but also does several things:

* Ensuring all images are fully loaded.
* Ensuring all fonts are rendered.
* Confirming the absence of any `aria-busy` (loading) elements on the page.
* Concealing scrollbars.
* Obscuring text cursors or carets.
* Providing CSS utilities to simplify content hiding.

### Installation

{% stepper %}
{% step %}

### Install package

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

{% endstep %}

{% step %}

### Use in your tests

`argosScreenshot` command stabilizes the UI and takes a screenshot.

*How to take a screenshot with `argosScreenshot` command*

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

describe("Integration test with visual testing", () => {
  it("loads the homepage", async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto("http://localhost:3000");
    await argosScreenshot(page, "homepage");
    await browser.close();
  });
});
```

Screenshots are stored in `screenshots/argos` folder, relative to current directory.
{% endstep %}
{% endstepper %}

### API Overview

#### argosScreenshot(page, name\[, options])

* `page` - A `puppeteer` page instance
* `name` - The screenshot name; must be unique. If it ends with `.png`, it is treated as a path.
* `options` - See [Page.screenshot command options](https://pptr.dev/api/puppeteer.page.screenshot)
* `options.element` - Accept an ElementHandle or a string selector to screenshot an element
* `options.viewports` - Specifies the viewports for which to capture screenshots. See [viewports configuration](/docs/learn/how-to-guides/visual-coverage/responsive-viewports).
* `options.argosCSS`: Specific CSS applied during the screenshot process. More on [injecting CSS](/docs/learn/how-to-guides/visual-coverage/injecting-css)
* `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`.
* `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.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.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.tag`: Tag or array of tags to attach to the screenshot for filtering in Argos.

Unlike [Puppeteer's `screenshot` method](https://pptr.dev/api/puppeteer.page.screenshot), `argosScreenshot` sets the `fullPage` option to `true` by default. Override it if you prefer partial screenshots of your pages.

### 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 (`visibility: hidden`).
* `[data-visual-test="removed"]`: Removes the element from view (`display: none`).
* `[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>
```

### Additional Resources

* [Quickstart with Argos + Puppeteer](/docs/quickstart/puppeteer-quickstart)
* [@argos-ci/puppeteer on GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/puppeteer)
* [@argos-ci/puppeteer on npm](https://www.npmjs.com/package/@argos-ci/puppeteer)


# CLI

Use the Argos CLI to upload screenshots, deploy static builds, inspect builds and flaky tests, and submit reviews from scripts, local workflows, or AI agents.

The Argos command-line interface (CLI) uploads screenshots, deploys static builds, and lets you inspect and review builds from a terminal, a CI pipeline, or an AI agent. It is distributed as the [`@argos-ci/cli`](https://www.npmjs.com/package/@argos-ci/cli) npm package; the source code is available on [GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/cli).

The examples on this page call the `argos` binary directly. Depending on your package manager, run it with `npm exec -- argos`, `yarn run argos`, `pnpm exec -- argos`, or `bun x argos`.

### Installation

The CLI requires **Node.js 22 or later**. Install it as a dev dependency:

{% 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 %}

### Authentication

Authentication depends on where the CLI runs:

* **In CI**, set the `ARGOS_TOKEN` environment variable to your project token (from **Settings → General → Token**), usually as a CI secret. On GitHub Actions, you can also use [OIDC or tokenless authentication](/docs/learn/integrations/github-actions-authentication) to avoid managing a secret.
* **Locally**, sign in once with the browser-based login flow:

```bash
argos login
```

`argos login` authorizes the CLI and stores a user token on your machine. This token is used by commands that act as a user, such as submitting a review.

Commands resolve authentication in this order:

1. The `--token <token>` argument.
2. The `ARGOS_TOKEN` environment variable.
3. The token stored by `argos login`.

{% hint style="warning" %}
Do not use `argos login` in CI. CI uploads should use `ARGOS_TOKEN` or [GitHub OIDC authentication](/docs/learn/integrations/github-actions-authentication).
{% endhint %}

#### Project tokens and personal access tokens

Uploads and read-only commands — `build get`, `build snapshots`, `test list`, `test get`, `test changes`, `change list`, `project get`, `project deployments`, `project domain get`, `media list`, `media get`, `media versions` — work with a **project token**. Anything attributed to a user requires a **personal access token**, because the action is checked against that user's permissions: submitting or dismissing a review, requesting reviewers, posting comments (on builds, tests, and media alike), ignoring a change, configuring a project, and administering a team.

Two read-only commands are the exception: `project contributor list` and `review reviewer list` return users, so they need a personal access token to resolve them against — a project token carries no identity.

To create a personal access token manually, go to your personal account settings, open **Tokens**, then select **Generate new token**.

![Personal settings tokens page](/files/bb1f094cf54d5eef3adc9f7964dc85a49b9c5dbb)

*A personal settings tokens page*

## Commands

Run `argos <command> --help` for a command's exact arguments, flags, and defaults — the CLI is the source of truth for those. This page covers what each command is for, and the things `--help` can't tell you.

| Command                                     | What it does                                                        |
| ------------------------------------------- | ------------------------------------------------------------------- |
| `upload <directory>`                        | Upload snapshots from a directory and create a build.               |
| `finalize`                                  | Close a parallel build once every shard has uploaded.               |
| `skip`                                      | Create a skipped build so a required check still reports success.   |
| `deploy <directory>`                        | Deploy a static build (Storybook or any static site).               |
| `build get <buildReference>`                | Fetch a build's status, branch, commit, stats, and URL.             |
| `build snapshots <buildReference>`          | Fetch a build's snapshot diffs, with flakiness data on each.        |
| `build subscribe \| unsubscribe`            | Follow or stop following a build's notifications.                   |
| `test list`                                 | List a project's tests, flakiest first.                             |
| `test get <testId>`                         | Fetch a test with its flakiness metrics.                            |
| `test changes <testId>`                     | List a test's distinct changes, most frequent first.                |
| `test subscribe \| unsubscribe <testId>`    | Follow or stop following a test's notifications.                    |
| `change list`                               | List the changes currently ignored in a project.                    |
| `change ignore \| unignore <changeId>`      | Silence a flaky change, or bring it back under review.              |
| `review create \| list \| dismiss`          | Submit, list, and dismiss reviews on a build.                       |
| `review reviewer list \| add \| remove`     | List, request, and cancel review requests on a build.               |
| `comment <subcommand>`                      | List, post, and act on the comments on a build.                     |
| `test comment <subcommand>`                 | The same, on the comments on a test.                                |
| `project get \| update`                     | Read a project's settings, and change them one flag at a time.      |
| `project transfer`                          | Move a project to another account.                                  |
| `project contributor list \| set \| remove` | Manage who can reach a project outside the team's own roles.        |
| `project deployments`                       | List a project's deployments, most recent first.                    |
| `project domain get \| set`                 | Read or set the domain production deployments are served on.        |
| `automation <subcommand>`                   | List, create, replace, and deactivate a project's automation rules. |
| `account get \| update`                     | Read plan and usage, and set the role users get when they join.     |
| `account member <subcommand>`               | List a team's members, change their role, remove them.              |
| `account invite <subcommand>`               | Invite people, cancel invites, rotate the invite link.              |
| `account domain <subcommand>`               | Manage the email domains a team is open to.                         |
| `media upload <files...>`                   | Upload standalone images or videos and print their share URLs.      |
| `media list`                                | List a project's uploaded media, most recent first.                 |
| `media get \| update \| delete <mediaId>`   | Fetch, edit, or delete one uploaded media.                          |
| `media versions <mediaId>`                  | List a media's uploaded versions, newest first.                     |
| `media comment <subcommand>`                | List, post, edit, resolve and react to comments on a media.         |
| `login`, `logout`, `whoami`                 | Manage the CLI's user session.                                      |
| `create-project <name>`                     | Create a project in an account you administer.                      |
| `analytics`                                 | Fetch build and screenshot metrics for an account.                  |
| `help [command]`                            | Display the available commands and options.                         |

### Uploading from CI

`upload` is the command that creates a build. Point it at the directory holding your snapshots:

```bash
argos upload ./screenshots
```

By default it uploads `**/*.{png,jpg,jpeg}`; narrow or widen that with `--files` and `--ignore`. Several flags map onto concepts documented elsewhere:

* `--mode` switches between [build modes](/docs/learn/platform-fundamentals/build-modes) (`ci` by default, or `monitoring`).
* `--parallel`, `--parallel-total`, `--parallel-index` and `--parallel-nonce` drive [parallel testing](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding).
* `--subset` marks a [subset build](/docs/learn/how-to-guides/ci-pipelines/subset-builds).
* `--build-name` runs [multiple builds on a single commit](/docs/learn/how-to-guides/ci-pipelines/monorepos-setup).
* `--reference-branch` and `--reference-commit` pin the [baseline](/docs/learn/platform-fundamentals/baseline-build) instead of letting Argos resolve it.
* `--threshold` sets diff sensitivity between 0 and 1 — the higher the threshold, the less sensitive the comparison.

Most of these also read an `ARGOS_*` environment variable, which is usually how you set them in CI.

**`finalize`** closes a [parallel build](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) running in finalize mode (`ARGOS_PARALLEL_TOTAL=-1`). Run it once every upload has completed — Argos then aggregates the shards and starts the comparison:

```bash
argos finalize
```

The shards are matched by their nonce, read from `--parallel-nonce` or `ARGOS_PARALLEL_NONCE`. In most CI environments the nonce is detected automatically, so no flag is needed as long as `finalize` runs in the same pipeline as the uploads.

When every upload step is conditional — skipped by a task cache such as Turborepo or Nx, or by change detection — a run may produce no shard at all. Use `--skip-if-empty` to create a [skipped build](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) in that case, so a required Argos status check still reports success:

```bash
argos finalize --skip-if-empty --build-name unit
```

**`skip`** creates that same [skipped build](/docs/learn/how-to-guides/ci-pipelines/skipping-a-build) directly: no screenshots, no comparison, an immediately successful status. It keeps a required Argos check green on commits where you intentionally don't run visual tests. Use `--build-name` to match the check you want to satisfy.

#### Snapshot size limit

Each snapshot uploaded to Argos is limited to **50 MB**. This applies to every artifact type — a screenshot, a [non-image snapshot](#compare-non-image-files), or a [Playwright trace](/docs/reference/playwright). Files larger than 50 MB are skipped and won't appear in your build.

A build is also limited to **5,000 screenshots**. Beyond that, the upload is rejected with an error — use [parallel mode](/docs/learn/how-to-guides/ci-pipelines/parallel-testing-sharding) to split a larger test suite across several uploads.

#### Compare non-image files

Use `-f` or `--files` to upload text-based artifacts such as JSON, YAML, XML, HTML, Markdown, CSS, or JavaScript files. See [Compare non-image files](/docs/learn/how-to-guides/visual-coverage/compare-non-image-files) for examples and the full list of supported content types.

#### Specify the project

Use `--project <slug>` to set the Argos project slug (`account/project-name`). This disambiguates [tokenless authentication](/docs/learn/integrations/github-actions-authentication#tokenless-authentication) when multiple Argos projects are linked to the same repository:

```bash
argos upload ./screenshots --project my-account/my-project
```

#### Override Git detection

`argos upload` detects the commit, branch, and pull request from your CI environment — or from the local Git repository when running outside CI. To override detection, set these environment variables (there are no flag equivalents):

| Environment variable   | Description                                                                         |
| ---------------------- | ----------------------------------------------------------------------------------- |
| `ARGOS_COMMIT`         | Commit SHA of the build. Must be a full 40-character SHA — short SHAs are rejected. |
| `ARGOS_BRANCH`         | Branch of the build.                                                                |
| `ARGOS_PR_NUMBER`      | Number of the pull request associated with the build.                               |
| `ARGOS_PR_HEAD_COMMIT` | Head commit of the pull request.                                                    |
| `ARGOS_PR_BASE_BRANCH` | Base branch of the pull request.                                                    |

In a non-Git environment, `ARGOS_COMMIT` and `ARGOS_BRANCH` are required — without them the upload fails with "Argos requires a branch and a commit to be set".

To find the [baseline](/docs/learn/platform-fundamentals/baseline-build), Argos resolves ancestor commits. When your project is connected to GitHub or GitLab, this happens server-side. Otherwise the CLI fetches history from the `origin` remote — in a repository without `origin` (for example a local mirror), it falls back to the local history, so make sure enough history is available locally, or pin the baseline explicitly with `--reference-commit` and `--reference-branch`. Parent commits are always computed automatically and cannot be set manually.

To see what the CLI detected, run it with debug output:

```bash
DEBUG=@argos-ci/core argos upload ./screenshots
```

### Deploying a static build

`deploy` publishes a static site — a Storybook, a docs build, anything — to Argos. See [Deployments](/docs/learn/deployments) for the overview:

```bash
argos deploy ./storybook-static
```

By default this creates a **preview** deployment. Add `--prod` to force a **production** one regardless of the branch. If the branch matches the project's production branch pattern, it is created as production even without the flag — see [Environments](/docs/learn/deployments/environments) for the full rules.

`deploy` uses the same [authentication](#authentication) as `upload`.

### Inspecting builds and tests

These commands read data, so a project token is enough. A `<buildReference>` is a build number or a full Argos build URL; with a number, add `--project team/project`, since a URL already carries it.

```bash
argos build get https://app.argos-ci.com/team/project/builds/72652
argos build snapshots 72652 --project team/project --needs-review --json
```

`build get` returns the build's status, branch, commit, stats, and URL. `build snapshots` returns its diffs — each with a status, score, diff mask URL, baseline file, current file, and the metadata your SDK provided. Add `--needs-review` to get only the diffs awaiting a decision.

When a diff belongs to a tracked test it also carries that test's [flakiness metrics](/docs/learn/reliability-and-flakiness/flaky-test-detection) under `test.metrics`, and — when the diff is a change — its ignore state and occurrence count under `change`. `--metrics-period` sets the window those are computed over (`24h`, `3d`, `7d`, `30d`, `90d`; defaults to `7d`).

To start from the project rather than from a build, `test list` returns the tests currently running in it, flakiest first — the first page is your flakiness backlog:

```bash
argos test list --project team/project --limit 20 --json
argos test list --project team/project --build-name unit --search carousel
```

A test is listed when it appeared in the latest reference build of its build name, so tests that were deleted, renamed, or skipped drop out on their own.

A high occurrence count or flakiness score is a strong flakiness signal. Take the diff's `test.id` and look at the whole test — the CLI equivalent of the [test page](/docs/learn/reliability-and-flakiness/test-page):

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

`test get` returns the test's name, build name, and `ongoing` or `removed` status; its `metrics` for the period; a `series` of the same counts bucketed over time, which distinguishes a test that has always been flaky from one that started recently; and `firstSeenChange` / `lastSeenChange`.

`test changes` lists its distinct changes, the ones that came back most often first. Each carries its `id`, whether it is `ignored`, its `occurrences`, when it was `firstSeen` and `lastSeen`, and the `diff` of its latest occurrence with the mask, baseline, and captured screenshot URLs. `--ignored true|false` narrows the list to ignored or still-reviewable changes.

A `<testId>` carries the project name but not the account, so pass `--project owner/project` (or `ARGOS_PROJECT`) — unless you authenticate with a project token, which already identifies its own project.

### Silencing a flaky change

`change ignore` stops a flaky change from requiring review, auto-approving it on future builds. `change unignore` reverses that. Both are the CLI equivalent of the **Ignore** button in a build review — see [Ignore changes](/docs/learn/reliability-and-flakiness/flaky-test-detection#ignore-changes) — and both need a [personal access token](#project-tokens-and-personal-access-tokens) with review permission.

The `<changeId>` comes from a diff's `change.id` in `argos build snapshots --json`, or from `argos test changes --json`. Like a test id it doesn't carry the account, so pass the project:

```bash
argos change ignore <changeId> --project team/project
argos change unignore <changeId> --project team/project
```

The ignore feature must be enabled for the project (**Project Settings → Flaky detection**, on by default). Argos can also [ignore recurring flaky changes automatically](/docs/learn/reliability-and-flakiness/flaky-test-detection#automatically-ignore-recurring-flaky-changes).

`change list` audits what has been silenced so far, most recently ignored first, with the test each change belongs to. Pass any of those ids back to `change unignore` to bring it under review again:

```bash
argos change list --project team/project --json
```

To let an agent do the investigation instead, see [Fix flaky tests with AI agents](/docs/learn/reliability-and-flakiness/fix-flaky-tests-with-ai-agents).

### Reviewing and commenting

Every command in this group acts as a user, so they all require a [personal access token](#project-tokens-and-personal-access-tokens).

```bash
argos review create <buildReference> --event approve
argos review create <buildReference> --event reject --body "The header spacing regressed."
argos review list <buildReference> --json
argos review dismiss <buildReference> <reviewId>
```

`--event` is one of `approve`, `reject`, or `comment`, and `--body` / `--body-file` attach a Markdown summary. Dismissing a review stops it counting toward the [build status](/docs/learn/review-workflow/review-a-build#how-reviews-decide-the-build-status).

Comments live in two groups: `comment` for a [build's review discussion](/docs/learn/review-workflow/review-a-build#comment-on-exactly-what-changed), and `test comment` for a [test's own thread](/docs/learn/reliability-and-flakiness/test-page#discuss-the-test-with-your-team). Both take the same subcommands — `list`, `create`, `get`, `edit`, `delete`, `resolve`, `unresolve`, `react`, `unreact`, `subscribe`, `unsubscribe`:

```bash
argos comment list <buildReference> --json
argos test comment create <testId> --project team/project --body "Flaky since the carousel landed."
```

Run `argos comment --help` or `argos test comment --help` for each subcommand's arguments.

`review reviewer` handles the review requests standing on a build. Requesting someone notifies them; requesting someone already requested is a no-op, and users without access to the project are ignored:

```bash
argos review reviewer list <buildReference> --json
argos review reviewer add <buildReference> <userId> <userId>
argos review reviewer remove <buildReference> <userId>
```

User ids come from `argos account member list --json` or `argos whoami --json`. You cannot request yourself.

To follow a build or a test without commenting on it, subscribe to its notifications. Unsubscribing is recorded as intentional, so Argos won't subscribe you again automatically:

```bash
argos build subscribe <buildReference>
argos test unsubscribe <testId> --project team/project
```

### Configuring a project

`project get` prints a project's settings; `project update` changes them. Only the settings you pass are touched, and every one takes an explicit value — so a script never has to guess whether an omitted flag means "false" or "leave alone":

```bash
argos project get --project team/project --json
argos project update --project team/project --summary-check auto --auto-ignore-after 3
argos project update --project team/project --default-user-level reviewer --deployments true
```

Run `argos project update --help` for the full list. A few conventions are worth knowing:

* Nullable settings reset to their inherited default when passed an empty value: `--default-base-branch ""` falls back to the repository's default branch.
* `--private inherit` returns the project's visibility to whatever the linked repository says.
* `--ignore-changes` and `--auto-ignore-after` drive [flaky change detection](/docs/learn/reliability-and-flakiness/flaky-test-detection); `--auto-ignore-after off` turns automatic ignoring off while leaving manual ignoring on.

`project contributor` grants access to users who aren't covered by their team role — team owners and members already reach every project, so this is for contributors:

```bash
argos project contributor list --project team/project --json
argos project contributor set <userId> --level reviewer --project team/project
argos project contributor remove <userId> --project team/project
```

Levels are `admin`, `reviewer`, and `viewer`. Revoking your own access never requires administrator rights — a contributor can always walk away from a project.

`project transfer` moves a project to another account, optionally renaming it on the way. You must administer both the project and the account receiving it:

```bash
argos project transfer --project team/project --to other-team --name web
```

`project deployments` and `project domain` cover [deployments](/docs/learn/deployments): the deployments a project has published, and the [custom domain](/docs/learn/deployments/urls-and-domains) its production ones are served on. Only domains under `argos-ci.live` are accepted.

```bash
argos project deployments --project team/project --environment production --json
argos project domain set acme-web.argos-ci.live --project team/project
```

### Automation rules

`automation` manages the [automation rules](/docs/learn/review-workflow/automations) that run when a build event matches. Rules are never deleted — deactivating one keeps its run history, which is what tells you why something fired:

```bash
argos automation list --project team/project --active true --json
argos automation get <ruleId> --project team/project
argos automation deactivate <ruleId> --project team/project
```

`create` and `update` take the rule definition as JSON, inline with `--definition` or from a file with `--definition-file`. `update` replaces the whole definition, so send the events, conditions, and actions you want the rule to end up with:

```bash
argos automation create --project team/project --definition-file rule.json
```

```json
{
  "name": "Notify on regressions",
  "events": ["build.completed"],
  "conditions": [{ "type": "build-conclusion", "value": "changes-detected" }],
  "actions": [
    { "type": "sendSlackMessage", "payload": { "name": "argos-alerts" } }
  ]
}
```

Action targets must belong to the project's account: a Slack channel connected to it, or the id of a Microsoft Teams or Discord webhook registered on it. Run `argos automation create --help` for the example inline.

### Managing a team

`account get` reports an account's plan and its usage for the current billing period — screenshots consumed against the plan, the consumption ratio, and the cost accrued beyond it. It's the CLI equivalent of watching your usage before it runs over:

```bash
argos account get --account my-team --json
```

`account member` lists a team's members and changes what they can reach:

```bash
argos account member list --account my-team --levels owner,member --json
argos account member set-level <userId> --level member --account my-team
argos account member remove <userId> --account my-team
```

Roles are `owner`, `member`, and `contributor` — owners administer the team, members see every project, contributors only the projects they are added to. A team can never be left without an administrator: the last member cannot be removed, and removing the second-to-last one promotes the survivor to owner.

`account invite` covers everything that brings someone in. Re-inviting an address that already has a pending invite refreshes it, so a lost invite can always be resent:

```bash
argos account invite create dev@acme.com --account my-team --level contributor
argos account invite list --account my-team --json
argos account invite cancel <inviteId> --account my-team
argos account invite reset-link --account my-team
```

`reset-link` rotates the team's shared invite link and invalidates the previous one.

`account domain` opens a team to an email domain, so anyone signing up with a verified address on it joins automatically, at the team's default role. Public email providers are refused, and you must hold a verified address on the domain yourself:

```bash
argos account domain add acme.com --account my-team
argos account domain list --account my-team --json
argos account domain remove acme.com --account my-team
```

Removing a domain only stops new sign-ups from joining — members who already joined through it stay. `account update --default-user-level <member|contributor>` sets the role given to everyone who joins through the invite link or a verified domain.

Every command in this section needs a [personal access token](#project-tokens-and-personal-access-tokens) with administrator rights on the team, and takes the account from `--account <slug>` or `ARGOS_ACCOUNT`.

### Paginated lists

`test list`, `change list`, `account member list`, `account invite list`, `project contributor list`, `project deployments`, and `automation list` follow pagination for you, up to `--limit` (100 by default). Raise it to walk a longer list:

```bash
argos project deployments --project team/project --limit 500 --json
```

### Managing your session

`login`, `logout` and `whoami` manage the CLI's user session — see [Authentication](#authentication):

```bash
argos login   # Log in to Argos by opening your browser
argos whoami  # Display the user authenticated with the current token
argos logout  # Log out from Argos
```

### Sharing images and videos

`media upload` uploads a standalone image or video — no build, no test run — and prints a share URL with ready-to-paste Markdown:

```bash
argos media upload before.png after.png
```

Add `--pr 1234` to publish to an existing pull request, or `--branch <branch>` while you are still working: the media is **staged**, and Argos publishes it — and posts a single managed comment listing every media — by itself once a pull request opens for that branch. Neither flag is inferred from the environment, CI included.

Re-uploading the same file name adds a **version** rather than a second media: the share URL keeps pointing at the newest upload, so Markdown already posted to a pull request never goes stale. A file named `checkout-before.png` uploads as `checkout.png` labelled `before` and pairs with its `after` for side-by-side comparison; `--state` sets the label for files not named that way.

Images are converted to WebP before upload — `--no-compress` opts out — while the media keeps your file's name and extension. Copy the Markdown the command prints rather than writing your own. For a video it is a poster frame wrapped in a link, which is the only form GitHub renders — an inline player only works for media GitHub hosts itself.

The rest of the group: `media list` filters by `--branch`, `--pr`, `--stage staged|published`, `--search` and `--type image|video`; `media update` edits a **staged** media's name, description or branch, which are fixed once it is published; `media versions` lists the uploads behind a media, newest first.

Media belongs to a project and inherits its access, its share page included: without `--visibility`, a public project's media is `public` and a private project's is `team`. `media upload` and `media list` accept either token type; with a [personal access token](#project-tokens-and-personal-access-tokens) pass `--project <owner/project>` or set `ARGOS_PROJECT`. `media delete` needs project administrator rights, since deleting a media breaks any share link already pasted somewhere.

A human can pin a comment to a point on an uploaded screenshot, which is how an agent gets told what to change about an image it cannot see:

```bash
argos media list --branch feat/checkout   # find what was uploaded for the branch
argos media comment list <mediaId>        # open threads, each with its pinned coordinates
argos media comment resolve <mediaId> <commentId>
```

A comment records the media **version** it was written against — `media versions` resolves it to the right file once the media has been re-uploaded. Every `media comment` command, reading included, needs a personal access token: a comment has an author.

See [Media sharing](/docs/learn/media) for retention, visibility and billing.

### Account commands

`create-project` creates a project in an account you administer:

```bash
argos create-project my-new-project --account my-team
```

`analytics` fetches build and screenshot metrics for an account. It reports totals and a per-period series for both screenshots and builds — including how many detected changes and how many were accepted or rejected — broken down by project:

```bash
argos analytics --account my-team --from 2026-01-01 --group-by month --json
```

`--from` defaults to 30 days ago and `--to` to now, with a range capped at 365 days; `--group-by` buckets by `day`, `week`, or `month`; and `--project` filters by project name, repeated for several. Both `create-project` and `analytics` take the account from `--account <slug>` or `ARGOS_ACCOUNT`, and need a [personal access token](#project-tokens-and-personal-access-tokens) scoped to it.

See [Analytics](/docs/learn/account-and-access/analytics) for the dashboard view of these metrics.

## AI agent skills

The [`argos-javascript`](https://github.com/argos-ci/argos-javascript) repository includes skills that help AI agents use Argos CLI commands and review pull requests with Argos build data:

* [`argos-cli`](https://github.com/argos-ci/argos-javascript/tree/main/skills/argos-cli): use Argos CLI commands, flags, authentication, and output formats.
* [`argos-pr-review`](https://github.com/argos-ci/argos-javascript/tree/main/skills/argos-pr-review): review a pull request with an Argos build as visual evidence.
* [`argos-upload`](https://github.com/argos-ci/argos-javascript/tree/main/skills/argos-upload): share a screenshot or a screen recording by link, and embed it in a pull request.

See [Review builds with AI agents](/docs/learn/review-workflow/review-builds-with-ai-agents) to install and use the skills in a pull request review workflow.


# Node.js SDK

Upload screenshots and standalone media programmatically from Node.js scripts, or build your own Argos integration with @argos-ci/core.

`@argos-ci/core` is the low-level SDK every Argos integration builds on. Use it to upload screenshots from your own Node.js scripts or to build a custom integration. It is available as an [npm package](https://www.npmjs.com/package/@argos-ci/core); the source code is on [GitHub](https://github.com/argos-ci/argos-javascript/tree/main/packages/core).

### Installation

```bash
npm install --save-dev @argos-ci/core
```

### Usage

To upload screenshots from a `./screenshots` directory, use the `upload` function:

```js
import { upload } from "@argos-ci/core";

await upload({ root: "./screenshots" });
```

`upload` accepts the same options as the [CLI `upload` command](/docs/reference/argos-command-line-interface-cli#uploading-from-ci) — files globs, build name, mode, parallel settings, threshold, and more.

### Uploading standalone media

`uploadMedia` uploads images or videos on their own — no build, no comparison — and returns each one's share URL and a ready-to-paste Markdown embed:

```js
import { uploadMedia } from "@argos-ci/core";

const [media] = await uploadMedia({
  files: ["checkout-after.png"],
  // Stage the media on the branch: Argos publishes it — and posts the pull
  // request comment — by itself once a pull request opens for that branch.
  branch: "feat/checkout",
});

// The `-after` suffix is lifted off the name: this media is
// `checkout.png`, labelled `after`.
console.log(media.url); // https://app.argos-ci.com/m/…
console.log(media.markdown); // ![checkout.png](https://app.argos-ci.com/m/…)
```

Re-uploading the same name adds a **version** and keeps the URL, so Markdown already posted to a pull request shows the newest upload. Options mirror the [CLI `media upload` command](/docs/reference/argos-command-line-interface-cli#sharing-images-and-videos): `token`, `project`, `branch`, `prNumber`, `state`, `description`, `visibility` (defaults to the project's — `public` for a public project, `team` for a private one), and `compress` (`true` by default — images are converted to WebP before upload). It returns one media per file, uploaded sequentially in input order. See [Media sharing](/docs/learn/media) for retention, visibility and billing.

### API reference

For a detailed breakdown of the available functions and options, see the [SDK reference documentation](https://js-sdk-reference.argos-ci.com/).




---

[Next Page](/docs/llms-full.txt/1)

