this repo has no description
1#!/usr/bin/env node
2/**
3 * Playwright test runner for x-ocaml browser tests.
4 *
5 * Usage:
6 * node run_tests.js [--headed]
7 *
8 * Serves x-ocaml build output + test HTML, runs tests in Chromium.
9 */
10
11const { chromium } = require('playwright');
12const http = require('http');
13const fs = require('fs');
14const path = require('path');
15
16const PORT = 8766;
17const TIMEOUT = 60000;
18
19const testDir = path.dirname(fs.realpathSync(__filename));
20const xocamlDir = path.resolve(testDir, '..');
21
22const mimeTypes = {
23 '.html': 'text/html',
24 '.js': 'application/javascript',
25 '.css': 'text/css',
26};
27
28function startServer() {
29 return new Promise((resolve, reject) => {
30 const server = http.createServer((req, res) => {
31 let filePath = req.url === '/' ? '/test_modes.html' : req.url;
32
33 // Serve from: test dir first, then x-ocaml root (for promoted JS),
34 // then jsoo-code-mirror includes (for bundle.js)
35 const searchPaths = [
36 path.join(testDir, filePath),
37 path.join(xocamlDir, filePath),
38 path.join(xocamlDir, 'jsoo-code-mirror/includes', filePath),
39 ];
40
41 let fullPath = searchPaths.find(p => fs.existsSync(p));
42
43 if (!fullPath) {
44 res.writeHead(404);
45 res.end('Not found: ' + filePath);
46 return;
47 }
48
49 const ext = path.extname(fullPath);
50 const contentType = mimeTypes[ext] || 'application/octet-stream';
51
52 fs.readFile(fullPath, (err, content) => {
53 if (err) {
54 res.writeHead(500);
55 res.end('Error reading file');
56 return;
57 }
58 res.writeHead(200, { 'Content-Type': contentType });
59 res.end(content);
60 });
61 });
62
63 server.listen(PORT, () => {
64 console.log(`Test server running at http://localhost:${PORT}/`);
65 resolve(server);
66 });
67
68 server.on('error', reject);
69 });
70}
71
72async function runTests(headed = false) {
73 let server;
74 let browser;
75 let exitCode = 0;
76
77 try {
78 server = await startServer();
79
80 browser = await chromium.launch({ headless: !headed });
81 const page = await browser.newPage();
82
83 const logs = [];
84 page.on('console', msg => {
85 const text = msg.text();
86 logs.push(text);
87 console.log(`[browser] ${text}`);
88 });
89
90 page.on('pageerror', err => {
91 console.error(`[browser error] ${err.message}`);
92 });
93
94 console.log('Loading test page...');
95 await page.goto(`http://localhost:${PORT}/`);
96
97 console.log('Waiting for tests to complete...');
98 await page.waitForFunction(
99 () => window.testResults && window.testResults.done,
100 { timeout: TIMEOUT }
101 );
102
103 const testResults = await page.evaluate(() => ({
104 total: window.testResults.total,
105 passed: window.testResults.passed,
106 failed: window.testResults.failed,
107 details: window.testResults.details || [],
108 }));
109
110 console.log('\n========================================');
111 console.log(`Test Results: ${testResults.passed}/${testResults.total} passed`);
112 if (testResults.details.length > 0) {
113 for (const d of testResults.details) {
114 const icon = d.ok ? 'PASS' : 'FAIL';
115 console.log(` [${icon}] ${d.name}`);
116 }
117 }
118 console.log('========================================\n');
119
120 if (testResults.failed > 0) {
121 console.log('FAILED: Some tests did not pass');
122 exitCode = 1;
123 } else {
124 console.log('SUCCESS: All tests passed');
125 }
126
127 } catch (err) {
128 console.error('Error running tests:', err.message);
129 exitCode = 1;
130 } finally {
131 if (browser) await browser.close();
132 if (server) server.close();
133 }
134
135 process.exit(exitCode);
136}
137
138const headed = process.argv.includes('--headed');
139runTests(headed);