1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
// Renders runs + results with mocked backend data so layerchart charts mount.
import { chromium } from 'playwright';
import http from 'node:http';
import { createReadStream } from 'node:fs';
import path from 'node:path';
const dist = path.resolve('dist');
const shotPrefix = "../artifacts/ui-shadcn/chart-probe";
const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css', '.svg': 'image/svg+xml', '.woff2': 'font/woff2' };
const server = http.createServer((req, res) => {
const url = req.url || '/';
if (url.startsWith('/api')) { res.writeHead(404, { 'content-type': 'application/json' }); res.end('{}'); return; }
let file = url.split('?')[0].split('#')[0];
if (file === '/' || file === '') file = '/index.html';
let responded = false;
const send = (fp, isFallback) => {
const s = createReadStream(fp);
s.on('open', () => { if (responded) return; responded = true; res.writeHead(200, { 'content-type': MIME[path.extname(fp)] || 'application/octet-stream' }); s.pipe(res); });
s.on('error', () => { if (!isFallback && !responded) send(path.join(dist, 'index.html'), true); else if (!responded) { responded = true; res.writeHead(404); res.end(); } });
};
send(path.join(dist, file));
});
await new Promise((r) => server.listen(0, '127.0.0.1', r));
const port = server.address().port;
const RUN_ID1 = '11111111-1111-1111-1111-111111111111';
const RUN_ID2 = '22222222-2222-2222-2222-222222222222';
const PROJ = '12345678-aaaa-bbbb-cccc-000000000000';
function makeResult() {
const equity = [];
for (let i = 0; i < 90; i++) {
equity.push({
date: new Date(2024, 0, 2 + i).toISOString().slice(0, 10),
equity: 100000 * (1 + 0.01 * Math.sin(i / 5) + i * 0.0004),
cash: 100000 * (1 - 0.2 + 0.01 * Math.sin(i / 7)),
benchmark: 100000 * (1 + i * 0.0002)
});
}
return {
status: 'succeeded', engine: { name: 'backtrader', version: '1.9' },
metrics: { total_return: 0.0123, annual_return: 0.05, max_drawdown: -0.033, sharpe: 0.9, trade_count: 1, final_equity: 101234.5 },
equity,
orders: [], trades: [], closed_trades: [], logs: [], warnings: [],
elapsed_ms: 1234, peak_rss_kb: 8192, data_manifest_hash: 'deadbeefdeadbeef1234'
};
}
const result = makeResult();
const run1 = { id: RUN_ID1, project_id: PROJ, version_id: 'v', dataset_id: 'd', status: 'succeeded', config: { capital: 100000, commission: 0.0003, slippage: 0.001 }, created_at: '2026-01-01T00:00:00Z', result };
const run2 = { ...run1, id: RUN_ID2 };
const runsList = { items: [run1, run2] };
const browser = await chromium.launch();
const out = {};
for (const route of ['runs', 'results']) {
for (const w of [375, 768, 1440]) {
const ctx = await browser.newContext({ viewport: { width: w, height: w * 2 } });
const page = await ctx.newPage();
const errors = [];
page.on('pageerror', (e) => errors.push(String(e)));
await page.route('**/api/**', (r) => {
const u = r.request().url();
const respond = (obj) => r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(obj) });
if (/\/api\/auth\/me/.test(u)) return respond({ user: { id: 'u1', name: 'probe', role: 'user', session_id: 's1' } });
if (/\/api\/runs\/[0-9a-f-]+$/.test(u)) return respond(u.includes(RUN_ID2) ? run2 : run1);
if (/\/api\/runs/.test(u)) return respond(runsList);
if (/\/api\/projects\//.test(u)) return respond({ id: PROJ, name: 'probe', draft_code: 'x=1', draft_generation: 1 });
return respond({ items: [] });
});
const hash = route === 'runs' ? '#/runs' : `#/projects/${PROJ}/results?run=${RUN_ID1}`;
await page.goto('http://127.0.0.1:' + port + '/' + hash, { waitUntil: 'networkidle' });
if (route === 'runs') {
// select up to 2 runs to trigger the comparison chart
await page.evaluate(() => {
const boxes = document.querySelectorAll('input[type="checkbox"]');
for (let k = 0; k < Math.min(2, boxes.length); k++) {
boxes[k].click();
}
});
await page.waitForTimeout(500);
}
await page.waitForTimeout(900);
const m = await page.evaluate(() => ({ sw: document.documentElement.scrollWidth, cw: document.documentElement.clientWidth, svgs: document.querySelectorAll('svg').length }));
const overflow = Math.max(0, m.sw - m.cw);
await page.screenshot({ path: shotPrefix + '-' + route + '-' + w + '.png', fullPage: true });
out[route + '@' + w] = { overflow, svgs: m.svgs, errors };
await ctx.close();
}
}
await browser.close();
server.close();
let bad = 0;
for (const k of Object.keys(out)) {
const r = out[k];
const ok = r.overflow === 0 && r.errors.length === 0 && r.svgs > 0;
console.log((ok ? 'OK ' : 'FAIL ') + k + ' overflow=' + r.overflow + ' svgs=' + r.svgs + ' errors=' + r.errors.length + (r.errors[0] ? ' :: ' + r.errors[0] : ''));
if (!ok) bad++;
}
process.exit(bad === 0 ? 0 : 1);
|