Design Tool Testing Guide

How to Test Figma File Actions with Playwright: Complete 2026 Guide

A scenario-by-scenario walkthrough of testing Figma file actions with Playwright. Canvas rendering challenges, REST API fallback assertions, file browser navigation, sharing and permission controls, plugin loading lifecycle, and the pitfalls that break real Figma test suites.

4M+

Figma reports over four million paying customers as of 2024, with design files rendered entirely on a WebGL canvas that has zero inspectable DOM nodes for traditional selectors.

0Inspectable DOM nodes on canvas
0File action scenarios covered
0Assertion strategies (UI + API)
0%Fewer lines with Assrt

Figma File Action End-to-End Flow

BrowserFigma App ShellWebGL CanvasFigma REST APIPlugin SandboxNavigate to file URLInitialize canvas rendererCanvas ready, render toolbarTrigger file action (rename, duplicate)Persist change via API200 OK with updated file metadataLoad plugin from communitySpawn plugin iframe sandboxPlugin modifies canvas nodes

1. Why Testing Figma File Actions Is Harder Than It Looks

Figma renders its entire design canvas using WebGL. Unlike traditional web applications where every button, text input, and list item is a DOM node you can query with CSS selectors or Playwright locators, the Figma canvas is a single <canvas> element. Every frame, component, text layer, and vector shape is drawn as pixels inside that canvas. There is no getByText("Rectangle 1") for objects on the artboard. This is the fundamental challenge: you cannot use standard DOM assertions for anything that lives on the canvas.

The complexity extends beyond the canvas itself. Figma's file browser (the dashboard at figma.com) is a standard React application with inspectable DOM nodes, but it uses virtualized lists that only render visible rows. Scrolling to a file that is off-screen requires programmatic scrolling and waiting for new rows to mount. Sharing dialogs use modal overlays with dynamic permission dropdowns that are not in the DOM until opened. Plugin loading spawns an isolated iframe sandbox that communicates with the main thread through a postMessage bridge, and the plugin UI elements live inside that iframe, requiring frameLocator in Playwright.

There are five structural reasons this flow is hard to test reliably. First, canvas rendering means no DOM nodes for design objects, so you must fall back to the Figma REST API to verify canvas state. Second, the file browser uses virtualized scrolling, so files off-screen do not exist in the DOM until you scroll to them. Third, sharing and permission modals are lazily rendered and destroyed when closed. Fourth, plugins run in sandboxed iframes with their own document context. Fifth, Figma aggressively rate limits API calls (roughly 30 requests per minute per token), so your test suite must batch assertions carefully.

Figma File Action Architecture

🌐

File Browser

React app, virtualized lists

↪️

Open File

Navigate to /file/:key

⚙️

Canvas Init

WebGL renderer boots

🌐

Toolbar + Panels

DOM-based UI shell

User Action

Rename, share, export

🔔

API Persist

REST or WebSocket sync

Plugin Loading Flow

🌐

User

Runs plugin from menu

📦

App Shell

Creates iframe sandbox

⚙️

Plugin Code

Executes in sandbox

🔔

postMessage

Plugin <-> Main thread

Canvas Update

Plugin modifies nodes

A robust Figma test suite combines DOM-based Playwright assertions for the app shell (toolbar, panels, modals) with REST API calls to verify canvas state. The sections below walk through each scenario with runnable TypeScript you can copy directly.

2. Setting Up Your Test Environment

Before writing any file action tests, you need three things: a Figma account with at least one team and project, a personal access token for the REST API, and a dedicated test file that your suite can safely modify without affecting real design work. Figma provides a free Starter plan that supports REST API access and plugin development. Create a dedicated test team to isolate your automation from production files.

Figma Test Environment Setup Checklist

  • Create a dedicated Figma team for E2E tests (separate from production)
  • Generate a personal access token at figma.com/developers
  • Create a test file with known structure (frames, components, text layers)
  • Note the file key from the URL: figma.com/file/:fileKey/:fileName
  • Create a test project folder within the team
  • Install @figma/rest-api-spec or use raw fetch for API calls
  • Store credentials in .env.test (never commit tokens)
  • Configure Playwright to persist auth cookies across tests

Environment Variables

.env.test

Figma REST API Helper

Since the canvas is not inspectable via DOM, you need a helper that calls the Figma REST API to verify file state. This helper wraps the most common endpoints: get file metadata, get file nodes, get file versions, and get project files. Rate limiting is critical; Figma enforces roughly 30 requests per minute per personal access token.

