Figma Dev Comparison
Figma Dev Comparison lets your Applitools Eyes tests use a Figma design directly as a visual baseline. Instead of manually re-approving a baseline every time a design changes, you link a test to a Figma frame, component, or component set. Eyes automatically detects when that design changes and compares your implementation against it, otherwise falling back to your last accepted baseline.
Why Use Figma Dev Comparison
Keeping an implementation visually aligned with its design is normally a manual process. When a designer updates a Figma frame, someone has to notice the change, re-run the affected tests, and manually accept a new baseline before those tests are trustworthy again. That delay means visual differences between what was designed and what shipped can go unnoticed, and teams end up spending time on manual re-baselining instead of building.
Figma Dev Comparison closes that gap by making the design itself part of your test's baseline logic. Point a test at a live Figma URL, and Eyes resolves the design, sizes the test viewport to match it, and decides when a comparison against the design is actually needed, so differences between design and implementation surface automatically.
How It Works
Figma Dev Comparison works by mapping each Eyes test's name to a Figma design URL, either by declaring a figmaBaselines map (test name → URL, or an array of URLs for multi-step tests) in your project configuration, or by calling the setup function directly in the test with a single URL. Before your test runs, Eyes looks up the test's name in that map and resolves the linked Figma node, using it to size the viewport and establish the baseline for comparison.
A test name that isn't found in the map is not an error. Eyes logs a note that no Figma baseline entry was found for that test, and the test simply runs as an ordinary regression test against its own accepted baseline — nothing about the test fails or is skipped because of it. If Figma "seems to do nothing," this is the first thing to check: the key in your map has to match the test name (or story name) exactly as Eyes resolves it, which varies slightly by SDK — see each code example below.
Mapping a test to an empty string ("") opts it out explicitly in the same way, and leaves any baseline environment name you've set yourself untouched. The one shape that is an error: a multi-step array made up of nothing but empty strings. A single "" is read as an intentional opt-out; an array of nothing else is read as a mistake, since there's no design anywhere to establish a baseline from. A non-figma.com URL also throws immediately as a validation error.
To make Figma API calls on your behalf, the integration requires a Figma personal access token with file_content:read scope. You can provide this by setting the FIGMA_ACCESS_TOKEN environment variable globally, in a .env file in your project, or by passing an accessToken value directly in figmaOptions when linking a test. If neither is set and offline/cache-only mode isn't enabled, the call fails with an error. Precedence is evaluated per setting, not per object — setting only mode in figmaOptions, for example, does not turn off the FIGMA_ACCESS_TOKEN environment-variable fallback for the token, or for anything else you didn't explicitly set.
By default, this reconciliation uses auto-baseline mode: a test compares against the linked Figma design only until an implementation is accepted for that test. Once accepted, that implementation becomes the baseline, and subsequent runs compare against it instead, skipping the design entirely, until the linked Figma design itself changes and a new comparison is accepted, at which point the test compares against the updated design again. This way, you get design-fidelity checks while a screen is being built, and fast, stable regression testing once it's done, without switching modes or editing your test code in between.
Every run of a Figma-linked test also triggers a short, throwaway Eyes test behind the scenes that renders the design and establishes it as the reference image; it's deleted immediately afterward, so you may briefly see an extra test appear and disappear in your dashboard. This is expected and not a sign of anything wrong.
Key Benefits
- Automatic design-drift detection: your tests compare against the Figma design only when it has actually changed, so you're not relying on someone to notice and manually re-baseline.
- Minimal setup: a one-time setup call links a test to a design; the rest of your test stays the same.
- Flexible comparison modes: choose whether a test should always follow the live design, always follow its last accepted baseline, or automatically switch between the two.
- Multi-step test support: for tests with multiple checkpoints, map an ordered list of Figma URLs to steps, each cached and compared independently. Steps without a finished design yet can use a placeholder that compares against a blank canvas tagged
(no design)so later steps keep their positions. - Accurate viewports, automatically: the browser/device viewport used for each test is sized to match the Figma design, so you don't have to configure it by hand.
- Design context in your dashboard: every run captures the design's name, type, file revision, last-modified time, and the mode it ran under as searchable, filterable properties, plus a link on each step straight back to the Figma frame it was compared against.
- Efficient by design: Figma API calls are batched per file (not per URL you link) and cached on disk, and rate-limit handling is built in, so the integration stays lightweight even across large test suites.
What's Supported Today
- Design-to-implementation comparison: Figma Dev Comparison compares a Figma design against your running implementation. It isn't a general design-QA tool, and it doesn't validate or edit Figma files.
- Web-based SDKs and frameworks: support currently covers:
- JavaScript/TypeScript: Playwright (Fixtures and Standard), Cypress, Storybook, Selenium, WebdriverIO
- Java: Selenium, Playwright
- Python: Selenium, Playwright
- .NET: Selenium, Playwright
Coming later: native mobile support (iOS and Android) in Appium, XCUI, and Espresso is planned for a future release.
Configuration Options
Use these options to control how Figma Dev Comparison authenticates, resolves designs, sizes viewports, and caches results.
| Option | Type | Default | Description |
|---|---|---|---|
accessToken | string | undefined | Falls back to the FIGMA_ACCESS_TOKEN environment variable | Your Figma personal access token, used to authenticate calls to the Figma API. |
mode | "figma-baseline" | "test-baseline" | "auto-baseline" | "disabled" | undefined | "auto-baseline" | Controls how comparisons are reconciled: auto-baseline compares against the design only when it has changed since the last accepted baseline; figma-baseline always compares against the current design, even if a newer non-Figma baseline was accepted since; test-baseline always compares against your accepted test baseline (the viewport and dashboard metadata are still taken from the design, but no design image is fetched); disabled turns the integration off entirely, and returns before even validating figmaBaselines. Equivalent to setting the APPLITOOLS_FIGMA_MODE environment variable — this option takes precedence if both are set. An unrecognized value throws. |
maxViewportHeight | number | undefined | 800 | The cap applied to the combined test viewport height, not each step individually. Eyes first sizes each step to its own design image (after any minViewportSize padding), then takes the bounding box across every real step — the widest width and the tallest height — and caps only that final height at this value. Width is never capped, since width is the form factor the design and the implementation have to share exactly, while a design's content can be arbitrarily tall. |
minViewportSize | number | null | undefined | null (no minimum) | A single dimension applied independently to each axis of a step's viewport, not a width×height pair. Setting it to 320 means "at least 320 wide and at least 320 tall," so a 100×900 design becomes 320×900 and a 100×100 design becomes 320×320. A design already above the floor on both axes is untouched. This exists because the Ultrafast Grid rejects viewports below its own supported minimum, which a small, component-sized design (a single button or icon) commonly falls under; a design centered under this floor renders on a white canvas of the floored size. |
maxRateLimitWaitSeconds | number | undefined | 60 | The maximum total time, in seconds, to wait across all retries after Figma returns a rate-limit (429) response. Each retry honors the Retry-After value Figma returns; once the cumulative wait would exceed this budget (or a single Retry-After already does), the call fails rather than sleeping through it. |
nodesCacheTtlSeconds | number | undefined | 120 | How long, in seconds, design metadata (/nodes) responses are cached before being refreshed. |
tempPath | string | undefined | os.tmpdir() (your system's default temp directory) | Where cached design images and metadata are stored on disk. Rendered images are cached keyed by file + node + the Figma file's last-modified timestamp, so an edit in Figma invalidates only the affected entries automatically — there's nothing to clear by hand. Because last-modified is a property of the whole file, editing any part of a file re-renders every frame taken from it. All nodes from one Figma file are fetched in a single metadata call, and all cache misses render in a single image call, so API usage scales with the number of distinct Figma files you link, not the number of URLs. |
Environment Variables
These environment variables provide defaults and alternate ways to configure Figma Dev Comparison without changing code. Each is the fallback for the matching configuration option above, except APPLITOOLS_FIGMA_SKIP_API, which has no code-level equivalent — it's an offline/debug switch, not a per-test option.
| Variable | Description |
|---|---|
FIGMA_ACCESS_TOKEN | Your Figma personal access token. Used as a fallback when accessToken isn't passed in figmaOptions. Not required when APPLITOOLS_FIGMA_SKIP_API is set. This is the only variable without the APPLITOOLS_ prefix. |
APPLITOOLS_FIGMA_MODE | Sets the reconciliation mode: auto-baseline (default), figma-baseline, test-baseline, or disabled. Equivalent to the mode option; the option takes precedence if both are set. An invalid value throws an error. |
APPLITOOLS_FIGMA_SKIP_API | Set to 1 or true to skip all Figma API calls and serve results from the disk cache only; no access token is required when this is set. A cache miss under this flag throws an error rather than falling back to a live call. Useful for avoiding API quota usage after an initial warm-up run. |
APPLITOOLS_FIGMA_TEMP_PATH | Fallback for the tempPath option, where the design-image and metadata cache lives on disk. |
APPLITOOLS_FIGMA_NODES_CACHE_TTL | Fallback for the nodesCacheTtlSeconds option, in seconds. |
Design Metadata & Dashboard
Every run of a Figma-linked test (in every mode except disabled) attaches these properties, so you can search and filter for them in your dashboard:
| Property | Value |
|---|---|
figma:name | Node name, e.g. "Login Screen" |
figma:type | Node type, e.g. FRAME, COMPONENT |
figma:file-revision | Figma file version id at the time of the fetch |
figma:file-last-modified | ISO 8601 timestamp of the file's last edit |
figma:mode | The reconciliation mode the run resolved to |
figma:file-revision and figma:file-last-modified describe the Figma file, not the individual frame, so every frame taken from the same file reports identical values.
Each step of a Figma baseline is also tagged with the design's URL, so a reviewer can go straight from a test step in the dashboard to the exact frame it was compared against. Figma's t= share-tracking parameter is stripped from that tag, so re-copying a link doesn't rename the step and orphan its accepted baseline. A placeholder step — one mapped to an empty string in a multi-step array — is tagged (no design) rather than a URL.
Good to Know
- The design's size wins over anything you configure. Setting your own viewport size has no effect in any mode except
disabled; see the Configuration Options table above for exactly how the size is derived. This holds true across all SDKs, including Storybook. - While you're iterating on an unfinished implementation, your test will have real differences from the design by definition, and most SDKs throw on a diff by default. Use your SDK's non-throwing close (for example,
eyes.close(false),eyes.Close(false),eyes.close(raise_ex=False), orrunner.getAllTestResults(false)), so those expected differences don't fail your build while a screen is still being built. - Figma rate-limits aggressively. If you're linking many designs from the same file, the batching described above keeps you well under typical limits; if you do hit one, raising
maxRateLimitWaitSecondsgives retries more leeway before failing.
Getting Started
To get started, ensure you have the latest Eyes SDK version and a Figma access token with the file_content:read scope.
- Add your Figma token as a
FIGMA_ACCESS_TOKENenvironment variable globally, in a.envfile in your project, or set it via theaccessTokenconfiguration option. - In Figma, right-click a design and select Copy/Paste as → Copy link to selection. Or, highlight/focus the design you want and copy the URL from the browser's address bar if using Figma web.
- Map your Figma design URLs to your Eyes test names (e.g., eyes.open(appName, testName), config.setTestName('testName'), or test('Playwright Fixtures testName', async (Page, Eyes)).
- See the code examples below.
- Run your tests and view the results. Eyes will display the Figma design as the baseline on the left and compare it to your implementation on the right.
- Once you're happy with your design-to-implementation comparison, approve and save the implementation. This becomes your active baseline, and all subsequent executions will be compared against it.
- However, if any of your designs change after you accept the implementation, this workflow will repeat and start comparing the design to the implementation again. You can disable this behavior by setting
figmaOptionsmode to either"test-baseline"or"disabled".
- However, if any of your designs change after you accept the implementation, this workflow will repeat and start comparing the design to the implementation again. You can disable this behavior by setting
You may not want this behavior when running your tests in CI. You can wrap a conditional around figmaOptions, such as figmaOptions: { mode: process.env.CI ? 'disabled' : 'auto-baseline' } (adjust to your language), or set this environment variable in your CI platform: APPLITOOLS_FIGMA_MODE='disabled'
Code Examples
Verified code examples for each SDK are added here. In the meantime, reach out to your Applitools contact or support if you don't see example code for your language and framework.
JavaScript/TypeScript
Java
Python
C#
// Implement globally in applitools.config.js
figmaBaselines: {
'Dashboard Screen': 'https://www.figma.com/design/...',
'Login Screen': [
'https://www.figma.com/design/...',
'',
'https://www.figma.com/design/...',
],
about: '',
},
figmaOptions: { mode: 'auto-baseline' }
// Implement in a specific spec
cy.eyesOpen({
appName: 'Hello World!',
testName: 'Login Screen',
figmaBaselines: {
'Login Screen': [
'https://www.figma.com/design/...',
'',
'https://www.figma.com/design/...',
],
},
figmaOptions: { mode: 'auto-baseline' },
});
figmaBaselines/figmaOptions passed to cy.eyesOpen replace applitools.config.js's values outright for that test, rather than merging field by field — if you set figmaOptions at the per-test level, include every field you need there, not just the one you're changing. The test-name key Eyes matches against for Cypress is the Mocha it(...) title (the enclosing describe isn't part of it); pass testName to cy.eyesOpen to pin it explicitly instead.
setFigmaBaselines takes a single Object, and accepts four shapes. This same API is used by both the Selenium Java and Playwright Java SDKs.
// Implement in a specific spec
// setFigmaBaselines takes a single Object, and accepts four shapes.
// 1. A URL, applied to every test:
eyes.configure().setFigmaBaselines(DESIGN_URL);
// 2. A map keyed by test name:
Map<String, Object> baselines = new HashMap<>();
baselines.put("hello world", DESIGN_URL);
// 3. A List<String> value -- one design per checkpoint of a multi-step test, in check() order:
baselines.put("checkout flow", Arrays.asList(
"https://www.figma.com/design/...",
"https://www.figma.com/design/...")
);
// 4. "" inside that list is a step whose design doesn't exist yet. The checkpoint
// still runs, against a blank white image at the Figma viewport, tagged "(no design)":
baselines.put("checkout flow", Arrays.asList(url1, "", url2));
baselines.put("about", ""); // skip the 'about' test
eyes.configure().setFigmaBaselines(baselines);
eyes.configure().setFigmaOptions(new FigmaOptions().setMode("auto-baseline"));
Both examples above apply the same way whether you're on the Selenium or Playwright Java bindings; nothing Figma-specific is implemented per-binding, and figmaBaselines/figmaOptions are resolved by the shared bundled core, which is also why the resulting behavior and log lines are identical across every language on this page.
# One design for every test:
FIGMA_BASELINES = "https://www.figma.com/design/<file>/<name>?node-id=1-2"
# Per test, keyed by the test name:
FIGMA_BASELINES = {
"test_hello_world": "https://www.figma.com/design/...",
"test_checkout": [ # one URL per check, in the order the test checks
"https://www.figma.com/design/...",
"", # step with no design yet: compared against a blank white image, tagged "(no design)"
"https://www.figma.com/design/...",
],
"test_about": "", # opted out; a list of only "" is an error instead
}
conf = eyes.get_configuration()
conf.set_figma_baselines(FIGMA_BASELINES)
# Add Figma Options
from applitools.selenium import FigmaOptions
conf.set_figma_options(
FigmaOptions(mode="auto-baseline")
)
eyes.set_configuration(conf)
Note the last line: eyes.get_configuration() returns a copy, so calling conf.set_figma_baselines(...) or conf.set_figma_options(...) has no effect until you call eyes.set_configuration(conf) again with the updated copy.
var config = eyes.GetConfiguration();
config.FigmaBaselines = new Dictionary<string, object>
{
{ "Dashboard Screen", "https://www.figma.com/design/<file>/<name>?node-id=1-2" },
{ "Login Screen", new List<string>
{
"https://www.figma.com/design/...", // compare checkpoint 1
"", // skip checkpoint 2
"https://www.figma.com/design/...", // compare checkpoint 3
}
},
{ "about", "" }, // skip the 'about' test
};
config.FigmaOptions = new FigmaOptions { Mode = "auto-baseline" };
eyes.SetConfiguration(config);
Both examples above apply the same way regardless of binding; FigmaBaselines/FigmaOptions live in Eyes.Images, the base package shared by every .NET Eyes SDK — this same code works unchanged whether you're on Eyes.Selenium4, Eyes.Selenium (Selenium 3), Eyes.Playwright, or Eyes.Images directly.