@admoai/sdk-scenarios 1.0.0 → 1.2.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.
- package/CHANGELOG.md +21 -0
- package/README.md +17 -5
- package/fixtures/golden-wire/branded_assets.full.json +74 -0
- package/fixtures/golden-wire/branded_assets.minimal.json +59 -0
- package/fixtures/golden-wire/card.derived.json +78 -0
- package/fixtures/golden-wire/card.full.json +83 -0
- package/fixtures/golden-wire/card.minimal.json +63 -0
- package/fixtures/golden-wire/carousel.derived.json +119 -0
- package/fixtures/golden-wire/carousel.full.json +199 -0
- package/fixtures/golden-wire/carousel.minimal.json +114 -0
- package/fixtures/golden-wire/expandable_banner.full.json +102 -0
- package/fixtures/golden-wire/expandable_banner.minimal.json +77 -0
- package/fixtures/golden-wire/sponsored_pin_location.full.json +147 -0
- package/fixtures/golden-wire/standard_video.full.json +118 -0
- package/fixtures/golden-wire/standard_video.minimal.json +83 -0
- package/package.json +3 -1
- package/scenarios.feature +597 -0
- package/scenarios.json +652 -2
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
# GENERATED from scenarios.json — do not edit; regenerate with: go run ./tools/sdkfeaturegen
|
|
2
|
+
#
|
|
3
|
+
# scenarios.json is what the iOS, Android and Flutter manifest runners execute: the live
|
|
4
|
+
# scenarios against a real decision-engine, the @fixture scenarios offline against the golden
|
|
5
|
+
# decision wire shipped in the package. This is its readable rendering: what every AdMoai SDK
|
|
6
|
+
# promises a publisher's app, in the same words for all three. CI regenerates it and fails on
|
|
7
|
+
# any difference, so it cannot drift from what the runners execute.
|
|
8
|
+
# How it is published and kept fresh: docs/guides/sdk-ci.md, Section 7.
|
|
9
|
+
# Manifest schemaVersion: 2
|
|
10
|
+
|
|
11
|
+
Feature: Cross-SDK decision & tracking contract
|
|
12
|
+
Shared cross-SDK E2E scenario manifest. ONE definition, executed by a thin interpreter in each
|
|
13
|
+
SDK, so the three suites cannot drift. Declarative only: given a request, expect an outcome.
|
|
14
|
+
Sponsored Pin tracking scenarios add `pinTracking`: the point-tracking calls an app makes on
|
|
15
|
+
the served pins, as data, and the beacons that must reach the engine. Procedural scenarios
|
|
16
|
+
(log capture, concurrency, retry, TTL waits) stay hand-written per SDK. Mirrored
|
|
17
|
+
byte-identically into all three repos; a checksum test fails the build if the copies diverge.
|
|
18
|
+
Every expected value here was verified against a live local decision-engine before being
|
|
19
|
+
written. `fixtureScenarios` (schemaVersion 2) run offline: each decodes one golden
|
|
20
|
+
decision-wire file (ui/platform/lib/ad-formats/golden-wire in adhub, shipped in this package
|
|
21
|
+
under fixtures/golden-wire/) after optional `edits`, and asserts what the publisher's app
|
|
22
|
+
reads from the decoded creative. `contentKeys` is the exact set of plain content keys (slides
|
|
23
|
+
and matched points are asserted through their own fields); a key in `contentKeysAbsent` must
|
|
24
|
+
decode as absent (null), never as an empty string or a placeholder; a null in `metadata` means
|
|
25
|
+
absent. A runner that does not execute fixtureScenarios must refuse schemaVersion 2.
|
|
26
|
+
|
|
27
|
+
@L @native
|
|
28
|
+
Scenario: L1 — a normal native ad serves with complete, well-formed metadata
|
|
29
|
+
Given the app requests placement "home"
|
|
30
|
+
Then an ad is served
|
|
31
|
+
And exactly 1 decision is returned
|
|
32
|
+
And the creative carries complete metadata: ad, creative, placement and template ids
|
|
33
|
+
And the creative's priority is one of standard, sponsorship, house
|
|
34
|
+
And the creative is not a Journey Ad
|
|
35
|
+
And the creative's format is "native"
|
|
36
|
+
|
|
37
|
+
@L @native
|
|
38
|
+
Scenario: L5 — a placement with no eligible ad returns a clean no-ad, not an error
|
|
39
|
+
Given the app requests placement "postroll"
|
|
40
|
+
Then no ad is served, and the placement answers with a clean no-fill
|
|
41
|
+
And exactly 1 decision is returned
|
|
42
|
+
|
|
43
|
+
@L @native
|
|
44
|
+
Scenario: L6 — the advertiser block is complete (name, legalName, logoUrl)
|
|
45
|
+
Given the app requests placement "home"
|
|
46
|
+
Then an ad is served
|
|
47
|
+
And the creative names its advertiser: name, legal name and logo
|
|
48
|
+
|
|
49
|
+
@L @native
|
|
50
|
+
Scenario: L7 — the template block carries a non-empty key
|
|
51
|
+
Given the app requests placement "home"
|
|
52
|
+
Then an ad is served
|
|
53
|
+
And the creative names its template
|
|
54
|
+
|
|
55
|
+
@L @native
|
|
56
|
+
Scenario: L8 — a served native ad exposes an impression URL, a click URL and non-empty contents
|
|
57
|
+
Given the app requests placement "home"
|
|
58
|
+
Then an ad is served
|
|
59
|
+
And the creative carries its content fields
|
|
60
|
+
And the creative exposes an impression URL
|
|
61
|
+
And the creative exposes at least 1 click URL
|
|
62
|
+
|
|
63
|
+
@M
|
|
64
|
+
Scenario: M1 — count=2 returns at most two creatives, and they are distinct
|
|
65
|
+
Given the app requests placement "promotions"
|
|
66
|
+
And placement "promotions" asks for 2 creatives
|
|
67
|
+
Then an ad is served
|
|
68
|
+
And exactly 1 decision is returned
|
|
69
|
+
And at most 2 creatives are returned
|
|
70
|
+
And the returned creatives are all different
|
|
71
|
+
|
|
72
|
+
@M
|
|
73
|
+
Scenario: M2 — three placements in one request each get their own correctly-keyed decision
|
|
74
|
+
Given the app requests placement "home"
|
|
75
|
+
And the app requests placement "promotions"
|
|
76
|
+
And the app requests placement "search"
|
|
77
|
+
Then an ad is served
|
|
78
|
+
And exactly 3 decisions are returned
|
|
79
|
+
And every requested placement comes back with its own decision
|
|
80
|
+
|
|
81
|
+
@M
|
|
82
|
+
Scenario: M3 — an advertiserId filter still serves that advertiser's ad
|
|
83
|
+
Given the app requests placement "promotions"
|
|
84
|
+
And placement "promotions" is restricted to advertiser "advertiser_01JEPGMJXARVYSJA0CAM9FQ79Y"
|
|
85
|
+
Then an ad is served
|
|
86
|
+
And the creative names its advertiser: name, legal name and logo
|
|
87
|
+
|
|
88
|
+
@M
|
|
89
|
+
Scenario: M5 — format=native on a mixed placement returns a native ad
|
|
90
|
+
Given the app requests placement "promotions"
|
|
91
|
+
And placement "promotions" asks for format "native"
|
|
92
|
+
Then an ad is served
|
|
93
|
+
And the creative's format is "native"
|
|
94
|
+
|
|
95
|
+
@M
|
|
96
|
+
Scenario: M6 — format=video on the same mixed placement returns a video ad instead
|
|
97
|
+
Given the app requests placement "promotions"
|
|
98
|
+
And placement "promotions" asks for format "video"
|
|
99
|
+
Then an ad is served
|
|
100
|
+
And the creative's format is "video"
|
|
101
|
+
|
|
102
|
+
@M
|
|
103
|
+
Scenario: M7 — omitting format still serves — the engine chooses
|
|
104
|
+
Given the app requests placement "promotions"
|
|
105
|
+
Then an ad is served
|
|
106
|
+
|
|
107
|
+
@M
|
|
108
|
+
Scenario: M8 — a journey-owned placement and a normal placement resolve independently in ONE request
|
|
109
|
+
Given the app requests placement "promotions"
|
|
110
|
+
And the app requests placement "home"
|
|
111
|
+
And the request carries a fresh session id
|
|
112
|
+
And the user has opted in to Journey Ads
|
|
113
|
+
Then an ad is served
|
|
114
|
+
And exactly 2 decisions are returned
|
|
115
|
+
|
|
116
|
+
@P @video
|
|
117
|
+
Scenario: P1 — a non-skippable JSON video exposes json delivery and five video events (no skip)
|
|
118
|
+
Given the app requests placement "json_none"
|
|
119
|
+
Then an ad is served
|
|
120
|
+
And the creative's format is "video"
|
|
121
|
+
And the creative is delivered as "json"
|
|
122
|
+
And the creative exposes exactly 5 video event URLs
|
|
123
|
+
And the video events include start, first_quartile, midpoint, third_quartile, complete
|
|
124
|
+
|
|
125
|
+
@P @video
|
|
126
|
+
Scenario: P2 — a vast_tag ad exposes its tag URL and surfaces NO engine-side beacons (no double count)
|
|
127
|
+
Given the app requests placement "vasttag_none"
|
|
128
|
+
Then an ad is served
|
|
129
|
+
And the creative is delivered as "vast_tag"
|
|
130
|
+
And the creative exposes no engine impression URL, because the VAST owns the impression
|
|
131
|
+
And the creative exposes exactly 0 video event URLs
|
|
132
|
+
And the creative exposes a VAST tag URL
|
|
133
|
+
|
|
134
|
+
@P @video
|
|
135
|
+
Scenario: P3 — a vast_xml ad exposes base64 that decodes to a VAST document, and no beacons
|
|
136
|
+
Given the app requests placement "vastxml_none"
|
|
137
|
+
Then an ad is served
|
|
138
|
+
And the creative is delivered as "vast_xml"
|
|
139
|
+
And the creative exposes no engine impression URL, because the VAST owns the impression
|
|
140
|
+
And the creative exposes exactly 0 video event URLs
|
|
141
|
+
And the creative's inline VAST XML decodes to a VAST document
|
|
142
|
+
|
|
143
|
+
@P @video
|
|
144
|
+
Scenario: P6 — a skippable video resolves skippability from ENGINE METADATA, plus a sixth skip event
|
|
145
|
+
Given the app requests placement "json_none_skippable"
|
|
146
|
+
Then an ad is served
|
|
147
|
+
And the creative exposes exactly 6 video event URLs
|
|
148
|
+
And the metadata says the video is skippable
|
|
149
|
+
And the metadata's skip offset is 5 seconds
|
|
150
|
+
And the SDK's skippable helper says the video is skippable
|
|
151
|
+
And the SDK's skip-offset helper returns "5"
|
|
152
|
+
|
|
153
|
+
@P @video
|
|
154
|
+
Scenario: P8 — an end-card video reports its configured endCardMode
|
|
155
|
+
Given the app requests placement "json_native_endcard"
|
|
156
|
+
Then an ad is served
|
|
157
|
+
And the end-card mode is "native_endcard"
|
|
158
|
+
|
|
159
|
+
@V @validation
|
|
160
|
+
Scenario: V1 — an unknown placement key raises a typed validation error, not a silent no-fill
|
|
161
|
+
Given the app requests placement "definitely_not_a_real_placement"
|
|
162
|
+
Then the request fails
|
|
163
|
+
And the SDK surfaces a typed validation error with HTTP status 422
|
|
164
|
+
And the engine's error code is 10017
|
|
165
|
+
|
|
166
|
+
@V @validation
|
|
167
|
+
Scenario: V3 — an unknown custom-targeting key is rejected by the engine
|
|
168
|
+
Given the app requests placement "home"
|
|
169
|
+
And the custom target "not_a_configured_preset" is "x"
|
|
170
|
+
Then the request fails
|
|
171
|
+
And the SDK surfaces a typed validation error with HTTP status 422
|
|
172
|
+
And the engine's error code is 10028
|
|
173
|
+
|
|
174
|
+
@V @validation
|
|
175
|
+
Scenario: V4 — the SDK deduplicates a repeated custom key, so the engine never sees the duplicate that would fail the whole request
|
|
176
|
+
Sent raw over HTTP this same payload returns 422 code 10031, and the engine aborts
|
|
177
|
+
validation for the ENTIRE request — every placement, not just the offending target. All
|
|
178
|
+
three SDKs dedupe by key (last write wins), so a publisher cannot reach that state through
|
|
179
|
+
the public API. This scenario is the live proof of that guard: it must SERVE, and both
|
|
180
|
+
placements must come back.
|
|
181
|
+
|
|
182
|
+
Given the app requests placement "home"
|
|
183
|
+
And the app requests placement "promotions"
|
|
184
|
+
And the custom target "category" is "sports"
|
|
185
|
+
And the custom target "category" is "news"
|
|
186
|
+
Then an ad is served
|
|
187
|
+
And exactly 2 decisions are returned
|
|
188
|
+
And every requested placement comes back with its own decision
|
|
189
|
+
|
|
190
|
+
@V @validation
|
|
191
|
+
Scenario: V5 — a single valid custom target does not break serving
|
|
192
|
+
Given the app requests placement "home"
|
|
193
|
+
And the custom target "category" is "sports"
|
|
194
|
+
Then an ad is served
|
|
195
|
+
|
|
196
|
+
@V @validation
|
|
197
|
+
Scenario: V6 — a count above the documented maximum is rejected
|
|
198
|
+
Given the app requests placement "home"
|
|
199
|
+
And placement "home" asks for 99 creatives
|
|
200
|
+
Then the request fails
|
|
201
|
+
And the SDK surfaces a typed validation error with HTTP status 422
|
|
202
|
+
And the engine's error code is 10016
|
|
203
|
+
|
|
204
|
+
@V @validation
|
|
205
|
+
Scenario: V9 — a request with no placements fails locally, before any network call
|
|
206
|
+
Given the app requests no placements
|
|
207
|
+
Then the request fails
|
|
208
|
+
And the SDK rejects the request itself, before any network call
|
|
209
|
+
|
|
210
|
+
@Z @legacy-version
|
|
211
|
+
Scenario: Z1 — API version 2025-01-01 still serves a native ad and never a journey block
|
|
212
|
+
Given the app sends X-Decision-Version "2025-01-01"
|
|
213
|
+
And the app requests placement "home"
|
|
214
|
+
Then an ad is served
|
|
215
|
+
And the creative carries complete metadata: ad, creative, placement and template ids
|
|
216
|
+
And the creative is not a Journey Ad
|
|
217
|
+
|
|
218
|
+
@Z @legacy-version
|
|
219
|
+
Scenario: Z2 — omitting the version header still serves (engine falls back to the oldest active version)
|
|
220
|
+
Given the app sends no X-Decision-Version header
|
|
221
|
+
And the app requests placement "home"
|
|
222
|
+
Then an ad is served
|
|
223
|
+
And the creative is not a Journey Ad
|
|
224
|
+
|
|
225
|
+
@SP @sponsored-pin
|
|
226
|
+
Scenario: SP1 — a radius search returns the campaign's own shops, nearest first
|
|
227
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
228
|
+
And the app requests placement "poi"
|
|
229
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
230
|
+
Then an ad is served
|
|
231
|
+
And the ad carries at least 2 pins
|
|
232
|
+
And the pins are ordered nearest first
|
|
233
|
+
And every pin is one of the advertiser's locations, with a name and a distance
|
|
234
|
+
|
|
235
|
+
@SP @sponsored-pin
|
|
236
|
+
Scenario: SP2 — a bounds search returns them too — the other shape a map can ask with
|
|
237
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
238
|
+
And the app requests placement "poi"
|
|
239
|
+
And the app searches for pins inside the map area north -33.3, south -33.55, east -70.45, west -70.65, around (-33.4175, -70.6065)
|
|
240
|
+
Then an ad is served
|
|
241
|
+
And the ad carries at least 2 pins
|
|
242
|
+
And every pin is one of the advertiser's locations, with a name and a distance
|
|
243
|
+
|
|
244
|
+
@SP @sponsored-pin
|
|
245
|
+
Scenario: SP3 — every returned shop carries its own view, tap and click beacons
|
|
246
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
247
|
+
And the app requests placement "poi"
|
|
248
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
249
|
+
Then an ad is served
|
|
250
|
+
And every pin carries its own view, tap and click beacons
|
|
251
|
+
|
|
252
|
+
@SP @sponsored-pin
|
|
253
|
+
Scenario: SP4 — every returned shop carries a resolved destination, its own or the creative's
|
|
254
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
255
|
+
And the app requests placement "poi"
|
|
256
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
257
|
+
Then an ad is served
|
|
258
|
+
And every pin carries a resolved destination URL
|
|
259
|
+
|
|
260
|
+
@SP @sponsored-pin
|
|
261
|
+
Scenario: SP5 — the request's limit narrows how many shops come back
|
|
262
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
263
|
+
And the app requests placement "poi"
|
|
264
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
265
|
+
And the search asks for at most 2 pins
|
|
266
|
+
Then an ad is served
|
|
267
|
+
And the ad carries exactly 2 pins
|
|
268
|
+
|
|
269
|
+
@SP @sponsored-pin
|
|
270
|
+
Scenario: SP6 — a tight radius excludes the shops outside it
|
|
271
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
272
|
+
And the app requests placement "poi"
|
|
273
|
+
And the app searches for pins within 1500 m of (-33.4175, -70.6065)
|
|
274
|
+
Then an ad is served
|
|
275
|
+
And the ad carries exactly 1 pin
|
|
276
|
+
|
|
277
|
+
@SP @sponsored-pin
|
|
278
|
+
Scenario: SP7 — without a distance search a Sponsored Pin campaign does not serve at all
|
|
279
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
280
|
+
And the app requests placement "poi"
|
|
281
|
+
Then no ad is served, and the placement answers with a clean no-fill
|
|
282
|
+
|
|
283
|
+
@SP @sponsored-pin
|
|
284
|
+
Scenario: SP8 — on the older API version the ad still serves, silently carrying no pins — the failure an integrator must recognise
|
|
285
|
+
Given the app sends no X-Decision-Version header
|
|
286
|
+
And the app requests placement "poi"
|
|
287
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
288
|
+
Then an ad is served
|
|
289
|
+
And the ad carries no pins
|
|
290
|
+
|
|
291
|
+
@SP @sponsored-pin
|
|
292
|
+
Scenario: SP9 — a map's first render sends every shop's view beacon exactly once
|
|
293
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
294
|
+
And the app requests placement "poi"
|
|
295
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
296
|
+
Then an ad is served
|
|
297
|
+
And the ad carries at least 1 pin
|
|
298
|
+
And every pin carries its own view, tap and click beacons
|
|
299
|
+
When the app shows every pin
|
|
300
|
+
Then each shown pin's view beacon reaches the engine exactly once per response, and no pin the app never showed sends one
|
|
301
|
+
And the engine accepts every beacon
|
|
302
|
+
|
|
303
|
+
@SP @sponsored-pin
|
|
304
|
+
Scenario: SP10 — re-rendering the same response sends no view beacon again
|
|
305
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
306
|
+
And the app requests placement "poi"
|
|
307
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
308
|
+
Then an ad is served
|
|
309
|
+
And the ad carries at least 1 pin
|
|
310
|
+
And every pin carries its own view, tap and click beacons
|
|
311
|
+
When the app shows every pin
|
|
312
|
+
And the app shows every pin
|
|
313
|
+
And the app shows every pin
|
|
314
|
+
Then each shown pin's view beacon reaches the engine exactly once per response, and no pin the app never showed sends one
|
|
315
|
+
And the engine accepts every beacon
|
|
316
|
+
|
|
317
|
+
@SP @sponsored-pin
|
|
318
|
+
Scenario: SP11 — a render that brings new shops into view sends views only for the new ones
|
|
319
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
320
|
+
And the app requests placement "poi"
|
|
321
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
322
|
+
Then an ad is served
|
|
323
|
+
And the ad carries at least 4 pins
|
|
324
|
+
And every pin carries its own view, tap and click beacons
|
|
325
|
+
When the app shows the first 2 pins
|
|
326
|
+
And the app shows pins 2 to 4
|
|
327
|
+
And the app shows the first 4 pins
|
|
328
|
+
Then each shown pin's view beacon reaches the engine exactly once per response, and no pin the app never showed sends one
|
|
329
|
+
And the engine accepts every beacon
|
|
330
|
+
|
|
331
|
+
@SP @sponsored-pin
|
|
332
|
+
Scenario: SP12 — a new decision response reports the same shops again, once
|
|
333
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
334
|
+
And the app requests placement "poi"
|
|
335
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
336
|
+
Then an ad is served
|
|
337
|
+
And the ad carries at least 1 pin
|
|
338
|
+
And every pin carries its own view, tap and click beacons
|
|
339
|
+
When the app shows every pin
|
|
340
|
+
And the app fetches a new decision response for the same search
|
|
341
|
+
And the app shows every pin
|
|
342
|
+
And the app shows every pin
|
|
343
|
+
Then each shown pin's view beacon reaches the engine exactly once per response, and no pin the app never showed sends one
|
|
344
|
+
And a new response carries new view beacons for the same pins
|
|
345
|
+
And the engine accepts every beacon
|
|
346
|
+
|
|
347
|
+
@SP @sponsored-pin
|
|
348
|
+
Scenario: SP13 — single-pin views follow the same once-per-response rule as the bulk call
|
|
349
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
350
|
+
And the app requests placement "poi"
|
|
351
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
352
|
+
Then an ad is served
|
|
353
|
+
And the ad carries at least 1 pin
|
|
354
|
+
And every pin carries its own view, tap and click beacons
|
|
355
|
+
When the app shows pin 1 on its own
|
|
356
|
+
And the app shows pin 1 on its own
|
|
357
|
+
And the app shows every pin
|
|
358
|
+
And the app shows pin 1 on its own
|
|
359
|
+
Then each shown pin's view beacon reaches the engine exactly once per response, and no pin the app never showed sends one
|
|
360
|
+
And the engine accepts every beacon
|
|
361
|
+
|
|
362
|
+
@SP @sponsored-pin
|
|
363
|
+
Scenario: SP14 — taps and clicks are sent on every call, never de-duplicated
|
|
364
|
+
Given the app sends X-Decision-Version "2025-11-01"
|
|
365
|
+
And the app requests placement "poi"
|
|
366
|
+
And the app searches for pins within 20000 m of (-33.4175, -70.6065)
|
|
367
|
+
Then an ad is served
|
|
368
|
+
And the ad carries at least 1 pin
|
|
369
|
+
And every pin carries its own view, tap and click beacons
|
|
370
|
+
When the app shows every pin
|
|
371
|
+
And the user taps pin 1
|
|
372
|
+
And the user taps pin 1
|
|
373
|
+
And the user clicks pin 1
|
|
374
|
+
And the user clicks pin 1
|
|
375
|
+
And the app shows every pin
|
|
376
|
+
Then each shown pin's view beacon reaches the engine exactly once per response, and no pin the app never showed sends one
|
|
377
|
+
And every tap and every click sends its beacon, on every call
|
|
378
|
+
And the engine accepts every beacon
|
|
379
|
+
|
|
380
|
+
@F @fixture
|
|
381
|
+
Scenario: F1 — a Native Display creative exposes its standard format, version and template source
|
|
382
|
+
Given the app decodes the golden card.full response
|
|
383
|
+
Then the response decodes
|
|
384
|
+
And the creative's effectiveStandardFormatKey is "card"
|
|
385
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
386
|
+
And the creative's templateSource is "admoai_standard"
|
|
387
|
+
And the template key is "card"
|
|
388
|
+
And the creative's contents are exactly "image", "brand_logo", "headline", "supporting_text", "cta_text", "destination_url"
|
|
389
|
+
And the click trackers are exactly "default"
|
|
390
|
+
And the creative has no custom event tracker
|
|
391
|
+
|
|
392
|
+
@F @fixture
|
|
393
|
+
Scenario: F2 — a Native Display creative leaves every optional field it was served without absent
|
|
394
|
+
Given the app decodes the golden card.minimal response
|
|
395
|
+
Then the response decodes
|
|
396
|
+
And the creative's effectiveStandardFormatKey is "card"
|
|
397
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
398
|
+
And the creative's templateSource is "admoai_standard"
|
|
399
|
+
And the template key is "card"
|
|
400
|
+
And the creative's contents are exactly "image", "destination_url"
|
|
401
|
+
And "brand_logo", "headline", "supporting_text", "cta_text" decode as absent, never as an empty string or a placeholder
|
|
402
|
+
And the click trackers are exactly "default"
|
|
403
|
+
And the creative has no custom event tracker
|
|
404
|
+
|
|
405
|
+
@F @fixture
|
|
406
|
+
Scenario: F3 — a template based on a standard keeps the standard's identity and exposes the operator's addition by key
|
|
407
|
+
Given the app decodes the golden card.derived response
|
|
408
|
+
Then the response decodes
|
|
409
|
+
And the creative's effectiveStandardFormatKey is "card"
|
|
410
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
411
|
+
And the creative's templateSource is "based_on_standard"
|
|
412
|
+
And the template key is "city_promo_display"
|
|
413
|
+
And the creative's contents are exactly "image", "headline", "destination_url", "promo_code"
|
|
414
|
+
And the click trackers are exactly "default"
|
|
415
|
+
And the custom event trackers are exactly "save_offer"
|
|
416
|
+
|
|
417
|
+
@F @fixture
|
|
418
|
+
Scenario: F4 — a Carousel exposes its slides in server order, each with its id, position, contents and own trackers, and no creative-level click
|
|
419
|
+
Given the app decodes the golden carousel.full response
|
|
420
|
+
Then the response decodes
|
|
421
|
+
And the creative's effectiveStandardFormatKey is "carousel"
|
|
422
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
423
|
+
And the creative's templateSource is "admoai_standard"
|
|
424
|
+
And the template key is "carousel"
|
|
425
|
+
And the creative's contents are exactly "brand_logo", "disclaimer"
|
|
426
|
+
And the creative has no click tracker
|
|
427
|
+
And the creative has no custom event tracker
|
|
428
|
+
And there are 3 slides in server order
|
|
429
|
+
And slide 1 is "slide_01JDY02N8MA2K2HKQ74989YS99" with contents "image", "headline", "supporting_text", "cta_text", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
430
|
+
And slide 2 is "slide_01JD04H8GY801VF8Y0FHETP6XA" with contents "image", "headline", "supporting_text", "cta_text", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
431
|
+
And slide 3 is "slide_01JDRGMFSTZXQ7ES1QBCF0DR2X" with contents "image", "headline", "supporting_text", "cta_text", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
432
|
+
|
|
433
|
+
@F @fixture
|
|
434
|
+
Scenario: F5 — a minimal Carousel still exposes its slides and leaves the optional brand logo and disclaimer absent
|
|
435
|
+
Given the app decodes the golden carousel.minimal response
|
|
436
|
+
Then the response decodes
|
|
437
|
+
And the creative's effectiveStandardFormatKey is "carousel"
|
|
438
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
439
|
+
And the creative's templateSource is "admoai_standard"
|
|
440
|
+
And the template key is "carousel"
|
|
441
|
+
And the creative has no plain contents
|
|
442
|
+
And "brand_logo", "disclaimer" decode as absent, never as an empty string or a placeholder
|
|
443
|
+
And the creative has no click tracker
|
|
444
|
+
And the creative has no custom event tracker
|
|
445
|
+
And there are 2 slides in server order
|
|
446
|
+
And slide 1 is "slide_01JDZ725G8017BHW9RQJX7Y56S" with contents "image", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
447
|
+
And slide 2 is "slide_01JD4GNH6TH0XPP5YWCMDKN029" with contents "image", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
448
|
+
|
|
449
|
+
@F @fixture
|
|
450
|
+
Scenario: F6 — a Carousel based on a standard exposes its slides and the operator's addition
|
|
451
|
+
Given the app decodes the golden carousel.derived response
|
|
452
|
+
Then the response decodes
|
|
453
|
+
And the creative's effectiveStandardFormatKey is "carousel"
|
|
454
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
455
|
+
And the creative's templateSource is "based_on_standard"
|
|
456
|
+
And the template key is "city_carousel"
|
|
457
|
+
And the creative's contents are exactly "promo_code"
|
|
458
|
+
And the creative has no click tracker
|
|
459
|
+
And the creative has no custom event tracker
|
|
460
|
+
And there are 2 slides in server order
|
|
461
|
+
And slide 1 is "slide_01JDS1H8VEM0ABE2PQ0JY6S38W" with contents "image", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
462
|
+
And slide 2 is "slide_01JDDGXJE0JDBY21Y9KEJDD54A" with contents "image", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
463
|
+
|
|
464
|
+
@F @fixture
|
|
465
|
+
Scenario: F7 — a Tap-to-expand creative exposes expand and close as custom events, apart from its click
|
|
466
|
+
Given the app decodes the golden expandable_banner.full response
|
|
467
|
+
Then the response decodes
|
|
468
|
+
And the creative's effectiveStandardFormatKey is "expandable_banner"
|
|
469
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
470
|
+
And the creative's templateSource is "admoai_standard"
|
|
471
|
+
And the template key is "expandable_banner"
|
|
472
|
+
And the creative's contents are exactly "teaser_image", "teaser_headline", "expanded_image", "headline", "supporting_text", "cta_text", "destination_url", "disclaimer"
|
|
473
|
+
And the click trackers are exactly "default"
|
|
474
|
+
And the custom event trackers are exactly "expand", "close"
|
|
475
|
+
|
|
476
|
+
@F @fixture
|
|
477
|
+
Scenario: F8 — a minimal Tap-to-expand creative leaves its optional texts absent and keeps expand and close
|
|
478
|
+
Given the app decodes the golden expandable_banner.minimal response
|
|
479
|
+
Then the response decodes
|
|
480
|
+
And the creative's effectiveStandardFormatKey is "expandable_banner"
|
|
481
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
482
|
+
And the creative's templateSource is "admoai_standard"
|
|
483
|
+
And the template key is "expandable_banner"
|
|
484
|
+
And the creative's contents are exactly "teaser_image", "expanded_image", "destination_url"
|
|
485
|
+
And "teaser_headline", "headline", "supporting_text", "cta_text", "disclaimer" decode as absent, never as an empty string or a placeholder
|
|
486
|
+
And the click trackers are exactly "default"
|
|
487
|
+
And the custom event trackers are exactly "expand", "close"
|
|
488
|
+
|
|
489
|
+
@F @fixture
|
|
490
|
+
Scenario: F9 — a Branded Assets creative exposes asset tap as a custom event, apart from its click
|
|
491
|
+
Given the app decodes the golden branded_assets.full response
|
|
492
|
+
Then the response decodes
|
|
493
|
+
And the creative's effectiveStandardFormatKey is "branded_assets"
|
|
494
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
495
|
+
And the creative's templateSource is "admoai_standard"
|
|
496
|
+
And the template key is "branded_assets"
|
|
497
|
+
And the creative's contents are exactly "branded_image", "accessible_label", "destination_url"
|
|
498
|
+
And the click trackers are exactly "default"
|
|
499
|
+
And the custom event trackers are exactly "asset_tap"
|
|
500
|
+
|
|
501
|
+
@F @fixture
|
|
502
|
+
Scenario: F10 — a Branded Assets creative without a destination has no click, only the asset tap
|
|
503
|
+
Given the app decodes the golden branded_assets.minimal response
|
|
504
|
+
Then the response decodes
|
|
505
|
+
And the creative's effectiveStandardFormatKey is "branded_assets"
|
|
506
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
507
|
+
And the creative's templateSource is "admoai_standard"
|
|
508
|
+
And the template key is "branded_assets"
|
|
509
|
+
And the creative's contents are exactly "branded_image"
|
|
510
|
+
And "accessible_label", "destination_url" decode as absent, never as an empty string or a placeholder
|
|
511
|
+
And the creative has no click tracker
|
|
512
|
+
And the custom event trackers are exactly "asset_tap"
|
|
513
|
+
|
|
514
|
+
@F @fixture
|
|
515
|
+
Scenario: F11 — a Standard Video exposes its video events and click, and no skippable setting
|
|
516
|
+
Given the app decodes the golden standard_video.full response
|
|
517
|
+
Then the response decodes
|
|
518
|
+
And the creative's effectiveStandardFormatKey is "standard_video"
|
|
519
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
520
|
+
And the creative's templateSource is "admoai_standard"
|
|
521
|
+
And the template key is "standard_video"
|
|
522
|
+
And the creative's contents are exactly "video_asset", "poster_image", "brand_logo", "headline", "supporting_text", "cta_text", "destination_url", "disclaimer"
|
|
523
|
+
And the click trackers are exactly "default"
|
|
524
|
+
And the creative has no custom event tracker
|
|
525
|
+
And the video event trackers are exactly "start", "first_quartile", "midpoint", "third_quartile", "complete"
|
|
526
|
+
And the creative exposes no skippable setting
|
|
527
|
+
|
|
528
|
+
@F @fixture
|
|
529
|
+
Scenario: F12 — a Standard Video without a destination has no click action or click tracker
|
|
530
|
+
Given the app decodes the golden standard_video.minimal response
|
|
531
|
+
Then the response decodes
|
|
532
|
+
And the creative's effectiveStandardFormatKey is "standard_video"
|
|
533
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
534
|
+
And the creative's templateSource is "admoai_standard"
|
|
535
|
+
And the template key is "standard_video"
|
|
536
|
+
And the creative's contents are exactly "video_asset", "poster_image"
|
|
537
|
+
And "brand_logo", "headline", "supporting_text", "cta_text", "destination_url", "disclaimer" decode as absent, never as an empty string or a placeholder
|
|
538
|
+
And the creative has no click tracker
|
|
539
|
+
And the creative has no custom event tracker
|
|
540
|
+
And the video event trackers are exactly "start", "first_quartile", "midpoint", "third_quartile", "complete"
|
|
541
|
+
And the creative exposes no skippable setting
|
|
542
|
+
|
|
543
|
+
@F @fixture
|
|
544
|
+
Scenario: F13 — a Sponsored Pin creative keeps decoding its matched points as it does today
|
|
545
|
+
Given the app decodes the golden sponsored_pin_location.full response
|
|
546
|
+
Then the response decodes
|
|
547
|
+
And the creative's effectiveStandardFormatKey is "sponsored_pin_location"
|
|
548
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
549
|
+
And the creative's templateSource is "admoai_standard"
|
|
550
|
+
And the template key is "sponsored_pin_location"
|
|
551
|
+
And the creative's contents are exactly "pinImage", "image", "headline", "description", "ctaText", "defaultDestinationUrl"
|
|
552
|
+
And the click trackers are exactly "default"
|
|
553
|
+
And the creative has no custom event tracker
|
|
554
|
+
And the creative exposes 2 matched points
|
|
555
|
+
|
|
556
|
+
@F @fixture
|
|
557
|
+
Scenario: F14 — a creative served before the standards (gate off) decodes as today, with the three standard fields absent
|
|
558
|
+
The engine omits the three standard fields while is_standard_native_formats_enabled is off;
|
|
559
|
+
this is that wire. Absent means null, never an empty string or a placeholder (BR-SDK-01,
|
|
560
|
+
BR-SDK-09).
|
|
561
|
+
|
|
562
|
+
Given the app decodes the golden card.full response
|
|
563
|
+
And with the metadata field "effectiveStandardFormatKey" removed
|
|
564
|
+
And with the metadata field "effectiveStandardFormatVersion" removed
|
|
565
|
+
And with the metadata field "templateSource" removed
|
|
566
|
+
Then the response decodes
|
|
567
|
+
And the creative's effectiveStandardFormatKey is absent
|
|
568
|
+
And the creative's effectiveStandardFormatVersion is absent
|
|
569
|
+
And the creative's templateSource is absent
|
|
570
|
+
And the template key is "card"
|
|
571
|
+
And the creative's contents are exactly "image", "brand_logo", "headline", "supporting_text", "cta_text", "destination_url"
|
|
572
|
+
And the click trackers are exactly "default"
|
|
573
|
+
And the creative has no custom event tracker
|
|
574
|
+
|
|
575
|
+
@F @fixture
|
|
576
|
+
Scenario: F15 — fields the SDK does not know yet, at any level, never break decoding
|
|
577
|
+
An additive field the engine starts sending later must be ignored, not fail the response
|
|
578
|
+
(BR-SDK-01).
|
|
579
|
+
|
|
580
|
+
Given the app decodes the golden carousel.full response
|
|
581
|
+
And with an unknown response field "futureField" added
|
|
582
|
+
And with an unknown creative field "futureField" added
|
|
583
|
+
And with an unknown metadata field "futureField" added
|
|
584
|
+
And with an unknown content field "futureField" added on every content entry
|
|
585
|
+
And with an unknown slide field "futureField" added on every slide
|
|
586
|
+
Then the response decodes
|
|
587
|
+
And the creative's effectiveStandardFormatKey is "carousel"
|
|
588
|
+
And the creative's effectiveStandardFormatVersion is 1
|
|
589
|
+
And the creative's templateSource is "admoai_standard"
|
|
590
|
+
And the template key is "carousel"
|
|
591
|
+
And the creative's contents are exactly "brand_logo", "disclaimer"
|
|
592
|
+
And the creative has no click tracker
|
|
593
|
+
And the creative has no custom event tracker
|
|
594
|
+
And there are 3 slides in server order
|
|
595
|
+
And slide 1 is "slide_01JDY02N8MA2K2HKQ74989YS99" with contents "image", "headline", "supporting_text", "cta_text", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
596
|
+
And slide 2 is "slide_01JD04H8GY801VF8Y0FHETP6XA" with contents "image", "headline", "supporting_text", "cta_text", "destination_url", view trackers "slide_view" and click trackers "default"
|
|
597
|
+
And slide 3 is "slide_01JDRGMFSTZXQ7ES1QBCF0DR2X" with contents "image", "headline", "supporting_text", "cta_text", "destination_url", view trackers "slide_view" and click trackers "default"
|