diff --git a/src/server/handlers/tdd-handler.js b/src/server/handlers/tdd-handler.js index ef8a90b..a4017d5 100644 --- a/src/server/handlers/tdd-handler.js +++ b/src/server/handlers/tdd-handler.js @@ -481,6 +481,15 @@ export const createTddHandler = ( // Update comparison in report data file updateComparison(newComparison); + // Log screenshot event for menubar + // Normalize status to match HTTP response ('failed' -> 'diff') + let logStatus = comparison.status === 'failed' ? 'diff' : comparison.status; + output.info(`Screenshot: ${sanitizedName}`, { + screenshot: sanitizedName, + status: logStatus, + diffPercentage: comparison.diffPercentage || 0, + }); + // Visual diffs return 200 with status: 'diff' - they're not errors // The SDK/user can decide whether to fail tests based on this if (comparison.status === 'failed') { @@ -572,7 +581,12 @@ export const createTddHandler = ( updateComparison(updatedComparison); - output.info(`Baseline accepted for comparison ${comparisonId}`); + // Log screenshot event for menubar + output.info(`Screenshot: ${comparison.name}`, { + screenshot: comparison.name, + status: 'accepted', + diffPercentage: 0, + }); return result; } catch (error) { output.error(`Failed to accept baseline for ${comparisonId}:`, error); @@ -602,7 +616,12 @@ export const createTddHandler = ( updateComparison(updatedComparison); - output.info(`Changes rejected for comparison ${comparisonId}`); + // Log screenshot event for menubar + output.info(`Screenshot: ${comparison.name}`, { + screenshot: comparison.name, + status: 'rejected', + diffPercentage: comparison.diffPercentage || 0, + }); return { success: true, id: comparisonId }; } catch (error) { output.error(`Failed to reject baseline for ${comparisonId}:`, error); diff --git a/tests/server/handlers/tdd-handler.test.js b/tests/server/handlers/tdd-handler.test.js index b348bb3..b98bc2f 100644 --- a/tests/server/handlers/tdd-handler.test.js +++ b/tests/server/handlers/tdd-handler.test.js @@ -934,6 +934,93 @@ describe('server/handlers/tdd-handler', () => { assert.strictEqual(result.statusCode, 400); assert.ok(result.body.error.includes('Invalid image input')); }); + + it('logs screenshot event with normalized status for menubar consumption', async () => { + let deps = createMockDeps({ + tddServiceOverrides: { + compareScreenshot: name => ({ + id: `comp-${name}`, + name, + status: 'failed', + diffPercentage: 5.5, + baseline: '/baselines/test.png', + current: '/current/test.png', + diff: '/diffs/test.png', + threshold: 2.0, + }), + }, + }); + let handler = createTddHandler({}, '/test', null, null, false, deps); + + await handler.handleScreenshot('build-1', 'homepage', 'base64data', {}); + + // Find the screenshot log - menubar looks for logs starting with "Screenshot:" + let screenshotLog = deps._mockOutput.calls.find( + c => c.method === 'info' && c.args[0].startsWith('Screenshot:') + ); + + assert.ok(screenshotLog, 'Screenshot event should be logged'); + assert.strictEqual(screenshotLog.args[1].screenshot, 'homepage'); + // Status should be normalized to 'diff' (not 'failed') to match HTTP response + assert.strictEqual(screenshotLog.args[1].status, 'diff'); + assert.strictEqual(screenshotLog.args[1].diffPercentage, 5.5); + }); + + it('logs passed screenshots with match-equivalent status', async () => { + let deps = createMockDeps({ + tddServiceOverrides: { + compareScreenshot: name => ({ + id: `comp-${name}`, + name, + status: 'passed', + baseline: '/baselines/test.png', + current: '/current/test.png', + diff: null, + }), + }, + }); + let handler = createTddHandler({}, '/test', null, null, false, deps); + + await handler.handleScreenshot('build-1', 'button', 'base64data', {}); + + let screenshotLog = deps._mockOutput.calls.find( + c => c.method === 'info' && c.args[0].startsWith('Screenshot:') + ); + + assert.ok(screenshotLog); + assert.strictEqual(screenshotLog.args[1].status, 'passed'); + assert.strictEqual(screenshotLog.args[1].diffPercentage, 0); + }); + + it('logs new baseline screenshots', async () => { + let deps = createMockDeps({ + tddServiceOverrides: { + compareScreenshot: name => ({ + id: `comp-${name}`, + name, + status: 'new', + baseline: '/baselines/test.png', + current: '/current/test.png', + diff: null, + }), + }, + }); + let handler = createTddHandler({}, '/test', null, null, false, deps); + + await handler.handleScreenshot( + 'build-1', + 'new-component', + 'base64data', + {} + ); + + let screenshotLog = deps._mockOutput.calls.find( + c => c.method === 'info' && c.args[0].startsWith('Screenshot:') + ); + + assert.ok(screenshotLog); + assert.strictEqual(screenshotLog.args[1].status, 'new'); + }); }); describe('getResults', () => { @@ -992,6 +1079,43 @@ describe('server/handlers/tdd-handler', () => { /Comparison not found/ ); }); + + it('logs screenshot event on acceptance for menubar consumption', async () => { + let deps = createMockDeps({ + tddServiceOverrides: { + acceptBaseline: () => ({ success: true }), + }, + }); + + let reportData = { + timestamp: Date.now(), + comparisons: [ + { + id: 'comp-1', + name: 'login-form', + status: 'failed', + diffPercentage: 2.1, + }, + ], + groups: [], + summary: { total: 1, passed: 0, failed: 1, errors: 0 }, + }; + deps._fileSystem['/test/.vizzly/report-data.json'] = + JSON.stringify(reportData); + + let handler = createTddHandler({}, '/test', null, null, false, deps); + + await handler.acceptBaseline('comp-1'); + + // Menubar looks for screenshot logs with structured metadata + let screenshotLog = deps._mockOutput.calls.find( + c => c.method === 'info' && c.args[0].startsWith('Screenshot:') + ); + assert.ok(screenshotLog, 'Screenshot event should be logged'); + assert.strictEqual(screenshotLog.args[1].screenshot, 'login-form'); + assert.strictEqual(screenshotLog.args[1].status, 'accepted'); + assert.strictEqual(screenshotLog.args[1].diffPercentage, 0); + }); }); describe('rejectBaseline', () => { @@ -1086,12 +1210,19 @@ describe('server/handlers/tdd-handler', () => { assert.strictEqual(comparison.properties.browser, 'chrome'); }); - it('logs info message on successful rejection', async () => { + it('logs screenshot event on rejection for menubar consumption', async () => { let deps = createMockDeps(); let reportData = { timestamp: Date.now(), - comparisons: [{ id: 'comp-1', name: 'test', status: 'failed' }], + comparisons: [ + { + id: 'comp-1', + name: 'button-hover', + status: 'failed', + diffPercentage: 3.2, + }, + ], groups: [], summary: { total: 1, passed: 0, failed: 1, errors: 0 }, }; @@ -1102,10 +1233,14 @@ describe('server/handlers/tdd-handler', () => { await handler.rejectBaseline('comp-1'); - let infoCall = deps._mockOutput.calls.find( - c => c.method === 'info' && c.args[0].includes('rejected') + // Menubar looks for screenshot logs with structured metadata + let screenshotLog = deps._mockOutput.calls.find( + c => c.method === 'info' && c.args[0].startsWith('Screenshot:') ); - assert.ok(infoCall); + assert.ok(screenshotLog, 'Screenshot event should be logged'); + assert.strictEqual(screenshotLog.args[1].screenshot, 'button-hover'); + assert.strictEqual(screenshotLog.args[1].status, 'rejected'); + assert.strictEqual(screenshotLog.args[1].diffPercentage, 3.2); }); });