From 4458a37fa93a7438c1b390eaffb2b361691e29b0 Mon Sep 17 00:00:00 2001 From: prklm10 Date: Thu, 11 Jun 2026 16:15:41 +0530 Subject: [PATCH 1/4] 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/4] 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); }); }); }); From 591ce00b9db8d4a5a756737729a153da909cd15f Mon Sep 17 00:00:00 2001 From: prklm10 Date: Tue, 16 Jun 2026 16:54:26 +0530 Subject: [PATCH 3/4] test(core): cover VRA recommendation telemetry failure path Add a spec that makes sendBuildEvents reject and asserts the build still finalizes, exercising the previously-uncovered catch block in the snapshot queue end handler (restores 100% coverage). Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/core/test/snapshot.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/core/test/snapshot.test.js b/packages/core/test/snapshot.test.js index 9496e5313..cc86acbe5 100644 --- a/packages/core/test/snapshot.test.js +++ b/packages/core/test/snapshot.test.js @@ -2259,6 +2259,26 @@ describe('Snapshot', () => { expect(logger.stderr.filter(l => l === tip).length).toEqual(1); }); + it('still finalizes the build when recommendation telemetry fails', async () => { + percy.loglevel('debug'); + spyOn(percy.client, 'sendBuildEvents').and.rejectWith(new Error('network down')); + + await percy.snapshot({ + name: 'test snapshot', + url: 'http://localhost:8000', + domSnapshot: '', + enableLayout: true + }); + + await percy.stop(); + + // telemetry failure is swallowed and logged at debug; build still finalizes + expect(logger.stderr).toContain('[percy:core] VRA recommendation telemetry failed'); + expect(logger.stdout).toContain( + '[percy:core] Finalized build #1: https://percy.io/test/test/123' + ); + }); + it('does not log the VRA tip when no snapshot has layout enabled', async () => { await percy.snapshot({ name: 'test snapshot', From e08ba2774162425dcc18dd937aed50a1a1264cb3 Mon Sep 17 00:00:00 2001 From: prklm10 Date: Wed, 17 Jun 2026 11:05:21 +0530 Subject: [PATCH 4/4] gitignore changes --- .gitignore | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a050b3ede..2db4ac1d6 100644 --- a/.gitignore +++ b/.gitignore @@ -12,12 +12,13 @@ packages/sdk-utils/test/client.js packs docs/ +# spec-level retry bookkeeping (CI only, see PER-9011) +.cli-test-failures.json + # bstack-ai-harness:begin (managed — do not edit between markers) bstack-ai-harness.yml .harness-docs.json +.harness-manifest.json CLAUDE.md .claude/ # bstack-ai-harness:end - -# spec-level retry bookkeeping (CI only, see PER-9011) -.cli-test-failures.json