prebid-universal-creative 1.16.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.circleci/config.yml +44 -30
  2. package/.github/workflows/codeql.yml +98 -0
  3. package/README.md +2 -2
  4. package/dist/amp.js +3 -3
  5. package/dist/banner.js +3 -3
  6. package/dist/caf7688498213fb0c19f.max.js +1046 -0
  7. package/dist/creative.js +3 -3
  8. package/dist/load-cookie-with-consent.html +1 -1
  9. package/dist/load-cookie.html +1 -1
  10. package/dist/mobile.js +3 -3
  11. package/dist/native-render.js +3 -3
  12. package/dist/native-trk.js +3 -3
  13. package/dist/native.js +3 -3
  14. package/dist/uid.js +2 -2
  15. package/dist/video.js +3 -3
  16. package/gulpfile.js +12 -24
  17. package/integ-test/fixtures/test.js +79 -0
  18. package/integ-test/pages/amp.html +80 -0
  19. package/integ-test/pages/banner.html +96 -0
  20. package/integ-test/pages/native_legacy.html +107 -0
  21. package/integ-test/spec/amp_spec.js +111 -0
  22. package/integ-test/spec/banner_spec.js +85 -0
  23. package/integ-test/spec/native_legacy_spec.js +213 -0
  24. package/package.json +7 -13
  25. package/playwright.config.js +108 -0
  26. package/src/adHtmlRender.js +11 -0
  27. package/src/cookieSync.js +3 -0
  28. package/src/cookieSyncWithConsent.js +3 -0
  29. package/src/domHelper.js +25 -15
  30. package/src/dynamicRenderer.js +56 -0
  31. package/src/messaging.js +23 -2
  32. package/src/mobileAndAmpRender.js +17 -20
  33. package/src/nativeAssetManager.js +98 -79
  34. package/src/nativeORTBTrackerManager.js +1 -1
  35. package/src/nativeRenderManager.js +7 -12
  36. package/src/nativeTrackerManager.js +2 -2
  37. package/src/renderingManager.js +13 -19
  38. package/test/helpers/mocks.js +1 -0
  39. package/test/spec/dynamicRenderer_spec.js +167 -0
  40. package/test/spec/messaging_spec.js +98 -3
  41. package/test/spec/mobileAndAmpRender_spec.js +47 -65
  42. package/test/spec/nativeAssetManager_spec.js +73 -23
  43. package/test/spec/renderingManager_spec.js +20 -6
  44. package/webpack.conf.js +0 -1
  45. package/.nvmrc +0 -1
  46. package/dist/creative.max.js +0 -3102
  47. package/src/postscribeRender.js +0 -10
  48. package/test/e2e/specs/hello_world_banner_non_sf.spec.js +0 -14
  49. package/test/e2e/specs/outstream_non_sf.spec.js +0 -14
  50. package/test/e2e/specs/outstream_sf.spec.js +0 -14
  51. package/wdio.conf.js +0 -50
package/gulpfile.js CHANGED
@@ -17,6 +17,7 @@ const KarmaServer = require('karma').Server;
17
17
  const karmaConfMaker = require('./karma.conf.maker');
18
18
  const execa = require('execa');
19
19
  const path = require('path');
20
+ const {execSync} = require('child_process');
20
21
 
21
22
  const dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10);
22
23
  const banner = '/* <%= creative.name %> v<%= creative.version %>\n' + dateString + ' */\n';
