LazyBoy2/tools/playwright/frontend-resources.test.mjs

71 lines
3.6 KiB
JavaScript
Raw Normal View History

2026-09-16 06:47:32 +00:00
import assert from 'node:assert/strict';
import { chromium } from 'playwright';
// Run against Vite with: node tools/playwright/frontend-resources.test.mjs [url]
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ ignoreHTTPSErrors: true });
const errors = [];
page.on('pageerror', error => errors.push(error.message));
const activity = { state: 'idle', running: false, queued: false, active_task_ids: [], observed_at_ms: 0 };
const agent = { id: 'test', name: 'Resource test', activity, running: false };
const transcript = Array.from({ length: 2000 }, (_, i) => ({ role: i % 2 ? 'assistant' : 'user', content: `Message ${i}\n\n**Markdown** content`, at: new Date(1700000000000 + i * 60000).toISOString() }));
transcript[1999].content = 'Large message ' + 'x'.repeat(25000);
let requests = 0;
let concurrent = 0;
let peak = 0;
await page.addInitScript(() => {
window.sources = [];
window.EventSource = class {
constructor() { window.sources.push(this); setTimeout(() => this.onopen?.(), 0); }
close() { this.closed = true; }
};
});
await page.route('**/api/**', async route => {
const path = new URL(route.request().url()).pathname;
let body = {};
if (path === '/api/agents') body = [agent];
if (path === '/api/agents/test/activity') body = activity;
if (path === '/api/agents/test/computer') body = { ready: true, viewer_url: '/fake-desktop' };
if (path === '/api/agents/test') {
requests++;
peak = Math.max(peak, ++concurrent);
await new Promise(resolve => setTimeout(resolve, 120));
concurrent--;
body = { ...agent, transcript };
}
await route.fulfill({ json: body });
});
await page.route('**/fake-desktop*', route => route.fulfill({ body: '<html>Desktop</html>', contentType: 'text/html' }));
await page.goto(process.argv[2] || 'https://127.0.0.1:5174');
await page.waitForSelector('.message-block');
assert.equal(await page.locator('.message-block').count(), 40);
assert.equal(await page.locator('.message-body pre').count(), 1, 'Large messages skip Markdown parsing');
await page.locator('.history-pages button').first().click();
assert.equal(await page.locator('.message-block').count(), 40);
assert.match(await page.locator('.message-block').first().textContent(), /Message 1920/);
await page.locator('.history-pages button').last().click();
await page.waitForSelector('iframe');
await page.locator('.meeting-stage-head button').click();
assert.equal(await page.locator('iframe').count(), 0, 'Collapsed desktop must unmount');
await page.waitForTimeout(300);
requests = 0; peak = 0;
await page.evaluate(() => {
const source = window.sources.filter(source => !source.closed).at(-1);
source.onmessage({ data: JSON.stringify({ events: Array.from({ length: 100 }, (_, id) => ({ id, kind: 'reply', payload: {} })) }) });
});
await page.waitForTimeout(600);
assert.equal(peak, 1, 'Reply bursts must not overlap transcript requests');
assert.equal(requests, 2, 'Reply burst should coalesce to one request and one trailing refresh');
await page.evaluate(() => {
Object.defineProperty(document, 'hidden', { configurable: true, value: true });
document.dispatchEvent(new Event('visibilitychange'));
});
await page.waitForTimeout(100);
assert.equal(await page.evaluate(() => window.sources.every(source => source.closed)), true);
assert.deepEqual(errors, []);
console.log('PASS: 2,000 messages → 40 mounted; older/latest navigation; large text fallback; collapsed iframe removed; 100 events → 2 serial requests; background SSE closed; no runtime errors.');
} finally {
await browser.close();
}