test/helpers/figma-api.ts

Playwright Configuration for Figma

Figma's canvas takes time to initialize the WebGL context. Set generous navigation and action timeouts. Use storageState to persist Figma session cookies so you only log in once per test run.

playwright.config.ts
Install Dependencies

3. Scenario: Navigating the File Browser

The Figma file browser at figma.com is the starting point for most file actions. It displays teams, projects, and files in a virtualized grid or list. The challenge is that files outside the visible viewport are not in the DOM. You need to scroll programmatically, wait for new file cards to render, and then locate your target. The search functionality is a faster alternative: type in the search bar and wait for results to appear.

1

Navigate to a Specific File via Search

Straightforward

Goal

From the Figma dashboard, search for a file by name, click the result, and confirm the file editor opens with the correct file loaded on the canvas.

Preconditions

  • Authenticated Figma session via storageState
  • Test file exists with a known name in the test team

Playwright Implementation

file-browser.spec.ts

What to Assert Beyond the UI

The canvas is visible, but you cannot confirm what is rendered on it through DOM inspection. Use the REST API to verify the file key matches.

file-browser.spec.ts (API assertion)

4. Scenario: Renaming and Duplicating a File

Renaming a Figma file happens in the toolbar at the top of the editor. The file name is an editable text input. Duplicating a file is accessible through the file menu or the right-click context menu on the dashboard. Both actions persist through the Figma backend and can be verified through the REST API. The tricky part is that rename is debounced; Figma does not save the new name on every keystroke. You need to trigger a blur event or press Enter to commit the change, then wait for the API to reflect it.

2

Rename a File from the Editor Toolbar

Moderate

Goal

Open a test file, rename it via the toolbar input, and confirm the new name persists both in the UI and in the REST API response.

Preconditions

  • Authenticated Figma session
  • Test file open in the editor
  • User has edit access to the file

Playwright Implementation

rename-file.spec.ts

Rename File: Playwright vs Assrt

import { test, expect } from '@playwright/test';
import { getFile } from '../helpers/figma-api';

test('rename file from toolbar', async ({ page }) => {
  const fileKey = process.env.FIGMA_TEST_FILE_KEY!;
  const newName = `Renamed ${Date.now()}`;
  await page.goto(`/file/${fileKey}`);
  await page.waitForSelector('canvas', {
    state: 'visible',
    timeout: 30_000,
  });
  const filenameInput = page.getByTestId('filename-input');
  await filenameInput.click({ clickCount: 3 });
  await filenameInput.fill(newName);
  await filenameInput.press('Enter');
  await page.waitForTimeout(3_000);
  const file = await getFile(fileKey);
  expect(file.name).toBe(newName);
});
44% fewer lines
3

Duplicate a File from the Dashboard

Moderate

Goal

From the file browser, right-click a file, select “Duplicate”, and verify a copy appears in the project with the expected naming convention (original name followed by “(Copy)”).

Playwright Implementation

duplicate-file.spec.ts

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

5. Scenario: Sharing and Permission Changes

Figma's sharing dialog is a modal that opens over the editor. It contains an email input for inviting collaborators, a permission dropdown (can view, can edit), and a list of existing collaborators. The dialog elements are not in the DOM until the share button is clicked. The permission dropdown is a custom component, not a native <select>, so you must click it to reveal options and then click the desired permission level. After sharing, you need to verify both the UI update (the new collaborator appears in the list) and the backend state (the REST API reflects the updated permissions).

4

Share a File with Edit Access

Complex

Goal

Open the sharing dialog, invite a collaborator by email with “can edit” permission, and verify the invitation both in the UI and through API confirmation.

Preconditions

  • Test file open in the Figma editor
  • User is the file owner or has “can edit” access
  • A second test account email to invite

Playwright Implementation

sharing.spec.ts

Share File: Playwright vs Assrt

test('share file with edit access', async ({ page }) => {
  const fileKey = process.env.FIGMA_TEST_FILE_KEY!;
  const inviteeEmail = `e2e-collab+${Date.now()}@co.com`;
  await page.goto(`/file/${fileKey}`);
  await page.waitForSelector('canvas', {
    state: 'visible', timeout: 30_000
  });
  await page.getByRole('button', { name: /share/i }).click();
  const modal = page.getByRole('dialog');
  await expect(modal).toBeVisible();
  await modal.getByPlaceholder(/email/i).fill(inviteeEmail);
  await page.keyboard.press('Enter');
  const perm = modal.getByRole('button', { name: /can view/i });
  await perm.click();
  await page.getByRole('option', { name: /can edit/i }).click();
  await modal.getByRole('button', { name: /invite/i }).click();
  await expect(modal.getByText(inviteeEmail)).toBeVisible();
});
50% fewer lines

