this repo has no description
1/**
2 * Playwright test for js_top_worker demo page
3 * Run with: node test/browser/test_demo.js
4 */
5
6const { chromium } = require('playwright');
7
8async function runTests() {
9 console.log('Launching browser...');
10 const browser = await chromium.launch({
11 headless: true,
12 args: ['--no-sandbox', '--disable-setuid-sandbox']
13 });
14
15 const context = await browser.newContext();
16 const page = await context.newPage();
17
18 // Collect console messages
19 const consoleMessages = [];
20 page.on('console', msg => {
21 consoleMessages.push({ type: msg.type(), text: msg.text() });
22 console.log(`[browser ${msg.type()}] ${msg.text()}`);
23 });
24
25 // Collect page errors
26 const pageErrors = [];
27 page.on('pageerror', err => {
28 pageErrors.push(err.message);
29 console.log(`[page error] ${err.message}`);
30 });
31
32 console.log('Navigating to demo page...');
33 await page.goto('http://localhost:8000/demo.html', { timeout: 30000 });
34
35 // Wait for initialization
36 console.log('Waiting for toplevel to initialize...');
37 try {
38 await page.waitForFunction(
39 () => document.getElementById('status-indicator')?.classList.contains('ready'),
40 { timeout: 60000 }
41 );
42 console.log('✓ Toplevel initialized successfully');
43 } catch (e) {
44 const status = await page.$eval('#status-text', el => el.textContent);
45 console.log(`✗ Initialization failed. Status: ${status}`);
46
47 // Print relevant console messages
48 const errors = consoleMessages.filter(m => m.type === 'error' || m.text.includes('error'));
49 if (errors.length > 0) {
50 console.log('Console errors:');
51 errors.slice(0, 10).forEach(m => console.log(` [${m.type}] ${m.text}`));
52 }
53
54 await browser.close();
55 process.exit(1);
56 }
57
58 const results = {
59 passed: [],
60 failed: []
61 };
62
63 // Test 1: Basic Execution
64 console.log('\nTesting Basic Execution...');
65 try {
66 await page.click('button:has-text("Execute"):near(#exec-input)');
67 await page.waitForFunction(
68 () => !document.getElementById('exec-output')?.textContent.includes('Executing...'),
69 { timeout: 10000 }
70 );
71 const execOutput = await page.$eval('#exec-output', el => el.textContent);
72 if (execOutput.includes('Hello, OCaml!')) {
73 console.log('✓ Basic Execution works');
74 results.passed.push('Basic Execution');
75 } else {
76 console.log(`✗ Basic Execution failed. Output: ${execOutput}`);
77 results.failed.push({ name: 'Basic Execution', error: execOutput });
78 }
79 } catch (e) {
80 console.log(`✗ Basic Execution error: ${e.message}`);
81 results.failed.push({ name: 'Basic Execution', error: e.message });
82 }
83
84 // Test 2: Multiple Environments
85 console.log('\nTesting Multiple Environments...');
86 try {
87 await page.click('button:has-text("+ New Env")');
88 await page.waitForTimeout(1000);
89
90 const envButtons = await page.$$eval('#env-selector .env-btn', btns => btns.map(b => b.textContent));
91 if (envButtons.length > 1) {
92 console.log('✓ Environment creation works');
93 results.passed.push('Multiple Environments');
94 } else {
95 console.log(`✗ Environment creation failed. Buttons: ${envButtons.join(', ')}`);
96 results.failed.push({ name: 'Multiple Environments', error: 'No new env button appeared' });
97 }
98 } catch (e) {
99 console.log(`✗ Multiple Environments error: ${e.message}`);
100 results.failed.push({ name: 'Multiple Environments', error: e.message });
101 }
102
103 // Test 3: MIME Output
104 console.log('\nTesting MIME Output...');
105 try {
106 await page.click('button:has-text("Execute"):near(#mime-input)');
107 await page.waitForFunction(
108 () => !document.getElementById('mime-output')?.textContent.includes('Executing...'),
109 { timeout: 10000 }
110 );
111
112 // Check if rendered MIME content is visible
113 const mimeRendered = await page.$('#mime-rendered');
114 const isHidden = await mimeRendered?.evaluate(el => el.classList.contains('hidden'));
115 const mimeOutput = await page.$eval('#mime-output', el => el.textContent);
116
117 if (!isHidden) {
118 const svgContent = await mimeRendered?.evaluate(el => el.innerHTML);
119 if (svgContent?.includes('svg') || svgContent?.includes('circle')) {
120 console.log('✓ MIME Output works (SVG rendered)');
121 results.passed.push('MIME Output');
122 } else {
123 console.log(`✗ MIME Output - SVG not rendered. Content: ${svgContent?.substring(0, 100)}`);
124 results.failed.push({ name: 'MIME Output', error: 'SVG not rendered' });
125 }
126 } else {
127 console.log(`✗ MIME Output - rendered area hidden. Output: ${mimeOutput}`);
128 results.failed.push({ name: 'MIME Output', error: mimeOutput });
129 }
130 } catch (e) {
131 console.log(`✗ MIME Output error: ${e.message}`);
132 results.failed.push({ name: 'MIME Output', error: e.message });
133 }
134
135 // Test 4: Autocomplete
136 console.log('\nTesting Autocomplete...');
137 try {
138 await page.click('button:has-text("Complete")');
139 await page.waitForFunction(
140 () => !document.getElementById('complete-output')?.textContent.includes('Loading...'),
141 { timeout: 10000 }
142 );
143
144 const completions = await page.$eval('#complete-output', el => el.textContent);
145 if (completions.includes('map') || completions.includes('mapi')) {
146 console.log('✓ Autocomplete works');
147 results.passed.push('Autocomplete');
148 } else {
149 console.log(`✗ Autocomplete failed. Output: ${completions}`);
150 results.failed.push({ name: 'Autocomplete', error: completions });
151 }
152 } catch (e) {
153 console.log(`✗ Autocomplete error: ${e.message}`);
154 results.failed.push({ name: 'Autocomplete', error: e.message });
155 }
156
157 // Test 5: Type Information
158 console.log('\nTesting Type Information...');
159 try {
160 await page.click('button:has-text("Get Type")');
161 await page.waitForFunction(
162 () => !document.getElementById('type-output')?.textContent.includes('Loading...'),
163 { timeout: 10000 }
164 );
165
166 const typeOutput = await page.$eval('#type-output', el => el.textContent);
167 if (typeOutput.includes('int') || typeOutput.includes('list') || typeOutput.includes('->')) {
168 console.log('✓ Type Information works');
169 results.passed.push('Type Information');
170 } else {
171 console.log(`✗ Type Information failed. Output: ${typeOutput}`);
172 results.failed.push({ name: 'Type Information', error: typeOutput });
173 }
174 } catch (e) {
175 console.log(`✗ Type Information error: ${e.message}`);
176 results.failed.push({ name: 'Type Information', error: e.message });
177 }
178
179 // Test 6: Error Reporting
180 console.log('\nTesting Error Reporting...');
181 try {
182 await page.click('button:has-text("Check Errors")');
183 await page.waitForFunction(
184 () => !document.getElementById('errors-output')?.textContent.includes('Analyzing...'),
185 { timeout: 10000 }
186 );
187
188 const errorsOutput = await page.$eval('#errors-output', el => el.textContent);
189 // Should find type error or unknown identifier
190 if (errorsOutput.includes('Line') || errorsOutput.includes('error') || errorsOutput.includes('Error')) {
191 console.log('✓ Error Reporting works');
192 results.passed.push('Error Reporting');
193 } else {
194 console.log(`✗ Error Reporting failed. Output: ${errorsOutput}`);
195 results.failed.push({ name: 'Error Reporting', error: errorsOutput });
196 }
197 } catch (e) {
198 console.log(`✗ Error Reporting error: ${e.message}`);
199 results.failed.push({ name: 'Error Reporting', error: e.message });
200 }
201
202 // Test 7: Directives - #show List
203 console.log('\nTesting Directives...');
204 try {
205 await page.click('button:has-text("show List")');
206 await page.waitForFunction(
207 () => !document.getElementById('directive-output')?.textContent.includes('Executing...'),
208 { timeout: 10000 }
209 );
210
211 const directiveOutput = await page.$eval('#directive-output', el => el.textContent);
212 if (directiveOutput.includes('module') || directiveOutput.includes('List') || directiveOutput.includes('val')) {
213 console.log('✓ Directives work');
214 results.passed.push('Directives');
215 } else {
216 console.log(`✗ Directives failed. Output: ${directiveOutput}`);
217 results.failed.push({ name: 'Directives', error: directiveOutput });
218 }
219 } catch (e) {
220 console.log(`✗ Directives error: ${e.message}`);
221 results.failed.push({ name: 'Directives', error: e.message });
222 }
223
224 // Test 8: Custom Printers
225 console.log('\nTesting Custom Printers...');
226 try {
227 await page.click('button:has-text("Execute"):near(#printer-input)');
228 await page.waitForFunction(
229 () => !document.getElementById('printer-output')?.textContent.includes('Executing...'),
230 { timeout: 15000 }
231 );
232
233 const printerOutput = await page.$eval('#printer-output', el => el.textContent);
234 if (printerOutput.includes('[COLOR:') || printerOutput.includes('pp_color')) {
235 console.log('✓ Custom Printers work');
236 results.passed.push('Custom Printers');
237 } else {
238 console.log(`✗ Custom Printers failed. Output: ${printerOutput.substring(0, 200)}`);
239 results.failed.push({ name: 'Custom Printers', error: printerOutput.substring(0, 200) });
240 }
241 } catch (e) {
242 console.log(`✗ Custom Printers error: ${e.message}`);
243 results.failed.push({ name: 'Custom Printers', error: e.message });
244 }
245
246 // Test 9: Library Loading (#require)
247 console.log('\nTesting Library Loading...');
248 try {
249 await page.click('button:has-text("Execute"):near(#require-input)');
250 await page.waitForFunction(
251 () => !document.getElementById('require-output')?.textContent.includes('Executing'),
252 { timeout: 30000 }
253 );
254
255 const requireOutput = await page.$eval('#require-output', el => el.textContent);
256 // Str.split should return a list
257 if (requireOutput.includes('["a"; "b"; "c"]') || requireOutput.includes('string list')) {
258 console.log('✓ Library Loading works');
259 results.passed.push('Library Loading');
260 } else if (requireOutput.includes('Error') || requireOutput.includes('not found')) {
261 console.log(`✗ Library Loading failed (library not available). Output: ${requireOutput}`);
262 results.failed.push({ name: 'Library Loading', error: requireOutput });
263 } else {
264 console.log(`? Library Loading unclear. Output: ${requireOutput}`);
265 results.failed.push({ name: 'Library Loading', error: requireOutput });
266 }
267 } catch (e) {
268 console.log(`✗ Library Loading error: ${e.message}`);
269 results.failed.push({ name: 'Library Loading', error: e.message });
270 }
271
272 // Test 10: Toplevel Script Execution
273 console.log('\nTesting Toplevel Script Execution...');
274 try {
275 await page.click('button:has-text("Execute Script")');
276 await page.waitForFunction(
277 () => !document.getElementById('toplevel-output')?.textContent.includes('Executing script...'),
278 { timeout: 15000 }
279 );
280
281 const toplevelOutput = await page.$eval('#toplevel-output', el => el.textContent);
282 // Should show the squared numbers [1; 4; 9; 16; 25]
283 if (toplevelOutput.includes('[1; 4; 9; 16; 25]') || toplevelOutput.includes('square')) {
284 console.log('✓ Toplevel Script Execution works');
285 results.passed.push('Toplevel Script Execution');
286 } else {
287 console.log(`✗ Toplevel Script Execution failed. Output: ${toplevelOutput.substring(0, 200)}`);
288 results.failed.push({ name: 'Toplevel Script Execution', error: toplevelOutput.substring(0, 200) });
289 }
290 } catch (e) {
291 console.log(`✗ Toplevel Script Execution error: ${e.message}`);
292 results.failed.push({ name: 'Toplevel Script Execution', error: e.message });
293 }
294
295 // Summary
296 console.log('\n' + '='.repeat(50));
297 console.log('SUMMARY');
298 console.log('='.repeat(50));
299 console.log(`Passed: ${results.passed.length}`);
300 console.log(`Failed: ${results.failed.length}`);
301
302 if (results.failed.length > 0) {
303 console.log('\nFailed tests:');
304 results.failed.forEach(f => {
305 console.log(` - ${f.name}: ${f.error.substring(0, 100)}`);
306 });
307 }
308
309 // Print any page errors
310 if (pageErrors.length > 0) {
311 console.log('\nPage errors encountered:');
312 pageErrors.forEach(e => console.log(` ${e}`));
313 }
314
315 await browser.close();
316
317 process.exit(results.failed.length > 0 ? 1 : 0);
318}
319
320runTests().catch(e => {
321 console.error('Test runner error:', e);
322 process.exit(1);
323});