import { test, expect, Page } from '@playwright/test'
// Helper to generate large table data
function generateTableHTML(rows: number, cols: number): string {
const headers = Array.from({ length: cols }, (_, i) => `
Column ${i + 1} | `).join('')
const tableRows = Array.from({ length: rows }, (_, i) => {
const cells = Array.from({ length: cols }, (_, j) => {
if (j === 0) return `Item ${Math.floor(Math.random() * 1000)} | `
if (j === 1) return `${Math.floor(Math.random() * 10000)} | `
return `Col${j}_${Math.random().toString(36).substr(2, 5)} | `
}).join('')
return `${cells}
`
}).join('')
return `
`
}
async function measureSortTime(page: Page, selector: string): Promise {
return await page.evaluate((sel) => {
return new Promise((resolve) => {
const header = document.querySelector(sel) as HTMLElement
const table = header.closest('table') as HTMLTableElement
let startTime: number
const onSortStart = () => {
startTime = performance.now()
}
const onSortEnd = () => {
const endTime = performance.now()
table.removeEventListener('sort-start', onSortStart)
table.removeEventListener('sort-end', onSortEnd)
resolve(endTime - startTime)
}
table.addEventListener('sort-start', onSortStart)
table.addEventListener('sort-end', onSortEnd)
header.click()
})
}, selector)
}
test.describe('Sortable Performance Benchmarks', () => {
test.beforeEach(async ({ page }) => {
// Load sortable script
await page.goto(
'data:text/html,',
)
})
// My current laptop has broken ram, so I need to double the time limits locally
const modifier = process.env.CI ? 1 : 2
const benchmarkConfigs = [
{ rows: 100, cols: 5, name: 'Small table', maxTime: 50 * modifier },
{ rows: 500, cols: 5, name: 'Medium table', maxTime: 200 * modifier },
{ rows: 1000, cols: 5, name: 'Large table', maxTime: 500 * modifier },
{ rows: 2000, cols: 5, name: 'Very large table', maxTime: 1000 * modifier },
{ rows: 100, cols: 10, name: 'Wide table', maxTime: 50 * modifier }, // width should have no impact
]
for (const config of benchmarkConfigs) {
test(`${config.name} (${config.rows} rows, ${config.cols} cols) sorts within ${config.maxTime}ms`, async ({
page,
}) => {
// Generate and inject table
const tableHTML = generateTableHTML(config.rows, config.cols)
await page.setContent(`
${tableHTML}
`)
// Wait for table to be ready
await page.waitForSelector('table.sortable th')
// Measure sort time
const sortTime = await measureSortTime(page, 'table.sortable th:first-child')
console.log(`${config.name}: ${sortTime.toFixed(2)}ms`)
// Assert performance threshold
expect(sortTime).toBeLessThan(config.maxTime)
})
}
test('Multiple rapid sorts performance', async ({ page }) => {
const tableHTML = generateTableHTML(500, 5)
await page.setContent(`
${tableHTML}
`)
await page.waitForSelector('table.sortable th')
// Measure time for 10 rapid sorts
const totalTime = await page.evaluate(() => {
const header = document.querySelector('table.sortable th:first-child') as HTMLElement
const startTime = performance.now()
for (let i = 0; i < 10; i++) {
header.click()
}
const endTime = performance.now()
return endTime - startTime
})
console.log(`10 rapid sorts: ${totalTime.toFixed(2)}ms (avg: ${(totalTime / 10).toFixed(2)}ms per sort)`)
expect(totalTime).toBeLessThan(2000) // 10 sorts should complete within 2 seconds
})
test('Memory usage stability', async ({ page }) => {
const tableHTML = generateTableHTML(1000, 5)
await page.setContent(`
${tableHTML}
`)
await page.waitForSelector('table.sortable th')
// Perform many sorts and check for memory leaks
await page.evaluate(() => {
const header = document.querySelector('table.sortable th:first-child') as HTMLElement
for (let i = 0; i < 50; i++) {
header.click()
}
})
// If we get here without timeout/crash, memory usage is stable
expect(true).toBe(true)
})
})