@@ -141,16 +142,12 @@ function includeStaticVastXmlFile() {
141
142
  function buildProd({ inputFile, outputFile }) {
142
143
  let cloned = _.cloneDeep(webpackConfig);
143
144
  delete cloned.devtool;
144
-
145
+ cloned.output.filename = outputFile;
145
146
  return gulp.src([inputFile])
146
147
  .pipe(webpackStream(cloned))
147
148
  .pipe(gulp.dest('dist'))
148
149
  .pipe(uglify())
149
150
  .pipe(header(banner, { creative: creative }))
150
- .pipe(rename({
151
- basename: outputFile.split('.')[0],
152
- extname: `.${outputFile.split('.')[1]}`
153
- }))
154
151
  .pipe(gulp.dest('dist'));
155
152
  }
156
153
 
@@ -205,20 +202,10 @@ function includeStaticVastXmlFile() {
205
202
  //
206
203
  // If --watch is given, the task will open the karma debug window
207
204
  // If --browserstack is given, it will run the full suite of currently supported browsers.
208
- // If --e2e is given, it will run test defined in ./test/e2e/specs in browserstack
209
205
 
210
206
  function test(done) {
211
- if (argv.e2e) {
212
- let wdioCmd = path.join(__dirname, 'node_modules/.bin/wdio');
213
- let wdioConf = path.join(__dirname, 'wdio.conf.js');
214
- let wdioOpts = [
215
- wdioConf
216
- ];
217
- return execa(wdioCmd, wdioOpts, { stdio: 'inherit' });
218
- } else {
219
- let karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch);
220
- new KarmaServer(karmaConf, newKarmaCallback(done)).start();
221
- }
207
+ let karmaConf = karmaConfMaker(false, argv.browserstack, argv.watch);
208
+ new KarmaServer(karmaConf, newKarmaCallback(done)).start();
222
209
  }
223
210
 
224
211
  function newKarmaCallback(done) {
@@ -237,17 +224,10 @@ function newKarmaCallback(done) {
237
224
  }
238
225
  }
239
226
 
240
- function setupE2E(done) {
241
- argv.e2e = true;
242
- done();
243
- }
244
-
245
227
  gulp.task('test', gulp.series(clean, test));
246
228
 
247
229
  const buildDevFunctions = [buildLegacyDev, buildBannerDev, buildVideoDev, buildAmpDev, buildMobileDev, buildNativeRenderLegacyDev, buildNativeDev, buildNativeRenderDev, buildCookieSync, buildCookieSyncWithConsent, buildUidDev, includeStaticVastXmlFile];
248
230
 
249
- gulp.task('e2e-test', gulp.series(clean, setupE2E, gulp.parallel(...buildDevFunctions, watch), test));
250
-
251
231
  function watch(done) {
252
232
  const mainWatcher = gulp.watch([
253
233
  'src/**/*.js',
@@ -275,6 +255,14 @@ gulp.task('test-coverage', (done) => {
275
255
  new KarmaServer(karmaConfMaker(true, false, false), newKarmaCallback(done)).start();
276
256
  });
277
257
 
258
+ function integTests(done) {
259
+ execSync('npx playwright test', {stdio: 'inherit'});
260
+ done();
261
+ }
262
+
263
+ gulp.task('integ-tests', gulp.series(clean, 'build', integTests));
264
+
265
+
278
266
  gulp.task('view-coverage', (done) => {
279
267
  const coveragePort = 1999;
280
268
  const localhost = (argv.host) ? argv.host : 'localhost';
@@ -0,0 +1,79 @@
1
+ import {test as baseTest} from '@playwright/test';
2
+ import path from 'path';
3
+ import {expect} from '@playwright/test';
4
+ export {expect} from '@playwright/test';
5
+ import process from 'process'
6
+ export const BASE_URL = 'https://www.prebid.org/puc-test/';
7
+ export const PUC_URL = 'https://cdn.jsdelivr.net/npm/prebid-universal-creative@latest/dist/';
8
+ export const PBJS_URL = 'https://cdn.jsdelivr.net/npm/prebid.js@latest/dist/not-for-prod/prebid.js'
9
+
10
+ const LOCAL_PBJS_URL = 'http://localhost:9999/build/dev/prebid.js';
11
+
12
+ const REDIRECTS = {
13
+ [BASE_URL]: '../pages',
14
+ [PUC_URL]: '../../dist'
15
+ };
16
+
17
+ export const test = baseTest.extend({
18
+ /**
19
+ * Replace requests for "https://www.prebid.org" with the contents of files under "pages",
20
+ * and requests for the PUC CDN with contents of files under "dist".
21
+ */
22
+ async context({context}, use) {
23
+ await Promise.all(
24
+ Object.entries(REDIRECTS).map(([url, localDir]) => {
25
+ context.route((u) => u.href.startsWith(url), (route, request) => {
26
+ const fpath = request.url().substring(url.length).split('?')[0];
27
+ route.fulfill({
28
+ path: path.resolve(__dirname, localDir, fpath)
29
+ });
30
+ });
31
+ })
32
+ );
33
+ if (process.env.LOCAL_PBJS) {
34
+ const localUrl = process.env.LOCAL_PBJS.startsWith('http') ? process.env.LOCAL_PBJS : LOCAL_PBJS_URL;
35
+ await context.route((u) => u.href.startsWith(PBJS_URL), (route) => route.fulfill({status: 302, headers: {Location: localUrl}}))
36
+ }
37
+ await use(context);
38
+ },
39
+ /**
40
+ * await crossLocator(selector): returns a locator for the first element matching 'selector' that appears on the
41
+ * page, across all frames.
42
+ */
43
+ async crossLocator({page}, use) {
44
+ await use(function (selector) {
45
+ let n = 0;
46
+ return new Promise((resolve, reject) => {
47
+ async function frameLocator(frame) {
48
+ if (!frame.isDetached()) {
49
+ n++;
50
+ try {
51
+ await frame.waitForSelector(selector);
52
+ resolve(frame.locator(selector));
53
+ } catch (e) {
54
+ n--;
55
+ if (n === 0) {
56
+ reject(e);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ page.on('frameattached', frameLocator);
62
+ function walkFrames(frame) {
63
+ frameLocator(frame);
64
+ frame.childFrames().forEach(walkFrames)
65
+ }
66
+ walkFrames(page.mainFrame());
67
+ })
68
+ })
69
+ },
70
+ async expectEvent({page}, use) {
71
+ await use(async function (predicate, numMatches = 1) {
72
+ await expect.poll(async () =>
73
+ ((await page.evaluate(() => window.pbjs?.getEvents && window.pbjs.getEvents())) || [])
74
+ .filter(predicate)
75
+ .length === numMatches
76
+ ).toBeTruthy();
77
+ });
78
+ }
79
+ });
@@ -0,0 +1,80 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
6
+ <link rel="canonical" href="https://www.prebid-org/puc-test/amp.html">
7
+ <style amp-boilerplate>body {
8
+ -webkit-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
9
+ -moz-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
10
+ -ms-animation: -amp-start 8s steps(1, end) 0s 1 normal both;
11
+ animation: -amp-start 8s steps(1, end) 0s 1 normal both
12
+ }
13
+
14
+ @-webkit-keyframes -amp-start {
15
+ from {
16
+ visibility: hidden
17
+ }
18
+ to {
19
+ visibility: visible
20
+ }
21
+ }
22
+
23
+ @-moz-keyframes -amp-start {
24
+ from {
25
+ visibility: hidden
26
+ }
27
+ to {
28
+ visibility: visible
29
+ }
30
+ }
31
+
32
+ @-ms-keyframes -amp-start {
33
+ from {
34
+ visibility: hidden
35
+ }
36
+ to {
37
+ visibility: visible
38
+ }
39
+ }
40
+
41
+ @-o-keyframes -amp-start {
42
+ from {
43
+ visibility: hidden
44
+ }
45
+ to {
46
+ visibility: visible
47
+ }
48
+ }
49
+
50
+ @keyframes -amp-start {
51
+ from {
52
+ visibility: hidden
53
+ }
54
+ to {
55
+ visibility: visible
56
+ }
57
+ }</style>
58
+ <noscript>
59
+ <style amp-boilerplate>body {
60
+ -webkit-animation: none;
61
+ -moz-animation: none;
62
+ -ms-animation: none;
63
+ animation: none
64
+ }</style>
65
+ </noscript>
66
+ <script async src="https://cdn.ampproject.org/v0.js"></script>
67
+ <script async custom-element="amp-ad" src="https://cdn.ampproject.org/v0/amp-ad-0.1.js"></script>
68
+ <title>AMP-RTC RP Test</title>
69
+ </head>
70
+ <body>
71
+ <h2>AMP-RTC RP Test</h2>
72
+
73
+ <amp-ad width="300" height="250"
74
+ type="doubleclick"
75
+ data-slot="/41758329/integ-test"
76
+ data-multi-size-validation="false"
77
+ rtc-config='{"vendors": {"prebidrubicon": {"REQUEST_ID": "14062-amp-300x50"}}}'
78
+ </amp-ad>
79
+ </body>
80
+ </html>
@@ -0,0 +1,96 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Prebid.js Banner Test</title>
7
+
8
+ <!-- Prebid.js -->
9
+ <script async src="https://cdn.jsdelivr.net/npm/prebid.js@latest/dist/not-for-prod/prebid.js"></script>
10
+
11
+ <!-- Google Publisher Tag -->
12
+ <script async src="https://www.googletagservices.com/tag/js/gpt.js"></script>
13
+
14
+ <script>
15
+ var pbjs = pbjs || {};
16
+ pbjs.que = pbjs.que || [];
17
+ window.params = new URLSearchParams(window.location.search);
18
+ </script>
19
+
20
+ <script>
21
+ var googletag = googletag || {};
22
+ googletag.cmd = googletag.cmd || [];
23
+
24
+ googletag.cmd.push(function () {
25
+ googletag.pubads().disableInitialLoad();
26
+ });
27
+
28
+ pbjs.que.push(function () {
29
+ pbjs.setConfig({
30
+ debug: true,
31
+ debugging: {
32
+ enabled: true,
33
+ intercept: [
34
+ {
35
+ when: {},
36
+ then: JSON.parse(params.get('bidResponse'))
37
+ }
38
+ ]
39
+ }
40
+ });
41
+
42
+ pbjs.requestBids({
43
+ bidsBackHandler: sendAdServerRequest,
44
+ adUnits: [
45
+ {
46
+ code: 'slot',
47
+ mediaTypes: {
48
+ banner: {
49
+ sizes: [[300, 250]],
50
+ }
51
+ },
52
+ bids: [{
53
+ bidder: 'appnexus',
54
+ params: {
55
+ placementId: 123
56
+ }
57
+ }]
58
+ }
59
+ ]
60
+ });
61
+ });
62
+
63
+ function sendAdServerRequest() {
64
+ googletag.cmd.push(function () {
65
+ pbjs.que.push(function () {
66
+ pbjs.setTargetingForGPTAsync('slot');
67
+ googletag.pubads().refresh();
68
+ });
69
+ });
70
+ }
71
+ </script>
72
+
73
+ <script>
74
+ googletag.cmd.push(function () {
75
+ googletag
76
+ .defineSlot('/41758329/integ-test', [[300, 250]], 'slot')
77
+ .setTargeting('creative', params.get('creative'))
78
+ .addService(googletag.pubads());
79
+
80
+ googletag.pubads().enableSingleRequest();
81
+ googletag.enableServices();
82
+ });
83
+ </script>
84
+ </head>
85
+
86
+ <body>
87
+ <h2>Prebid.js Banner Ad Unit Test</h2>
88
+ <div id='slot'>
89
+ <script>
90
+ googletag.cmd.push(function () {
91
+ googletag.display('slot');
92
+ });
93
+ </script>
94
+ </div>
95
+ </body>
96
+ </html>
@@ -0,0 +1,107 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Prebid.js Legacy Native Test</title>
7
+
8
+ <!-- Prebid.js -->
9
+ <script async src="https://cdn.jsdelivr.net/npm/prebid.js@latest/dist/not-for-prod/prebid.js"></script>
10
+
11
+ <!-- Google Publisher Tag -->
12
+ <script async src="https://www.googletagservices.com/tag/js/gpt.js"></script>
13
+
14
+ <script>
15
+ var pbjs = pbjs || {};
16
+ pbjs.que = pbjs.que || [];
17
+ window.params = new URLSearchParams(window.location.search);
18
+ </script>
19
+
20
+ <script>
21
+ var googletag = googletag || {};
22
+ googletag.cmd = googletag.cmd || [];
23
+
24
+ googletag.cmd.push(function () {
25
+ googletag.pubads().disableInitialLoad();
26
+ });
27
+
28
+ pbjs.que.push(function () {
29
+ pbjs.setConfig({
30
+ debug: true,
31
+ debugging: {
32
+ enabled: true,
33
+ intercept: [
34
+ {
35
+ when: {},
36
+ then: JSON.parse(params.get('bidResponse'))
37
+ }
38
+ ]
39
+ }
40
+ });
41
+
42
+ pbjs.requestBids({
43
+ bidsBackHandler: sendAdServerRequest,
44
+ adUnits: [
45
+ {
46
+ code: 'slot',
47
+ mediaTypes: {
48
+ native: {
49
+ image: {
50
+ required: false,
51
+ },
52
+ title: {
53
+ required: true
54
+ },
55
+ body: {
56
+ required: true
57
+ },
58
+ clickUrl: {
59
+ required: true
60
+ }
61
+ }
62
+ },
63
+ bids: [{
64
+ bidder: 'appnexus',
65
+ params: {
66
+ placementId: 123
67
+ }
68
+ }]
69
+ }
70
+ ]
71
+ });
72
+ });
73
+
74
+ function sendAdServerRequest() {
75
+ googletag.cmd.push(function () {
76
+ pbjs.que.push(function () {
77
+ pbjs.setTargetingForGPTAsync('slot');
78
+ googletag.pubads().refresh();
79
+ });
80
+ });
81
+ }
82
+ </script>
83
+
84
+ <script>
85
+ googletag.cmd.push(function () {
86
+ googletag
87
+ .defineSlot('/41758329/integ-test', params.get('banner') === 'true' ? [[640, 480]] : 'fluid', 'slot')
88
+ .setTargeting('creative', params.get('creative'))
89
+ .addService(googletag.pubads());
90
+
91
+ googletag.pubads().enableSingleRequest();
92
+ googletag.enableServices();
93
+ });
94
+ </script>
95
+ </head>
96
+
97
+ <body>
98
+ <h2>Prebid.js Legacy Native Ad Unit Test</h2>
99
+ <div id='slot'>
100
+ <script>
101
+ googletag.cmd.push(function () {
102
+ googletag.display('slot');
103
+ });
104
+ </script>
105
+ </div>
106
+ </body>
107
+ </html>
@@ -0,0 +1,111 @@
1
+ import {test, expect} from '../fixtures/test.js';
2
+
3
+ test.describe('AMP', () => {
4
+ const PBS_AMP_URL = 'https://prebid-server.rubiconproject.com/openrtb2/amp';
5
+ const CACHE_HOST = 'cache-host.prebid-server.com';
6
+ const CACHE_PATH = '/cache';
7
+ const CACHE_ID = 'test-cache-id'
8
+ const TRIGGERED_URL_BASE = 'https://url-triggers.com/';
9
+ const URLS = Object.fromEntries(['burl', 'nurl', 'wurl'].map((t) => [t, `${TRIGGERED_URL_BASE}${t}`]))
10
+
11
+ let creative, imp, nurlResponse, triggers;
12
+
13
+ test.beforeEach(async ({page}) => {
14
+ creative = null;
15
+ imp = {};
16
+ nurlResponse = null;
17
+ triggers = {};
18
+ await page.route((u) => u.href.startsWith(PBS_AMP_URL), (route) => {
19
+ route.fulfill({
20
+ contentType: 'application/json',
21
+ body: JSON.stringify({
22
+ targeting: {
23
+ hb_cache_id: CACHE_ID,
24
+ hb_pb: "1.00",
25
+ hb_pb_rubicon: "1.00",
26
+ hb_cache_path: CACHE_PATH,
27
+ hb_size: "300x50",
28
+ hb_bidder: "rubicon",
29
+ hb_cache_host: CACHE_HOST,
30
+ creative
31
+ }
32
+ })
33
+ })
34
+ });
35
+ await page.route((u) => u.host.startsWith(CACHE_HOST) && u.pathname === CACHE_PATH, (route) => {
36
+ route.fulfill({
37
+ contentType: 'application/json',
38
+ body: JSON.stringify(imp)
39
+ })
40
+ })
41
+ await page.route((u) => u.href.startsWith(TRIGGERED_URL_BASE), (route, req)=> {
42
+ const trigger = req.url().substring(TRIGGERED_URL_BASE.length);
43
+ triggers[trigger] = true;
44
+ route.fulfill({
45
+ contentType: 'text/html',
46
+ body: trigger === 'nurl' && nurlResponse || ''
47
+ })
48
+ })
49
+ });
50
+
51
+ test.describe('Banner', () => {
52
+ Object.entries({
53
+ 'safeframe': 'banner-safeframe',
54
+ 'non safeframe': 'banner-noframe',
55
+ }).forEach(([t, cr]) => {
56
+ test.describe(t, () => {
57
+ const AD = `
58
+ <p id="the-ad">
59
+ This is the ad
60
+ <span id="ad-price">\${AUCTION_PRICE}</span>
61
+ </p>
62
+ `
63
+
64
+ test.beforeEach(() => {
65
+ creative = cr;
66
+ });
67
+
68
+ Object.entries({
69
+ 'imp.adm': {
70
+ adm: AD,
71
+ setup() {}
72
+ },
73
+ 'imp.nurl': {
74
+ setup() {
75
+ nurlResponse = AD;
76
+ }
77
+ }
78
+ }).forEach(([t, {adm, setup}]) => {
79
+ test.describe(t, () => {
80
+ test.beforeEach(async ({page}) => {
81
+ imp = {
82
+ price: 1.23,
83
+ adm,
84
+ ...URLS
85
+ };
86
+ setup();
87
+ await page.goto('amp.html');
88
+ });
89
+
90
+
91
+ test('should display ad', async ({crossLocator}) => {
92
+ await expect(await crossLocator('#the-ad')).toBeVisible();
93
+ })
94
+
95
+ if (adm != null) { // TODO: should AUCTION_PRICE work in the nurl case? see https://github.com/prebid/prebid-universal-creative/issues/183
96
+ test('should replace auction price', async ({crossLocator}) => {
97
+ await expect(await crossLocator('#the-ad #ad-price')).toHaveText('1.23');
98
+ })
99
+ }
100
+
101
+ Object.keys(URLS).forEach((trigger) => {
102
+ test(`should trigger ${trigger}`, () => {
103
+ expect.poll(() => triggers[trigger]).toBeTruthy();
104
+ });
105
+ });
106
+ });
107
+ });
108
+ });
109
+ });
110
+ });
111
+ });
@@ -0,0 +1,85 @@
1
+ import {test, expect} from '../fixtures/test.js';
2
+
3
+ test.describe('Banner', () => {
4
+ Object.entries({
5
+ 'safeframe': 'banner-safeframe',
6
+ 'non safeframe': 'banner-noframe'
7
+ }).forEach(([t, creative]) => {
8
+ test.describe(t, () => {
9
+ [
10
+ {
11
+ t: 'bidResponse.ad',
12
+ bidResponse: {
13
+ adId: 'mock-ad',
14
+ cpm: 1.23,
15
+ ad: `
16
+ <p id="the-ad">
17
+ This is the ad
18
+ <span id="ad-price">\${AUCTION_PRICE}</span>
19
+ </p>'
20
+ `
21
+ },
22
+ setup: async () => null
23
+ },
24
+ (() => {
25
+ const mockAdUrl = 'https://mock-ad.com/'
26
+ return {
27
+ t: 'bidResponse.adUrl',
28
+ bidResponse: {
29
+ adId: 'mock-ad',
30
+ cpm: 1.23,
31
+ adUrl: mockAdUrl + '${AUCTION_PRICE}',
32
+ mediaType: 'banner',
33
+ },
34
+ setup: async (page) => {
35
+ await page.route((u) => u.href.startsWith(mockAdUrl), (route, request) => {
36
+ const price = request.url().substring(mockAdUrl.length);
37
+ route.fulfill({
38
+ contentType: 'text/html',
39
+ body: `
40
+ <p id="the-ad">
41
+ This is the ad
42
+ <span id="ad-price">${price}</span>
43
+ </p>'
44
+ `
45
+ })
46
+ });
47
+ }
48
+ };
49
+
50
+ })()
51
+ ].forEach(({t, bidResponse, setup}) => {
52
+ test.describe(t, () => {
53
+ test.beforeEach(async ({page}) => {
54
+ await setup(page);
55
+ await page.goto(`banner.html?creative=${creative}&bidResponse=${encodeURIComponent(JSON.stringify(bidResponse))}`)
56
+ });
57
+ test('should display ad', async ({crossLocator}) => {
58
+ await expect(await crossLocator('#the-ad')).toBeVisible();
59
+ });
60
+ ['adRenderSucceeded', 'bidWon'].forEach(ev => {
61
+ test(`should emit '${ev}'`, async ({expectEvent}) => {
62
+ await expectEvent((event) => event.eventType === ev && event.args.adId === 'mock-ad')
63
+ })
64
+ });
65
+ test('should replace AUCTION_PRICE macro', async ({crossLocator}) => {
66
+ await expect(await crossLocator('#the-ad #ad-price')).toHaveText('1.23');
67
+ });
68
+
69
+ })
70
+ });
71
+ test.describe('Missing ad', () => {
72
+ const bidResponse = {
73
+ adId: 'mock-ad',
74
+ ad: null,
75
+ }
76
+ test.beforeEach(async ({page}) => {
77
+ await page.goto(`banner.html?creative=${creative}&bidResponse=${encodeURIComponent(JSON.stringify(bidResponse))}`);
78
+ });
79
+ test('should emit \'adRenderFailed\'', async ({expectEvent}) => {
80
+ await expectEvent((ev) => ev.eventType === 'adRenderFailed')
81
+ })
82
+ });
83
+ });
84
+ })
85
+ });