diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 5e9be67c6bb..9dbf2e93dc6 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -13,7 +13,6 @@ import { } from '../src/utils.js'; import {getAd} from '../libraries/targetVideoUtils/bidderUtils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import { EVENTS } from '../src/constants.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -21,7 +20,8 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; -const NM_VERSION = '3.1.0'; +const NM_VERSION = '4.0.0'; +const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; @@ -81,65 +81,46 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const requests = []; window.nmmRefreshCounts = window.nmmRefreshCounts || {}; + const site = getSiteObj(); + const device = getDeviceObj(); - _each(validBidRequests, (bid) => { - window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; - const id = getPlacementId(bid); - const auctionId = bid.auctionId; - const bidId = bid.bidId; - - const site = getSiteObj(); - const device = getDeviceObj(); - const {cur, mediaTypes} = getCurrency(bid); + const postBody = { + id: bidderRequest?.bidderRequestId, + ext: { + next_mil_imps: [], + }, - const postBody = { - id: bidderRequest?.bidderRequestId, - cur, - ext: { - prebid: { - storedrequest: { - id, - }, - }, + device, + site, + imp: [], + }; - nextMillennium: { - nm_version: NM_VERSION, - pbjs_version: getGlobal()?.version || undefined, - refresh_count: window.nmmRefreshCounts[bid.adUnitCode]++, - elOffsets: getBoundingClient(bid), - scrollTop: window.pageYOffset || document.documentElement.scrollTop, - }, - }, + setConsentStrings(postBody, bidderRequest); + setOrtb2Parameters(postBody, bidderRequest?.ortb2); - device, - site, - imp: [], - }; + const urlParameters = parseUrl(getWindowTop().location.href).search; + const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; + setEids(postBody, validBidRequests); + _each(validBidRequests, (bid, i) => { + window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; + const id = getPlacementId(bid); + const {cur, mediaTypes} = getCurrency(bid); + if (i === 0) postBody.cur = cur; postBody.imp.push(getImp(bid, id, mediaTypes)); - setConsentStrings(postBody, bidderRequest); - setOrtb2Parameters(postBody, bidderRequest?.ortb2); - setEids(postBody, bid); - - const urlParameters = parseUrl(getWindowTop().location.href).search; - const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; - const params = bid.params; - - requests.push({ - method: 'POST', - url: isTest ? TEST_ENDPOINT : ENDPOINT, - data: JSON.stringify(postBody), - options: { - contentType: 'text/plain', - withCredentials: true, - }, + postBody.ext.next_mil_imps.push(getExtNextMilImp(bid)); + }); - bidId, - params, - auctionId, - }); + this.getUrlPixelMetric(EVENTS.BID_REQUESTED, validBidRequests); - this.getUrlPixelMetric(EVENTS.BID_REQUESTED, bid); + requests.push({ + method: 'POST', + url: isTest ? TEST_ENDPOINT : ENDPOINT, + data: JSON.stringify(postBody), + options: { + contentType: 'text/plain', + withCredentials: true, + }, }); return requests; @@ -149,16 +130,15 @@ export const spec = { const response = serverResponse.body; const bidResponses = []; + const bids = []; _each(response.seatbid, (resp) => { _each(resp.bid, (bid) => { const requestId = bidRequest.bidId; - const params = bidRequest.params; const {ad, adUrl, vastUrl, vastXml} = getAd(bid); const bidResponse = { requestId, - params, cpm: bid.price, width: bid.w, height: bid.h, @@ -182,11 +162,13 @@ export const spec = { }; bidResponses.push(bidResponse); - - this.getUrlPixelMetric(EVENTS.BID_RESPONSE, bid); }); + + bids.push(resp.bid); }); + this.getUrlPixelMetric(EVENTS.BID_RESPONSE, bids.flat()); + return bidResponses; }, @@ -226,22 +208,26 @@ export const spec = { triggerPixel(url); }, - _getUrlPixelMetric(eventName, bid) { - const bidder = bid.bidder || bid.bidderCode; + _getUrlPixelMetric(eventName, bids) { + if (!Array.isArray(bids)) bids = [bids]; + + const bidder = bids[0]?.bidder || bids[0]?.bidderCode; if (bidder != BIDDER_CODE) return; - let params; - if (bid.params) { - params = Array.isArray(bid.params) ? bid.params : [bid.params]; - } else { - if (Array.isArray(bid.bids)) params = bid.bids.map(bidI => bidI.params); - }; + let params = []; + _each(bids, bid => { + if (bid.params) { + params.push(bid.params); + } else { + if (Array.isArray(bid.bids)) params.push(bid.bids.map(bidI => bidI.params)); + }; + }); if (!params.length) return; const placementIdsArray = []; const groupIdsArray = []; - params.forEach(paramsI => { + params.flat().forEach(paramsI => { if (paramsI.group_id) { groupIdsArray.push(paramsI.group_id); } else { @@ -252,9 +238,7 @@ export const spec = { const placementIds = (placementIdsArray.length && `&placements=${placementIdsArray.join(';')}`) || ''; const groupIds = (groupIdsArray.length && `&groups=${groupIdsArray.join(';')}`) || ''; - if (!(groupIds || placementIds)) { - return; - }; + if (!(groupIds || placementIds)) return; const url = `${REPORT_ENDPOINT}?event=${eventName}&bidder=${bidder}&source=pbjs${groupIds}${placementIds}`; @@ -268,6 +252,21 @@ export const spec = { }, }; +function getExtNextMilImp(bid) { + if (typeof window?.nmmRefreshCounts[bid.adUnitCode] === 'number') ++window.nmmRefreshCounts[bid.adUnitCode]; + const nextMilImp = { + impId: bid.adUnitCode, + nextMillennium: { + nm_version: NM_VERSION, + pbjs_version: PBJS_VERSION, + refresh_count: window?.nmmRefreshCounts[bid.adUnitCode] || 0, + scrollTop: window.pageYOffset || document.documentElement.scrollTop, + }, + }; + + return nextMilImp; +} + export function getImp(bid, id, mediaTypes) { const {banner, video} = mediaTypes; const imp = { @@ -362,10 +361,16 @@ export function setOrtb2Parameters(postBody, ortb2 = {}) { } } -export function setEids(postBody, bid) { - if (!isArray(bid.userIdAsEids) || !bid.userIdAsEids.length) return; +export function setEids(postBody = {}, bids = []) { + let isFind = false; + _each(bids, bid => { + if (isFind || !isArray(bid.userIdAsEids) || !bid.userIdAsEids.length) return; - deepSetValue(postBody, 'user.eids', bid.userIdAsEids); + if (bid.userIdAsEids.length) { + deepSetValue(postBody, 'user.eids', bid.userIdAsEids); + isFind = true; + }; + }); } export function replaceUsersyncMacros(url, gdprConsent = {}, uspConsent = '', gppConsent = {}, type = '') { @@ -411,21 +416,7 @@ function getCurrency(bid = {}) { return {cur, mediaTypes}; } -function getAdEl(bid) { - // best way I could think of to get El, is by matching adUnitCode to google slots... - const slot = window.googletag && window.googletag.pubads && window.googletag.pubads().getSlots().find(slot => slot.getAdUnitPath() === bid.adUnitCode); - const slotElementId = slot && slot.getSlotElementId(); - if (!slotElementId) return null; - return document.querySelector('#' + slotElementId); -} - -function getBoundingClient(bid) { - const el = getAdEl(bid); - if (!el) return {}; - return el.getBoundingClientRect(); -} - -function getPlacementId(bid) { +export function getPlacementId(bid) { const groupId = getBidIdParameter('group_id', bid.params); const placementId = getBidIdParameter('placement_id', bid.params); if (!groupId) return placementId; @@ -433,8 +424,8 @@ function getPlacementId(bid) { let windowTop = getTopWindow(window); let sizes = []; if (bid.mediaTypes) { - if (bid.mediaTypes.banner) sizes = bid.mediaTypes.banner.sizes; - if (bid.mediaTypes.video) sizes = [bid.mediaTypes.video.playerSize]; + if (bid.mediaTypes.banner) sizes = [...bid.mediaTypes.banner.sizes]; + if (bid.mediaTypes.video) sizes.push(bid.mediaTypes.video.playerSize); }; const host = (windowTop && windowTop.location && windowTop.location.host) || ''; diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index ff58671b17b..01b09320055 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -15,6 +15,7 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - banner', data: { id: '123', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, adUnitCode: 'test-banner-1', @@ -42,6 +43,7 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - video', data: { id: '234', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}}, adUnitCode: 'test-video-1', @@ -74,6 +76,7 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - mediaTypes.video is empty', data: { id: '234', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {w: 640, h: 480}}, adUnitCode: 'test-video-2', @@ -98,8 +101,8 @@ describe('nextMillenniumBidAdapterTests', () => { for (let {title, data, expected} of dataTests) { it(title, () => { - const {bid, id, mediaTypes} = data; - const imp = getImp(bid, id, mediaTypes); + const {bid, id, mediaTypes, postBody} = data; + const imp = getImp(bid, id, mediaTypes, postBody); expect(imp).to.deep.equal(expected); }); } @@ -485,9 +488,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: undefined, - }, + }], }, expected: {}, @@ -497,9 +500,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids - array is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: [], - }, + }], }, expected: {}, @@ -509,19 +512,34 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids is', data: { postBody: {}, - bid: { - userIdAsEids: [ - { - source: '33across.com', - uids: [{id: 'some-random-id-value', atype: 1}], - }, + bids: [ + { + userIdAsEids: [], + }, - { - source: 'utiq.com', - uids: [{id: 'some-random-id-value', atype: 1}], - }, - ], - }, + { + userIdAsEids: [ + { + source: '33across.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + + { + source: 'utiq.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + + { + userIdAsEids: [ + { + source: 'test.test', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + ], }, expected: { @@ -544,8 +562,8 @@ describe('nextMillenniumBidAdapterTests', () => { for (let { title, data, expected } of dataTests) { it(title, () => { - const { postBody, bid } = data; - setEids(postBody, bid); + const { postBody, bids } = data; + setEids(postBody, bids); expect(postBody).to.deep.equal(expected); }); } @@ -606,110 +624,144 @@ describe('nextMillenniumBidAdapterTests', () => { } }; - const bidRequestDataGI = [ - { - adUnitCode: 'test-banner-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - banner: { - sizes: [[300, 250]] + const bidRequestDataGI = getBidRequestDataGI(); + function getBidRequestDataGI(adUnitCodes = ['test-banner-gi', 'test-banner-gi', 'test-video-gi']) { + return [ + { + adUnitCode: adUnitCodes[0], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + + sizes: [[300, 250]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - sizes: [[300, 250]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, + { + adUnitCode: adUnitCodes[1], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, - { - adUnitCode: 'test-banner-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 300]] + sizes: [[300, 250], [300, 300]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - sizes: [[300, 250], [300, 300]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, + { + adUnitCode: adUnitCodes[2], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + video: { + playerSize: [640, 480], + } + }, - { - adUnitCode: 'test-video-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - video: { - playerSize: [640, 480], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, - ]; + ]; + } it('validate_generated_params', function() { const request = spec.buildRequests(bidRequestData, {bidderRequestId: 'mock-uuid'}); - expect(request[0].bidId).to.equal('bid1234'); expect(JSON.parse(request[0].data).id).to.exist; }); - it('use parameters group_id', function() { + it('check parameters group_id or placement_id', function() { for (let test of bidRequestDataGI) { const request = spec.buildRequests([test]); const requestData = JSON.parse(request[0].data); - const storeRequestId = requestData.ext.prebid.storedrequest.id; - const templateRE = /^g[1-9]\d*;(?:[1-9]\d*x[1-9]\d*\|)*[1-9]\d*x[1-9]\d*;/; - expect(templateRE.test(storeRequestId)).to.be.true; + const storeRequestId = (requestData.imp[0].ext.prebid.storedrequest.id || ''); + expect(storeRequestId.length).to.be.not.equal(0); + + const srId = storeRequestId.split(';'); + const isGroupId = (/^g[1-9]\d*/).test(srId[0]); + if (isGroupId) { + expect(srId.length).to.be.equal(3); + expect((/^g[1-9]\d*/).test(srId[0])).to.be.true; + const sizes = srId[1].split('|'); + for (let size of sizes) { + if (!(/^[1-9]\d*[xX,][1-9]\d*$/).test(size)) { + expect(storeRequestId).to.be.equal(''); + } + + expect((/^[1-9]\d*[xX,][1-9]\d*$/).test(size)).to.be.true; + } + } else { + expect(srId.length).to.be.equal(1); + expect((/^[1-9]\d*/).test(srId[0])).to.be.true; + }; }; }); - it('Check if refresh_count param is incremented', function() { - const request = spec.buildRequests(bidRequestData); - expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(1); + describe('Check ext.next_mil_imps', function() { + const expectedNextMilImps = [ + { + impId: 'nmi-test-0', + nextMillennium: {refresh_count: 1}, + }, + + { + impId: 'nmi-test-1', + nextMillennium: {refresh_count: 1}, + }, + + { + impId: 'nmi-test-2', + nextMillennium: {refresh_count: 1}, + }, + ]; + + const dataForRequest = getBidRequestDataGI(expectedNextMilImps.map(el => el.impId)); + for (let j = 0; j < 2; j++) { + const request = spec.buildRequests(dataForRequest); + const bidRequest = JSON.parse(request[0].data); + for (let i = 0; i < bidRequest.ext.next_mil_imps.length; i++) { + it(`test - ${j * i + 1}`, () => { + const nextMilImp = bidRequest.ext.next_mil_imps[i]; + expect(nextMilImp.impId).to.deep.equal(expectedNextMilImps[i].impId); + expect(nextMilImp.nextMillennium.refresh_count).to.deep.equal(expectedNextMilImps[i].nextMillennium.refresh_count + j); + }) + }; + }; }); it('Check if domain was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).site.domain).to.exist - }) - - it('Check if elOffsets was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).ext.nextMillennium.elOffsets).to.be.an('object') - }) + const request = spec.buildRequests(bidRequestData); + expect(JSON.parse(request[0].data).site.domain).to.exist; + }); it('Check if imp object was added', function() { const request = spec.buildRequests(bidRequestData) expect(JSON.parse(request[0].data).imp).to.be.an('array') }); - it('Check if imp prebid stored id is correct', function() { - const request = spec.buildRequests(bidRequestData) - const requestData = JSON.parse(request[0].data); - const storedReqId = requestData.ext.prebid.storedrequest.id; - expect(requestData.imp[0].ext.prebid.storedrequest.id).to.equal(storedReqId) - }); - it('validate_response_params', function() { let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); expect(bids).to.have.lengthOf(1); @@ -806,11 +858,12 @@ describe('nextMillenniumBidAdapterTests', () => { expect(bid.currency).to.equal('USD'); }); - it('Check function of getting URL for sending statistics data', function() { + describe('function spec._getUrlPixelMetric', function() { const dataForTests = [ { + title: 'Check function of getting URL for sending statistics data - 1', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'appnexus', bids: [{bidder: 'appnexus', params: {}}], }, @@ -819,8 +872,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 2', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'appnexus', bids: [{bidder: 'appnexus', params: {placement_id: '807'}}], }, @@ -829,8 +883,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 3', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [{bidder: 'nextMillennium', params: {placement_id: '807'}}], }, @@ -839,8 +894,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 4', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [ {bidder: 'nextMillennium', params: {placement_id: '807'}}, @@ -852,8 +908,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 5', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [{bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}], }, @@ -862,8 +919,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 6', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [ {bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}, @@ -876,8 +934,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 7', eventName: 'bidResponse', - bid: { + bids: { bidderCode: 'appnexus', }, @@ -885,8 +944,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 8', eventName: 'bidResponse', - bid: { + bids: { bidderCode: 'nextMillennium', params: {placement_id: '807'}, }, @@ -895,8 +955,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 9', eventName: 'noBid', - bid: { + bids: { bidder: 'appnexus', }, @@ -904,8 +965,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 10', eventName: 'noBid', - bid: { + bids: { bidder: 'nextMillennium', params: {placement_id: '807'}, }, @@ -914,8 +976,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 11', eventName: 'bidTimeout', - bid: { + bids: { bidder: 'appnexus', }, @@ -923,19 +986,49 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 12', eventName: 'bidTimeout', - bid: { + bids: { bidder: 'nextMillennium', params: {placement_id: '807'}, }, expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidTimeout&bidder=nextMillennium&source=pbjs&placements=807', }, + + { + title: 'Check function of getting URL for sending statistics data - 13', + eventName: 'bidRequested', + bids: [ + { + bidderCode: 'nextMillennium', + bids: [ + {bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}, + {bidder: 'nextMillennium', params: {group_id: '456'}}, + {bidder: 'nextMillennium', params: {placement_id: '222'}}, + ], + }, + + { + bidderCode: 'nextMillennium', + params: {group_id: '7777'}, + }, + + { + bidderCode: 'nextMillennium', + params: {placement_id: '8888'}, + }, + ], + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidRequested&bidder=nextMillennium&source=pbjs&groups=123;456;7777&placements=222;8888', + }, ]; - for (let {eventName, bid, expected} of dataForTests) { - const url = spec._getUrlPixelMetric(eventName, bid); - expect(url).to.equal(expected); + for (let {title, eventName, bids, expected} of dataForTests) { + it(title, () => { + const url = spec._getUrlPixelMetric(eventName, bids); + expect(url).to.equal(expected); + }); }; - }) + }); });