diff --git a/lib/runner.js b/lib/runner.js index db53f82..23e9c3d 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -2,13 +2,57 @@ const fs = require('fs'); const path = require('path'); +// Simple Queue Implementation to prevent concurrency crashes +class TestQueue { + constructor() { + this.queue = []; + this.running = false; + } + + add(task) { + return new Promise((resolve, reject) => { + this.queue.push({ task, resolve, reject }); + this.process(); + }); + } + + async process() { + if (this.running || this.queue.length === 0) return; + + this.running = true; + const { task, resolve, reject } = this.queue.shift(); + + try { + const result = await task(); + resolve(result); + } catch (error) { + reject(error); + } finally { + this.running = false; + this.process(); // Process next item + } + } +} + +const runnerQueue = new TestQueue(); + /** - * Run a performance test using Lighthouse + * Run a performance test using Lighthouse (Serialized) * @param {string} url - The URL to test * @param {object} options - Configuration options (mobile vs desktop, etc.) * @returns {Promise} - Test results */ async function runTest(url, options = {}) { + // Wrap the actual test logic in a queue task + return runnerQueue.add(async () => { + return _executeTest(url, options); + }); +} + +/** + * Internal function to execute the test (NOT exported directly) + */ +async function _executeTest(url, options) { // Dynamically import dependencies (ESM) const { default: lighthouse } = await import('lighthouse'); const chromeLauncher = await import('chrome-launcher'); @@ -25,10 +69,25 @@ async function runTest(url, options = {}) { // Launch Chrome console.log('Launching Chrome...'); const chromePath = process.platform === 'linux' ? '/usr/bin/chromium' : undefined; + + // Use a unique user data dir to prevent profile locking issues + const userDataDir = path.join(os.tmpdir(), `lh-profile-${uuidv4()}`); + + // Ensure tmp dir exists if likely to crash + if (!fs.existsSync(userDataDir)) { + fs.mkdirSync(userDataDir, { recursive: true }); + } + const chrome = await chromeLauncher.launch({ - chromeFlags: ['--headless', '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], + chromeFlags: [ + '--headless', + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-dev-shm-usage', + `--user-data-dir=${userDataDir}` // Unique profile for this run + ], chromePath: chromePath, - port: 0 // Force random port to avoid concurrency issues with default 9222 + port: 0 // Force random port }); // Lighthouse Config @@ -87,6 +146,13 @@ async function runTest(url, options = {}) { fs.writeFileSync(jsonPath, JSON.stringify(summary, null, 2)); await chrome.kill(); + + // Cleanup User Data Dir + try { + fs.rmSync(userDataDir, { recursive: true, force: true }); + } catch (e) { + console.error('Failed to cleanup temp profile:', e); + } // Insert into Database // We expect user_uuid and user_ip to be passed in options, or handle gracefully if not @@ -120,6 +186,8 @@ async function runTest(url, options = {}) { } catch (error) { if (chrome) await chrome.kill(); + // Try cleanup again in error case + try { fs.rmSync(userDataDir, { recursive: true, force: true }); } catch (e) {} throw error; } } @@ -160,6 +228,9 @@ async function getHistory(userUuid, userIp) { } } +// Need os for tmpdir +const os = require('os'); + module.exports = { runTest, getHistory