mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
109 lines
4.9 KiB
JavaScript
109 lines
4.9 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
|
||
|
|
import { chromium } from "@playwright/test";
|
||
|
|
import fs from "node:fs";
|
||
|
|
import os from "node:os";
|
||
|
|
import path from "node:path";
|
||
|
|
|
||
|
|
const baseUrl = (process.env.PAPERCLIP_PERF_BASE_URL || "http://localhost:3100").replace(/\/$/, "");
|
||
|
|
const companyPrefix = process.env.PAPERCLIP_PERF_COMPANY_PREFIX;
|
||
|
|
const url = companyPrefix
|
||
|
|
? `${baseUrl}/${companyPrefix}/tests/perf/long-thread`
|
||
|
|
: `${baseUrl}/tests/perf/long-thread`;
|
||
|
|
const origin = new URL(url).origin;
|
||
|
|
|
||
|
|
function loadBoardToken() {
|
||
|
|
const authPath = path.resolve(os.homedir(), ".paperclip/auth.json");
|
||
|
|
try {
|
||
|
|
const auth = JSON.parse(fs.readFileSync(authPath, "utf-8"));
|
||
|
|
const credentials = auth.credentials || {};
|
||
|
|
const matching = Object.values(credentials).find((entry) => {
|
||
|
|
if (!entry || !entry.token || !entry.apiBase) return false;
|
||
|
|
return new URL(entry.apiBase).origin === origin;
|
||
|
|
});
|
||
|
|
if (matching?.token) return matching.token;
|
||
|
|
const fallback = Object.values(credentials).find((entry) => entry?.token);
|
||
|
|
return fallback?.token ?? null;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const browser = await chromium.launch({ headless: true });
|
||
|
|
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
|
||
|
|
const boardToken = process.env.PAPERCLIP_PERF_BEARER_TOKEN || loadBoardToken();
|
||
|
|
|
||
|
|
if (boardToken) {
|
||
|
|
await page.route(`${origin}/**`, async (route) => {
|
||
|
|
await route.continue({
|
||
|
|
headers: { ...route.request().headers(), Authorization: `Bearer ${boardToken}` },
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const startedAt = Date.now();
|
||
|
|
await page.goto(url, { waitUntil: "networkidle" });
|
||
|
|
await page.waitForSelector('[data-testid="issue-chat-long-thread-perf"]', { timeout: 30_000 });
|
||
|
|
await page.waitForFunction(() => {
|
||
|
|
const target = Number(document.querySelector('[data-testid="perf-fixture-row-target"]')?.textContent ?? "450");
|
||
|
|
const renderedRows = document.querySelectorAll('[data-testid="issue-chat-message-row"]').length;
|
||
|
|
const virtualizer = document.querySelector('[data-testid="issue-chat-thread-virtualizer"]');
|
||
|
|
if (!virtualizer) return renderedRows >= target;
|
||
|
|
const virtualCount = Number(virtualizer.getAttribute("data-virtual-count") ?? "0");
|
||
|
|
return virtualCount >= target && renderedRows > 0 && renderedRows < target;
|
||
|
|
}, null, { timeout: 60_000 });
|
||
|
|
const rowReadyMs = Date.now() - startedAt;
|
||
|
|
|
||
|
|
const metrics = await page.evaluate(async () => {
|
||
|
|
const text = (testId) => document.querySelector(`[data-testid="${testId}"]`)?.textContent?.trim() ?? "";
|
||
|
|
const numericMs = (testId) => {
|
||
|
|
const value = text(testId).replace(/\s*ms$/, "");
|
||
|
|
const parsed = Number(value);
|
||
|
|
return Number.isFinite(parsed) ? parsed : null;
|
||
|
|
};
|
||
|
|
|
||
|
|
const rowCount = document.querySelectorAll('[data-testid="issue-chat-message-row"]').length;
|
||
|
|
const virtualizer = document.querySelector('[data-testid="issue-chat-thread-virtualizer"]');
|
||
|
|
const virtualCount = Number(virtualizer?.getAttribute("data-virtual-count") ?? "0");
|
||
|
|
const assistantRowCount = document.querySelectorAll('[data-testid="issue-chat-message-row"][data-message-role="assistant"]').length;
|
||
|
|
const systemRowCount = document.querySelectorAll('[data-testid="issue-chat-message-row"][data-message-role="system"]').length;
|
||
|
|
const userRowCount = document.querySelectorAll('[data-testid="issue-chat-message-row"][data-message-role="user"]').length;
|
||
|
|
const markdownRows = Number(text("perf-fixture-markdown-rows"));
|
||
|
|
const commitCount = Number(text("perf-commit-count"));
|
||
|
|
const scrollStartY = window.scrollY;
|
||
|
|
const scrollTarget = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
|
||
|
|
const scrollStartedAt = performance.now();
|
||
|
|
window.scrollTo({ top: scrollTarget, behavior: "instant" });
|
||
|
|
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
||
|
|
await new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
||
|
|
const scrollResponsiveMs = performance.now() - scrollStartedAt;
|
||
|
|
|
||
|
|
return {
|
||
|
|
url: window.location.href,
|
||
|
|
fixtureRowTarget: Number(text("perf-fixture-row-target")),
|
||
|
|
virtualized: Boolean(virtualizer),
|
||
|
|
virtualCount,
|
||
|
|
rowCount,
|
||
|
|
assistantRowCount,
|
||
|
|
userRowCount,
|
||
|
|
systemRowCount,
|
||
|
|
markdownRows,
|
||
|
|
commitCount,
|
||
|
|
mountActualDurationMs: numericMs("perf-mount-duration"),
|
||
|
|
latestActualDurationMs: numericMs("perf-latest-duration"),
|
||
|
|
maxActualDurationMs: numericMs("perf-max-duration"),
|
||
|
|
totalActualDurationMs: numericMs("perf-total-duration"),
|
||
|
|
reactProfilerAvailable: commitCount > 0,
|
||
|
|
scrollResponsiveMs: Number(scrollResponsiveMs.toFixed(1)),
|
||
|
|
scrollDeltaPx: Math.round(Math.abs(window.scrollY - scrollStartY)),
|
||
|
|
documentHeightPx: Math.round(document.documentElement.scrollHeight),
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
const elapsedMs = Date.now() - startedAt;
|
||
|
|
console.log(JSON.stringify({ ...metrics, renderReadyMs: rowReadyMs, elapsedMs }, null, 2));
|
||
|
|
} finally {
|
||
|
|
await browser.close();
|
||
|
|
}
|