write-e2e-tests
bởi tldraw
Viết bài kiểm tra Playwright E2E cho tldraw. Sử dụng khi tạo kiểm tra trình duyệt, kiểm tra tương tác UI, hoặc thêm phạm vi E2E trong apps/examples/e2e hoặc…
npx skills add https://github.com/tldraw/tldraw --skill write-e2e-testsWriting 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:
import { Editor } from 'tldraw'
declare const editor: Editor
declare const __tldraw_ui_event: { name: string; data?: any }
Basic test structure
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)
test.beforeEach(setupOrReset) // Smart: navigates first run, fast reset after
Shared page for performance
For tests that don't need full isolation:
let page: Page
test.describe('Feature', () => {
test.beforeAll(async ({ browser }) => {
page = await browser.newPage()
await setupPage(page)
})
test.beforeEach(async () => {
await hardResetEditor(page)
})
})
Setup with shapes
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
test('example', async ({
page, // Playwright page
toolbar, // Toolbar page object
stylePanel, // Style panel
actionsMenu, // Actions menu
mainMenu, // Main menu
pageMenu, // Page menu
navigationPanel, // Navigation panel
richTextToolbar, // Rich text toolbar
api, // tldrawApi methods
isMobile, // Mobile viewport check
isMac, // Mac platform check
}) => {})
Interacting with the editor
Via page.evaluate
// Execute code in browser context
await 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 editor
const shape = await page.evaluate(() => editor.getOnlySelectedShape())
expect(shape).toMatchObject({ type: 'geo', x: 100, y: 100 })
Testing UI events
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
await page.getByTestId('tools.rectangle').click()
await page.getByTestId('tools.more.cloud').click() // In popover
await expect(page.getByTestId('tools.select')).toHaveAttribute('aria-pressed', 'true')
Via toolbar fixture
const { select, draw, arrow, rectangle } = toolbar.tools
await rectangle.click()
await toolbar.isSelected(rectangle)
await toolbar.isNotSelected(select)
// More tools popover
await toolbar.moreToolsButton.click()
await toolbar.popOverTools.popoverCloud.click()
Menu interactions
import { clickMenu, withMenu } from '../shared-e2e'
// Click a menu item
await clickMenu(page, 'main-menu.edit.copy')
await clickMenu(page, 'context-menu.copy-as.copy-as-png')
// Focus and interact with menu item
await 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
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 iteration
await page.evaluate(() => {
editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
})
}
})
Platform-specific handling
Modifier keys
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
test('desktop only feature', async ({ isMobile }) => {
if (isMobile) return
// Desktop-specific test
})
Helper functions
import { getAllShapeTypes, getAllShapeLabels, sleep, sleepFrames } from '../shared-e2e'
// Get shape types on canvas
const shapes = await getAllShapeTypes(page)
expect(shapes).toEqual(['geo', 'arrow'])
// Wait for async operations
await sleep(100)
await sleepFrames(2) // Wait for animation frames
Assertions
// Shape assertions
expect(await page.evaluate(() => editor.getOnlySelectedShape())).toMatchObject({
type: 'geo',
props: { w: 100, h: 100 },
})
// Attribute assertions
await expect(page.getByTestId('tools.select')).toHaveAttribute('aria-pressed', 'true')
// CSS assertions (for selection state)
await expect(tool).toHaveCSS('color', 'rgb(255, 255, 255)')
// Visibility
await expect(toolbar.moreToolsPopover).toBeVisible()
await expect(toolbar.toolLock).toBeHidden()
Skipping flaky tests
test.describe.skip('clipboard tests', () => {
// Skipped because flaky in CI
})
test.skip('known issue', async () => {})
Running E2E tests
yarn e2e # Examples E2E
yarn e2e-dotcom # Dotcom E2E
yarn e2e-ui # With Playwright UI
yarn 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
Thêm skills từ tldraw
write-example
tldraw
Viết các ví dụ cho ứng dụng ví dụ của tldraw SDK. Sử dụng khi tạo ví dụ mới, thêm minh họa SDK, hoặc viết mã ví dụ trong apps/examples.
official
write-issue
tldraw
Các tiêu chuẩn tham khảo để viết và duy trì các issue GitHub trong kho lưu trữ tldraw. Sử dụng như hướng dẫn hỗ trợ khi một kỹ năng hoặc quy trình làm việc khác cần issue…
official
write-pr
tldraw
Các tiêu chuẩn tham khảo để viết tiêu đề và mô tả pull request trong kho lưu trữ tldraw. Sử dụng như hướng dẫn hỗ trợ khi một kỹ năng hoặc quy trình làm việc khác cần…
official
write-release-notes
tldraw
Viết bài ghi chú phát hành cho các bản phát hành SDK tldraw. Sử dụng khi tạo tài liệu phát hành mới, soạn thảo ghi chú phát hành từ đầu, hoặc xem xét phát hành…
official
write-tbp
tldraw
Viết bài blog kỹ thuật về các tính năng và chi tiết triển khai của tldraw. Sử dụng khi tạo nội dung blog về cách tldraw giải quyết các vấn đề thú vị.
official
write-unit-tests
tldraw
Viết unit test và integration test cho tldraw SDK. Sử dụng khi tạo test mới, thêm phạm vi kiểm thử, hoặc sửa test lỗi trong packages/editor hoặc…
official
clean-copy
tldraw
Triển khai lại nhánh hiện tại trên một nhánh mới với lịch sử commit git sạch sẽ, có chất lượng tường thuật. Sử dụng khi được yêu cầu tạo một nhánh sao chép sạch, dọn dẹp commit…
official
commit-changes
tldraw
Tạo một git commit cho các thay đổi hiện tại. Sử dụng khi được yêu cầu commit các thay đổi, tạo commit, tạo thông điệp commit, hoặc commit worktree hiện tại với…
official