From 4458a37fa93a7438c1b390eaffb2b361691e29b0 Mon Sep 17 00:00:00 2001 From: prklm10 Date: Thu, 11 Jun 2026 16:15:41 +0530 Subject: [PATCH 1/2] feat(core): tip about VRA when a build uses Layout review mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emit a single warn-level tip just before the "Finalized build" log whenever any snapshot in the build had enableLayout set (via global config or per-snapshot). Detection flips a per-build flag in the queue push handler; emission is gated in the end handler, so it logs at most once per process. Parallel builds surface the tip per shard via core — cli-build finalize needs no change. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/src/snapshot.js | 6 +++ packages/core/test/snapshot.test.js | 68 +++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/packages/core/src/snapshot.js b/packages/core/src/snapshot.js index 8e33750bf..7f797d858 100644 --- a/packages/core/src/snapshot.js +++ b/packages/core/src/snapshot.js @@ -428,6 +428,9 @@ export function createSnapshotsQueue(percy) { await runDoctorOnFailure(percy); } else if (build?.id) { await percy.client.finalizeBuild(build.id); + if (build.layoutUsed) { + percy.log.warn('Tip: VRA is Percy\'s recommended visual review mode — more accurate and adaptable than Layout. Learn more: https://www.browserstack.com/docs/percy/ai-agents/visual-review-agent/overview.'); + } percy.log.info(`Finalized build #${build.number}: ${build.url}`, { build }); } else { percy.log.warn('Build not created', { build }); @@ -444,6 +447,9 @@ export function createSnapshotsQueue(percy) { .handle('push', (snapshot, existing) => { let { name, meta } = snapshot; + // track layout usage to tip about VRA when the build is finalized + if (snapshot.enableLayout) build.layoutUsed = true; + // log immediately when not deferred or dry-running if (!percy.deferUploads) percy.log.info(`Snapshot taken: ${snapshotLogName(name, meta)}`, meta); if (percy.dryRun) percy.log.info(`Snapshot found: ${snapshotLogName(name, meta)}`, meta); diff --git a/packages/core/test/snapshot.test.js b/packages/core/test/snapshot.test.js index 93e8e281e..7776db293 100644 --- a/packages/core/test/snapshot.test.js +++ b/packages/core/test/snapshot.test.js @@ -2194,6 +2194,74 @@ describe('Snapshot', () => { }); }); }); + + describe('VRA layout tip', () => { + let tip = '[percy] Tip: VRA is Percy\'s recommended visual review mode — more accurate and adaptable than Layout. Learn more: https://www.browserstack.com/docs/percy/ai-agents/visual-review-agent/overview.'; + + it('logs a VRA tip before finalizing when a snapshot has layout enabled', async () => { + await percy.snapshot({ + name: 'test snapshot', + url: 'http://localhost:8000', + domSnapshot: '', + enableLayout: true + }); + + await percy.stop(); + + expect(logger.stderr).toContain(tip); + expect(logger.stdout).toContain( + '[percy] Finalized build #1: https://percy.io/test/test/123' + ); + }); + + it('logs the VRA tip when enableLayout is set globally in config', async () => { + percy.config.snapshot.enableLayout = true; + + await percy.snapshot({ + name: 'test snapshot', + url: 'http://localhost:8000', + domSnapshot: '' + }); + + await percy.stop(); + + expect(logger.stderr).toContain(tip); + }); + + it('logs the VRA tip only once when multiple snapshots have layout enabled', async () => { + await percy.snapshot({ + name: 'snapshot one', + url: 'http://localhost:8000', + domSnapshot: '', + enableLayout: true + }); + await percy.snapshot({ + name: 'snapshot two', + url: 'http://localhost:8000', + domSnapshot: '', + enableLayout: true + }); + + await percy.stop(); + + expect(logger.stderr.filter(l => l === tip).length).toEqual(1); + }); + + it('does not log the VRA tip when no snapshot has layout enabled', async () => { + await percy.snapshot({ + name: 'test snapshot', + url: 'http://localhost:8000', + domSnapshot: '' + }); + + await percy.stop(); + + expect(logger.stderr).not.toContain(tip); + expect(logger.stdout).toContain( + '[percy] Finalized build #1: https://percy.io/test/test/123' + ); + }); + }); }); // ── runDoctorOnFailure ──────────────────────────────────────────────────────── From 46efb501b06cd7967bed2d43f00e4180e684952a Mon Sep 17 00:00:00 2001 From: prklm10 Date: Tue, 16 Jun 2026 11:39:38 +0530 Subject: [PATCH 2/2] feat(core): instrument VRA layout recommendation via send-events Emit a percy_cli_vra_recommendation_emitted build event (category percy:cli) when the VRA tip is shown, so the recommendation can be instrumented. Extends the client's sendBuildEvents to optionally send the newer top-level event_name and category params; existing callers are unaffected (params default and the API applies its own defaults). The event is sent before finalizing the build and is wrapped so telemetry can never fail a build. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/client/src/client.js | 5 ++++- packages/client/test/client.test.js | 15 +++++++++++++++ packages/core/src/snapshot.js | 14 +++++++++++++- packages/core/test/snapshot.test.js | 16 ++++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/packages/client/src/client.js b/packages/client/src/client.js index 43b21614d..c3cff616b 100644 --- a/packages/client/src/client.js +++ b/packages/client/src/client.js @@ -886,10 +886,13 @@ export class PercyClient { return comparison; } - async sendBuildEvents(buildId, body, meta = {}) { + async sendBuildEvents(buildId, body, meta = {}, { eventName, category } = {}) { validateId('build', buildId); this.log.debug('Sending Build Events'); return this.post(`builds/${buildId}/send-events`, { + // newer params are optional; when omitted the API applies its defaults + ...(eventName && { event_name: eventName }), + ...(category && { category }), data: body }, { identifier: 'build.send_events', ...meta }); } diff --git a/packages/client/test/client.test.js b/packages/client/test/client.test.js index 84f0e3b0a..597a58b9e 100644 --- a/packages/client/test/client.test.js +++ b/packages/client/test/client.test.js @@ -2510,6 +2510,21 @@ describe('PercyClient', () => { } }); }); + + it('includes event_name and category when provided', async () => { + await expectAsync(client.sendBuildEvents(123, [ + { message: 'some event' } + ], {}, { + eventName: 'percy_cli_vra_recommendation_emitted', + category: 'percy:cli' + })).toBeResolved(); + + expect(api.requests['/builds/123/send-events'][0].body).toEqual({ + event_name: 'percy_cli_vra_recommendation_emitted', + category: 'percy:cli', + data: [{ message: 'some event' }] + }); + }); }); describe('#sendBuildLogs', () => { diff --git a/packages/core/src/snapshot.js b/packages/core/src/snapshot.js index 7f797d858..8d23e9352 100644 --- a/packages/core/src/snapshot.js +++ b/packages/core/src/snapshot.js @@ -427,10 +427,22 @@ export function createSnapshotsQueue(percy) { percy.log.warn(`Build #${build.number} failed: ${build.url}`, { build }); await runDoctorOnFailure(percy); } else if (build?.id) { - await percy.client.finalizeBuild(build.id); if (build.layoutUsed) { percy.log.warn('Tip: VRA is Percy\'s recommended visual review mode — more accurate and adaptable than Layout. Learn more: https://www.browserstack.com/docs/percy/ai-agents/visual-review-agent/overview.'); + + // instrument the recommendation; telemetry must never fail a build + try { + await percy.client.sendBuildEvents(build.id, { + message: 'VRA recommendation shown for a build using Layout review mode' + }, {}, { + eventName: 'percy_cli_vra_recommendation_emitted', + category: 'percy:cli' + }); + } catch (err) { + percy.log.debug('VRA recommendation telemetry failed', err); + } } + await percy.client.finalizeBuild(build.id); percy.log.info(`Finalized build #${build.number}: ${build.url}`, { build }); } else { percy.log.warn('Build not created', { build }); diff --git a/packages/core/test/snapshot.test.js b/packages/core/test/snapshot.test.js index 7776db293..9496e5313 100644 --- a/packages/core/test/snapshot.test.js +++ b/packages/core/test/snapshot.test.js @@ -2212,6 +2212,18 @@ describe('Snapshot', () => { expect(logger.stdout).toContain( '[percy] Finalized build #1: https://percy.io/test/test/123' ); + + // the recommendation is instrumented with the newer send-events params + let vraEvents = (api.requests['/builds/123/send-events'] || []) + .filter(r => r.body.event_name === 'percy_cli_vra_recommendation_emitted'); + expect(vraEvents.length).toEqual(1); + expect(vraEvents[0].body).toEqual({ + event_name: 'percy_cli_vra_recommendation_emitted', + category: 'percy:cli', + data: { + message: 'VRA recommendation shown for a build using Layout review mode' + } + }); }); it('logs the VRA tip when enableLayout is set globally in config', async () => { @@ -2260,6 +2272,10 @@ describe('Snapshot', () => { expect(logger.stdout).toContain( '[percy] Finalized build #1: https://percy.io/test/test/123' ); + // no recommendation event is sent when Layout is not used + let vraEvents = (api.requests['/builds/123/send-events'] || []) + .filter(r => r.body.event_name === 'percy_cli_vra_recommendation_emitted'); + expect(vraEvents.length).toEqual(0); }); }); });