Files
upstream-tldraw/apps/examples/e2e/fixtures/heavy-board-generator.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

305 lines
7.9 KiB
TypeScript

import { Page } from '@playwright/test'
export interface HeavyBoardOptions {
shapeCount?: number
includeGroups?: boolean
includeText?: boolean
includeArrows?: boolean
canvasWidth?: number
canvasHeight?: number
seed?: number // For reproducible layouts
}
export class HeavyBoardGenerator {
constructor(private page: Page) {}
async generateHeavyBoard(options: HeavyBoardOptions = {}): Promise<void> {
const {
shapeCount = 300,
includeGroups = true,
includeText = true,
includeArrows = true,
canvasWidth = 2000,
canvasHeight = 1500,
seed = 12345,
} = options
// Clear existing shapes first
await this.page.keyboard.press('Control+a')
await this.page.keyboard.press('Backspace')
// Wait for canvas to be ready
await this.page.waitForTimeout(100)
// Generate shapes using editor API injection
await this.page.evaluate(
({
shapeCount,
includeGroups: _includeGroups,
includeText,
includeArrows,
canvasWidth,
canvasHeight,
seed,
}) => {
const editor = (window as any).editor
if (!editor) {
throw new Error('Editor not found on window')
}
// Simple seeded random number generator for reproducible tests
class SeededRandom {
private seed: number
constructor(seed: number) {
this.seed = seed
}
next(): number {
this.seed = (this.seed * 9301 + 49297) % 233280
return this.seed / 233280
}
range(min: number, max: number): number {
return Math.floor(this.next() * (max - min + 1)) + min
}
choice<T>(array: T[]): T {
return array[this.range(0, array.length - 1)]
}
}
const random = new SeededRandom(seed)
const shapes: any[] = []
// Shape types and their weights
const shapeTypes = [
{ type: 'geo', weight: 50 }, // rectangles, ellipses, etc.
{ type: 'text', weight: includeText ? 25 : 0 },
{ type: 'arrow', weight: includeArrows ? 25 : 0 },
{ type: 'note', weight: 25 },
{ type: 'draw', weight: 25 },
].filter((s) => s.weight > 0)
// Generate base shapes
for (let i = 0; i < shapeCount; i++) {
const x = random.range(0, canvasWidth)
const y = random.range(0, canvasHeight)
const width = random.range(50, 200)
const height = random.range(30, 150)
// Pick shape type based on weights
let weightSum = 0
const targetWeight = random.next() * shapeTypes.reduce((sum, s) => sum + s.weight, 0)
const shapeType =
shapeTypes.find((s) => (weightSum += s.weight) >= targetWeight)?.type || 'geo'
let shape: any
switch (shapeType) {
case 'geo': {
const geoTypes = ['rectangle', 'ellipse', 'triangle', 'diamond', 'hexagon', 'star']
shape = {
id: `shape:${i}`,
type: 'geo',
x,
y,
props: {
w: width,
h: height,
geo: random.choice(geoTypes),
color: random.choice([
'black',
'blue',
'green',
'red',
'yellow',
'orange',
'violet',
]),
fill: random.choice(['none', 'semi', 'solid']),
size: random.choice(['s', 'm', 'l', 'xl']),
},
}
break
}
case 'draw': {
// Create a simple drawn shape with some points
const numPoints = random.range(3, 8)
const points: Array<{ x: number; y: number; z: number }> = []
for (let p = 0; p < numPoints; p++) {
points.push({
x: random.range(0, width),
y: random.range(0, height),
z: 0.5,
})
}
// Access b64Vecs from the window.tldrawApi object
const tldrawApi = (window as any).tldrawApi
const b64VecsEncodePoints = tldrawApi.b64VecsEncodePoints
shape = {
id: `shape:${i}`,
type: 'draw',
x,
y,
props: {
segments: [
{
type: 'free',
path: b64VecsEncodePoints(points),
},
],
color: random.choice(['black', 'blue', 'green', 'red']),
size: random.choice(['s', 'm', 'l']),
},
}
break
}
case 'text': {
const tldrawApi = (window as any).tldrawApi
const textSamples = [
'Performance Test',
'Lorem ipsum dolor sit amet',
'Heavy Board Test',
'Sample Text ' + i,
'Multi-line\nText Content',
'Longer text content for testing purposes',
]
shape = {
id: `shape:${i}`,
type: 'text',
x,
y,
props: {
richText: tldrawApi.toRichText(random.choice(textSamples)),
color: random.choice(['black', 'blue', 'green', 'red']),
size: random.choice(['s', 'm', 'l', 'xl']),
font: 'draw',
},
}
break
}
case 'arrow': {
const endX = x + random.range(-200, 200)
const endY = y + random.range(-200, 200)
shape = {
id: `shape:${i}`,
type: 'arrow',
x,
y,
props: {
start: { x: 0, y: 0 },
end: { x: endX - x, y: endY - y },
color: random.choice(['black', 'blue', 'green', 'red']),
size: random.choice(['s', 'm', 'l']),
arrowheadStart: random.choice(['none', 'arrow', 'triangle']),
arrowheadEnd: random.choice(['arrow', 'triangle', 'dot']),
},
}
break
}
case 'note': {
const tldrawApi = (window as any).tldrawApi
shape = {
id: `shape:${i}`,
type: 'note',
x,
y,
props: {
richText: tldrawApi.toRichText(`Note ${i}\nPerformance testing note`),
color: random.choice(['black', 'blue', 'green', 'red', 'yellow']),
size: random.choice(['s', 'm', 'l']),
},
}
break
}
}
if (shape) {
shapes.push(shape)
}
}
// Create all shapes at once
editor.createShapes(shapes)
// Skip grouping for now - will add back later when debugging shape creation issues
// TODO: Re-enable grouping once basic shape generation is working
// Zoom to fit all content
editor.zoomToFit()
// eslint-disable-next-line no-console
console.log(`Generated heavy board with ${shapes.length} shapes`)
},
{ shapeCount, includeGroups, includeText, includeArrows, canvasWidth, canvasHeight, seed }
)
// Wait for rendering to settle
await this.page.waitForTimeout(1000)
}
async setupStressTestBoard(shapeCount: number): Promise<void> {
// Pre-configured stress test with maximum complexity
await this.generateHeavyBoard({
shapeCount,
includeGroups: true,
includeText: true,
includeArrows: true,
canvasWidth: 3000,
canvasHeight: 2500,
seed: 42, // Fixed seed for consistent tests
})
}
async getShapeCount(): Promise<number> {
return await this.page.evaluate(() => {
const editor = (window as any).editor
return editor ? editor.getCurrentPageShapes().length : 0
})
}
async selectRandomShapes(count: number, seed: number = 12345): Promise<void> {
await this.page.evaluate(
({ count, seed }) => {
const editor = (window as any).editor
if (!editor) return
// Simple seeded random number generator for reproducible tests
class SeededRandom {
private seed: number
constructor(seed: number) {
this.seed = seed
}
next(): number {
this.seed = (this.seed * 9301 + 49297) % 233280
return this.seed / 233280
}
range(min: number, max: number): number {
return Math.floor(this.next() * (max - min + 1)) + min
}
}
const random = new SeededRandom(seed)
const allShapes = editor.getCurrentPageShapes()
const selectedCount = Math.min(count, allShapes.length)
// Select random shapes using seeded random
const shuffled = allShapes.sort(() => 0.5 - random.next())
const selected = shuffled.slice(0, selectedCount)
const shapeIds = selected.map((shape: any) => shape.id)
editor.setSelectedShapes(shapeIds)
},
{ count, seed }
)
}
}