snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
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
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
This commit is contained in:
163
apps/examples/e2e/shared-e2e.ts
Normal file
163
apps/examples/e2e/shared-e2e.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user