<< All versions
Skill v1.0.0
currentAutomated scan96/100whiteg2030-alt/wuxianhuabu/write-e2e-tests
──Details
PublishedSeptember 28, 2026 at 05:13 PM
Content Hashsha256:62d00d26ea2d0f42...
Git SHA
──Files
Files (1 file, 6.8 KB)
SKILL.md6.8 KBactive
SKILL.md · 290 lines · 6.8 KB
version: "1.0.0" name: write-e2e-tests description: Writing Playwright E2E tests for tldraw. Use when creating browser tests, testing UI interactions, or adding E2E coverage in apps/examples/e2e or apps/dotcom/client/e2e.
Writing E2E tests
E2E tests use Playwright. Located in apps/examples/e2e/ (SDK examples) and apps/dotcom/client/e2e/ (tldraw.com).
Test file structure
apps/examples/e2e/├── fixtures/│ ├── fixtures.ts # Test fixtures (toolbar, menus, etc.)│ └── menus/ # Page object models├── tests/│ └── test-*.spec.ts # Test files└── shared-e2e.ts # Shared utilities
Name test files test-<feature>.spec.ts.
Required declarations
When using page.evaluate() to access the editor or UI events:
typescript
import { Editor } from 'tldraw'declare const editor: Editordeclare const __tldraw_ui_event: { name: string; data?: any }
Basic test structure
typescript
import { expect } from '@playwright/test'import test from '../fixtures/fixtures'import { setupOrReset } from '../shared-e2e'test.describe('Feature name', () => {test.beforeEach(setupOrReset)test('does something', async ({ page, toolbar }) => {// Test implementation})})
Setup patterns
Standard setup (recommended)
typescript
test.beforeEach(setupOrReset) // Smart: navigates first run, fast reset after
Shared page for performance
For tests that don't need full isolation:
typescript
let page: Pagetest.describe('Feature', () => {test.beforeAll(async ({ browser }) => {page = await browser.newPage()await setupPage(page)})test.beforeEach(async () => {await hardResetEditor(page)})})
Setup with shapes
typescript
import { setupPageWithShapes, hardResetWithShapes } from '../shared-e2e'test.beforeEach(async ({ browser }) => {if (!page) {page = await browser.newPage()await setupPage(page)} else {await hardResetEditor(page)}await setupPageWithShapes(page)})
Available fixtures
typescript
test('example', async ({page, // Playwright pagetoolbar, // Toolbar page objectstylePanel, // Style panelactionsMenu, // Actions menumainMenu, // Main menupageMenu, // Page menunavigationPanel, // Navigation panelrichTextToolbar, // Rich text toolbarapi, // tldrawApi methodsisMobile, // Mobile viewport checkisMac, // Mac platform check}) => {})
Interacting with the editor
Via page.evaluate
typescript
// Execute code in browser contextawait page.evaluate(() => {editor.createShapes([{ type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } }])})// Fast reset (faster than keyboard shortcuts)await page.evaluate(() => {editor.selectAll().deleteShapes(editor.getSelectedShapeIds())editor.setCurrentTool('select')})// Get data from editorconst shape = await page.evaluate(() => editor.getOnlySelectedShape())expect(shape).toMatchObject({ type: 'geo', x: 100, y: 100 })
Testing UI events
typescript
await page.keyboard.press('Control+a')expect(await page.evaluate(() => __tldraw_ui_event)).toMatchObject({name: 'select-all-shapes',data: { source: 'kbd' },})
Selecting tools and UI elements
By test ID
typescript
await page.getByTestId('tools.rectangle').click()await page.getByTestId('tools.more.cloud').click() // In popoverawait expect(page.getByTestId('tools.select')).toHaveAttribute('aria-pressed', 'true')
Via toolbar fixture
typescript
const { select, draw, arrow, rectangle } = toolbar.toolsawait rectangle.click()await toolbar.isSelected(rectangle)await toolbar.isNotSelected(select)// More tools popoverawait toolbar.moreToolsButton.click()await toolbar.popOverTools.popoverCloud.click()
Menu interactions
typescript
import { clickMenu, withMenu } from '../shared-e2e'// Click a menu itemawait clickMenu(page, 'main-menu.edit.copy')await clickMenu(page, 'context-menu.copy-as.copy-as-png')// Focus and interact with menu itemawait page.mouse.click(200, 200, { button: 'right' })await withMenu(page, 'context-menu.arrange.distribute-horizontal', (item) => item.focus())await page.keyboard.press('Enter')
Data-driven tests
typescript
const tools = [{ tool: 'rectangle', shape: 'geo' },{ tool: 'arrow', shape: 'arrow' },{ tool: 'draw', shape: 'draw' },]test('creates shapes with tools', async ({ page, toolbar }) => {for (const { tool, shape } of tools) {await page.getByTestId(`tools.${tool}`).click()await page.mouse.click(200, 200)expect(await getAllShapeTypes(page)).toContain(shape)// Reset for next iterationawait page.evaluate(() => {editor.selectAll().deleteShapes(editor.getSelectedShapeIds())})}})
Platform-specific handling
Modifier keys
typescript
test('copy paste', async ({ page, isMac }) => {const modifier = isMac ? 'Meta' : 'Control'await page.keyboard.down(modifier)await page.keyboard.press('KeyC')await page.keyboard.press('KeyV')await page.keyboard.up(modifier)})
Skip on mobile
typescript
test('desktop only feature', async ({ isMobile }) => {if (isMobile) return// Desktop-specific test})
Helper functions
typescript
import { getAllShapeTypes, getAllShapeLabels, sleep, sleepFrames } from '../shared-e2e'// Get shape types on canvasconst shapes = await getAllShapeTypes(page)expect(shapes).toEqual(['geo', 'arrow'])// Wait for async operationsawait sleep(100)await sleepFrames(2) // Wait for animation frames
Assertions
typescript
// Shape assertionsexpect(await page.evaluate(() => editor.getOnlySelectedShape())).toMatchObject({type: 'geo',props: { w: 100, h: 100 },})// Attribute assertionsawait expect(page.getByTestId('tools.select')).toHaveAttribute('aria-pressed', 'true')// CSS assertions (for selection state)await expect(tool).toHaveCSS('color', 'rgb(255, 255, 255)')// Visibilityawait expect(toolbar.moreToolsPopover).toBeVisible()await expect(toolbar.toolLock).toBeHidden()
Skipping flaky tests
typescript
test.describe.skip('clipboard tests', () => {// Skipped because flaky in CI})test.skip('known issue', async () => {})
Running E2E tests
bash
yarn e2e # Examples E2Eyarn e2e-dotcom # Dotcom E2Eyarn e2e-ui # With Playwright UIyarn e2e -- --grep "toolbar" # Filter by pattern
Key patterns summary
- Use
setupOrResetinbeforeEachfor test isolation - Declare
editorand__tldraw_ui_eventforpage.evaluate() - Use
page.evaluate()for fast editor manipulation (faster than keyboard) - Use
getByTestId()withtools.<name>pattern for tool selection - Use
clickMenu()/withMenu()for menu interactions - Handle platform differences with
isMacandisMobilefixtures - Test against
localhost:5420/end-to-endexample