From 11cd6f5fc05e759b7f482aa80914912854c019b7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:14:48 +0000 Subject: [PATCH 1/5] Initial plan From edc76ca6bcdcde2232b88e4ff5803cd46c35988e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:24:47 +0000 Subject: [PATCH 2/5] Initial analysis: browser tests working but need server startup improvements Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com> --- codecept.conf.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 codecept.conf.js diff --git a/codecept.conf.js b/codecept.conf.js new file mode 100644 index 0000000..84ec08a --- /dev/null +++ b/codecept.conf.js @@ -0,0 +1,15 @@ +module.exports = { + tests: './test/e2e/tests/*.js', + output: './output', + helpers: { + Playwright: { + url: 'http://localhost:3000', + browser: 'chromium', + show: false, + } + }, + include: {}, + bootstrap: null, + mocha: {}, + name: 'ui-test' +}; \ No newline at end of file From a615767b5be42c696e0bc464a40809f789488fea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 12:32:49 +0000 Subject: [PATCH 3/5] Fix browser test connectivity with improved server startup and error handling Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com> --- bin/codecept-ui.js | 96 ++++++++++++++++++++++++++++++++----- src/main.js | 42 ++++++++++++---- test/server-startup.spec.js | 50 +++++++++++++++++++ 3 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 test/server-startup.spec.js diff --git a/bin/codecept-ui.js b/bin/codecept-ui.js index e075a36..731c96a 100755 --- a/bin/codecept-ui.js +++ b/bin/codecept-ui.js @@ -17,7 +17,27 @@ const io = require('socket.io')({ methods: ["GET", "POST"], transports: ['websocket', 'polling'] }, - allowEIO3: true // Support for older Socket.IO clients + allowEIO3: true, // Support for older Socket.IO clients + // Add additional configuration for better reliability + pingTimeout: 60000, + pingInterval: 25000, + connectTimeout: 45000, + serveClient: true, + // Allow connections from localhost variations + allowRequest: (req, callback) => { + const origin = req.headers.origin; + const host = req.headers.host; + + // Allow localhost connections and same-host connections + if (!origin || + origin.includes('localhost') || + origin.includes('127.0.0.1') || + (host && origin.includes(host.split(':')[0]))) { + callback(null, true); + } else { + callback(null, true); // Allow all for now, can be more restrictive if needed + } + } }); const { events } = require('../lib/model/ws-events'); @@ -65,17 +85,71 @@ codeceptjsFactory.create({}, options).then(() => { const applicationPort = options.port; const webSocketsPort = options.wsPort; - io.listen(webSocketsPort); - app.listen(applicationPort); - - // eslint-disable-next-line no-console - console.log('🌟 CodeceptUI started!'); - - // eslint-disable-next-line no-console - console.log(`šŸ‘‰ Open http://localhost:${applicationPort} to see CodeceptUI in a browser\n\n`); + // Start servers with proper error handling and readiness checks + let httpServer; + let wsServer; + + try { + // Start WebSocket server first + wsServer = io.listen(webSocketsPort); + debug(`WebSocket server started on port ${webSocketsPort}`); + + // Start HTTP server + httpServer = app.listen(applicationPort, () => { + // eslint-disable-next-line no-console + console.log('🌟 CodeceptUI started!'); + // eslint-disable-next-line no-console + console.log(`šŸ‘‰ Open http://localhost:${applicationPort} to see CodeceptUI in a browser\n\n`); + // eslint-disable-next-line no-console + debug(`Listening for websocket connections on port ${webSocketsPort}`); + }); + + // Handle server errors + httpServer.on('error', (err) => { + if (err.code === 'EADDRINUSE') { + console.error(`āŒ Port ${applicationPort} is already in use. Please try a different port or stop the service using this port.`); + } else { + console.error(`āŒ Failed to start HTTP server: ${err.message}`); + } + process.exit(1); + }); + + wsServer.on('error', (err) => { + if (err.code === 'EADDRINUSE') { + console.error(`āŒ WebSocket port ${webSocketsPort} is already in use. Please try a different port or stop the service using this port.`); + } else { + console.error(`āŒ Failed to start WebSocket server: ${err.message}`); + } + process.exit(1); + }); + + } catch (error) { + console.error(`āŒ Server startup failed: ${error.message}`); + process.exit(1); + } - // eslint-disable-next-line no-console - debug(`Listening for websocket connections on port ${webSocketsPort}`); + // Graceful shutdown handling + const gracefulShutdown = () => { + console.log('\nšŸ›‘ Shutting down CodeceptUI...'); + if (httpServer) { + httpServer.close(() => { + debug('HTTP server closed'); + }); + } + if (wsServer) { + wsServer.close(() => { + debug('WebSocket server closed'); + }); + } + process.exit(0); + }; + + process.on('SIGINT', gracefulShutdown); + process.on('SIGTERM', gracefulShutdown); + process.on('uncaughtException', (err) => { + console.error('āŒ Uncaught Exception:', err); + gracefulShutdown(); + }); if (options.app) { // open electron app diff --git a/src/main.js b/src/main.js index 8abf306..ab61376 100644 --- a/src/main.js +++ b/src/main.js @@ -31,17 +31,35 @@ const store = require('./store').default; // Use relative paths for reverse proxy setups wsConnection = baseUrl.replace('http', 'ws'); } else { - // Standard configuration - fetch port info - try { - const response = await axios.get('/api/ports'); - const data = await response.data; - wsConnection = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.hostname}:${data.wsPort}`; - } catch (err) { - // Fallback to same origin if port fetch fails - wsConnection = baseUrl.replace('http', 'ws'); + // Standard configuration - fetch port info with retry logic + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + try { + const response = await axios.get('/api/ports', { timeout: 5000 }); + const data = await response.data; + wsConnection = `${window.location.protocol === 'https:' ? 'wss:' : 'ws:'}//${window.location.hostname}:${data.wsPort}`; + console.log('āœ… Successfully fetched WebSocket port info:', data); + break; + } catch (err) { + retryCount++; + console.warn(`āš ļø Failed to fetch port info (attempt ${retryCount}/${maxRetries}):`, err.message); + + if (retryCount >= maxRetries) { + console.warn('šŸ”„ Using fallback WebSocket connection to same origin'); + // Fallback to same origin if port fetch fails after retries + wsConnection = baseUrl.replace('http', 'ws'); + } else { + // Wait before retrying + await new Promise(resolve => setTimeout(resolve, 1000 * retryCount)); + } + } } } + console.log('šŸ”Œ Connecting to WebSocket:', wsConnection); + Vue.use(new VueSocketIO({ debug: true, connection: wsConnection, @@ -50,6 +68,14 @@ const store = require('./store').default; actionPrefix: 'SOCKET_', mutationPrefix: 'SOCKET_' }, + options: { + // Add connection options for better reliability + timeout: 10000, + reconnection: true, + reconnectionAttempts: 5, + reconnectionDelay: 1000, + forceNew: false + } })); })(); Vue.config.productionTip = false; diff --git a/test/server-startup.spec.js b/test/server-startup.spec.js new file mode 100644 index 0000000..d7a7f54 --- /dev/null +++ b/test/server-startup.spec.js @@ -0,0 +1,50 @@ +const test = require('ava'); +const fs = require('fs'); +const path = require('path'); + +test('Server startup script has proper error handling', (t) => { + const startupScript = path.join(__dirname, '..', 'bin', 'codecept-ui.js'); + const content = fs.readFileSync(startupScript, 'utf8'); + + // Check for error handling patterns + t.true(content.includes('httpServer.on(\'error\''), 'Should have HTTP server error handling'); + t.true(content.includes('wsServer.on(\'error\''), 'Should have WebSocket server error handling'); + t.true(content.includes('EADDRINUSE'), 'Should handle port already in use error'); + t.true(content.includes('gracefulShutdown'), 'Should have graceful shutdown handling'); + t.true(content.includes('SIGINT'), 'Should handle SIGINT signal'); + t.true(content.includes('SIGTERM'), 'Should handle SIGTERM signal'); +}); + +test('Frontend has retry logic for port fetching', (t) => { + const mainScript = path.join(__dirname, '..', 'src', 'main.js'); + const content = fs.readFileSync(mainScript, 'utf8'); + + // Check for retry logic patterns + t.true(content.includes('maxRetries'), 'Should have maximum retry limit'); + t.true(content.includes('retryCount'), 'Should track retry attempts'); + t.true(content.includes('Successfully fetched WebSocket port info'), 'Should log successful port fetch'); + t.true(content.includes('Failed to fetch port info'), 'Should log failed port fetch attempts'); + t.true(content.includes('setTimeout'), 'Should have delay between retries'); +}); + +test('Socket.IO configuration includes reliability improvements', (t) => { + const startupScript = path.join(__dirname, '..', 'bin', 'codecept-ui.js'); + const content = fs.readFileSync(startupScript, 'utf8'); + + // Check for Socket.IO reliability configurations + t.true(content.includes('pingTimeout'), 'Should configure ping timeout'); + t.true(content.includes('pingInterval'), 'Should configure ping interval'); + t.true(content.includes('connectTimeout'), 'Should configure connection timeout'); + t.true(content.includes('allowRequest'), 'Should have custom connection validation'); +}); + +test('Frontend Socket.IO has connection options for reliability', (t) => { + const mainScript = path.join(__dirname, '..', 'src', 'main.js'); + const content = fs.readFileSync(mainScript, 'utf8'); + + // Check for Socket.IO client options + t.true(content.includes('options:'), 'Should have Socket.IO client options'); + t.true(content.includes('reconnection: true'), 'Should enable reconnection'); + t.true(content.includes('reconnectionAttempts'), 'Should configure reconnection attempts'); + t.true(content.includes('reconnectionDelay'), 'Should configure reconnection delay'); +}); \ No newline at end of file From b5e6f0cfb98317f39d991de3dda10fd92d51ce1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Sep 2025 13:06:16 +0000 Subject: [PATCH 4/5] Add dynamic language detection for code editor syntax highlighting Co-authored-by: kobenguyent <7845001+kobenguyent@users.noreply.github.com> --- src/components/ScenarioSource.vue | 15 ++- src/utils/languageDetection.js | 160 ++++++++++++++++++++++++++++++ test/language-detection.spec.js | 109 ++++++++++++++++++++ 3 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 src/utils/languageDetection.js create mode 100644 test/language-detection.spec.js diff --git a/src/components/ScenarioSource.vue b/src/components/ScenarioSource.vue index c1b02ca..109200f 100644 --- a/src/components/ScenarioSource.vue +++ b/src/components/ScenarioSource.vue @@ -5,7 +5,7 @@ v-if="!isEditing" class="source-view" > -
+

- Lines {{ currentStartLine }}-{{ currentEndLine }} | CodeceptJS {{ mode }} Mode + Lines {{ currentStartLine }}-{{ currentEndLine }} | {{ languageDisplayName }} | CodeceptJS {{ mode }} Mode

@@ -75,7 +75,7 @@ v-model="editorContent" :options="editorOptions" @editorDidMount="onEditorMounted" - language="javascript" + :language="detectedLanguage.monaco" theme="vs-light" height="400" /> @@ -140,6 +140,7 @@