test(vast): [PKG-VAST-02] cover SDK failures and lifecycle races

This commit is contained in:
Harvey Zhao committed 2026-09-12 09:19:01 +08:00
1 parent 4f39d6f2b9
commit d5e1feadf5
16 files changed
+1402 -12

No files matched your search

+3 -2
View File
@@ -51,7 +51,7 @@
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
"typecheck": "node scripts/typecheck.mjs",
"test:unit": "node --test test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
"test:unit": "node --test test/vast.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
"test": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test",
@@ -64,7 +64,8 @@
"check:types": "node scripts/build-types.mjs --check",
"test:dash-types-package": "node refactor/scripts/dash-package-types.mjs",
"test:ads-types-package": "node refactor/scripts/ads-package-types.mjs",
"test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs"
"test:ads-native-visibility": "node refactor/scripts/ads-native-visibility.mjs",
"test:vast": "node --test test/vast.test.js"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -0,0 +1,139 @@
{
"schemaVersion": 1,
"task": "PKG-VAST-02",
"status": "done",
"sourceCommit": "4f39d6f2",
"checks": [
{
"command": "node --test test/vast.test.js",
"result": "pass",
"tests": 45,
"log": {
"file": "refactor/.cache/vast02-node-first.log",
"sha256": "b1c09921f58ce666c1821d27c62d1f47efedd909effa0b7d85a13a2cc7ad17f5"
}
},
{
"command": "node --test refactor/scripts/vast-core.test.mjs",
"result": "pass",
"tests": 1,
"archiveMembers": 197,
"log": {
"file": "refactor/.cache/vast02-core-final.log",
"sha256": "ca1f47472f291b1398486296a45c0a9a64fa28cf89a8a0d93d72c71fc4bebec7"
}
},
{
"command": "yarn test:browser vast.spec.js",
"result": "pass",
"tests": 90,
"engines": [
"chromium",
"firefox",
"webkit"
],
"cores": [
"5.1.7",
"5.4.1",
"candidate"
],
"sdk": "controlled wrapper recorder",
"stats": {
"startTime": "2026-09-12T01:14:19.958Z",
"duration": 46382.678,
"expected": 90,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"log": {
"file": "refactor/.cache/vast02-browser-final.log",
"sha256": "75fb8c623a8f4b8fc35a8cfc46b896acf689c5e5065482c5d7419f278cf59ae3"
},
"report": {
"file": "refactor/.cache/vast02-browser-final-report.json",
"sha256": "2cc94fa3417b9395363159187ea84cb56735ec6c500b6b1dca981d1b388a43c0"
},
"results": "refactor/.cache/vast02-browser-final-results"
},
{
"command": "yarn ci:check",
"result": "pass",
"tests": {
"unit": 817,
"engineering": 14,
"baseline": 54,
"total": 885
},
"log": {
"file": "refactor/.cache/vast02-ci.log",
"sha256": "7849c7f9a1f38fdc23185713fd8a048a8cafb596cc86ca4276a326c01b2db02d"
}
}
],
"limitations": [
"External SDK is a controlled recorder, not Google IMA",
"Production VAST sources/types/distribution unchanged",
"VPN exception has not been used",
"Historical defect observations are not candidate acceptance assertions"
],
"inputs": [
{
"file": "package.json",
"sha256LF": "1439c59c47faa2ea18ef1fbc041377766706f9bcd1a6fb342106d5abb65195fe"
},
{
"file": "test/helpers/vast-sdk.js",
"sha256LF": "877936c0255c4cff3bcd4688f69b1e4bfe58476f9e7ac6a2d87803af673233af"
},
{
"file": "test/helpers/vast.js",
"sha256LF": "ea985ca97946645a8b6fc76c9d69ec4fc12f959ab175402cd34e93bc81c14ce9"
},
{
"file": "test/vast.test.js",
"sha256LF": "b02c12c0440540f4f13929bbdcb3607621a42fdc6f29debbc912189c3aa025a6"
},
{
"file": "test/browser/vast.spec.js",
"sha256LF": "0f849e361faef9e49dbcaec3c0c692f94946cde98654cab329a1c4b1e056aaea"
},
{
"file": "test/browser/server.mjs",
"sha256LF": "af513e1e64f875668b6a7cbd060276d85d930e0cc1715577e97907cc26c3b58d"
},
{
"file": "refactor/scripts/vast-core.test.mjs",
"sha256LF": "74233f2ba5d87314cd3cb6ae8b3bdf8569faf538bd87ee9422e45f461a21cc4a"
},
{
"file": "refactor/baselines/vast-core.json",
"sha256LF": "bb703cf37591211f78719665aed0e0cb2524ec5618dad48e6b8e2dbfef132b8e"
},
{
"file": "refactor/baselines/vast-release.json",
"sha256LF": "493e15fa8d3cd1a9215a409dd78db61ecaef5f82f9629dfdc3f6090e7fad8453"
}
],
"previousRuns": [
{
"result": "90 passed before adding content source-switch assertions",
"log": {
"file": "refactor/.cache/vast02-browser-first.log",
"sha256": "6cd71f04c25e14a316918fedc5008b1802fc602493f2507529a9bcfb358200ed"
},
"report": {
"file": "refactor/.cache/vast02-browser-first-report.json",
"sha256": "e320f1f99cdc2fbfe7c04755fc86d85d2d7aef88116c3b5e5bb5f860ac76789b"
},
"results": "refactor/.cache/vast02-browser-first-results"
},
{
"result": "test runner import incorrectly autofixed to absent vitest; restored node:test without dependency changes",
"log": {
"file": "refactor/.cache/vast02-core.log",
"sha256": "531007deca9c236348ae77f988cff21f437171260e9de0688b89360d14427d90"
}
}
]
}
+439
View File
@@ -0,0 +1,439 @@
{
"schemaVersion": 1,
"task": "PKG-VAST-02",
"sourceCommit": "4f39d6f2b964d47437e164c15127b610bf693cfb",
"capturedAt": "2026-09-12T01:10:35.029Z",
"release": {
"name": "artplayer",
"version": "5.1.7",
"metadataUrl": "https://registry.npmjs.org/artplayer/5.1.7",
"tarball": "https://registry.npmjs.org/artplayer/-/artplayer-5.1.7.tgz",
"integrity": "sha512-N/QdNlTwsIHUsWOHMPj0pq97v3qmgXZHfhy1q7I+8YN3/XakJiJJyVJgGaeBjU+y/fUIrzBhwpBaX+NFwK9/qw==",
"sha256": "61dabf34f3317316d8b04fc9ffeb3d9c5c2b81042c98f0347cd69ec0e15c2525",
"files": {
"package/README.md": "2070b94087839a80dc01616a3114cbe2d3a4950018188f433752880daf7843f8",
"package/dist/artplayer.js": "484ccc3f22446ec6fd0dd8b8a5602c740b601d55097a1ae043262d92aab35357",
"package/dist/artplayer.legacy.js": "504e41f7ecf2e685c1bc03f2ae05536edb3cbd6b00de301b8c1178accb311e98",
"package/dist/i18n/cs.js": "0e353e313076ab5bc812c69d95c65dd4f41304be8cd3248b4fb1e66cc96361ef",
"package/dist/i18n/es.js": "e95e0c3d9e67d9aa9560a1d4ad34d7e524bb654573af63b583b0e230f397a918",
"package/dist/i18n/fa.js": "9e491847bacba5115af6c436461cce7b62b27913d73201a25f1ab33357fac820",
"package/dist/i18n/fr.js": "47f8c2741b8c65a71d02061b4d9659d5c9634c9b4670d16852d649fe5ca3674c",
"package/dist/i18n/id.js": "eb485192730c9a23aafb6b82b71cb3440e66790b9527fa735e1a23e132597170",
"package/dist/i18n/pl.js": "1ca2be239bda3d652431e8d4d8e41170d737b90193c151bbbfe9c1716d67c4f8",
"package/dist/i18n/ru.js": "ff7d1323f5fb21ead2834ae140da9e4a35cc2f80070f213623795b63daad79e6",
"package/dist/i18n/tr.js": "3981931b76ade9f8b8c6a862a18e702aed2644445b18a338bf2b58f9b16cde87",
"package/dist/i18n/zh-tw.js": "e6ed01c7cf38d47f36027ee916181aa5a243de4872d2fbcb2bca91668df84aa3",
"package/package.json": "8bd532103099c367fb2c289e0d410f144fdeb00b1b62cf850762a24b61e63c8a",
"package/src/config/index.js": "6419ff29300032a0c3535a396d7c977cf4ac09d9edc6b3a04eeec7256ea5f6d4",
"package/src/contextmenu/aspectRatio.js": "2e880ea29ed71d6e05192f10de246e0646754817f8135ac71bc4f082c7f60ec1",
"package/src/contextmenu/close.js": "4df0e913b1755409f034cbb54027b4518f05490ef66ad84b6f0e995899f41bc4",
"package/src/contextmenu/flip.js": "6f7574f7b5dfb0464e86e5910cc7b6f64ae06cbbef8d9a2a30086050a3952404",
"package/src/contextmenu/index.js": "ec1eda48492829362679290df860e2fd11d7006de265e3a8ab272923e0b49f8e",
"package/src/contextmenu/info.js": "b7286de4d650d115493ef98891b77ff0deddb7e6c5725d93c8874890304885c1",
"package/src/contextmenu/playbackRate.js": "2b470a632bba8c40ffc46c00374e80bd3ea2325cf0c9e5ddfad81a03fd149847",
"package/src/contextmenu/version.js": "d15f750e2af7c9de92ce85fd765541cd0deb00dbff6fa7544b90ba2e00354f40",
"package/src/control/airplay.js": "3f5a5c82edfd8e0104311d1bc6224a216e8823d977f4d3d2efd2230232f1096e",
"package/src/control/fullscreen.js": "fab9bd25ace5b85b643207cc781fc1ccf73bdc6abdeeb35d7863cfe21ce25da3",
"package/src/control/fullscreenWeb.js": "c4e8f2ed78556ed5a1fef61477d4fd4acd2bbf2226b0969604cabea3b8cf8152",
"package/src/control/index.js": "c8fc25552412e741fd7de438498552d34f73f766e9f11d1e5770b594974c166a",
"package/src/control/pip.js": "abf03561ccf8209e58b961c32d013a24e190b275f5c420f237eb5c9eea836de7",
"package/src/control/playAndPause.js": "e29d017f0c629a583b970d08ffe91169d43c573e2fa50e5467182a3e2ca06d34",
"package/src/control/progress.js": "8fe464a3cdb45323b8e080cf9e0d8b7ef9e3046e87deac011f14a9607945c8c9",
"package/src/control/screenshot.js": "66d1baddda0e7aeecfa13b345b95c388e9b030e56ed41b6ab5bd7fd65305fb8c",
"package/src/control/setting.js": "5aafdb3a26b0526d38c1de2a721d818a61ea06c1c3ef9fce54aa467cb3999c7e",
"package/src/control/time.js": "b29e4abdab8a52a120534d268b56ea502e7394a1d0a3ce0986b5f90a4251dded",
"package/src/control/volume.js": "4411722264c3f27adf65d2f3191e95ceb3b89734fc1afa436d5e40f4450a67a7",
"package/src/events/clickInit.js": "c71e5709da54b6dbd650744c3cfea43f74cbad39080da4db5c049452e4a8c595",
"package/src/events/documentInit.js": "3588e34c434555ac474c92519f1aba8d8f9b442b9cf32d15a12e136bcbca0ae2",
"package/src/events/gestureInit.js": "d11294980ffa4a5c68d9059d1fcf773c60ee4dcd3230ff1adb6de78b29d5da09",
"package/src/events/hoverInit.js": "e7ad02a7b3a2fa654b29d6016cc8b70950185a9fae422fa259b443046c9f0b5d",
"package/src/events/index.js": "9e89b16e3082c9d4c74accd8e58b8e5229bfd07a29e63018be1c3ce81f18ecda",
"package/src/events/moveInit.js": "396c374c161a62c7b74fc52ff8e8efe99016eac2b3ce9fa4f2476751b8f46d51",
"package/src/events/resizeInit.js": "c1cfb19f57bab631d5a6fe0691c4b1c26e1cc9d7f412069368aa2a467ecd9c1f",
"package/src/events/updateInit.js": "40cd0fd049e660ff58c83e20823409b9461077924a9a777406866c3aa345296e",
"package/src/events/viewInit.js": "b04b1356495e5edc19c435b3263665d8b0906a21329391a2e24051d1b5943868",
"package/src/hotkey.js": "ca9ecd041f2d2081fdc8f6bc2ba13c9eddc90afb1cdddef3bdb9907866efaa94",
"package/src/i18n/cs.js": "ebf7b10a0c1eab982935047019016e1e4e639c3184cf01805c651fa5473caf42",
"package/src/i18n/es.js": "90f2acedb4387b323ce57de3d693361caa130aa85f979f748ff3e5126b849b62",
"package/src/i18n/fa.js": "5b97bc2a038a4e75b56c2e5408412355c0eb54efae3033dcebce889442b3bb55",
"package/src/i18n/fr.js": "91901814ebcffc0c282e817fe7467c59406eb2d7c388fe5d6513078dd06db3a8",
"package/src/i18n/id.js": "7207ba2e88336d6342665ab8037e1ef1710c1ee09ac9d7c040237e4a3d5b8c5b",
"package/src/i18n/index.js": "5408964b30ab298ebfa8bdaa4c45a1bc5f517682a0032c15ac98fb11867b8855",
"package/src/i18n/pl.js": "1f0b0849c3de39faebcf5a14c068374344cfb9de5745f6e5abedc5d569aea529",
"package/src/i18n/ru.js": "44ec0ab1ba6794315069adf178ab9ac645922ba408aedfe09ef8b79daf70e776",
"package/src/i18n/tr.js": "f94e874dbe8e4a7aacbd4444a7040e9bf9ac140e442f33fed31e376ce8f52a41",
"package/src/i18n/zh-cn.js": "10707cf170c062e3450587d21cc12156cb88843a6766c61eedafed0d2cf34a12",
"package/src/i18n/zh-tw.js": "b7dedc5616f12345b17c8ec185853b08d0ef572fb7460df79fc0206f0c8b6d97",
"package/src/icons/airplay.svg": "095f71bbac1aabde07963c645cb2071f790873fb2730363224e004321a01242a",
"package/src/icons/arrow-left.svg": "ac8cb60bf1f9436b9fc27eef2c65787dd879f01fb7ed2070b74be8a4d3167e0e",
"package/src/icons/arrow-right.svg": "9e2aa8a84ee9879bb70fb4fbae8eeb2a020e9a02c7bde14dede819d33acdff3c",
"package/src/icons/aspect-ratio.svg": "a71df3064cba45f402698b4c11fe21798b9af9aa6c5d0d689262889ffa386ab1",
"package/src/icons/check.svg": "672230ecd40c42b5354aad702ba6b018a9b765655bc96a53ba003fee41d8413a",
"package/src/icons/close.svg": "bb9e23e5305d11ec3d8f75a50ecdf3150deab8614c7df2af588873f2d78f06c2",
"package/src/icons/config.svg": "adbe547dfde4b44399da319ccdfc29fca57bcdbec8c6af5e36a2eeb97ecc2041",
"package/src/icons/error.svg": "d85a72e27ceaa38221637405cb0a5be270a297da58f1b0428498f568fab7570a",
"package/src/icons/fast-forward.svg": "709887f67e76e76d17fec37025c7405d85e9be28466d49602c1aa18732c89cef",
"package/src/icons/flip.svg": "ea6d9328823bb8454af65250030f02584239fcf8fbc2fb937dd1a1bac7602c28",
"package/src/icons/fullscreen-off.svg": "612577a443da55433bef698f1f4a426d0223c70cf1bcec3b27221bff753ae621",
"package/src/icons/fullscreen-on.svg": "bbec64253a42ef5ea26f6289d58254a7e86af3c0d627b2eb2ae357ef47cd04db",
"package/src/icons/fullscreen-web-off.svg": "57fa0e8780baa7f27befdeeb9ec2e772a1185a469a5f7ce05ed760d93ac8c434",
"package/src/icons/fullscreen-web-on.svg": "c6283f3ef124ea47e27b725ef5757a0b586dd5371cbefcdf2180dfabfe6375a9",
"package/src/icons/index.js": "b0107676f57ad6d04e507976f5582e64d4dd71309ae395897f608f2591896320",
"package/src/icons/loading.svg": "ffcf5d814e6f3931a370bb4f0d2675381c2adb8f6d29dd1ee8f5df33d8175e75",
"package/src/icons/lock.svg": "1acb0566c08175200cdd47f0ce7f72ca1ae2761962cc3b134be4d44398cfcee2",
"package/src/icons/pause.svg": "9efe05ebf5610befddebcac51b3c9a491001189ff86204cb47928eb1617f6c7a",
"package/src/icons/pip.svg": "52d1e150d1364aed20917d83a2f7ab7589fb9df9655f3d3afaea3c4387800812",
"package/src/icons/play.svg": "c35562d20028d52ce329f35f90496a06d4dc5b6dc39df7ebcfb027f6f2393958",
"package/src/icons/playback-rate.svg": "cbc025c7780a5e5c31160182d70131202fa41a661d20ec2933e0bb1f488f1cd8",
"package/src/icons/screenshot.svg": "49f7d90198fb0bcbc29848f40b0a8c042b78c07ecef1cfc5184e2e84a61b4bd4",
"package/src/icons/setting.svg": "7f6454d1c7f037d5a7144a898abedc89ca339534166d620dfa45efcd4c74ee3c",
"package/src/icons/state.svg": "9d777729e9287c6dc7f8d0687a257ffd40bd817fa84f24eb98dfebb3256b6e35",
"package/src/icons/switch-off.svg": "0bf5803437823035b816fb8bec148c06b4e01f85ea987cedda87269da6aab412",
"package/src/icons/switch-on.svg": "1d87b42af5e7141f9defdc790567534288d84858d5068dd15093d5ef12f37f9c",
"package/src/icons/unlock.svg": "f341e80530c2b6b218495c7ed3b9e4f268d70d2c931c0bf76efed1add8bab745",
"package/src/icons/volume-close.svg": "b9dbeaf9043c8014cd29d0e4a21a82ce567bc6b85497c310cca5f68bceeb31b2",
"package/src/icons/volume.svg": "baebc4ede3013202a4b13202a8d961cb0e5e065ee03733816022b2541d13884f",
"package/src/index.js": "de9b770879f13219be7d4afe6d9fbd327f571c8d8d14f98171d76fca32052278",
"package/src/info.js": "7fc50f9549ab4cc91422dea5182fe6250cf8273501425cb371a5870393bc39c1",
"package/src/layer.js": "ed576b5a805684852de55b56e65bf295ef8422e51e58a7f0fb7ad9987639f262",
"package/src/libs/screenfull.js": "3ff8b0abec9b100b4c25476627f7bd505466ae77a0700c95347e9e6e05f45bc6",
"package/src/loading.js": "f47a310ae4acfbd45bf34786901377844a08b4acc40d169f99b1af7ab02e4988",
"package/src/mask.js": "c28ab44ab96255f9357d39e184a048aa12f0822073d38a82c63d59b6cd2a0936",
"package/src/notice.js": "ef17bef61d6ff1e67f5e722a100ab5f5f9e4779f1297b06e16b5a9a32168cd67",
"package/src/player/airplayMix.js": "83346ea049d0a17b6943926f67e6c3a1e7242bcd978933dd77cd16ae3dca39ef",
"package/src/player/aspectRatioMix.js": "9f262f3fb47d23dc784aa31ea3264980567dcecdbfed592f2a26251d9473bdac",
"package/src/player/attrMix.js": "ae4103f3ede68f9ae87a16fa2c1e2b3bd3b03ec6190eb0ea8ace0c4b61216a29",
"package/src/player/autoHeightMix.js": "3c24c9b2a8ccc95f0b6b682e506565c0616f1b96f3d98bd20d219616d2dc4234",
"package/src/player/autoSizeMix.js": "65a480384c0939fda230e01b0deea43df757174d44828259f43abc3f63ed65c0",
"package/src/player/cssVarMix.js": "ea05acb553183ae60d42829ccf96deef06fbdb400424071777bd39c6ed6e683c",
"package/src/player/currentTimeMix.js": "41e5a6ac03d0dcd6883d5b08e2c74e0421b3d68e819f2b9c1e7f27aaf26bd7e8",
"package/src/player/durationMix.js": "87922cfb4a55db593e1c0187bf1912cf5b7c9b3b0136341723692a60f95efe01",
"package/src/player/eventInit.js": "f5f7412e652e8b2709443e2e2833d949e3563c002794d78cf466200e27af7be1",
"package/src/player/flipMix.js": "416485570461e7ef3c0346b528bedd8846bd24473d51ce6e33ac7abf61a24dfd",
"package/src/player/fullscreenMix.js": "44607d23d05d45816dd51503678d70dd73a212ec01caf66af8f2be5ab94ea271",
"package/src/player/fullscreenWebMix.js": "168aa2b5ddf6b0305a565bacb73ade7c49942d7e8abcbe3198d4089e071d17b3",
"package/src/player/index.js": "810c2cee3075bc4b670037601cdf599eeb4776abbe745768fa034ae529e2746d",
"package/src/player/loadedMix.js": "cd984138d0bee78d8e822ae424a4f61adc5530327283e2db72b1942f6b464f22",
"package/src/player/miniMix.js": "37cba99a8a8b9ca666cf79f85d47096a4f475da376ada1adc9d5d39f905bf2fc",
"package/src/player/optionInit.js": "ca8978d55edd199a8ea6ccffdafa6d5ee0b1eebc818333b73d81574daadb0493",
"package/src/player/pauseMix.js": "f96b7d2f17a01144bb5299612a794b4e971d7e389444f3769a272fa164f82886",
"package/src/player/pipMix.js": "f6c1970557258374f1007b16eaea05a2ceaf4319ad1cc6c7c0e4971b080775a8",
"package/src/player/playMix.js": "1e37d4ff7d5a5508c9f656d391993ec28a5a4627aca7917d4601164f6fdc5d63",
"package/src/player/playbackRateMix.js": "51f862cfec9744b7414a75fd1cd2aaaaf41d240300a21d024320ca85ede71415",
"package/src/player/playedMix.js": "0d345bac5914a3403cc2b7f5c8d68ab61a4c53e9085602d8e28950dab7175936",
"package/src/player/playingMix.js": "2ba6c2c9e495bcabb91da43d0c8162742c80f9fe39ee60e15eb01e7c5761348f",
"package/src/player/posterMix.js": "b50ee061fa1ded74b77a2e0b0e7306d9f8f87854d8b72368e2e84f2a89568850",
"package/src/player/qualityMix.js": "3e43a8a6e0c0004e8f88a42457db61c20bb2184203675d19cd5761f936ae15a3",
"package/src/player/rectMix.js": "9f319e6d2cf20af90d9c5f8291099132c982e299034474e735ad5e8b1ddf7c6e",
"package/src/player/screenshotMix.js": "d39590d64e41888d819f4d9fec7897db0c2a135b2171f47a45b6bb5961f0d80a",
"package/src/player/seekMix.js": "2bb1f1923aad7a989b4cadb3fe79e02176adb123425a356a93541eb8cd0885d4",
"package/src/player/stateMix.js": "ff8618ab6424f9d34337ebf5294449d2eea5cf76307b654cfff1e53e71c3ae54",
"package/src/player/subtitleOffsetMix.js": "5dfc80bce7921ce305259fe0a6f92a79f6bb09c1c7ca00132e3595a25fcdfca7",
"package/src/player/switchMix.js": "34bcc9d0c55ccb4b859583e22c0516171c719fa95c2b9b0af3232ad2455d789e",
"package/src/player/themeMix.js": "4f5394da3fc6fae0b5996a4d0a9d875fd8f9a9def759e946fc6a64af595dc3de",
"package/src/player/thumbnailsMix.js": "751ef443d8d85cc51a8fe3f46233cb220db0233ec3d1643d443f84b96eeb9ca5",
"package/src/player/toggleMix.js": "9ec0e69ade2445e8e583eb94d226825efe2c109274eeed244c1e035e39b024a9",
"package/src/player/typeMix.js": "2a2284a7c1368c33054c8f8df473fa2cac71bb97dced04b3c61097854e05351e",
"package/src/player/urlMix.js": "f30f285ab3b071a47c3f82b6150f89664ab03fc8c403a8c953e197a7d457e5fd",
"package/src/player/volumeMix.js": "9847e9cdd994023db1ab1df7f71177ec52b9eea16105f9297cc0cfab0d5fe040",
"package/src/plugins/autoOrientation.js": "b334635916271821f15d5669c78bd54353c73f789e5f3881b3f42892c25e3887",
"package/src/plugins/autoPlayback.js": "9aaf05c22f7ca3c36fe4f8adcb1cc0374d4aceeef0e22d0f11b1c2cb99aebf35",
"package/src/plugins/fastForward.js": "77f2bf8f4d3b81328da0ca95bc74eb5f8b36ab70b0f0fa406d90ee5448ada697",
"package/src/plugins/index.js": "5a25986996ffa174cfd7678a072d2b703c4ab903b357c4635678703defdd03ef",
"package/src/plugins/lock.js": "e3aa7dfe8380cec6ff8f64fc6b97211dc3a2f6d0b6fd67d40cc6ecc176c8db0d",
"package/src/plugins/miniProgressBar.js": "8d22005b3a42820c7b6c53f0abaaed38f94a5fa12239e27f28efccd379c2c5eb",
"package/src/scheme/index.js": "42f14c584e0e663f8f0a111954fdb26e56ebabd4c5e45c455fa740f03d268b47",
"package/src/setting/aspectRatio.js": "b1cf508b7806776ac7e952cd1791da8dabce4177219f8c59e9fe4784be5aa1fa",
"package/src/setting/flip.js": "1ecf3b33579f8fb564763a83b2aaca1949fa582816f185b58684cc341aa39dee",
"package/src/setting/index.js": "259b5749eda09fbcb8ec3ffb300c2d7c25c87a5710fb58401b75745a0e771540",
"package/src/setting/playbackRate.js": "77826765484758cafab1d000baab857d856d38caca80d25cacfc52988d3a1259",
"package/src/setting/subtitleOffset.js": "7112aa437e203b4449eba9a95a5885fce9bb09c15c879bceb79536b3f152823b",
"package/src/storage.js": "416f090c331fa27ff49838e58967c77f334bff4bb000cdc116d2579f5ac56021",
"package/src/style/artplayer.less": "662cfc3f079e75e50c18199511d533f4e35f7eac21aaf8f1171cfc93dcca243d",
"package/src/style/aspectRatio.less": "5ab50ae065c4a21734f17a32f5838b940f97070083143dabe1070d45630a3aca",
"package/src/style/backdrop.less": "8530dcc3e390b7a1cd767463023aab2ca410478792dd5b4f00b24ae55967ed33",
"package/src/style/bottom.less": "447283e979e53e6c777dc8f513d0b42488ab98afb8f5f5bb8cb0d645df47a085",
"package/src/style/contextmenu.less": "3aca4d6f32e5fa7022b22bd2f7bc81d8b17224158a7de41ca9e6b8ed84837252",
"package/src/style/controls.less": "89d35e4154a82a99096df18bc01b1f7939a54b1823c12bd2c43e0ce67e91fa0c",
"package/src/style/danmuku.less": "e75aae6ff39b1d255d250486562e9db8324dada097552d62a0a85f2de7035caa",
"package/src/style/flip.less": "7b7be6602d1672c7d17a726051273c320eec83a925be509e2545df7a387123b7",
"package/src/style/fullscreen.less": "0ba5cddcd264a2750281a309916ab0f3d8b01131666e052a24bc0ec89e870e76",
"package/src/style/fullscreenWeb.less": "1e0bac3295e5269ac28888cdc9060f6720983792dead34bfad897e2d33f499bd",
"package/src/style/hideCursor.less": "fcf26ad3ca2ce28486594975a045304048bdd98996bc80ac74e36740fd7ef4e7",
"package/src/style/hint.less": "e29017145d831b23337b500b9ed5ff3706ce56f31dc4ac2720615c6ea3a9d97d",
"package/src/style/icon.less": "a75dcb23a859cb42fa50cadada36508ce752aed6ea03984a8f5947d8584d10b7",
"package/src/style/index.less": "1d9cecc59fe0d0b7f2d057c91998e3a2492ea52999369512ef355fc166781474",
"package/src/style/info.less": "ba577cbe605cd541d0171d4e0b2fe15333963da4bc040d466bfb347f9be79bcc",
"package/src/style/layers.less": "93791f5e52176a166835e1f4b3d24a07d305531f91647cc8aea75496ec72b8ab",
"package/src/style/loading.less": "7f211e95694e0d3aa64b6f8e25deb6f60c2c65303dff9df44b1acab6cbc30bcd",
"package/src/style/mask.less": "251ea4a6b4ca20de2baaa8b13b420a77180fb570ca1744a7bd757d646b5b3664",
"package/src/style/mini.less": "de25c47271e807fc311f33110e651ea0ee143e61b15ae92cdcbe4ea8d2e78683",
"package/src/style/mixins.less": "3bb2205bf2b2ff9c224010fc1a170f1e56614f6e710b222f2e8807747f772c04",
"package/src/style/mobile.less": "088a21666ebf5b631f2d15527c2a283890b25285d0d31ea5f60adfaf3689d936",
"package/src/style/notice.less": "98baf3481e7269bb8b4d399d561a15f6f5189e45f50225adf41aabb64d0e94d1",
"package/src/style/plugins.less": "97b4baef8a0493f152ae73ce1b988cab0d7eebbe4a6c119e944247d19bcfd2bc",
"package/src/style/poster.less": "939f33aa2a48806eb1da3bc4b49a8f9ab541ff2a53796260f475b2c7a98cba60",
"package/src/style/progress.less": "1440c2dee6edce1edf0dd41cb8efa03fd504d295e119af9c7735b1757db73a38",
"package/src/style/selector.less": "5320e35c670912a57534fdf4122aef566c9ca273e14b52af5d882c8cd4591f74",
"package/src/style/setting.less": "1951153c3fa2ae6541c0a046404db043a1287b80847a499c6386c49c3e929695",
"package/src/style/subtitle.less": "b26cdc6aa6e5a113a0197f08e895ce73f0557e54d0e4d1dc4cac03a2fd48b5c0",
"package/src/style/variables.less": "58f64242b0838815fc96083a698a44aea15ff876fd0a1154ba496171b4187e3c",
"package/src/style/video.less": "a2a7ca14711238c18acdfa7d094c5223f269618cbb884866a6cdc22af7579b8d",
"package/src/style/volume.less": "7593ef9f5e34713b30b5d690182c6510a0912dd659f3405f7f38242398fbdfcb",
"package/src/subtitle.js": "7847d732cbecd3fabb0683b69d22972d9c47b1b13a991fa6b46e2b95d3ba4a65",
"package/src/template.js": "6561ac281682a65f3423dffd232b56e14f10d88713743d6505e1fbe0cf5402a9",
"package/src/utils/compatibility.js": "079c4621b7856d2f34bfba27376048c47b538cfc3e07c9d019701bd05a8b62c8",
"package/src/utils/component.js": "1a96e1ce80fab7a2c88e0849bf8519edd2554e332a4d3461eb7de8d3c709d638",
"package/src/utils/dom.js": "6ec4e5bff93aa9a8203a8fafcc6fb0781f22f78bb38e85e230dd1d7c1c50077a",
"package/src/utils/emitter.js": "637c2b65c89a34ca3208335a892258540eefc49cb8f87f981c69496fe49efa89",
"package/src/utils/error.js": "3b395e07922fb3fcbb7de34e662ada1914b3499dac6f109e522c432bd705f3d1",
"package/src/utils/file.js": "c02139c54e569bf635f90d32b7ccb9d6cb8ba5ff126071e6c79b34a134b11cb2",
"package/src/utils/format.js": "318a9e844fdbb64168a1d076c45697e35b0f907103089bce94b414f132ab3e9c",
"package/src/utils/index.js": "585394c7bfda8e8548cfb92c73ae1e45cccc55c48d683ca9a48099debdf4b98e",
"package/src/utils/property.js": "48faa8d72daf7bea824146ecf9ccec0575fd32995d482cb94731d859b1f9a437",
"package/src/utils/subtitle.js": "4f5450fd6d4881d2499d2c4f54e8ddc39c5edb6d2bb2d6c6dd7555672bd28e5c",
"package/src/utils/time.js": "d471d1cbde1a108b908a083005a3af1e17a3df55b084cec1f1866368f1f3d07b",
"package/tsconfig.json": "bf1616499e177df22713d30f25dd255295c43d34310185403b2dfb1c4970c793",
"package/types/artplayer.d.ts": "dfdd29480f937d0c10d8bbb5903704f751dd80eb3ab8288ad11b821a8a354069",
"package/types/component.d.ts": "7a4ea13e3f6400918190d2f34992b7de96e6145b4ebdf2030976594d5673f50d",
"package/types/config.d.ts": "3e2e3294802653fbab62f05d5d656411c657641d8f7a3ce64295eef44b37ef62",
"package/types/cssVar.d.ts": "a83429be37936c98850975c1a3a6896bb8a113a270b8cbe3994967f1d41458a3",
"package/types/events.d.ts": "f5b052edb9ef5fee846fe7aea9393e80df964226f1d4de21e6dfc0164662d0ce",
"package/types/i18n.d.ts": "75a61026bbc08cf29498700d1ed73b115734d9284085ab1ea0769d3d1a3c3d8b",
"package/types/icons.d.ts": "a4d2fa1a85755cc40d0ac9d0dadb7bedae1872b3306ba434f0fd817c0b2ac3b6",
"package/types/option.d.ts": "12e16a7b94675b252688c6f53503b5bdbfb60cce4f77e4bc0b64c039ffb09409",
"package/types/player.d.ts": "3172e8635820c10e2ba9f5ac4b95d1b575ec7d09302dba4885a9c9f0ec63d552",
"package/types/quality.d.ts": "b07e6525dcadbbb6fcd7c5ccb5e24eca87d25b24b8a23b49891c032a479fe660",
"package/types/setting.d.ts": "fbf66bb3fd75e4d57052aafc41f752f8fca1c801ad502ff06ef11a6ab6b86200",
"package/types/subtitle.d.ts": "76d899e4f72aeafedfd8f063f9dcbe2ada9e367d0bb2dc6a01b9929d3ebbd58f",
"package/types/template.d.ts": "a6065449803fc0adca1b39a2588c01c4d82e4b2e3be73bb66f601d579aa2a583",
"package/types/utils.d.ts": "5cf2785edc48a462f28603b43f1176ee1f7bccc5be9d0824798cc9af32b5d45d"
},
"manifest": {
"license": "MIT",
"name": "artplayer",
"version": "5.1.7",
"main": "dist/artplayer.js",
"legacy": "dist/artplayer.legacy.js",
"types": "types/artplayer.d.ts",
"homepage": "https://artplayer.org",
"author": "Harvey Zack <laozhaochaguan@gmail.com>",
"browserslist": "last 1 Chrome version",
"description": "ArtPlayer.js is a modern and full featured HTML5 video player",
"repository": {
"type": "git",
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
},
"bugs": {
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
},
"keywords": [
"html5",
"video",
"player"
],
"dependencies": {
"option-validator": "^2.0.6"
}
}
},
"archiveMembers": [
"package/README.md",
"package/dist/artplayer.js",
"package/dist/artplayer.legacy.js",
"package/dist/i18n/cs.js",
"package/dist/i18n/es.js",
"package/dist/i18n/fa.js",
"package/dist/i18n/fr.js",
"package/dist/i18n/id.js",
"package/dist/i18n/pl.js",
"package/dist/i18n/ru.js",
"package/dist/i18n/tr.js",
"package/dist/i18n/zh-tw.js",
"package/package.json",
"package/src/config/index.js",
"package/src/contextmenu/aspectRatio.js",
"package/src/contextmenu/close.js",
"package/src/contextmenu/flip.js",
"package/src/contextmenu/index.js",
"package/src/contextmenu/info.js",
"package/src/contextmenu/playbackRate.js",
"package/src/contextmenu/version.js",
"package/src/control/airplay.js",
"package/src/control/fullscreen.js",
"package/src/control/fullscreenWeb.js",
"package/src/control/index.js",
"package/src/control/pip.js",
"package/src/control/playAndPause.js",
"package/src/control/progress.js",
"package/src/control/screenshot.js",
"package/src/control/setting.js",
"package/src/control/time.js",
"package/src/control/volume.js",
"package/src/events/clickInit.js",
"package/src/events/documentInit.js",
"package/src/events/gestureInit.js",
"package/src/events/hoverInit.js",
"package/src/events/index.js",
"package/src/events/moveInit.js",
"package/src/events/resizeInit.js",
"package/src/events/updateInit.js",
"package/src/events/viewInit.js",
"package/src/hotkey.js",
"package/src/i18n/cs.js",
"package/src/i18n/es.js",
"package/src/i18n/fa.js",
"package/src/i18n/fr.js",
"package/src/i18n/id.js",
"package/src/i18n/index.js",
"package/src/i18n/pl.js",
"package/src/i18n/ru.js",
"package/src/i18n/tr.js",
"package/src/i18n/zh-cn.js",
"package/src/i18n/zh-tw.js",
"package/src/icons/airplay.svg",
"package/src/icons/arrow-left.svg",
"package/src/icons/arrow-right.svg",
"package/src/icons/aspect-ratio.svg",
"package/src/icons/check.svg",
"package/src/icons/close.svg",
"package/src/icons/config.svg",
"package/src/icons/error.svg",
"package/src/icons/fast-forward.svg",
"package/src/icons/flip.svg",
"package/src/icons/fullscreen-off.svg",
"package/src/icons/fullscreen-on.svg",
"package/src/icons/fullscreen-web-off.svg",
"package/src/icons/fullscreen-web-on.svg",
"package/src/icons/index.js",
"package/src/icons/loading.svg",
"package/src/icons/lock.svg",
"package/src/icons/pause.svg",
"package/src/icons/pip.svg",
"package/src/icons/play.svg",
"package/src/icons/playback-rate.svg",
"package/src/icons/screenshot.svg",
"package/src/icons/setting.svg",
"package/src/icons/state.svg",
"package/src/icons/switch-off.svg",
"package/src/icons/switch-on.svg",
"package/src/icons/unlock.svg",
"package/src/icons/volume-close.svg",
"package/src/icons/volume.svg",
"package/src/index.js",
"package/src/info.js",
"package/src/layer.js",
"package/src/libs/screenfull.js",
"package/src/loading.js",
"package/src/mask.js",
"package/src/notice.js",
"package/src/player/airplayMix.js",
"package/src/player/aspectRatioMix.js",
"package/src/player/attrMix.js",
"package/src/player/autoHeightMix.js",
"package/src/player/autoSizeMix.js",
"package/src/player/cssVarMix.js",
"package/src/player/currentTimeMix.js",
"package/src/player/durationMix.js",
"package/src/player/eventInit.js",
"package/src/player/flipMix.js",
"package/src/player/fullscreenMix.js",
"package/src/player/fullscreenWebMix.js",
"package/src/player/index.js",
"package/src/player/loadedMix.js",
"package/src/player/miniMix.js",
"package/src/player/optionInit.js",
"package/src/player/pauseMix.js",
"package/src/player/pipMix.js",
"package/src/player/playMix.js",
"package/src/player/playbackRateMix.js",
"package/src/player/playedMix.js",
"package/src/player/playingMix.js",
"package/src/player/posterMix.js",
"package/src/player/qualityMix.js",
"package/src/player/rectMix.js",
"package/src/player/screenshotMix.js",
"package/src/player/seekMix.js",
"package/src/player/stateMix.js",
"package/src/player/subtitleOffsetMix.js",
"package/src/player/switchMix.js",
"package/src/player/themeMix.js",
"package/src/player/thumbnailsMix.js",
"package/src/player/toggleMix.js",
"package/src/player/typeMix.js",
"package/src/player/urlMix.js",
"package/src/player/volumeMix.js",
"package/src/plugins/autoOrientation.js",
"package/src/plugins/autoPlayback.js",
"package/src/plugins/fastForward.js",
"package/src/plugins/index.js",
"package/src/plugins/lock.js",
"package/src/plugins/miniProgressBar.js",
"package/src/scheme/index.js",
"package/src/setting/aspectRatio.js",
"package/src/setting/flip.js",
"package/src/setting/index.js",
"package/src/setting/playbackRate.js",
"package/src/setting/subtitleOffset.js",
"package/src/storage.js",
"package/src/style/artplayer.less",
"package/src/style/aspectRatio.less",
"package/src/style/backdrop.less",
"package/src/style/bottom.less",
"package/src/style/contextmenu.less",
"package/src/style/controls.less",
"package/src/style/danmuku.less",
"package/src/style/flip.less",
"package/src/style/fullscreen.less",
"package/src/style/fullscreenWeb.less",
"package/src/style/hideCursor.less",
"package/src/style/hint.less",
"package/src/style/icon.less",
"package/src/style/index.less",
"package/src/style/info.less",
"package/src/style/layers.less",
"package/src/style/loading.less",
"package/src/style/mask.less",
"package/src/style/mini.less",
"package/src/style/mixins.less",
"package/src/style/mobile.less",
"package/src/style/notice.less",
"package/src/style/plugins.less",
"package/src/style/poster.less",
"package/src/style/progress.less",
"package/src/style/selector.less",
"package/src/style/setting.less",
"package/src/style/subtitle.less",
"package/src/style/variables.less",
"package/src/style/video.less",
"package/src/style/volume.less",
"package/src/subtitle.js",
"package/src/template.js",
"package/src/utils/compatibility.js",
"package/src/utils/component.js",
"package/src/utils/dom.js",
"package/src/utils/emitter.js",
"package/src/utils/error.js",
"package/src/utils/file.js",
"package/src/utils/format.js",
"package/src/utils/index.js",
"package/src/utils/property.js",
"package/src/utils/subtitle.js",
"package/src/utils/time.js",
"package/tsconfig.json",
"package/types/artplayer.d.ts",
"package/types/component.d.ts",
"package/types/config.d.ts",
"package/types/cssVar.d.ts",
"package/types/events.d.ts",
"package/types/i18n.d.ts",
"package/types/icons.d.ts",
"package/types/option.d.ts",
"package/types/player.d.ts",
"package/types/quality.d.ts",
"package/types/setting.d.ts",
"package/types/subtitle.d.ts",
"package/types/template.d.ts",
"package/types/utils.d.ts"
]
}
@@ -0,0 +1,32 @@
# PKG-VAST-02 行为、失败与生命周期测试
起点4f39d6f2。本步建立回归基线,未修改VAST生产源码、声明、依赖或dist。
新增`yarn test:vast`并接入`test:unit`;没有新依赖或锁文件变更。
45项Node测试覆盖当前入口、实际发布1.0.0源码及冻结工作区1.2.0源码:29项正常
及特有行为、16项历史缺陷观察。精确替换SDK导入,保留原wrapper逻辑;晚到回调
通过保存旧SDK订阅函数复现,并非要求SDK在destroy后正常派发事件。
实际核心5.1.7已从npm下载并冻结SHA512/SHA256、manifest及197成员,新增持续验证。
浏览器server增加5.1.7映射,继续保存各核心实际执行字节的manifest。90项浏览器测试
覆盖5.1.7/5.4.1/候选核心、三个wrapper实现及Chromium/Firefox/WebKit;包含异步
注册、受控SDK拒绝后的真实主片播放、DOM显隐、显式清理/重建、主片切源以及历史
销毁后晚到初始化。实际Google IMA广告流程仍未验证,不因本次通过关闭SDK-07。
第一轮90项通过,随后补充主片切源后重建广告的断言并重新执行。新增Node文件的
格式问题已修正;一次eslint自动修复把新归档测试的node:test错误替换为vitest,
直接运行发现缺失模块后恢复Node runner并加精确lint说明,没有安装vitest。
最终执行结果和输入哈希见 [验证记录](../baselines/vast-behavior-validation.json)。
VAST-LIFE-01提升为已复现并保持open,具体10类问题及修复方向见
[测试维护](../vast-validation.md)。这里只观察/固定问题,03逐项修复并增加候选验收
断言;不把缺陷观察通过等同于生产修复。初始化和类型兼容冲突仍由03/04处理。
包内模块地图随03实际拆分更新,本步提供测试文件/命令/边界说明,避免描述尚不存在
的生产架构。版本升到2.0.0由REL-09统一处理;VPN例外尚未使用。
回退本提交只移除本步测试、浏览器core映射、脚本接入和文档台账,不改变生产行为。
任务完成独立本地提交PKG-VAST-02;没有push、tag、publish或merge。
最终:90项浏览器测试全通过,无跳过/重试;完整本地CI885项通过(817单元、14工程、
54基线),严格类型和37份核心声明无漂移。没有将受控广告SDK验收扩大为实际供应商服务。
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
状态:todo 133 / doing 5 / blocked 0 / done 79 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 132 / doing 5 / blocked 0 / done 80 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -268,7 +268,7 @@
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
| PKG-VAST-01 | artplayer-plugin-vast<br>核对包契约与历史用法 | BASE-05 | 异步工厂、callback context、playUrl/playRes/init 与 SDK 版本;实际 npm 1.0.0 与未发布 1.2.0、SDK 1.21.0/1.21.2 的60成员归档和发布关联核心5.1.7 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
| PKG-VAST-02 | artplayer-plugin-vast<br>建立特有行为与错误测试 | PKG-VAST-01, ENG-03, ENG-05 | SDK 失败、重复初始化、广告事件、内容恢复和销毁竞态;发布 id/$container/eager 与工作区 lazy/config 覆盖、callback拒绝、加载中销毁、同毫秒多实例与晚到事件 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo |
| PKG-VAST-02 | artplayer-plugin-vast<br>建立特有行为与错误测试 | PKG-VAST-01, ENG-03, ENG-05 | SDK 失败、重复初始化、广告事件、内容恢复和销毁竞态;发布 id/$container/eager 与工作区 lazy/config 覆盖、callback拒绝、加载中销毁、同毫秒多实例与晚到事件 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-VAST-03 | artplayer-plugin-vast<br>整理内部职责与资源 | PKG-VAST-02, CORE-10, CORE-13 | SDK loader、IMA adapter、广告状态和 DOM 清理分离;保持发布 callback 别名,明确初始化冲突;核心终止与显式 destroy 后重建区分 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
| PKG-VAST-04 | artplayer-plugin-vast<br>迁移自有源码和公开类型 | PKG-VAST-03, ENG-04, ENG-06, CORE-07 | IMA/context/Promise 真实类型,外部 SDK 动态对象限定边界;修复 @alugha/ima 声明依赖链,保留 export= 和 require.default 消费;同步错误声明的兼容决策单列 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-VAST-05 | artplayer-plugin-vast<br>验证新旧核心和组合 | PKG-VAST-04, CORE-22 | 官方测试广告或受控 SDK 环境,错误后主视频状态恢复;核验真实5.1.7/5.4.1/候选核心,VAST VPN外部脚本例外逐项记录,不将受控SDK当作实际IMA验收 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
@@ -490,3 +490,4 @@
- PKG-ADS-04: [记录](changes/2026-09-12-PKG-ADS-04-types.md) [记录](baselines/ads-types-validation.json)
- PKG-ADS-05: [记录](changes/2026-09-12-PKG-ADS-05-browser.md) [记录](baselines/ads-ui-visibility-validation.json)
- PKG-VAST-01: [记录](baselines/vast-contract.md) [记录](baselines/vast-release.json) [记录](changes/2026-09-12-PKG-VAST-01-contract.md) [记录](baselines/vast-contract-validation.json)
- PKG-VAST-02: [记录](changes/2026-09-12-PKG-VAST-02-tests.md) [记录](baselines/vast-behavior-validation.json) [记录](baselines/vast-core.json) [记录](vast-validation.md)
+10
View File
@@ -1,5 +1,15 @@
# 进度与证据
## 最新完成:VAST-02 行为与生命周期回归
45项Node测试通过,覆盖当前源码及两套冻结历史源码;实际核心5.1.7的197成员已归档。
三引擎/三核心90项真实浏览器测试通过,含异步注册、SDK失败后主片解码、DOM显隐、
切源后广告重建及历史销毁竞态。SDK为受控记录器,实际Google IMA广告验收仍待05。
完整本地CI885项通过(817单元、14工程、54基线)。十类历史资源/异常问题已复现,
由03修复;本步没有VAST生产源码/类型/依赖/产物修改。当前217项:80 done、5 doing、
132 todo。详见 [本步记录](changes/2026-09-12-PKG-VAST-02-tests.md) 和
[验证证据](baselines/vast-behavior-validation.json)。
## 最新完成:VAST-01 实际发布契约
固定实际发布1.0.0、未发布工作区1.2.0及SDK1.21.0/1.21.2的60个归档成员。
+1 -1
View File
@@ -149,5 +149,5 @@
| ADS-UI-01 | resolved / 已复现 | Ads 零等待跳过与初始状态不符,全屏图标/外部状态需核验 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
| VAST-CONTEXT-01 | open / 已复现 | Published VAST callback aliases and eager Player differ from the unpublished lazy workspace | PKG-VAST-02, PKG-VAST-03, PKG-VAST-04 |
| VAST-TYPE-01 | open / 已复现 | VAST declares an async factory as synchronous and imports an unavailable SDK type dependency | PKG-VAST-04, PKG-VAST-06 |
| VAST-LIFE-01 | open / 源码/产物事实 | VAST SDK and container lack core destruction ownership and pending work cancellation | PKG-VAST-02, PKG-VAST-03, PKG-VAST-05 |
| VAST-LIFE-01 | open / 已复现 | VAST SDK and container lack core destruction ownership and pending work cancellation | PKG-VAST-02, PKG-VAST-03, PKG-VAST-05 |
| VAST-DIST-01 | open / 已复现 | Published VAST CommonJS default namespace differs from the current callable distribution | PKG-VAST-04, PKG-VAST-06 |
+11 -4
View File
@@ -706,7 +706,9 @@
"refactor/baselines/vast-contract.md",
"refactor/baselines/vast-release.json",
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
"refactor/baselines/vast-contract-validation.json"
"refactor/baselines/vast-contract-validation.json",
"refactor/vast-validation.md",
"refactor/baselines/vast-behavior-validation.json"
],
"compatibleResolution": "@glomex/vast-ima-player loadImaSdk loads external IMA; preserve event/content/ad state and teardown, do not equate mocks with vendor service acceptance.",
"closureCriteria": "固定 SDK/外部资源版本和环境;成功/失败/切换/销毁有真实证据,设备和网络限制逐项记录。",
@@ -3205,7 +3207,7 @@
{
"id": "VAST-LIFE-01",
"title": "VAST SDK and container lack core destruction ownership and pending work cancellation",
"confirmation": "source-observed",
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-VAST-02",
@@ -3216,10 +3218,15 @@
"refactor/baselines/vast-contract.md",
"refactor/baselines/vast-release.json",
"refactor/changes/2026-09-12-PKG-VAST-01-contract.md",
"refactor/baselines/vast-contract-validation.json"
"refactor/baselines/vast-contract-validation.json",
"refactor/changes/2026-09-12-PKG-VAST-02-tests.md",
"refactor/baselines/vast-behavior-validation.json",
"refactor/baselines/vast-core.json",
"refactor/vast-validation.md"
],
"compatibleResolution": "Reproduce pending SDK/callback destruction, errors, duplicate IDs and stale ad events before adding compatible cleanup and state ownership.",
"closureCriteria": "Tests prove cleanup during load/callback rejection/core destruction and distinguish explicit plugin recreation from terminal core destruction; vendor media restoration is separately verified."
"closureCriteria": "Tests prove cleanup during load/callback rejection/core destruction and distinguish explicit plugin recreation from terminal core destruction; vendor media restoration is separately verified.",
"workspaceState": "45 Node tests include 16 frozen historical observations across 10 lifecycle/error categories. Real browser core destruction plus late SDK resolution leaves a newly allocated recorder in the detached player across 5.1.7/5.4.1/candidate. Google IMA allocation itself is not simulated as evidence; exact wrapper SDK boundary is controlled. All production fixes remain PKG-VAST-03 work."
},
{
"id": "VAST-DIST-01",
+18
View File
@@ -0,0 +1,18 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
// eslint-disable-next-line test/no-import-node-test -- Archive verification uses the repository Node baseline runner.
import test from 'node:test'
import { archiveFiles, ensureArchive, hash, readMember } from './releases.mjs'
test('VAST historical core 5.1.7 archive matches the release association and all frozen members', async () => {
const baseline = JSON.parse(fs.readFileSync(new URL('../baselines/vast-core.json', import.meta.url)))
const vast = JSON.parse(fs.readFileSync(new URL('../baselines/vast-release.json', import.meta.url)))
assert.equal(baseline.release.name, 'artplayer')
assert.equal(baseline.release.version, vast.historicalCore.version)
const archive = await ensureArchive(baseline.release)
assert.deepEqual(archiveFiles(archive), baseline.archiveMembers)
assert.deepEqual(baseline.archiveMembers, Object.keys(baseline.release.files).sort())
for (const [member, expected] of Object.entries(baseline.release.files))
assert.equal(hash(readMember(archive, member)), expected)
assert.deepEqual(JSON.parse(readMember(archive, 'package/package.json')), baseline.release.manifest)
})
+7 -2
View File
@@ -2669,11 +2669,16 @@
"ENG-03",
"ENG-05"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "SDK 失败、重复初始化、广告事件、内容恢复和销毁竞态;发布 id/$container/eager 与工作区 lazy/config 覆盖、callback拒绝、加载中销毁、同毫秒多实例与晚到事件",
"acceptance": "旧版本行为可重跑,成功/失败/切源/销毁有必要断言",
"evidence": []
"evidence": [
"changes/2026-09-12-PKG-VAST-02-tests.md",
"baselines/vast-behavior-validation.json",
"baselines/vast-core.json",
"vast-validation.md"
]
},
{
"id": "PKG-VAST-03",
+52
View File
@@ -0,0 +1,52 @@
# VAST 测试维护
当前完成 PKG-VAST-01/02;生产重构由03/04继续。来源见
[实际契约](baselines/vast-contract.md)、[VAST/SDK归档](baselines/vast-release.json)
和 [核心5.1.7归档](baselines/vast-core.json)。工作区1.2.0不是npm发布版本。
## 文件与执行
- `test/helpers/vast-sdk.js`:可在Node和浏览器使用的SDK记录器,控制加载、构造、
监听、请求、销毁失败和晚到事件;不实现IMA、广告请求或主片暂停恢复。
- `test/helpers/vast.js`:只在精确的`@glomex/vast-ima-player`导入边界替换SDK。
当前JS/TS入口可打包加载;两套历史源码必须来自哈希验证的npm归档/Git内容。
Node宿主仅提供事件和DOM所有权意图,不模拟布局或视频解码。
- `test/vast.test.js`:共享契约、工作区新增功能、独立的历史缺陷观察。
修复时为当前候选增加正确行为断言,保留历史缺陷复现,不能把旧缺陷移入共享契约。
- `test/browser/vast.spec.js`:三真实浏览器×三核心,测试异步注册、加载失败、真实
DOM显隐、显式清理重建、主片解码/切源和销毁后晚到初始化。SDK仍是同一受控记录器。
- `refactor/scripts/vast-core.test.mjs`:实际5.1.7 tarball、197成员和发布关联检查。
- `refactor/scripts/vast-contract*.mjs`:VAST及SDK归档、真实发布导出和冻结源码契约。
使用仓库固定Node24.21.0与Yarn1.22.22:`yarn test:vast`、`yarn test:baseline`、
`yarn test:browser vast.spec.js`。Node用例纳入`test:unit`,浏览器用例由常规Playwright
流程发现。8084浏览器运行串行执行;每次报告/完整results先归档再开启下一轮。
所有失败日志保留;不改超时、不加重试、不能把外部SDK受控替换说成供应商服务通过。
## 已复现问题与修复入口
VAST-LIFE-01由源码观察提升为受控运行复现,问题如下:
1. 核心destroy不清理SDK;真实浏览器中旧插件在核心销毁后仍能分配到已脱离文档的player。
2. SDK加载中销毁后仍执行callback并初始化。
3. callback拒绝后已分配SDK和容器保留。
4. callback等待中销毁,恢复后仍可请求广告。
5. SDK构造失败后容器不回滚。
6. 同毫秒多个实例产生重复ID。
7. 工作区活动广告destroy后状态未复位,新的play请求被永久忽略。
8. 工作区晚到SDK回调在destroy后抛错,重建后可污染替代容器与状态。
9. 工作区SDK destroy抛错阻止DOM及引用清理。
10. 工作区事件注册失败后留下不完整SDK,下一次init错误地认为它已就绪。
03应建立明确的生命周期/SDK/DOM所有权,并区分可重建的显式广告destroy与终止性的
核心destroy。04处理准确类型、SDK声明依赖和同步误声明;初始化冲突应以发布及
工作区消费者证据明确处置,Ads的专属类型批准不能扩展到VAST。
## 验证边界
这些测试不是实际Google IMA加载、广告加载/播放/跳过/内容恢复的验收,也没有检查
物理移动设备或最终npm tarball。SDK记录器对load错误、constructor错误、destroy错误
使用精确注入;Node固定Date.now用于同毫秒ID复现,未改变生产时钟。
只有实际VAST外部脚本持续因VPN无法加载时,可按用户授权记录并跳过该网络验证;
它不豁免类型、生命周期或其他包。到02为止未用该例外。03~06和SDK-07继续开放。
+6 -1
View File
@@ -64,6 +64,11 @@ const adsCoreMember = `package/${adsCore.manifest.main}`
const adsCoreBytes = readMember(await ensureArchive(adsCore), adsCoreMember)
assert.equal(hash(adsCoreBytes), adsCore.files[adsCoreMember], 'Historical Ads core differs from frozen release')
add('/published-4.5.5/artplayer.js', adsCoreBytes, { kind: 'npm-release', version: adsCore.version, integrity: adsCore.integrity, member: adsCoreMember })
const vastCore = JSON.parse(fs.readFileSync(path.join(workspace, 'refactor/baselines/vast-core.json'), 'utf8')).release
const vastCoreMember = `package/${vastCore.manifest.main}`
const vastCoreBytes = readMember(await ensureArchive(vastCore), vastCoreMember)
assert.equal(hash(vastCoreBytes), vastCore.files[vastCoreMember], 'Historical VAST core differs from frozen release')
add('/published-5.1.7/artplayer.js', vastCoreBytes, { kind: 'npm-release', version: vastCore.version, integrity: vastCore.integrity, member: vastCoreMember })
add('/test/audio-tone.m4a', fs.readFileSync(path.join(workspace, 'test/browser/media/audio-tone.m4a')), { kind: 'generated-media', file: 'test/browser/media/audio-tone.m4a' })
const mime = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json', '.css': 'text/css', '.mp4': 'video/mp4', '.m4a': 'audio/mp4', '.webm': 'video/webm', '.vtt': 'text/vtt', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg' }
@@ -128,7 +133,7 @@ const server = http.createServer((req, res) => {
if (url.pathname === '/test/player.html') {
const core = url.searchParams.get('core') || 'candidate'
const chapter = url.searchParams.get('chapter') || 'candidate'
assert(['candidate', 'published', 'published-4.5.5'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
assert(['candidate', 'published', 'published-4.5.5', 'published-5.1.7'].includes(core) && ['candidate', 'published'].includes(chapter), 'Invalid test combination')
const html = fs.readFileSync(path.join(workspace, 'test/browser/player.html'), 'utf8')
.replace('__CORE__', core)
.replace('__CHAPTER__', chapter)
+169
View File
@@ -0,0 +1,169 @@
import { hash } from '../../refactor/scripts/releases.mjs'
import { createVastSdk } from '../helpers/vast-sdk.js'
import { vastImplementations } from '../helpers/vast.js'
import { expect, test } from './fixtures.js'
const implementations = await vastImplementations()
async function setup(page, core, implementation, testInfo, deferLoad = false) {
await page.goto(`/test/player.html?core=${core}`)
await page.addScriptTag({ content: `window.createVastSdk = ${createVastSdk.toString()};` })
await page.evaluate((deferLoad) => {
window.vastSdk = window.createVastSdk({ deferLoad })
window.google = { ima: window.vastSdk.ima }
window.art = new window.Artplayer({ container: '.player', url: '/test/pattern.mp4', muted: true })
}, deferLoad)
await page.addScriptTag({ content: `(() => {
const module = { exports: {} };
const exports = module.exports;
const require = name => {
if (name !== '@glomex/vast-ima-player') throw new Error('Unexpected dependency: ' + name);
return window.vastSdk.sdk;
};
${implementation.code}
window.artplayerPluginVast = module.exports.default || module.exports;
})();` })
await testInfo.attach('vast-inputs', { contentType: 'application/json', body: JSON.stringify({ core, implementation: implementation.name, sha256: hash(implementation.code), sdk: 'controlled wrapper recorder, no IMA execution' }) })
}
test.afterEach(async ({ page }, testInfo) => {
const state = await page.evaluate(() => ({
destroyed: window.art?.isDestroy,
callbackCalls: window.vastCalls,
players: window.vastSdk?.state.players.map(player => ({ requests: player.requests, destroyCalls: player.destroyCalls, connected: player.args[2].isConnected, display: player.args[2].style.display })),
coreVersion: window.Artplayer?.version,
}))
await testInfo.attach('vast-state', { contentType: 'application/json', body: JSON.stringify(state) })
await page.evaluate(() => {
if (window.art && !window.art.isDestroy)
window.art.destroy()
// Historical wrappers leak; explicit fixture cleanup runs only after assertions/evidence.
for (const player of window.vastSdk?.state.players || []) player.destroy()
})
})
for (const core of ['published-5.1.7', 'published', 'candidate']) {
for (const implementation of implementations) {
const label = `${core} / ${implementation.name}`
test(`${label}: async SDK and callback gate real core registration and preserve requests`, async ({ page }, testInfo) => {
await setup(page, core, implementation, testInfo, true)
await page.evaluate(() => {
window.vastCalls = 0
window.vastDone = false
const gate = new Promise((resolve) => {
window.finishVastCallback = resolve
})
window.vastRegistration = window.art.plugins.add(window.artplayerPluginVast(async (context) => {
window.vastContext = context
window.vastCalls++
await gate
context.playUrl('/test/ad-tag.xml')
context.playRes('<VAST/>')
})).then(() => {
window.vastDone = true
})
})
expect(await page.evaluate(() => ({ calls: window.vastCalls, players: window.vastSdk.state.players.length, registered: Object.hasOwn(window.art.plugins, 'artplayerPluginVast') }))).toEqual({ calls: 0, players: 0, registered: false })
await page.evaluate(() => window.vastSdk.resolveLoad())
await expect.poll(() => page.evaluate(() => window.vastCalls)).toBe(1)
expect(await page.evaluate(() => window.vastDone)).toBe(false)
expect(await page.evaluate(() => Object.hasOwn(window.art.plugins, 'artplayerPluginVast'))).toBe(false)
await page.evaluate(() => window.finishVastCallback())
await page.evaluate(() => window.vastRegistration)
const result = await page.evaluate(() => ({
done: window.vastDone,
name: window.art.plugins.artplayerPluginVast.name,
requests: window.vastSdk.state.players[0].requests,
owned: window.vastSdk.state.players[0].args[2].parentNode === window.art.template.$player,
video: window.vastSdk.state.players[0].args[1] === window.art.template.$video,
}))
expect(result).toEqual({ done: true, name: 'artplayerPluginVast', requests: [{ adTagUrl: '/test/ad-tag.xml' }, { adsResponse: '<VAST/>' }], owned: true, video: true })
})
test(`${label}: SDK rejection stays observable and core can still play content`, async ({ page }, testInfo) => {
await setup(page, core, implementation, testInfo, true)
const result = await page.evaluate(async () => {
const failure = new Error('controlled SDK failure')
let calls = 0
const registration = window.art.plugins.add(window.artplayerPluginVast(() => calls++))
const observed = registration.catch(error => error === failure)
window.vastSdk.rejectLoad(failure)
return { sameFailure: await observed, calls, players: window.vastSdk.state.players.length, registered: Object.hasOwn(window.art.plugins, 'artplayerPluginVast') }
})
expect(result).toEqual({ sameFailure: true, calls: 0, players: 0, registered: false })
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await page.evaluate(() => {
document.querySelector('#play').onclick = () => window.art.play()
})
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.2)
expect(await page.evaluate(() => window.art.template.$video.videoWidth)).toBeGreaterThan(0)
})
if (!implementation.published) {
test(`${label}: real overlay follows SDK events and explicit cleanup supports inactive recreation`, async ({ page }, testInfo) => {
await setup(page, core, implementation, testInfo)
await page.evaluate(async () => {
await window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastContext = context
}))
window.vastContext.playUrl('/first.xml')
})
const container = page.locator('[id^="art-vast-"]')
await expect(container).toHaveCount(1)
await expect(container).toBeHidden()
await page.evaluate(() => window.vastContext.imaPlayer.emit('AdStarted'))
await expect(container).toBeVisible()
await page.evaluate(() => {
window.vastContext.playUrl('/suppressed.xml')
window.vastContext.imaPlayer.emit('AdError', { code: 301 })
})
await expect(container).toBeHidden()
expect(await page.evaluate(() => window.vastContext.imaPlayer.requests.length)).toBe(1)
await page.evaluate(() => {
window.vastContext.playRes('<VAST/>')
window.vastContext.imaPlayer.emit('AdContentPauseRequested')
})
await expect(container).toBeVisible()
await page.evaluate(() => window.vastContext.imaPlayer.emit('AdContentResumeRequested'))
await expect(container).toBeHidden()
await page.evaluate(() => {
window.art.plugins.artplayerPluginVast.destroy()
window.art.plugins.artplayerPluginVast.destroy()
})
await expect(container).toHaveCount(0)
expect(await page.evaluate(() => window.vastSdk.state.players[0].destroyCalls)).toBe(1)
await page.evaluate(() => window.art.switchUrl('/test/pattern.mp4?source=next'))
await expect.poll(() => page.evaluate(() => window.art.template.$video.currentSrc)).toContain('?source=next')
await expect.poll(() => page.evaluate(() => window.art.template.$video.videoWidth)).toBeGreaterThan(0)
await page.evaluate(() => window.vastContext.playUrl('/new-session.xml'))
await expect(container).toHaveCount(1)
expect(await page.evaluate(() => window.vastSdk.state.players[1].requests[0].adTagUrl)).toBe('/new-session.xml')
expect(await page.evaluate(() => window.vastSdk.state.players[1].args[1] === window.art.template.$video)).toBe(true)
})
}
if (implementation.historical) {
test(`${label} historical: late SDK after destroy allocates into the detached player`, async ({ page }, testInfo) => {
await setup(page, core, implementation, testInfo, true)
await page.evaluate(() => {
window.vastCalls = 0
window.vastRegistration = window.art.plugins.add(window.artplayerPluginVast((context) => {
window.vastCalls++
context.playUrl('/late.xml')
}))
window.art.destroy()
window.vastSdk.resolveLoad()
})
await page.evaluate(() => window.vastRegistration)
expect(await page.evaluate(() => ({
calls: window.vastCalls,
players: window.vastSdk.state.players.length,
destroys: window.vastSdk.state.players[0].destroyCalls,
connected: window.vastSdk.state.players[0].args[2].isConnected,
url: window.vastSdk.state.players[0].requests[0].adTagUrl,
}))).toEqual({ calls: 1, players: 1, destroys: 0, connected: false, url: '/late.xml' })
})
}
}
}
+60
View File
@@ -0,0 +1,60 @@
// Controlled wrapper boundary, not an implementation of Google IMA or media playback.
// Self-contained so the same recorder can run in Node and a real browser realm.
export function createVastSdk(options = {}) {
const state = { players: [], loads: 0, errors: [], ...options }
let resolveLoad
let rejectLoad
const pending = new Promise((resolve, reject) => {
resolveLoad = resolve
rejectLoad = reject
})
const ima = { AdsRenderingSettings: class {}, AdsRequest: class {} }
const sdk = {
loadImaSdk() {
state.loads++
return state.deferLoad ? pending : Promise.resolve(ima)
},
PlayerOptions: class {},
Player: class {
constructor(...args) {
if (state.constructError)
throw state.constructError
this.args = args
this.requests = []
this.listeners = new Map()
this.destroyCalls = 0
state.players.push(this)
}
addEventListener(name, callback) {
if (state.listenError)
throw state.listenError
if (!this.listeners.has(name))
this.listeners.set(name, new Set())
this.listeners.get(name).add(callback)
}
removeEventListener(name, callback) {
this.listeners.get(name)?.delete(callback)
}
emit(name, detail) {
for (const callback of [...(this.listeners.get(name) || [])]) callback({ type: name, detail })
}
playAds(request) {
if (state.playError)
throw state.playError
this.requests.push(request)
}
destroy() {
this.destroyCalls++
if (state.destroyError)
throw state.destroyError
this.listeners.clear()
}
},
}
return { state, sdk, ima, resolveLoad: () => resolveLoad(ima), rejectLoad }
}
+107
View File
@@ -0,0 +1,107 @@
import assert from 'node:assert/strict'
import vm from 'node:vm'
import { build, transform } from 'esbuild'
import { readMember } from '../../refactor/scripts/releases.mjs'
import { verifyVastContract } from '../../refactor/scripts/vast-contract.mjs'
import { resolveSource } from './load.js'
import { createVastSdk } from './vast-sdk.js'
export async function vastImplementations() {
const contract = await verifyVastContract()
const archive = contract.archives.get('artplayer-plugin-vast@1.0.0')
const { outputFiles } = await build({ entryPoints: [resolveSource('packages/artplayer-plugin-vast/src/index')], bundle: true, write: false, format: 'cjs', platform: 'browser', external: ['@glomex/vast-ima-player'] })
const frozen = async source => (await transform(source, { loader: 'js', format: 'cjs', target: 'es2020' })).code
return [
{ name: 'source', code: outputFiles[0].text },
{ name: 'published-1.0.0-source', historical: true, published: true, code: await frozen(readMember(archive, 'package/src/index.js').toString()) },
{ name: 'frozen-workspace-1.2.0', historical: true, code: await frozen(contract.sources.get('packages/artplayer-plugin-vast/src/index.js')) },
]
}
export function vastEnvironment(implementation, options = {}) {
const fixture = createVastSdk(options)
const module = { exports: {} }
class FixedDate extends Date {
static now() { return 1000 }
}
vm.runInNewContext(implementation.code, {
module,
exports: module.exports,
window: { google: { ima: fixture.ima } },
Date: FixedDate,
console: { error: (...args) => fixture.state.errors.push(args) },
require(name) {
assert.equal(name, '@glomex/vast-ima-player', 'Only the external SDK may be substituted')
return fixture.sdk
},
}, { timeout: 5000 })
const factory = module.exports.default || module.exports
function host() {
const listeners = new Map()
const calls = []
const node = () => ({ style: {}, parentNode: null })
const parent = {
children: [],
appendChild(child) {
if (child.parentNode)
child.parentNode.removeChild(child)
child.parentNode = this
this.children.push(child)
return child
},
removeChild(child) {
const index = this.children.indexOf(child)
assert(index !== -1, 'removeChild requires an owned node')
this.children.splice(index, 1)
child.parentNode = null
return child
},
}
const art = {
template: { $player: parent, $video: {} },
constructor: { utils: { createElement: node, setStyles: (target, styles) => Object.assign(target.style, styles), append: (target, child) => target.appendChild(child) } },
isDestroy: false,
on(name, callback) {
if (!listeners.has(name))
listeners.set(name, new Set())
listeners.get(name).add(callback)
return art
},
off(name, callback) {
listeners.get(name)?.delete(callback)
return art
},
once(name, callback) {
const once = (...args) => {
art.off(name, once)
callback(...args)
}
return art.on(name, once)
},
emit(name, ...args) {
calls.push([name, ...args])
for (const callback of [...(listeners.get(name) || [])]) callback(...args)
return art
},
destroy() {
if (art.isDestroy)
return
art.isDestroy = true
art.emit('destroy')
listeners.clear()
},
}
return { art, parent, calls, listeners }
}
return { ...fixture, factory, host }
}
export function deferred() {
let resolve
let reject
const promise = new Promise((yes, no) => {
resolve = yes
reject = no
})
return { promise, resolve, reject }
}
+345
View File
@@ -0,0 +1,345 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- The external SDK boundary is explicitly controlled.
import test from 'node:test'
import { deferred, vastEnvironment, vastImplementations } from './helpers/vast.js'
const implementations = await vastImplementations()
for (const implementation of implementations) {
const { name, published } = implementation
test(`${name}: SDK resolution precedes callback and result waits for callback completion`, async () => {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
const started = deferred()
const finish = deferred()
let calls = 0
let settled = false
const attaching = env.factory(async (context) => {
calls++
started.resolve(context)
await finish.promise
})(host.art)
attaching.then(() => {
settled = true
})
assert.equal(env.state.loads, 1)
assert.equal(calls, 0)
assert.equal(host.parent.children.length, 0)
env.resolveLoad()
const context = await started.promise
assert.equal(context.art, host.art)
assert.equal(context.ima, env.ima)
assert.equal(settled, false)
finish.resolve()
assert.equal((await attaching).name, 'artplayerPluginVast')
assert.equal(calls, 1)
})
test(`${name}: SDK rejection propagates unchanged and does not invoke callback or allocate DOM`, async () => {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
let calls = 0
const attaching = env.factory(() => calls++)(host.art)
const failure = new Error('SDK unavailable')
const rejected = assert.rejects(attaching, error => error === failure)
env.rejectLoad(failure)
await rejected
assert.equal(calls, 0)
assert.equal(env.state.players.length, 0)
assert.equal(host.parent.children.length, 0)
})
test(`${name}: absent callback remains accepted at runtime`, async () => {
const env = vastEnvironment(implementation)
const result = await env.factory()(env.host().art)
assert.equal(result.name, 'artplayerPluginVast')
})
test(`${name}: playUrl and playRes preserve request content and return void`, async () => {
const env = vastEnvironment(implementation)
let context
await env.factory((value) => {
context = value
})(env.host().art)
assert.equal(context.playUrl('https://example.test/ad.xml'), undefined)
assert.equal(context.playRes('<VAST version="3.0"/>'), undefined)
const [player] = env.state.players
assert.equal(player.requests[0].adTagUrl, 'https://example.test/ad.xml')
assert.equal(player.requests[1].adsResponse, '<VAST version="3.0"/>')
assert.equal(player.requests[0] instanceof env.ima.AdsRequest, true)
assert.notEqual(player.requests[0], player.requests[1])
})
test(`${name}: one attach factory creates independent contexts, players and requests`, async () => {
const env = vastEnvironment(implementation)
const contexts = []
const attach = env.factory(context => contexts.push(context))
const one = env.host()
const two = env.host()
await Promise.all([attach(one.art), attach(two.art)])
contexts[0].playUrl('/first.xml')
contexts[1].playRes('<VAST/>')
assert.notEqual(contexts[0], contexts[1])
assert.equal(env.state.players.length, 2)
assert.equal(env.state.players[0].args[1], one.art.template.$video)
assert.equal(env.state.players[1].args[1], two.art.template.$video)
assert.equal(env.state.players[0].requests.length, 1)
assert.equal(env.state.players[1].requests.length, 1)
})
test(`${name}: playAds exceptions remain synchronous and are not converted to silent success`, async () => {
const failure = new Error('request failure')
const env = vastEnvironment(implementation, { playError: failure })
let context
await env.factory((value) => {
context = value
})(env.host().art)
assert.throws(() => context.playUrl('/ad.xml'), error => error === failure)
env.state.playError = null
context.playUrl('/retry.xml')
assert.equal(env.state.players[0].requests[0].adTagUrl, '/retry.xml')
})
test(`${name}: callback throws and rejected promises propagate their original value`, async () => {
for (const asynchronous of [false, true]) {
const env = vastEnvironment(implementation)
const failure = new Error('callback failure')
const attaching = env.factory(() => {
if (asynchronous)
return Promise.reject(failure)
throw failure
})(env.host().art)
await assert.rejects(attaching, error => error === failure)
}
})
if (published)
continue
test(`${name}: lazy init is idempotent and settings identities reach the Player constructor`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
let context
await env.factory((value) => {
context = value
})(host.art)
assert.equal(context.imaPlayer, null)
assert.equal(context.container, null)
context.playerOptions.custom = 'caller option'
context.adsRenderingSettings.enablePreloading = false
const player = context.init()
assert.equal(context.init(), player)
assert.equal(context.imaPlayer, player)
assert.equal(context.container, player.args[2])
assert.equal(player.args[3], context.adsRenderingSettings)
assert.equal(player.args[4], context.playerOptions)
assert.equal(player.args[3].enablePreloading, false)
assert.equal(host.parent.children.length, 1)
})
test(`${name}: request config is read live with own and inherited fields and primary overrides`, async () => {
const env = vastEnvironment(implementation)
let context
await env.factory((value) => {
context = value
})(env.host().art)
const config = Object.assign(Object.create({ inherited: 'legacy field' }), { adTagUrl: '/override.xml', adsResponse: 'override response' })
context.playUrl('/ignored.xml', config)
config.adTagUrl = '/changed.xml'
context.playRes('ignored response', config)
const [player] = env.state.players
assert.equal(player.requests[0].adTagUrl, '/override.xml')
assert.equal(player.requests[1].adTagUrl, '/changed.xml')
assert.equal(player.requests[1].adsResponse, 'override response')
assert.equal(player.requests[0].inherited, 'legacy field')
})
test(`${name}: four SDK events update visibility and duplicate ad requests are suppressed only while active`, async () => {
const env = vastEnvironment(implementation)
let context
await env.factory((value) => {
context = value
})(env.host().art)
context.playUrl('/first.xml')
const player = context.imaPlayer
assert.equal(context.container.style.display, 'none')
player.emit('AdContentPauseRequested')
assert.equal(context.container.style.display, 'block')
context.playUrl('/suppressed.xml')
context.playRes('suppressed')
assert.equal(player.requests.length, 1)
player.emit('AdContentResumeRequested')
assert.equal(context.container.style.display, 'none')
context.playUrl('/second.xml')
player.emit('AdStarted')
assert.equal(context.container.style.display, 'block')
const detail = { errorCode: 301 }
player.emit('AdError', detail)
assert.equal(context.container.style.display, 'none')
assert.equal(env.state.errors[0][1], detail)
context.playUrl('/after-error.xml')
assert.equal(player.requests.length, 3)
})
test(`${name}: explicit destroy is repeatable and permits a new inactive ad session`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
let context
const result = await env.factory((value) => {
context = value
})(host.art)
const first = context.init()
assert.equal(result.destroy(), undefined)
result.destroy()
assert.equal(first.destroyCalls, 1)
assert.equal(context.imaPlayer, null)
assert.equal(context.container, null)
assert.equal(host.parent.children.length, 0)
context.playUrl('/next.xml')
assert.equal(env.state.players.length, 2)
assert.equal(context.imaPlayer.requests[0].adTagUrl, '/next.xml')
})
}
// Historical observations, never candidate acceptance requirements.
for (const implementation of implementations.filter(item => item.historical)) {
const { name, published } = implementation
const initialize = context => context.init?.() || context.imaPlayer
test(`${name} historical: core destroy leaves the SDK allocated`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
await env.factory(context => initialize(context))(host.art)
const [player] = env.state.players
host.art.destroy()
assert.equal(player.destroyCalls, 0)
assert.equal(host.parent.children.length, 1)
})
test(`${name} historical: SDK resolution after core destruction still invokes callback and allocates`, async () => {
const env = vastEnvironment(implementation, { deferLoad: true })
const host = env.host()
let calls = 0
const attaching = env.factory((context) => {
calls++
initialize(context)
})(host.art)
host.art.destroy()
env.resolveLoad()
await attaching
assert.equal(calls, 1)
assert.equal(env.state.players.length, 1)
assert.equal(env.state.players[0].destroyCalls, 0)
})
test(`${name} historical: rejected callback leaks its already allocated SDK/container`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
const failure = new Error('after allocation')
await assert.rejects(env.factory((context) => {
initialize(context)
return Promise.reject(failure)
})(host.art), error => error === failure)
assert.equal(env.state.players[0].destroyCalls, 0)
assert.equal(host.parent.children.length, 1)
})
test(`${name} historical: callback resumed after core destroy creates or reuses stale resources`, async () => {
const env = vastEnvironment(implementation)
const host = env.host()
const started = deferred()
const finish = deferred()
const attaching = env.factory(async (context) => {
started.resolve()
await finish.promise
context.playUrl('/late.xml')
})(host.art)
await started.promise
host.art.destroy()
finish.resolve()
await attaching
assert.equal(env.state.players[0].requests[0].adTagUrl, '/late.xml')
assert.equal(env.state.players[0].destroyCalls, 0)
})
test(`${name} historical: SDK constructor failure leaves an attached container`, async () => {
const failure = new Error('constructor failure')
const env = vastEnvironment(implementation, { constructError: failure })
const host = env.host()
await assert.rejects(env.factory(context => initialize(context))(host.art), error => error === failure)
assert.equal(host.parent.children.length, 1)
assert.equal(env.state.players.length, 0)
})
test(`${name} historical: simultaneous instances generate duplicate container IDs`, async () => {
const env = vastEnvironment(implementation)
const one = env.host()
const two = env.host()
const attach = env.factory(context => initialize(context))
await Promise.all([attach(one.art), attach(two.art)])
assert.equal(one.parent.children[0].id, two.parent.children[0].id)
assert.notEqual(one.parent.children[0], two.parent.children[0])
})
if (published)
continue
test(`${name} historical: active-ad destroy keeps the request suppression flag stuck`, async () => {
const env = vastEnvironment(implementation)
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
context.playUrl('/first.xml')
context.imaPlayer.emit('AdStarted')
result.destroy()
context.playUrl('/second.xml')
assert.equal(context.imaPlayer, null)
assert.equal(env.state.players.length, 1)
})
test(`${name} historical: stale SDK callbacks throw after explicit destroy and corrupt a replacement`, async () => {
const env = vastEnvironment(implementation)
let context
const result = await env.factory((value) => {
context = value
})(env.host().art)
const first = context.init()
const lateStart = [...first.listeners.get('AdStarted')][0]
result.destroy()
assert.throws(() => lateStart({}), /null/)
const replacement = context.init()
lateStart({})
assert.equal(context.container.style.display, 'block')
context.playUrl('/blocked-by-stale-event.xml')
assert.equal(replacement.requests.length, 0)
})
test(`${name} historical: SDK destroy exception prevents DOM and reference cleanup`, async () => {
const failure = new Error('destroy failure')
const env = vastEnvironment(implementation, { destroyError: failure })
const host = env.host()
let context
const result = await env.factory((value) => {
context = value
})(host.art)
const player = context.init()
assert.throws(() => result.destroy(), error => error === failure)
assert.equal(context.imaPlayer, player)
assert.equal(host.parent.children.length, 1)
})
test(`${name} historical: event registration failure leaves a partial SDK that init treats as ready`, async () => {
const failure = new Error('listener failure')
const env = vastEnvironment(implementation, { listenError: failure })
const host = env.host()
let context
await env.factory((value) => {
context = value
})(host.art)
assert.throws(() => context.init(), error => error === failure)
env.state.listenError = null
const player = context.init()
assert.equal(player.listeners.size, 0)
assert.equal(host.parent.children.length, 1)
})
}