@admoai/sdk-scenarios 1.2.0 → 1.4.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 CHANGED
@@ -4,6 +4,45 @@ Versioning: a scenario added or an `expect` tightened is a **minor** release; a
4
4
  removed, or a `request`/`expect` key renamed (every SDK's interpreter must change), is a
5
5
  **major** one. Each SDK vendors one version and its own CI checks the vendored bytes against it.
6
6
 
7
+ ## 1.4.0 — 2026-11-08
8
+
9
+ Adds live scenarios ST1-ST12 for the standard native formats (admoai/adhub#4733): what the
10
+ engine now serves, asserted against a real decision-engine, and moves `schemaVersion` to 4.
11
+ Five new live `expect.creative` keys, each with exactly the semantics of the fixture key of the
12
+ same name, so an interpreter can share one check between `fixtureScenarios` and `scenarios`:
13
+ - `metadata`: an object limited to `effectiveStandardFormatKey`, `effectiveStandardFormatVersion`,
14
+ `templateSource` and `assetContext`. Each listed key must equal the served value exactly; `null`
15
+ means the key is absent from the served metadata.
16
+ - `templateKey`: the served `template.key`.
17
+ - `contentKeys`: the served creative's plain content keys are exactly this set (order is not
18
+ asserted; slides and matched points are not plain contents).
19
+ - `styleIn`: a non-empty list; the served `template.style` is one of them.
20
+ - `journeyStageKey`: the served creative's `journey.stageKey` equals it; only valid with
21
+ `"journey": true`.
22
+ ST1-ST8 serve the min and full Ad of Card, Expandable Banner, Branded Assets and Standard Video;
23
+ ST9 is a Card whose creative cannot satisfy its contract and is not served (no ad); ST10 is the
24
+ legacy `home` placement, whose metadata names no standard and carries no asset context; ST11 is a
25
+ Journey whose first stage's screen cannot satisfy its standard, skipped, so the next stage's screen
26
+ answers; ST12 is a Journey screen serving the saved Card style. Carousel is not in this version:
27
+ its slides are not served yet. Live runs need the `sdk_st_*` placements of the mock seed (11: nine
28
+ Ad placements and the two Journey entry placements `sdk_st_journey_skip` and
29
+ `sdk_st_journey_styles`). A runner that does not know `schemaVersion` 4 must refuse the manifest;
30
+ adopting 1.4.0 requires executing the five keys. The 39 live scenarios and F1-F25 are unchanged.
31
+
32
+ ## 1.3.0 — 2026-11-05
33
+
34
+ Adds fixture scenarios F16–F25 with a `slideTracking` block (admoai/adhub#4733): what the app
35
+ dispatches when it reports slides and interactions on a decoded standard creative, offline.
36
+ A slide view goes out once per served response, keyed on its signed URL, and again for a new
37
+ response (F16, F17). A slide click before its view sends the view, then the click, then the
38
+ creative's applicable third-party click trackers once per click. A slide view never sends them
39
+ (F18-F20). A Carousel has no creative click (F21). `fireClick` with no key sends `default`
40
+ (F22). Expand, close and asset tap are verbatim custom events, not clicks (F23, F24). A
41
+ Standard Video without a destination sends no click (F25). New edit level `tracking` (the
42
+ creative's tracking object), used to inject third-party trackers on `.example` hosts. A
43
+ `schemaVersion` moves to 3, so a runner that knows only 2 refuses the manifest instead of
44
+ skipping `slideTracking` silently; adopting 1.3.0 requires executing it.
45
+
7
46
  ## 1.2.0 — 2026-11-05
8
47
 
9
48
  Adds `fixtureScenarios` F1–F15 and moves `schemaVersion` to 2 (admoai/adhub#4733): offline
package/README.md CHANGED
@@ -37,3 +37,21 @@ or a key renamed), add a `CHANGELOG.md` entry, and run `sdk/scenarios/check.sh`.
37
37
  into the package's `fixtures/golden-wire/`; that directory is never committed, and a change to a
38
38
  golden file needs a version bump too); each SDK then
39
39
  moves to it in its own PR. See `docs/guides/sdk-ci.md`.
40
+
41
+ ## Schema versions
42
+
43
+ A runner must refuse a `schemaVersion` it does not execute.
44
+
45
+ | `schemaVersion` | Adds | Package |
46
+ | --- | --- | --- |
47
+ | 1 | live `scenarios` | 1.0.0 |
48
+ | 2 | `fixtureScenarios` (offline, golden wire) | 1.2.0 |
49
+ | 3 | `slideTracking` on a fixture scenario | 1.3.0 |
50
+ | 4 | live `expect.creative` keys `metadata`, `templateKey`, `contentKeys`, `styleIn`, `journeyStageKey` (ST1-ST12) | 1.4.0 |
51
+
52
+ The v4 keys, same semantics as the fixture keys of the same name: `metadata` is limited to
53
+ `effectiveStandardFormatKey`, `effectiveStandardFormatVersion`, `templateSource` and `assetContext`
54
+ (exact value per key, `null` = absent); `templateKey` is the served `template.key`; `contentKeys`
55
+ is the exact set of plain content keys (order not asserted); `styleIn` is a non-empty list the
56
+ served `template.style` belongs to; `journeyStageKey` is the served `journey.stageKey` (only with
57
+ `"journey": true`). Live runs of ST1-ST12 need the `sdk_st_*` placements of adhub's mock seed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@admoai/sdk-scenarios",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "The cross-SDK decision & tracking scenarios every AdMoai SDK asserts against a real decision-engine. Data only.",
5
5
  "license": "MIT",
6
6
  "files": [
package/scenarios.feature CHANGED
@@ -6,7 +6,7 @@
6
6
  # promises a publisher's app, in the same words for all three. CI regenerates it and fails on
7
7
  # any difference, so it cannot drift from what the runners execute.
8
8
  # How it is published and kept fresh: docs/guides/sdk-ci.md, Section 7.
9
- # Manifest schemaVersion: 2
9
+ # Manifest schemaVersion: 4
10
10
 
11
11
  Feature: Cross-SDK decision & tracking contract
12
12
  Shared cross-SDK E2E scenario manifest. ONE definition, executed by a thin interpreter in each
@@ -22,7 +22,15 @@ Feature: Cross-SDK decision & tracking contract
22
22
  reads from the decoded creative. `contentKeys` is the exact set of plain content keys (slides
23
23
  and matched points are asserted through their own fields); a key in `contentKeysAbsent` must
24
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.
25
+ absent. A runner that does not execute fixtureScenarios must refuse schemaVersion 2. A fixture
26
+ scenario may add `slideTracking` (package 1.3.0, schemaVersion 3; a runner that does not
27
+ execute it must refuse schemaVersion 3): `steps` the app takes on the decoded creative
28
+ (`slideView`/`slideClick` with a 1-based slide index, `newResponse`, `fireClick` with a key or
29
+ null for no key, `custom` with a key) and the beacons that must be dispatched: `beacons` is
30
+ the exact ordered list of first-party beacons, each named by the tracker it must be
31
+ (`slide`+`view`/`click`, or creative `click`/`custom`, with `response`), and
32
+ `thirdPartyBeacons` the exact multiset of third-party tracker ids dispatched (their order is
33
+ not asserted). An edit `at` `tracking` targets the creative's tracking object.
26
34
 
27
35
  @L @native
28
36
  Scenario: L1 — a normal native ad serves with complete, well-formed metadata
@@ -377,6 +385,153 @@ Feature: Cross-SDK decision & tracking contract
377
385
  And every tap and every click sends its beacon, on every call
378
386
  And the engine accepts every beacon
379
387
 
388
+ @ST @standard-formats
389
+ Scenario: ST1 — a Card with only its required content serves under the Card standard
390
+ Given the app requests placement "sdk_st_card_min"
391
+ Then an ad is served
392
+ And exactly 1 decision is returned
393
+ And the creative is not a Journey Ad
394
+ And the creative's assetContext is absent
395
+ And the creative's effectiveStandardFormatKey is "card"
396
+ And the creative's effectiveStandardFormatVersion is 1
397
+ And the creative's templateSource is "admoai_standard"
398
+ And the template key is "card"
399
+ And the creative's contents are exactly "image", "destination_url"
400
+
401
+ @ST @standard-formats
402
+ Scenario: ST2 — a Card with every optional content serves them all, in a style the standard defines
403
+ Given the app requests placement "sdk_st_card_full"
404
+ Then an ad is served
405
+ And exactly 1 decision is returned
406
+ And the creative is not a Journey Ad
407
+ And the creative's assetContext is absent
408
+ And the creative's effectiveStandardFormatKey is "card"
409
+ And the creative's effectiveStandardFormatVersion is 1
410
+ And the creative's templateSource is "admoai_standard"
411
+ And the template key is "card"
412
+ And the creative's contents are exactly "image", "brand_logo", "headline", "supporting_text", "cta_text", "destination_url"
413
+ And the template style is one of "default", "tall"
414
+
415
+ @ST @standard-formats
416
+ Scenario: ST3 — an Expandable Banner with only its required content serves under the Expandable Banner standard
417
+ Given the app requests placement "sdk_st_expandable_banner_min"
418
+ Then an ad is served
419
+ And exactly 1 decision is returned
420
+ And the creative is not a Journey Ad
421
+ And the creative's assetContext is absent
422
+ And the creative's effectiveStandardFormatKey is "expandable_banner"
423
+ And the creative's effectiveStandardFormatVersion is 1
424
+ And the creative's templateSource is "admoai_standard"
425
+ And the template key is "expandable_banner"
426
+ And the creative's contents are exactly "teaser_image", "expanded_image", "destination_url"
427
+
428
+ @ST @standard-formats
429
+ Scenario: ST4 — an Expandable Banner with every optional content serves them all
430
+ Given the app requests placement "sdk_st_expandable_banner_full"
431
+ Then an ad is served
432
+ And exactly 1 decision is returned
433
+ And the creative is not a Journey Ad
434
+ And the creative's assetContext is absent
435
+ And the creative's effectiveStandardFormatKey is "expandable_banner"
436
+ And the creative's effectiveStandardFormatVersion is 1
437
+ And the creative's templateSource is "admoai_standard"
438
+ And the template key is "expandable_banner"
439
+ And the creative's contents are exactly "teaser_image", "teaser_headline", "expanded_image", "headline", "supporting_text", "cta_text", "destination_url", "disclaimer"
440
+
441
+ @ST @standard-formats
442
+ Scenario: ST5 — a Branded Asset with only its required content serves with its asset context
443
+ Given the app requests placement "sdk_st_branded_assets_min"
444
+ Then an ad is served
445
+ And exactly 1 decision is returned
446
+ And the creative is not a Journey Ad
447
+ And the creative's assetContext is "vehicle_marker"
448
+ And the creative's effectiveStandardFormatKey is "branded_assets"
449
+ And the creative's effectiveStandardFormatVersion is 1
450
+ And the creative's templateSource is "admoai_standard"
451
+ And the template key is "branded_assets"
452
+ And the creative's contents are exactly "branded_image"
453
+
454
+ @ST @standard-formats
455
+ Scenario: ST6 — a Branded Asset with every optional content serves them all, with its asset context
456
+ Given the app requests placement "sdk_st_branded_assets_full"
457
+ Then an ad is served
458
+ And exactly 1 decision is returned
459
+ And the creative is not a Journey Ad
460
+ And the creative's assetContext is "vehicle_marker"
461
+ And the creative's effectiveStandardFormatKey is "branded_assets"
462
+ And the creative's effectiveStandardFormatVersion is 1
463
+ And the creative's templateSource is "admoai_standard"
464
+ And the template key is "branded_assets"
465
+ And the creative's contents are exactly "branded_image", "accessible_label", "destination_url"
466
+
467
+ @ST @standard-formats
468
+ Scenario: ST7 — a Standard Video with only its required content serves under the Standard Video standard
469
+ Given the app requests placement "sdk_st_standard_video_min"
470
+ Then an ad is served
471
+ And exactly 1 decision is returned
472
+ And the creative is not a Journey Ad
473
+ And the creative's assetContext is absent
474
+ And the creative's effectiveStandardFormatKey is "standard_video"
475
+ And the creative's effectiveStandardFormatVersion is 1
476
+ And the creative's templateSource is "admoai_standard"
477
+ And the template key is "standard_video"
478
+ And the creative's contents are exactly "video_asset", "poster_image"
479
+
480
+ @ST @standard-formats
481
+ Scenario: ST8 — a Standard Video with every optional content serves them all
482
+ Given the app requests placement "sdk_st_standard_video_full"
483
+ Then an ad is served
484
+ And exactly 1 decision is returned
485
+ And the creative is not a Journey Ad
486
+ And the creative's assetContext is absent
487
+ And the creative's effectiveStandardFormatKey is "standard_video"
488
+ And the creative's effectiveStandardFormatVersion is 1
489
+ And the creative's templateSource is "admoai_standard"
490
+ And the template key is "standard_video"
491
+ And the creative's contents are exactly "video_asset", "poster_image", "brand_logo", "headline", "supporting_text", "cta_text", "destination_url", "disclaimer"
492
+
493
+ @ST @standard-formats
494
+ Scenario: ST9 — a standard Ad whose creative cannot satisfy its standard's contract is not served
495
+ Given the app requests placement "sdk_st_card_incomplete"
496
+ Then no ad is served, and the placement answers with a clean no-fill
497
+
498
+ @ST @standard-formats
499
+ Scenario: ST10 — a legacy creative names no standard and carries no asset context
500
+ Given the app requests placement "home"
501
+ Then an ad is served
502
+ And exactly 1 decision is returned
503
+ And the creative is not a Journey Ad
504
+ And the creative's assetContext is absent
505
+ And the creative's effectiveStandardFormatKey is absent
506
+ And the creative's effectiveStandardFormatVersion is absent
507
+ And the creative's templateSource is absent
508
+
509
+ @ST @standard-formats
510
+ Scenario: ST11 — a Journey screen that cannot satisfy its standard is skipped and the next stage's screen answers
511
+ Given the app requests placement "sdk_st_journey_skip"
512
+ And the request carries a fresh session id
513
+ And the user has opted in to Journey Ads
514
+ Then an ad is served
515
+ And exactly 1 decision is returned
516
+ And the creative is a Journey Ad
517
+ And the creative's assetContext is absent
518
+ And the creative's effectiveStandardFormatKey is "card"
519
+ And the creative's effectiveStandardFormatVersion is 1
520
+ And the creative's templateSource is "admoai_standard"
521
+ And the template key is "card"
522
+ And the Journey stage is "complete"
523
+
524
+ @ST @standard-formats
525
+ Scenario: ST12 — a Journey screen serves the Card style saved on it
526
+ Given the app requests placement "sdk_st_journey_styles"
527
+ And the request carries a fresh session id
528
+ And the user has opted in to Journey Ads
529
+ Then an ad is served
530
+ And exactly 1 decision is returned
531
+ And the creative is a Journey Ad
532
+ And the template key is "card"
533
+ And the template style is one of "default", "tall"
534
+
380
535
  @F @fixture
381
536
  Scenario: F1 — a Native Display creative exposes its standard format, version and template source
382
537
  Given the app decodes the golden card.full response
@@ -595,3 +750,141 @@ Feature: Cross-SDK decision & tracking contract
595
750
  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
751
  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
752
  And slide 3 is "slide_01JDRGMFSTZXQ7ES1QBCF0DR2X" with contents "image", "headline", "supporting_text", "cta_text", "destination_url", view trackers "slide_view" and click trackers "default"
753
+
754
+ @F @fixture
755
+ Scenario: F16 — a slide's view beacon is sent once per served response, however often the slide is shown
756
+ Keyed on the signed view URL, never on the slideId: re-rendering or scrolling back to a
757
+ slide must not count it again (BR-SDK-04, Q19).
758
+
759
+ Given the app decodes the golden carousel.full response
760
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
761
+ When the app reports slide 1 as viewed
762
+ And the app reports slide 1 as viewed
763
+ And the app reports slide 2 as viewed
764
+ And the app reports slide 1 as viewed
765
+ Then the response decodes
766
+ And exactly these beacons are sent, in this order:
767
+ And slide 1's "slide_view" view beacon
768
+ And slide 2's "slide_view" view beacon
769
+ And no third-party tracker is sent
770
+
771
+ @F @fixture
772
+ Scenario: F17 — the same slide in a new decision response counts again
773
+ A new response carries new signed URLs for the same slides. The runner simulates it by
774
+ decoding the same golden file again with every first-party tracker URL suffixed
775
+ `&response=N` (N = 2, 3, ...); `response` in an expected beacon names which one (default 1).
776
+
777
+ Given the app decodes the golden carousel.full response
778
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
779
+ When the app reports slide 1 as viewed
780
+ And the app decodes a new decision response for the same ad
781
+ And the app reports slide 1 as viewed
782
+ And the app reports slide 1 as viewed
783
+ Then the response decodes
784
+ And exactly these beacons are sent, in this order:
785
+ And slide 1's "slide_view" view beacon
786
+ And slide 1's "slide_view" view beacon from response 2
787
+ And no third-party tracker is sent
788
+
789
+ @F @fixture
790
+ Scenario: F18 — a slide clicked before its view was sent sends the view, then the click, then the creative's applicable third-party click trackers once
791
+ Applicable means a click tracker matching any click, or the `default` click specifically; a
792
+ tracker for another click key and an impression tracker do not fire (BR-SDK-05, Q14, Q18).
793
+
794
+ Given the app decodes the golden carousel.full response
795
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
796
+ When the user clicks slide 2
797
+ Then the response decodes
798
+ And exactly these beacons are sent, in this order:
799
+ And slide 2's "slide_view" view beacon
800
+ And slide 2's "default" click beacon
801
+ And the third-party trackers sent are exactly "tpt_fixture_click_any", "tpt_fixture_click_default"
802
+
803
+ @F @fixture
804
+ Scenario: F19 — a slide clicked after its view sends only the click, on every click
805
+ Given the app decodes the golden carousel.full response
806
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
807
+ When the app reports slide 2 as viewed
808
+ And the user clicks slide 2
809
+ And the user clicks slide 2
810
+ Then the response decodes
811
+ And exactly these beacons are sent, in this order:
812
+ And slide 2's "slide_view" view beacon
813
+ And slide 2's "default" click beacon
814
+ And slide 2's "default" click beacon
815
+ And the third-party trackers sent are exactly "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_any", "tpt_fixture_click_default"
816
+
817
+ @F @fixture
818
+ Scenario: F20 — slide views never send third-party click trackers
819
+ Given the app decodes the golden carousel.full response
820
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
821
+ When the app reports slide 1 as viewed
822
+ And the app reports slide 2 as viewed
823
+ And the app reports slide 3 as viewed
824
+ Then the response decodes
825
+ And exactly these beacons are sent, in this order:
826
+ And slide 1's "slide_view" view beacon
827
+ And slide 2's "slide_view" view beacon
828
+ And slide 3's "slide_view" view beacon
829
+ And no third-party tracker is sent
830
+
831
+ @F @fixture
832
+ Scenario: F21 — a Carousel has no creative-level click: fireClick sends nothing, so slide clicks go through the slide helper
833
+ Publishers must not route a slide tap through fireClick (BR-SDK-03, Q9).
834
+
835
+ Given the app decodes the golden carousel.full response
836
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
837
+ When the app calls fireClick with no key
838
+ Then the response decodes
839
+ And no first-party beacon is sent
840
+ And no third-party tracker is sent
841
+
842
+ @F @fixture
843
+ Scenario: F22 — fireClick with no key sends the default click of a standard
844
+ Every standard seeds its click with key `default`, which is what fireClick sends without a
845
+ key (BR-SDK-06, Q12).
846
+
847
+ Given the app decodes the golden card.full response
848
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
849
+ When the app calls fireClick with no key
850
+ Then the response decodes
851
+ And exactly these beacons are sent, in this order:
852
+ And the creative's "default" click beacon
853
+ And the third-party trackers sent are exactly "tpt_fixture_click_any", "tpt_fixture_click_default"
854
+
855
+ @F @fixture
856
+ Scenario: F23 — expand and close are sent verbatim as custom events and are not clicks
857
+ The publisher calls them when the banner actually expands or closes; no click is sent and no
858
+ third-party click tracker fires (BR-SDK-07).
859
+
860
+ Given the app decodes the golden expandable_banner.full response
861
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
862
+ When the app sends the custom event "expand"
863
+ And the app sends the custom event "close"
864
+ Then the response decodes
865
+ And exactly these beacons are sent, in this order:
866
+ And the creative's "expand" custom beacon
867
+ And the creative's "close" custom beacon
868
+ And no third-party tracker is sent
869
+
870
+ @F @fixture
871
+ Scenario: F24 — an asset tap is sent verbatim as a custom event and is not a click
872
+ BR-SDK-07.
873
+
874
+ Given the app decodes the golden branded_assets.full response
875
+ And with third-party trackers "tpt_fixture_click_any", "tpt_fixture_click_default", "tpt_fixture_click_other", "tpt_fixture_impression"
876
+ When the app sends the custom event "asset_tap"
877
+ Then the response decodes
878
+ And exactly these beacons are sent, in this order:
879
+ And the creative's "asset_tap" custom beacon
880
+ And no third-party tracker is sent
881
+
882
+ @F @fixture
883
+ Scenario: F25 — a Standard Video without a destination sends no click
884
+ No destination means no click action and no click tracker (BR-SDK-08).
885
+
886
+ Given the app decodes the golden standard_video.minimal response
887
+ When the app calls fireClick with no key
888
+ Then the response decodes
889
+ And no first-party beacon is sent
890
+ And no third-party tracker is sent