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:
261
apps/examples/e2e/fixtures/baseline-manager.ts
Normal file
261
apps/examples/e2e/fixtures/baseline-manager.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { FPSMetrics } from './fps-tracker'
|
||||
|
||||
export interface PerformanceBaseline {
|
||||
avgFps: number
|
||||
minFps: number
|
||||
maxFps: number
|
||||
timestamp: string
|
||||
environment: {
|
||||
platform: string
|
||||
viewport: string
|
||||
browser: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface BaselineData {
|
||||
baselines: {
|
||||
[platform: string]: {
|
||||
[interaction: string]: PerformanceBaseline
|
||||
}
|
||||
}
|
||||
metadata: {
|
||||
lastUpdated: string
|
||||
version: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface ComparisonResult {
|
||||
interaction: string
|
||||
current: FPSMetrics
|
||||
baseline: PerformanceBaseline
|
||||
avgFpsChange: number // Percentage change
|
||||
minFpsChange: number
|
||||
isRegression: boolean
|
||||
isImprovement: boolean
|
||||
status: 'pass' | 'warning' | 'fail'
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface Environment {
|
||||
platform: string
|
||||
viewport: string
|
||||
browser: string
|
||||
}
|
||||
|
||||
const __dirname = path.dirname(new URL(import.meta.url).pathname)
|
||||
|
||||
export class BaselineManager {
|
||||
private baselineFilePath: string
|
||||
private regressionThreshold = 15 // Percentage
|
||||
private warningThreshold = 10 // Percentage
|
||||
|
||||
constructor(baselineDir = path.join(__dirname, '..', 'baselines')) {
|
||||
// Ensure baseline directory exists
|
||||
if (!fs.existsSync(baselineDir)) {
|
||||
fs.mkdirSync(baselineDir, { recursive: true })
|
||||
}
|
||||
this.baselineFilePath = path.join(baselineDir, 'fps-baselines.json')
|
||||
}
|
||||
|
||||
private loadBaselines(): BaselineData {
|
||||
if (!fs.existsSync(this.baselineFilePath)) {
|
||||
return {
|
||||
baselines: {},
|
||||
metadata: {
|
||||
lastUpdated: new Date().toISOString(),
|
||||
version: '1.0.0',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(this.baselineFilePath, 'utf-8')
|
||||
return JSON.parse(content)
|
||||
} catch (error) {
|
||||
console.warn('Failed to load baseline file, creating new one:', error)
|
||||
return {
|
||||
baselines: {},
|
||||
metadata: {
|
||||
lastUpdated: new Date().toISOString(),
|
||||
version: '1.0.0',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private saveBaselines(data: BaselineData): void {
|
||||
try {
|
||||
const content = JSON.stringify(data, null, 2)
|
||||
fs.writeFileSync(this.baselineFilePath, content, 'utf-8')
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save baselines: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
private getPlatformKey(env: Environment): string {
|
||||
return `${env.platform}-${env.viewport}`
|
||||
}
|
||||
|
||||
private calculatePercentageChange(current: number, baseline: number): number {
|
||||
if (baseline === 0) return 0
|
||||
return ((current - baseline) / baseline) * 100
|
||||
}
|
||||
|
||||
updateBaseline(
|
||||
interaction: string,
|
||||
metrics: FPSMetrics,
|
||||
environment: Environment,
|
||||
force = false
|
||||
): void {
|
||||
const data = this.loadBaselines()
|
||||
const platformKey = this.getPlatformKey(environment)
|
||||
|
||||
if (!data.baselines[platformKey]) {
|
||||
data.baselines[platformKey] = {}
|
||||
}
|
||||
|
||||
const existingBaseline = data.baselines[platformKey][interaction]
|
||||
const newBaseline: PerformanceBaseline = {
|
||||
avgFps: metrics.averageFps,
|
||||
minFps: metrics.minFps,
|
||||
maxFps: metrics.maxFps,
|
||||
timestamp: new Date().toISOString(),
|
||||
environment,
|
||||
}
|
||||
|
||||
// Only update if it's an improvement, forced, or no existing baseline
|
||||
if (!existingBaseline || force || metrics.averageFps > existingBaseline.avgFps) {
|
||||
data.baselines[platformKey][interaction] = newBaseline
|
||||
data.metadata.lastUpdated = new Date().toISOString()
|
||||
this.saveBaselines(data)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Updated baseline for ${interaction} on ${platformKey}:`, newBaseline)
|
||||
}
|
||||
}
|
||||
|
||||
compareWithBaseline(
|
||||
interaction: string,
|
||||
metrics: FPSMetrics,
|
||||
environment: Environment,
|
||||
autoCreateBaseline = true
|
||||
): ComparisonResult {
|
||||
const data = this.loadBaselines()
|
||||
const platformKey = this.getPlatformKey(environment)
|
||||
const baseline = data.baselines[platformKey]?.[interaction]
|
||||
|
||||
if (!baseline) {
|
||||
if (autoCreateBaseline) {
|
||||
// Automatically create baseline on first run
|
||||
this.updateBaseline(interaction, metrics, environment, true)
|
||||
return {
|
||||
interaction,
|
||||
current: metrics,
|
||||
baseline: {
|
||||
avgFps: metrics.averageFps,
|
||||
minFps: metrics.minFps,
|
||||
maxFps: metrics.maxFps,
|
||||
timestamp: new Date().toISOString(),
|
||||
environment,
|
||||
},
|
||||
avgFpsChange: 0,
|
||||
minFpsChange: 0,
|
||||
isRegression: false,
|
||||
isImprovement: false,
|
||||
status: 'pass',
|
||||
message: `New baseline created for ${interaction} on ${platformKey}. Average FPS: ${metrics.averageFps}`,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
interaction,
|
||||
current: metrics,
|
||||
baseline: {
|
||||
avgFps: 0,
|
||||
minFps: 0,
|
||||
maxFps: 0,
|
||||
timestamp: '',
|
||||
environment,
|
||||
},
|
||||
avgFpsChange: 0,
|
||||
minFpsChange: 0,
|
||||
isRegression: false,
|
||||
isImprovement: false,
|
||||
status: 'warning',
|
||||
message: `No baseline found for ${interaction} on ${platformKey}. Consider setting a baseline first.`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const avgFpsChange = this.calculatePercentageChange(metrics.averageFps, baseline.avgFps)
|
||||
const minFpsChange = this.calculatePercentageChange(metrics.minFps, baseline.minFps)
|
||||
|
||||
const isRegression = avgFpsChange < -this.regressionThreshold
|
||||
const isImprovement = avgFpsChange > this.warningThreshold
|
||||
|
||||
let status: 'pass' | 'warning' | 'fail' = 'pass'
|
||||
let message = `Performance stable. Average FPS: ${metrics.averageFps} (${avgFpsChange.toFixed(1)}% change)`
|
||||
|
||||
if (isRegression) {
|
||||
status = 'fail'
|
||||
message = `Performance regression detected! Average FPS dropped by ${Math.abs(avgFpsChange).toFixed(1)}% (${baseline.avgFps} → ${metrics.averageFps})`
|
||||
} else if (isImprovement) {
|
||||
status = 'warning' // Warning to indicate a significant change that might warrant baseline update
|
||||
message = `Performance improvement detected! Average FPS improved by ${avgFpsChange.toFixed(1)}% (${baseline.avgFps} → ${metrics.averageFps}). Consider updating baseline.`
|
||||
}
|
||||
|
||||
return {
|
||||
interaction,
|
||||
current: metrics,
|
||||
baseline,
|
||||
avgFpsChange,
|
||||
minFpsChange,
|
||||
isRegression,
|
||||
isImprovement,
|
||||
status,
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
getAllBaselines(): BaselineData {
|
||||
return this.loadBaselines()
|
||||
}
|
||||
|
||||
getBaseline(interaction: string, environment: Environment): PerformanceBaseline | null {
|
||||
const data = this.loadBaselines()
|
||||
const platformKey = this.getPlatformKey(environment)
|
||||
return data.baselines[platformKey]?.[interaction] || null
|
||||
}
|
||||
|
||||
deleteBaseline(interaction: string, environment: Environment): boolean {
|
||||
const data = this.loadBaselines()
|
||||
const platformKey = this.getPlatformKey(environment)
|
||||
|
||||
if (data.baselines[platformKey]?.[interaction]) {
|
||||
delete data.baselines[platformKey][interaction]
|
||||
data.metadata.lastUpdated = new Date().toISOString()
|
||||
this.saveBaselines(data)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
setThresholds(regressionThreshold: number, warningThreshold: number): void {
|
||||
if (regressionThreshold <= 0 || warningThreshold <= 0) {
|
||||
throw new Error('Thresholds must be positive numbers')
|
||||
}
|
||||
if (warningThreshold > regressionThreshold) {
|
||||
throw new Error('Warning threshold must be less than or equal to regression threshold')
|
||||
}
|
||||
|
||||
this.regressionThreshold = regressionThreshold
|
||||
this.warningThreshold = warningThreshold
|
||||
}
|
||||
|
||||
getThresholds(): { regressionThreshold: number; warningThreshold: number } {
|
||||
return {
|
||||
regressionThreshold: this.regressionThreshold,
|
||||
warningThreshold: this.warningThreshold,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user