Files
upstream-tldraw/apps/examples/e2e/shared-e2e.ts
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

164 lines
5.0 KiB
TypeScript

import {
BrowserContext,
Locator,
Page,
PlaywrightTestArgs,
PlaywrightWorkerArgs,
} from '@playwright/test'
import { type Editor } from 'tldraw'
declare const editor: Editor
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms))
}
export async function setup({ page, context }: PlaywrightTestArgs & PlaywrightWorkerArgs) {
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
await setupPage(page)
}
/**
* Smart setup that navigates on first run, then uses fast reset on subsequent runs.
* Use this in beforeEach for optimal test performance with page reuse.
*/
export async function setupOrReset({ page, context }: { page: Page; context: BrowserContext }) {
const url = page.url()
if (!url.includes('end-to-end')) {
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
await setupPage(page)
} else {
await hardResetEditor(page)
}
}
export async function setupWithShapes({ page }: PlaywrightTestArgs & PlaywrightWorkerArgs) {
await setupPage(page)
await setupPageWithShapes(page)
}
export async function cleanup({ page }: PlaywrightTestArgs) {
await cleanupPage(page)
}
export async function setupPage(page: PlaywrightTestArgs['page']) {
await page.goto('http://localhost:5420/end-to-end')
await page.waitForSelector('.tl-canvas')
await page.evaluate(() => {
editor.user.updateUserPreferences({ animationSpeed: 0 })
})
await page.mouse.move(50, 50)
// Ensure the container has focus for keyboard events
await page.locator('.tl-container').focus()
}
/**
* Fast reset of the editor state without page navigation.
* Use this in beforeEach when the page is already set up via beforeAll.
*/
export async function hardResetEditor(page: Page) {
await page.evaluate(() => {
// Clear all shapes and reset editor state
editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
editor.setCurrentTool('select')
editor.zoomToFit()
editor.resetZoom()
})
await page.mouse.move(50, 50)
// Ensure the container has focus for keyboard events
await page.locator('.tl-container').focus()
}
/**
* Fast reset and create test shapes without page navigation.
* Use this in beforeEach when tests need shapes but want to avoid full setup.
*/
export async function hardResetWithShapes(page: Page) {
await page.evaluate(() => {
// Clear all shapes and create test shapes
editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
editor.createShapes([
{ type: 'geo', x: 200, y: 200, props: { w: 100, h: 100, geo: 'rectangle' } },
{ type: 'geo', x: 200, y: 250, props: { w: 100, h: 100, geo: 'rectangle' } },
{ type: 'geo', x: 250, y: 300, props: { w: 100, h: 100, geo: 'rectangle' } },
])
editor.selectNone()
editor.setCurrentTool('select')
})
await page.mouse.move(50, 50)
// Ensure the container has focus for keyboard events
await page.locator('.tl-container').focus()
}
export async function setupPageWithShapes(page: PlaywrightTestArgs['page']) {
// delete everything
await page.keyboard.press('Control+a')
await page.keyboard.press('Backspace')
// create shapes
await page.keyboard.press('r')
await page.mouse.click(200, 200)
await page.keyboard.press('r')
await page.mouse.click(200, 250)
await page.keyboard.press('r')
await page.mouse.click(250, 300)
// deselect everything
await page.keyboard.press('Escape')
await page.keyboard.press('Escape')
}
export async function cleanupPage(page: PlaywrightTestArgs['page']) {
await page.keyboard.press('Control+a')
await page.keyboard.press('Delete')
}
export async function getAllShapeLabels(page: PlaywrightTestArgs['page']) {
return await page
.locator('.tl-shapes')
.first()
.evaluate((e) => {
const labels: { index: string; label: string }[] = []
for (const child of e.children) {
const index = (child as HTMLDivElement).style.zIndex
const label = child.querySelector('.tl-text-content') as HTMLDivElement
labels.push({ index, label: label.innerText })
}
labels.sort((a, b) => (a.index > b.index ? 1 : -1))
return labels.map((l) => l.label)
})
}
export async function getAllShapeTypes(page: PlaywrightTestArgs['page']) {
return await page
.locator('.tl-shape')
.elementHandles()
.then((handles) => Promise.all(handles.map((h) => h.getAttribute('data-shape-type'))))
}
export async function withMenu<T>(page: Page, path: string, cb: (item: Locator) => Promise<T>) {
const parts = path.split('.')
const lastPartIdx = parts.length - 1
for (let i = 0; i < lastPartIdx; i++) {
const part = parts[i]
if (i === 0) {
// context menu should already be open
if (part !== 'context-menu') {
await page.getByTestId(`${part}.button`).click()
}
} else {
await page.getByTestId(`${parts[0]}-sub.${part}-button`).click()
}
}
// last part!
return await cb(page.getByTestId(`${parts[0]}.${parts[lastPartIdx]}`))
}
export async function clickMenu(page: Page, path: string) {
await withMenu(page, path, (item) => item.click())
}
// We need a way to wait for the editor to finish a tick
export function sleepFrames(frames = 2): Promise<void> {
return sleep(frames * (1000 / 60))
}