6. Scenario: Asserting Canvas State via the REST API

This is the core challenge unique to Figma testing. When a user creates a frame, adds text, or moves an element on the canvas, those changes are invisible to Playwright's DOM queries. The canvas is a bitmap. Your only reliable option for verifying canvas state is the Figma REST API. The GET /v1/files/:key/nodes endpoint returns the full node tree for specific node IDs, including position, size, text content, fills, and child relationships. You can query this endpoint after a UI action to confirm the change persisted.

The timing challenge is significant. After a user performs an action on the canvas (for example, renaming a text layer), Figma does not immediately persist the change to the API. There is a sync delay, typically between 1 and 5 seconds, before the REST API reflects the latest state. Your test must include a polling mechanism or a fixed wait to account for this delay. Polling is preferable because it is faster on average and more reliable than a fixed sleep.

5

Verify a Text Layer via REST API

Complex

Goal

After a UI action that modifies a text layer on the canvas, poll the Figma REST API to confirm the change persisted in the file's node tree.

Playwright Implementation

canvas-api-assertion.spec.ts
Canvas API Assertion Test Run

7. Scenario: Loading and Running a Plugin

Figma plugins run in an isolated iframe sandbox. When a user launches a plugin from the Resources menu (or via the quick actions search), Figma creates a sandboxed iframe that loads the plugin's UI. The plugin communicates with the main Figma application through a postMessage bridge. From a Playwright perspective, this means plugin UI elements are inside a different frame context. You must use frameLocator to access plugin DOM nodes.

The plugin loading sequence has several stages that can fail silently. First, the plugin manifest is fetched. Second, the sandbox iframe is created. Third, the plugin code executes and renders its UI. If the plugin has a network dependency (for example, fetching data from an external API), that adds another asynchronous step. Each stage can introduce timing issues that cause flaky tests if you do not wait for the right signals.

6

Launch a Plugin and Interact with Its UI

Complex

Goal

Open a file, launch a known plugin from the menu, wait for its UI to appear in the plugin iframe, interact with a button inside the plugin, and verify the plugin action completed.

Preconditions

  • Test file open in the Figma editor
  • A known test plugin installed in the team (or published to the community)
  • Plugin has a visible UI panel (not a run-once plugin)

Playwright Implementation

plugin-loading.spec.ts

Plugin Loading: Playwright vs Assrt

test('launch plugin and interact', async ({ page }) => {
  const fileKey = process.env.FIGMA_TEST_FILE_KEY!;
  await page.goto(`/file/${fileKey}`);
  await page.waitForSelector('canvas', {
    state: 'visible', timeout: 30_000
  });
  await page.keyboard.press('Shift+i');
  const search = page.getByPlaceholder(/search/i);
  await search.fill('My Test Plugin');
  await page.waitForTimeout(1_000);
  await page.getByText('My Test Plugin').first().click();
  await page.getByRole('button', { name: /run/i }).click();
  const frame = page.frameLocator('iframe[name*="plugin"]');
  const btn = frame.getByRole('button', { name: /generate/i });
  await expect(btn).toBeVisible({ timeout: 10_000 });
  await btn.click();
  await expect(
    frame.getByText(/done|success|complete/i)
  ).toBeVisible({ timeout: 15_000 });
});
53% fewer lines

8. Scenario: Version History and Restore

Figma automatically saves versions as users work. Named versions can be created manually through the File menu or the version history panel. Testing version creation and restoration is important for workflows that rely on version snapshots (for example, saving a “before” state before a design review). The version history panel opens as a sidebar, and each version entry is a clickable row. Restoring a version triggers a confirmation dialog.

The Figma REST API provides a GET /v1/files/:key/versions endpoint that returns all named versions. You can use this to verify that a version was created with the correct label and timestamp. Combining the UI interaction (creating a named version) with an API assertion (verifying it appears in the versions list) gives you full confidence that the feature works end to end.

7

Create and Verify a Named Version

Moderate

Goal

Create a named version through the Figma UI, then verify it appears in both the version history panel and the REST API response.

