diff --git a/packages/cli-exec/src/exec.js b/packages/cli-exec/src/exec.js index b1acb2c03..2bab1fc03 100644 --- a/packages/cli-exec/src/exec.js +++ b/packages/cli-exec/src/exec.js @@ -2,6 +2,9 @@ import command from '@percy/cli-command'; import start from './start.js'; import stop from './stop.js'; import ping from './ping.js'; +import { getPackageJSON } from '@percy/cli-command/utils'; + +const pkg = getPackageJSON(import.meta.url); export const exec = command('exec', { description: 'Start and stop Percy around a supplied command', @@ -123,9 +126,10 @@ async function* spawn(cmd, args, percy) { if (process.env.PERCY_TOKEN) { const myObject = { errorKind: 'cli', - errorMessage: '1' + cliVersion: pkg.version, + message: '1' }; - percy.client.sendFailedEvents(percy.build.id, myObject); + percy.client.sendBuildEvents(percy.build.id, myObject); } } }); diff --git a/packages/cli-exec/test/exec.test.js b/packages/cli-exec/test/exec.test.js index 3fa091515..79ec908d5 100644 --- a/packages/cli-exec/test/exec.test.js +++ b/packages/cli-exec/test/exec.test.js @@ -1,5 +1,6 @@ import { logger, api, setupTest } from '@percy/cli-command/test/helpers'; import exec from '@percy/cli-exec'; +import { getPackageJSON } from '@percy/cli-command/utils'; describe('percy exec', () => { beforeEach(async () => { @@ -144,6 +145,7 @@ describe('percy exec', () => { }); it('tests process.stderr when token is present', async () => { + const pkg = getPackageJSON(import.meta.url); let stderrSpy = spyOn(process.stderr, 'write').and.resolveTo('some response'); await expectAsync( exec(['--', 'node', 'random.js']) // invalid command @@ -157,14 +159,11 @@ describe('percy exec', () => { '[percy] Finalized build #1: https://percy.io/test/test/123' ]); - expect(api.requests['/builds/123/failed-events']).toBeDefined(); - expect(api.requests['/builds/123/failed-events'][0].body).toEqual({ + expect(api.requests['/builds/123/send-events']).toBeDefined(); + expect(api.requests['/builds/123/send-events'][0].body).toEqual({ data: { - buildId: '123', errorKind: 'cli', - client: null, - clientVersion: null, - cliVersion: null, + cliVersion: pkg.version, message: '1' } }); @@ -186,7 +185,7 @@ describe('percy exec', () => { '[percy] Running "node random.js"' ]); - expect(api.requests['/builds/123/failed-events']).not.toBeDefined(); + expect(api.requests['/builds/123/send-events']).not.toBeDefined(); }); it('does not run the command if canceled beforehand', async () => { diff --git a/packages/client/src/client.js b/packages/client/src/client.js index 042edcf06..b8fc12f5e 100644 --- a/packages/client/src/client.js +++ b/packages/client/src/client.js @@ -516,18 +516,11 @@ export class PercyClient { return comparison; } - async sendFailedEvents(buildId, { errorKind = 'sdk', client = null, clientVersion = null, cliVersion = null, errorMessage = null } = {}) { + async sendBuildEvents(buildId, body) { validateId('build', buildId); - this.log.debug('Sending FailedEvents'); - return this.post(`builds/${buildId}/failed-events`, { - data: { - buildId: buildId, - errorKind: errorKind, - client: client, - clientVersion: clientVersion, - cliVersion: cliVersion, - message: errorMessage - } + this.log.debug('Sending Build Events'); + return this.post(`builds/${buildId}/send-events`, { + data: body }); } diff --git a/packages/client/test/client.test.js b/packages/client/test/client.test.js index 199056cab..e2d0ab793 100644 --- a/packages/client/test/client.test.js +++ b/packages/client/test/client.test.js @@ -1333,37 +1333,20 @@ describe('PercyClient', () => { }); }); - describe('sendFailedEvents', () => { - it('should send failed event with default values', async () => { - await expectAsync(client.sendFailedEvents(123)).toBeResolved(); - expect(api.requests['/builds/123/failed-events']).toBeDefined(); - expect(api.requests['/builds/123/failed-events'][0].method).toBe('POST'); - expect(api.requests['/builds/123/failed-events'][0].body).toEqual({ - data: { - buildId: 123, - errorKind: 'sdk', - client: null, - clientVersion: null, - cliVersion: null, - message: null - } - }); - }); - - it('should send failed event with default values', async () => { - await expectAsync(client.sendFailedEvents(123, { + describe('sendBuildEvents', () => { + it('should send build event with default values', async () => { + await expectAsync(client.sendBuildEvents(123, { errorKind: 'cli', client: 'percy-appium-dotnet', clientVersion: '3.0.1', cliVersion: '1.27.3', - errorMessage: 'some error' + message: 'some error' })).toBeResolved(); - expect(api.requests['/builds/123/failed-events']).toBeDefined(); - expect(api.requests['/builds/123/failed-events'][0].method).toBe('POST'); - expect(api.requests['/builds/123/failed-events'][0].body).toEqual({ + expect(api.requests['/builds/123/send-events']).toBeDefined(); + expect(api.requests['/builds/123/send-events'][0].method).toBe('POST'); + expect(api.requests['/builds/123/send-events'][0].body).toEqual({ data: { - buildId: 123, errorKind: 'cli', client: 'percy-appium-dotnet', clientVersion: '3.0.1', diff --git a/packages/core/src/api.js b/packages/core/src/api.js index 852df30de..e31a7c480 100644 --- a/packages/core/src/api.js +++ b/packages/core/src/api.js @@ -3,7 +3,7 @@ import path from 'path'; import { createRequire } from 'module'; import logger from '@percy/logger'; import { normalize } from '@percy/config/utils'; -import { getPackageJSON, Server, percyAutomateRequestHandler, percyFailedEventHandler } from './utils.js'; +import { getPackageJSON, Server, percyAutomateRequestHandler, percyBuildEventHandler } from './utils.js'; import WebdriverUtils from '@percy/webdriver-utils'; // need require.resolve until import.meta.resolve can be transpiled export const PERCY_DOM = createRequire(import.meta.url).resolve('@percy/dom'); @@ -123,8 +123,8 @@ export function createPercyServer(percy, port) { }) // Recieves events from sdk's. .route('post', '/percy/events', async (req, res) => { - percyFailedEventHandler(req, pkg.version); - await percy.client.sendFailedEvents(percy.build?.id, req.body); + percyBuildEventHandler(req, pkg.version); + await percy.client.sendBuildEvents(percy.build?.id, req.body); res.json(200, { success: true }); }) // stops percy at the end of the current event loop diff --git a/packages/core/src/utils.js b/packages/core/src/utils.js index 344562ba3..f0f0fc78e 100644 --- a/packages/core/src/utils.js +++ b/packages/core/src/utils.js @@ -60,20 +60,32 @@ export function percyAutomateRequestHandler(req, percy) { req.body.buildInfo = percy.build; } -// Returns the body for failedEvent structure -export function percyFailedEventHandler(req, cliVersion) { - if (req.body.clientInfo) { - const [client, clientVersion] = req.body.clientInfo.split('/'); +// Returns the body for sendEvent structure +export function percyBuildEventHandler(req, cliVersion) { + if (Array.isArray(req.body)) { + req.body.forEach(element => { + processSendEventData(element, cliVersion); + }); + } else { + // Treat the input as an object and perform instructions + processSendEventData(req.body, cliVersion); + } +} + +// Process sendEvent object +function processSendEventData(input, cliVersion) { + if (input.clientInfo) { + const [client, clientVersion] = input.clientInfo.split('/'); // Add the client and clientVersion fields to the existing object - req.body.client = client; - req.body.clientVersion = clientVersion; + input.client = client; + input.clientVersion = clientVersion; // Remove the original clientInfo field - delete req.body.clientInfo; + delete input.clientInfo; } - if (!req.body.cliVersion) { - req.body.cliVersion = cliVersion; + if (!input.cliVersion) { + input.cliVersion = cliVersion; } } diff --git a/packages/core/test/api.test.js b/packages/core/test/api.test.js index 640574261..cfd05200b 100644 --- a/packages/core/test/api.test.js +++ b/packages/core/test/api.test.js @@ -316,11 +316,11 @@ describe('API Server', () => { resolve(); // no hanging promises }); - it('has a /events endpoint that calls #sendFailedEvents() async with provided options with clientInfo present', async () => { + it('has a /events endpoint that calls #sendBuildEvents() async with provided options with clientInfo present', async () => { let { getPackageJSON } = await import('@percy/client/utils'); let pkg = getPackageJSON(import.meta.url); let resolve, test = new Promise(r => (resolve = r)); - let sendFailedEventsSpy = spyOn(percy.client, 'sendFailedEvents').and.resolveTo('some response'); + let sendBuildEventsSpy = spyOn(percy.client, 'sendBuildEvents').and.resolveTo('some response'); await percy.start(); @@ -332,7 +332,7 @@ describe('API Server', () => { method: 'post' })).toBeResolvedTo({ success: true }); - expect(sendFailedEventsSpy).toHaveBeenCalledOnceWith(percy.build.id, jasmine.objectContaining({ + expect(sendBuildEventsSpy).toHaveBeenCalledOnceWith(percy.build.id, jasmine.objectContaining({ errorMessage: 'some error', client: 'percy-appium-dotnet', clientVersion: '3.0.1', @@ -343,9 +343,53 @@ describe('API Server', () => { resolve(); // no hanging promises }); - it('has a /events endpoint that calls #sendFailedEvents() async with provided options with clientInfo absent', async () => { + it('has a /events endpoint called with body array that calls #sendBuildEvents() async with provided options with clientInfo present', async () => { + let { getPackageJSON } = await import('@percy/client/utils'); + let pkg = getPackageJSON(import.meta.url); + let resolve, test = new Promise(r => (resolve = r)); + let sendBuildEventsSpy = spyOn(percy.client, 'sendBuildEvents').and.resolveTo('some response'); + + await percy.start(); + + await expectAsync(request('/percy/events', { + body: [ + { + errorMessage: 'some error 1', + clientInfo: 'percy-appium-dotnet/3.0.1' + }, + { + errorMessage: 'some error 2', + clientInfo: 'percy-appium-dotnet/3.0.1' + } + ], + method: 'post' + })).toBeResolvedTo({ success: true }); + + expect(sendBuildEventsSpy).toHaveBeenCalledOnceWith(percy.build.id, jasmine.objectContaining( + [ + { + errorMessage: 'some error 1', + client: 'percy-appium-dotnet', + clientVersion: '3.0.1', + cliVersion: pkg.version + }, + { + errorMessage: 'some error 2', + client: 'percy-appium-dotnet', + clientVersion: '3.0.1', + cliVersion: pkg.version + + } + ] + )); + + await expectAsync(test).toBePending(); + resolve(); // no hanging promises + }); + + it('has a /events endpoint that calls #sendBuildEvents() async with provided options with clientInfo absent', async () => { let resolve, test = new Promise(r => (resolve = r)); - let sendFailedEventsSpy = spyOn(percy.client, 'sendFailedEvents').and.resolveTo('some response'); + let sendBuildEventsSpy = spyOn(percy.client, 'sendBuildEvents').and.resolveTo('some response'); await percy.start(); @@ -357,7 +401,7 @@ describe('API Server', () => { method: 'post' })).toBeResolvedTo({ success: true }); - expect(sendFailedEventsSpy).toHaveBeenCalledOnceWith(percy.build.id, jasmine.objectContaining({ + expect(sendBuildEventsSpy).toHaveBeenCalledOnceWith(percy.build.id, jasmine.objectContaining({ errorMessage: 'some error', cliVersion: '1.2.3' })); diff --git a/packages/sdk-utils/src/index.js b/packages/sdk-utils/src/index.js index 061245574..cd42c9271 100644 --- a/packages/sdk-utils/src/index.js +++ b/packages/sdk-utils/src/index.js @@ -6,7 +6,7 @@ import waitForPercyIdle from './percy-idle.js'; import fetchPercyDOM from './percy-dom.js'; import postSnapshot from './post-snapshot.js'; import postComparison from './post-comparison.js'; -import postFailedEvent from './post-failed-event.js'; +import postBuildEvents from './post-build-event.js'; import flushSnapshots from './flush-snapshots.js'; import captureAutomateScreenshot from './post-screenshot.js'; @@ -21,7 +21,7 @@ export { postComparison, flushSnapshots, captureAutomateScreenshot, - postFailedEvent + postBuildEvents }; // export the namespace by default diff --git a/packages/sdk-utils/src/post-failed-event.js b/packages/sdk-utils/src/post-build-event.js similarity index 69% rename from packages/sdk-utils/src/post-failed-event.js rename to packages/sdk-utils/src/post-build-event.js index 03f9031b8..05809e8f2 100644 --- a/packages/sdk-utils/src/post-failed-event.js +++ b/packages/sdk-utils/src/post-build-event.js @@ -1,10 +1,10 @@ import request from './request.js'; // Post failed event data to the CLI event endpoint. -export async function postFailedEvent(options) { +export async function postBuildEvents(options) { return await request.post('/percy/events', options).catch(err => { throw err; }); } -export default postFailedEvent; +export default postBuildEvents; diff --git a/packages/sdk-utils/test/index.test.js b/packages/sdk-utils/test/index.test.js index 5a5638418..3ce0890ce 100644 --- a/packages/sdk-utils/test/index.test.js +++ b/packages/sdk-utils/test/index.test.js @@ -298,8 +298,8 @@ describe('SDK Utils', () => { }); }); - describe('postFailedEvent(options)', () => { - let { postFailedEvent } = utils; + describe('postBuildEvents(options)', () => { + let { postBuildEvents } = utils; let options; beforeEach(() => { @@ -312,14 +312,14 @@ describe('SDK Utils', () => { it('posts comparison options to the CLI API event endpoint', async () => { spyOn(utils.request, 'post').and.callFake(() => Promise.resolve()); - await expectAsync(postFailedEvent(options)).toBeResolved(); + await expectAsync(postBuildEvents(options)).toBeResolved(); await expectAsync(helpers.get('requests')).toBeResolvedTo({}); }); it('throws when the event API fails', async () => { await helpers.test('error', '/percy/events'); - await expectAsync(postFailedEvent({})) + await expectAsync(postBuildEvents({})) .toBeRejectedWithError('testing'); }); });