Matrix Generation

The Matrix System is Reshot's core differentiator. It automatically generates all combinations of your variants—no Playwright scripts required.

The Problem

Your product has:

  • 3 user roles (Admin, User, Guest)
  • 4 languages (English, German, French, Korean)
  • 2 themes (Light, Dark)

That's 24 variations for every screenshot. Writing and maintaining 24 Playwright scripts is a nightmare.

The Solution

Define your dimensions once:

JSON
{
  "variants": {
    "dimensions": {
      "role": {
        "options": { "admin": {...}, "user": {...}, "guest": {...} }
      },
      "locale": {
        "options": { "en": {...}, "de": {...}, "fr": {...}, "ko": {...} }
      },
      "theme": {
        "options": { "light": {...}, "dark": {...} }
      }
    }
  }
}

Run one command:

Terminal
reshot run --all-variants

Get 24 looping GIFs, automatically organized, automatically hosted.

Use Cases

  • Theme: Dark mode, light mode, high contrast
  • Locale: Different languages (en, ko, de, ja)
  • Role: Admin, user, guest permissions
  • Device: Desktop, tablet, mobile viewports

Defining Dimensions

Coverage flows
Each declared dimension becomes a column in Coverage → Flows, so you can see which flow × context pairs have a capture and which do not.

Configure variants in reshot.config.json:

JSON
{
  "variants": {
    "dimensions": {
      "theme": {
        "label": "Theme",
        "options": {
          "dark": {
            "name": "Dark Mode",
            "inject": [
              { "method": "localStorage", "key": "theme", "value": "dark" },
              {
                "method": "cookie",
                "name": "theme_preference",
                "value": "dark"
              }
            ]
          },
          "light": {
            "name": "Light Mode",
            "inject": [
              { "method": "localStorage", "key": "theme", "value": "light" }
            ]
          }
        }
      },
      "locale": {
        "label": "Language",
        "options": {
          "ko": {
            "name": "Korean",
            "inject": [
              {
                "method": "browser",
                "locale": "ko-KR",
                "timezone": "Asia/Seoul"
              }
            ]
          },
          "en": {
            "name": "English",
            "inject": [
              {
                "method": "browser",
                "locale": "en-US",
                "timezone": "America/New_York"
              }
            ]
          }
        }
      }
    }
  }
}

Choosing which contexts a flow is captured in

Declaring a dimension makes an axis available. A flow opts into an axis by naming it in scenarios[].variants.dimensions — an allow-list of dimension keys:

JSON
{
  "scenarios": [
    {
      "key": "checkout",
      "name": "Checkout",
      "url": "/checkout",
      "variants": { "dimensions": ["theme", "locale"] },
      "steps": []
    }
  ]
}

The expansion rule

There are two expansion rules in the CLI today and which one applies is decided by how the run was invoked, not by config alone. Read both before you rely on a coverage count.

Rule A — what reshot run actually does. When any dimension is declared in the top-level variants.dimensions, reshot run builds the cartesian product of every declared dimension and runs every selected flow once per combination. The per-flow allow-list below is not consulted on this path. This is what lands on disk, and it is what the expected flow × context set sent to the merge gate is computed from.

Rule B — what scenarios[].variants.dimensions means. The scenario runner honours the per-flow allow-list, but only when no variant override is in play — which the default reshot run path never satisfies.

Rule B, in full, for when it does apply:

  • Every dimension named in variants.dimensions expands across all of its declared options.

  • Named dimensions are combined as a full cartesian product. Two axes with 2 and 4 options produce 8 captures of that flow.

  • A dimension you do not name is not varied. The flow is captured once on that axis, in whatever state the application defaults to — it is not expanded across every option.

  • An empty or absent variants.dimensions means the flow is captured exactly once, unvaried.

  • A key that names a dimension which does not exist, or one with no options, is dropped silently. If every named key drops, the flow is captured once.

  • variants.dimensions is an allow-list of axes, not a selection of options. To pin a flow to one option, use variant, which is merged underneath the expanded combination:

    JSON
      {
    

"variant": { "role": "admin" }, "variants": { "dimensions": ["theme"] } }

captures the admin role in every theme.

Excluding a context

Some combinations are deliberately not verified — a role that cannot reach a route, a locale a flow does not support. Without a way to say so, "excluded" and "uncovered" look identical in Coverage. variants.exclude says it:

JSON
{
  "variants": {
    "dimensions": ["role", "theme", "locale"],
    "exclude": [
      { "role": "member", "theme": "dark", "locale": "en-US" },
      { "role": "guest" }
    ]
  }
}

Each entry is a dimension → option map. A combination is excluded when every pair in the entry matches it, so a partial entry excludes a whole slice: the second entry above drops every guest context, whatever theme and locale.

Injection Methods

MethodDescriptionExample
localStorageSets a localStorage key{ "method": "localStorage", "key": "theme", "value": "dark" }
cookieSets a browser cookie{ "method": "cookie", "name": "session", "value": "abc123" }
urlParamAdds URL parameter{ "method": "urlParam", "key": "lang", "value": "ko" }
browserBrowser settings{ "method": "browser", "locale": "ko-KR", "timezone": "Asia/Seoul" }

Running with Variants

All Variants

Terminal
reshot run --all-variants

Specific Variant

Terminal
reshot run --variant "theme:dark"

Multiple Dimensions

Terminal
reshot run --variant "theme:dark,locale:ko"

Output Structure

Assets are organized by variant:

Text
.reshot/output/
├── default/
│   └── homepage.png
├── theme-dark/
│   └── homepage.png
├── theme-light/
│   └── homepage.png
└── locale-ko/
    └── homepage.png

Accessing Variants via CDN

If using Reshot Platform, access variants with query parameters:

Text
https://cdn.reshot.dev/v1/assets/[project]/[visual-key]?context=dark
https://cdn.reshot.dev/v1/assets/[project]/[visual-key]?context=ko-KR