Playwright Implementation

version-history.spec.ts
Version History Test Run

9. Common Pitfalls That Break Figma Test Suites

Figma testing has a unique set of failure modes because of the canvas rendering architecture and the dynamic nature of the application shell. The following pitfalls come from real issues reported in Figma community forums, GitHub discussions about Figma plugin development, and common patterns observed in production test suites.

Common Pitfalls (Avoid These)

  • Trying to query canvas objects with DOM selectors. Canvas elements have zero DOM presence. Use the REST API instead.
  • Not accounting for Figma's API sync delay. Changes take 1 to 5 seconds to appear in REST responses. Always poll or add explicit waits.
  • Exceeding the 30 requests/minute rate limit. Batch your API assertions and reuse GET responses across multiple expect() calls.
  • Hardcoding pixel coordinates for canvas clicks without accounting for viewport zoom. Always reset zoom to 100% before coordinate-based interactions.
  • Forgetting that plugin UIs run in sandboxed iframes. Use page.frameLocator() to access plugin DOM nodes, not page.getByRole() on the main frame.
  • Assuming the file browser renders all files. Figma virtualizes the file list; only visible rows exist in the DOM. Use search or scroll programmatically.
  • Not resetting the test file state between runs. Previous test runs may leave renamed files, extra collaborators, or unwanted versions. Add a cleanup step in globalSetup.
  • Ignoring Figma's session cookie expiration. Personal access tokens last indefinitely, but browser sessions expire. Re-authenticate in globalSetup if cookies are stale.

Debugging Tip: Network Interception for Canvas Actions

When canvas-based actions seem to fail silently, intercept Figma's WebSocket traffic to see what the client is actually sending. Figma uses WebSockets for real-time collaboration and canvas sync. You can use Playwright's page.on('websocket') listener to log messages for debugging, although the binary protocol is not human-readable without Figma's internal schema.

debug-websocket.ts

10. Writing These Scenarios in Plain English with Assrt

The code samples above are detailed, but maintaining them is expensive. Figma ships updates frequently, and even minor UI changes (a renamed button, a restructured menu, a new modal layout) can silently break your Playwright selectors. Assrt lets you describe test scenarios in plain English. The Assrt compiler generates the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can read, run, and modify.

Here is how the file browser navigation, rename, sharing, canvas assertion, and plugin loading scenarios look as a single .assrt file:

# figma-file-actions.assrt
config:
  baseURL: https://www.figma.com
  auth: storageState from ./test/.auth/figma-session.json

---
scenario: Navigate to file via search
steps:
  - go to the Figma dashboard
  - click the search button
  - type "E2E Test File" in the search field
  - click the first search result matching the file name
expect:
  - the URL contains "/file/"
  - a canvas element is visible
  - the toolbar shows "E2E Test File" as the filename

---
scenario: Rename file from toolbar
steps:
  - open the test file in Figma editor
  - triple-click the filename in the toolbar to select it
  - type a new unique file name
  - press Enter to commit the rename
expect:
  - the toolbar shows the new file name
  - the Figma REST API returns the new name for this file key

---
scenario: Share file with edit access
steps:
  - open the test file in Figma
  - click the "Share" button in the toolbar
  - type a collaborator email in the invite field
  - press Enter
  - change the permission to "can edit"
  - click "Invite"
expect:
  - the collaborator email appears in the share list
  - their permission shows "can edit"

---
scenario: Verify canvas text via API
steps:
  - open the test file in Figma
  - double-click the known text layer position on the canvas
  - select all text and type new content
  - click outside to deselect
expect:
  - the Figma REST API reflects the new text content for the target node ID

---
scenario: Launch plugin and interact
steps:
  - open the test file in Figma
  - press Shift+I to open Resources
  - search for "My Test Plugin"
  - click the plugin result
  - click "Run"
  - click the "Generate" button inside the plugin UI
expect:
  - the plugin displays a success message

Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can read, run, and modify. When Figma renames a menu item from “Save to Version History” to “Add to Version History” or restructures the sharing modal, Assrt detects the failure, analyzes the new DOM, and opens a pull request with the updated locators. Your scenario files stay untouched.

Start with the file browser search scenario. Once it is green in your CI, add the rename test, then sharing permissions, then the canvas API assertion, then plugin loading, and finally version history. In a single afternoon you can have complete Figma file action coverage that accounts for the canvas rendering gap and the dynamic app shell that most teams never manage to test by hand.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk