71 lines
3.6 KiB
JavaScript
71 lines
3.6 KiB
JavaScript
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();
|
|
}
|