From 09459955c6be594660c2be1636de1a64413b4045 Mon Sep 17 00:00:00 2001 From: Harvey Zhao Date: Mon, 14 Sep 2026 23:57:20 +0800 Subject: [PATCH] test(vast): [PKG-VAST-05] checkpoint native tag requests and skip controls --- .../artplayer-plugin-vast/ARCHITECTURE.md | 5 + playwright.vast-native.config.js | 2 +- refactor/baselines/vast-skip-validation.json | 1595 +++++++++++++++++ .../2026-09-14-PKG-VAST-05-skip-checkpoint.md | 56 + refactor/plan.md | 2 +- refactor/progress.md | 9 + refactor/risks.json | 12 +- refactor/tasks.json | 4 +- refactor/vast-validation.md | 8 + test/browser/server.mjs | 9 +- test/browser/vast-fixture.mjs | 10 + test/browser/vast-skip.native.js | 78 + test/browser/vast.native.js | 8 +- 13 files changed, 1789 insertions(+), 9 deletions(-) create mode 100644 refactor/baselines/vast-skip-validation.json create mode 100644 refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md create mode 100644 test/browser/vast-fixture.mjs create mode 100644 test/browser/vast-skip.native.js diff --git a/packages/artplayer-plugin-vast/ARCHITECTURE.md b/packages/artplayer-plugin-vast/ARCHITECTURE.md index 263705022..73ab420f4 100644 --- a/packages/artplayer-plugin-vast/ARCHITECTURE.md +++ b/packages/artplayer-plugin-vast/ARCHITECTURE.md @@ -128,4 +128,9 @@ availability is an explicit environment gate. No SDK boundary is substituted. Reports include bundle/media hashes, XML, SDK/browser versions, event order, decoded ad frames and resumed content state. See the validation record for actual results; adding this command does not establish device or final distribution acceptance. +The native skip case requests a local HTTP VAST tag via `playUrl`, verifies its +response and clicks IMA's own enabled skip button after the skippable event. Its +countdown already has an accessible skip name; visibility alone is insufficient. +The local XML server permits only IMA origins on that fixture route. Native SDK +timeouts remain failures even when a late ad subsequently plays or skips correctly. Physical device validation remains05 and full distribution06. diff --git a/playwright.vast-native.config.js b/playwright.vast-native.config.js index 874558ac8..af862677a 100644 --- a/playwright.vast-native.config.js +++ b/playwright.vast-native.config.js @@ -5,7 +5,7 @@ import base from './playwright.config.js' assert(!process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Native VAST source validation does not accept unrelated artifact maps') export default defineConfig(base, { - testMatch: ['**/vast.native.js', '**/vast-recovery.native.js'], + testMatch: ['**/vast.native.js', '**/vast-recovery.native.js', '**/vast-skip.native.js'], timeout: 30000, workers: 1, outputDir: 'refactor/.cache/vast-native/results', diff --git a/refactor/baselines/vast-skip-validation.json b/refactor/baselines/vast-skip-validation.json new file mode 100644 index 000000000..76ec2a20c --- /dev/null +++ b/refactor/baselines/vast-skip-validation.json @@ -0,0 +1,1595 @@ +{ + "task": "PKG-VAST-05", + "status": "doing", + "date": "2026-09-14T15:54:25.981Z", + "sourceCommit": "75da50a454eeb41b67858526d02ce0f590b92d8b", + "productionChanged": false, + "vpnExceptionUsed": false, + "reports": [ + { + "file": "refactor/.cache/vast05-skip-first-report/report.json", + "sha256": "592bfee1c87fdc1a7f3ee43482ffc02ddcf6f89e6d194e3dc740208e38ab2a84", + "stats": { + "startTime": "2026-09-14T15:42:25.921Z", + "duration": 66004.132, + "expected": 0, + "skipped": 0, + "unexpected": 2, + "flaky": 0 + }, + "cases": [ + { + "title": "real IMA npm-default requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "timedOut", + "retry": 0, + "state": { + "events": [ + { + "type": "AdError", + "at": 9042.300000011921, + "time": 5.034082, + "paused": false, + "error": "There was a problem requesting ads from the server." + } + ], + "imaVersion": "3.789.0", + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "time": 26.012737, + "width": 640, + "paused": false + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + } + ] + }, + "errors": [ + "Test timeout of 30000ms exceeded.", + "Error: page.waitForResponse: Test timeout of 30000ms exceeded.\n\n 29 | })\n 30 | const beforeAd = await page.evaluate(() => window.art.currentTime)\n> 31 | const responsePromise = page.waitForResponse(response => response.url().endsWith('/test/vast-skippable.xml'))\n | ^\n 32 | await page.locator('#play').click()\n 33 | const response = await responsePromise\n 34 | expect(response.status()).toBe(200)\n at D:\\github\\ArtPlayer\\test\\browser\\vast-skip.native.js:31:34" + ], + "consoleErrors": [ + "Access to fetch at 'http://127.0.0.1:8084/test/vast-skippable.xml' from origin 'http://imasdk.googleapis.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.", + "Access to fetch at 'http://127.0.0.1:8084/test/vast-skippable.xml' from origin 'http://imasdk.googleapis.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource." + ], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "04ae498d312511cb6738e1cd4ccc5070a4525845f0a266fc1d726eae5a4c4808", + "bytes": 760 + } + }, + { + "title": "real IMA workspace-1.2 requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "timedOut", + "retry": 0, + "state": { + "events": [ + { + "type": "AdError", + "at": 7622.399999976158, + "time": 5.964329, + "paused": false, + "error": "No adsManagerLoadedEvent within 5000ms." + }, + { + "type": "AdError", + "at": 7642, + "time": 5.982995, + "paused": false, + "error": "There was a problem requesting ads from the server." + } + ], + "imaVersion": "3.789.0", + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "time": 28.353584, + "width": 640, + "paused": false + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + } + ] + }, + "errors": [ + "Test timeout of 30000ms exceeded.", + "Error: page.waitForResponse: Test timeout of 30000ms exceeded.\n\n 29 | })\n 30 | const beforeAd = await page.evaluate(() => window.art.currentTime)\n> 31 | const responsePromise = page.waitForResponse(response => response.url().endsWith('/test/vast-skippable.xml'))\n | ^\n 32 | await page.locator('#play').click()\n 33 | const response = await responsePromise\n 34 | expect(response.status()).toBe(200)\n at D:\\github\\ArtPlayer\\test\\browser\\vast-skip.native.js:31:34" + ], + "consoleErrors": [ + "VAST Ad Error: {error: i2: No adsManagerLoadedEvent within 5000ms.\n at :221:18}", + "Access to fetch at 'http://127.0.0.1:8084/test/vast-skippable.xml' from origin 'http://imasdk.googleapis.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.", + "Access to fetch at 'http://127.0.0.1:8084/test/vast-skippable.xml' from origin 'http://imasdk.googleapis.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.", + "VAST Ad Error: {error: i2: There was a problem requesting ads from the server.\n at e2.B (:410:36)\n at e2.…}" + ], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "04ae498d312511cb6738e1cd4ccc5070a4525845f0a266fc1d726eae5a4c4808", + "bytes": 760 + } + } + ] + }, + { + "file": "refactor/.cache/vast05-skip-cors-report/report.json", + "sha256": "61b9f016766107d24bfd4a511298c5a3ca312c4a1de96c0a9fe32c9e4f2d5688", + "stats": { + "startTime": "2026-09-14T15:44:29.083Z", + "duration": 43585.581, + "expected": 0, + "skipped": 0, + "unexpected": 2, + "flaky": 0 + }, + "cases": [ + { + "title": "real IMA npm-default requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "failed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 7350.400000035763, + "time": 5.135506, + "paused": true + }, + { + "type": "AdStarted", + "at": 7375.300000011921, + "time": 5.135506, + "paused": true + }, + { + "type": "AdComplete", + "at": 11449.700000047684, + "time": 5.135506, + "paused": true + }, + { + "type": "AdContentResumeRequested", + "at": 11452.5, + "time": 5.135506, + "paused": true + } + ], + "imaVersion": "3.789.0", + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "time": 7.449357, + "width": 640, + "paused": false + }, + { + "src": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "time": 0, + "width": 0, + "paused": true + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + } + ] + }, + "xmlSha256": "04ae498d312511cb6738e1cd4ccc5070a4525845f0a266fc1d726eae5a4c4808", + "errors": [ + "Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 51 | }\n 52 | return false\n> 53 | }).toBe(true)\n | ^\n 54 | await testInfo.attach('native-vast-skip-button.png', { contentType: 'image/png', body: await page.screenshot() })\n 55 | await skipButton.click()\n 56 | await expect.poll(() => page.evaluate(() => window.skipEvents.filter(event => event.type === 'AdSkipped').length)).toBe(1)\n at D:\\github\\ArtPlayer\\test\\browser\\vast-skip.native.js:53:8" + ], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "04ae498d312511cb6738e1cd4ccc5070a4525845f0a266fc1d726eae5a4c4808", + "bytes": 760 + } + }, + { + "title": "real IMA workspace-1.2 requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "failed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 17888.599999964237, + "time": 2.712263, + "paused": true + }, + { + "type": "AdStarted", + "at": 17914.399999976158, + "time": 2.712263, + "paused": true + }, + { + "type": "AdComplete", + "at": 21990.19999998808, + "time": 2.712263, + "paused": true + }, + { + "type": "AdContentResumeRequested", + "at": 21993, + "time": 2.712263, + "paused": true + } + ], + "imaVersion": "3.789.0", + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "time": 5.02871, + "width": 640, + "paused": false + }, + { + "src": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "time": 0, + "width": 0, + "paused": true + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + } + ] + }, + "xmlSha256": "04ae498d312511cb6738e1cd4ccc5070a4525845f0a266fc1d726eae5a4c4808", + "errors": [ + "Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: true\nReceived: false\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 51 | }\n 52 | return false\n> 53 | }).toBe(true)\n | ^\n 54 | await testInfo.attach('native-vast-skip-button.png', { contentType: 'image/png', body: await page.screenshot() })\n 55 | await skipButton.click()\n 56 | await expect.poll(() => page.evaluate(() => window.skipEvents.filter(event => event.type === 'AdSkipped').length)).toBe(1)\n at D:\\github\\ArtPlayer\\test\\browser\\vast-skip.native.js:53:8" + ], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "04ae498d312511cb6738e1cd4ccc5070a4525845f0a266fc1d726eae5a4c4808", + "bytes": 760 + } + } + ] + }, + { + "file": "refactor/.cache/vast05-skip-countdown-report/report.json", + "sha256": "e135865451e52a2e0553710f7a18302a61ec7ae4eccc3064b7e5ccd38aed1f6b", + "stats": { + "startTime": "2026-09-14T15:47:12.273Z", + "duration": 36801.609, + "expected": 0, + "skipped": 0, + "unexpected": 2, + "flaky": 0 + }, + "cases": [ + { + "title": "real IMA npm-default requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "failed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdError", + "at": 8420.800000011921, + "time": 5.92161, + "paused": false, + "error": "No adsManagerLoadedEvent within 5000ms." + } + ], + "imaVersion": "3.789.0", + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "time": 7.94346, + "width": 640, + "paused": false + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + } + ] + }, + "errors": [ + "TimeoutError: page.waitForResponse: Timeout 7000ms exceeded while waiting for event \"response\"\n\n 29 | })\n 30 | const beforeAd = await page.evaluate(() => window.art.currentTime)\n> 31 | const responsePromise = page.waitForResponse(response => response.url().endsWith('/test/vast-skippable.xml'), { timeout: 7000 })\n | ^\n 32 | await page.locator('#play').click()\n 33 | const response = await responsePromise\n 34 | expect(response.status()).toBe(200)\n at D:\\github\\ArtPlayer\\test\\browser\\vast-skip.native.js:31:34" + ], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA workspace-1.2 requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "failed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 12190.099999964237, + "time": 5.523727, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 12214.099999964237, + "time": 5.523727, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 17236.899999976158, + "time": 5.523727, + "paused": true, + "skipOffset": 5 + } + ], + "imaVersion": "3.789.0", + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "time": 5.523727, + "width": 640, + "paused": true + }, + { + "src": "http://127.0.0.1:8084/test/pattern.mp4?skippable=1", + "time": 7.550633, + "width": 320, + "paused": false + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + } + ] + }, + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "errors": [ + "Error: expect(received).toBe(expected) // Object.is equality\n\nExpected: 1\nReceived: 0\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 55 | await testInfo.attach('native-vast-skip-button.png', { contentType: 'image/png', body: await page.screenshot() })\n 56 | await skipButton.click()\n> 57 | await expect.poll(() => page.evaluate(() => window.skipEvents.filter(event => event.type === 'AdSkipped').length)).toBe(1)\n | ^\n 58 | await expect.poll(() => page.evaluate(() => window.skipEvents.some(event => event.type === 'AdContentResumeRequested'))).toBe(true)\n 59 | await expect(page.locator('[id^=\"art-\"]')).toBeHidden()\n 60 | await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(beforeAd + 0.2)\n at D:\\github\\ArtPlayer\\test\\browser\\vast-skip.native.js:57:120" + ], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + } + ] + }, + { + "file": "refactor/.cache/vast05-skip-final-report/report.json", + "sha256": "dfed93e80ea01c09fdd87bc2bfd112be28ba087705edbda64d08f2b7024eb4c1", + "stats": { + "startTime": "2026-09-14T15:48:49.453Z", + "duration": 78324.46500000001, + "expected": 5, + "skipped": 0, + "unexpected": 1, + "flaky": 0 + }, + "cases": [ + { + "title": "real IMA npm-default requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "failed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdError", + "at": 9416.699999988079, + "time": 5.932711, + "paused": false, + "error": "No adsManagerLoadedEvent within 5000ms." + }, + { + "type": "AdContentPauseRequested", + "at": 9681.099999964237, + "time": 6.196551, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 9705.199999988079, + "time": 6.196551, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 14732.599999964237, + "time": 6.196551, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 14988.299999952316, + "time": 6.196551, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 14991.799999952316, + "time": 6.196551, + "paused": true, + "skipOffset": 5 + } + ], + "imaVersion": "3.789.0", + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "time": 6.305583, + "width": 640, + "paused": false + }, + { + "src": "http://127.0.0.1:8084/test/pattern.mp4?skippable=1", + "time": 0, + "width": 0, + "paused": true + }, + { + "src": "", + "time": 0, + "width": 0, + "paused": true + } + ] + }, + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "errors": [ + "Error: expect(received).toEqual(expected) // deep equality\n\n- Expected - 1\n+ Received + 10\n\n- Array []\n+ Array [\n+ Object {\n+ \"at\": 9416.699999988079,\n+ \"error\": \"No adsManagerLoadedEvent within 5000ms.\",\n+ \"paused\": false,\n+ \"skipOffset\": undefined,\n+ \"time\": 5.932711,\n+ \"type\": \"AdError\",\n+ },\n+ ]\n\n 62 | const state = await page.evaluate(() => ({ events: window.skipEvents, time: window.art.currentTime, paused: window.art.template.$video.paused, imaVersion: window.google.ima.VERSION }))\n 63 | expect(state.paused).toBe(false)\n> 64 | expect(state.events.filter(event => event.type === 'AdError' || event.type === 'AdComplete')).toEqual([])\n | ^\n 65 | await testInfo.attach('native-vast-skip', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(code), xmlSha256: hash(xml), mediaSha256: hash(fs.readFileSync('test/browser/media/pattern.mp4')), compatibility: compatibility || 'npm-default', beforeAd, ...state }) })\n 66 | await page.evaluate(() => window.art.destroy())\n 67 | await expect(page.locator('[id^=\"art-\"]')).toHaveCount(0)\n at D:\\github\\ArtPlayer\\test\\browser\\vast-skip.native.js:64:99" + ], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA workspace-1.2 requests playUrl and skips through the SDK button", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "passed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 5147.599999964237, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 5182, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 10212, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 10447.199999988079, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 10450.800000011921, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + } + ], + "imaVersion": "3.789.0", + "videos": [] + }, + "success": { + "sourceSha256": "d934b7735b59d6689b0481c60ea673c9ea282e6697322a669b5fef9b0ad61d34", + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "compatibility": "workspace-1.2", + "beforeAd": 0.858536, + "events": [ + { + "type": "AdContentPauseRequested", + "at": 5147.599999964237, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 5182, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 10212, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 10447.199999988079, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 10450.800000011921, + "time": 3.46906, + "paused": true, + "skipOffset": 5 + } + ], + "time": 3.5351, + "paused": false, + "imaVersion": "3.789.0" + }, + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA npm-default requests playUrl and skips through the SDK button", + "browser": "firefox", + "browserVersion": "155.0", + "status": "passed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 6206, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 6237, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 11276, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 11578, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 11583, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + } + ], + "imaVersion": "3.789.0", + "videos": [] + }, + "success": { + "sourceSha256": "d934b7735b59d6689b0481c60ea673c9ea282e6697322a669b5fef9b0ad61d34", + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "compatibility": "npm-default", + "beforeAd": 0.908701, + "events": [ + { + "type": "AdContentPauseRequested", + "at": 6206, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 6237, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 11276, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 11578, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 11583, + "time": 2.844527, + "paused": true, + "skipOffset": 5 + } + ], + "time": 2.844527, + "paused": false, + "imaVersion": "3.789.0" + }, + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA workspace-1.2 requests playUrl and skips through the SDK button", + "browser": "firefox", + "browserVersion": "155.0", + "status": "passed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 4533, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 4557, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 9807, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 10891, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 10897, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + } + ], + "imaVersion": "3.789.0", + "videos": [] + }, + "success": { + "sourceSha256": "d934b7735b59d6689b0481c60ea673c9ea282e6697322a669b5fef9b0ad61d34", + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "compatibility": "workspace-1.2", + "beforeAd": 0.9087, + "events": [ + { + "type": "AdContentPauseRequested", + "at": 4533, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 4557, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 9807, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 10891, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 10897, + "time": 2.919877, + "paused": true, + "skipOffset": 5 + } + ], + "time": 2.961824, + "paused": false, + "imaVersion": "3.789.0" + }, + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA npm-default requests playUrl and skips through the SDK button", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 3169, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 3353, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 8578, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 9786, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 9830, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + } + ], + "imaVersion": "3.789.0", + "videos": [] + }, + "success": { + "sourceSha256": "d934b7735b59d6689b0481c60ea673c9ea282e6697322a669b5fef9b0ad61d34", + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "compatibility": "npm-default", + "beforeAd": 0.9757709, + "events": [ + { + "type": "AdContentPauseRequested", + "at": 3169, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 3353, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 8578, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 9786, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 9830, + "time": 2.1891878, + "paused": true, + "skipOffset": 5 + } + ], + "time": 2.4265628, + "paused": false, + "imaVersion": "3.789.0" + }, + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA workspace-1.2 requests playUrl and skips through the SDK button", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "state": { + "events": [ + { + "type": "AdContentPauseRequested", + "at": 2894, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 3104, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 8350, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 9470, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 9511, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + } + ], + "imaVersion": "3.789.0", + "videos": [] + }, + "success": { + "sourceSha256": "d934b7735b59d6689b0481c60ea673c9ea282e6697322a669b5fef9b0ad61d34", + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "mediaSha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233", + "compatibility": "workspace-1.2", + "beforeAd": 1.0175834, + "events": [ + { + "type": "AdContentPauseRequested", + "at": 2894, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdStarted", + "at": 3104, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkippableStateChanged", + "at": 8350, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdSkipped", + "at": 9470, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + }, + { + "type": "AdContentResumeRequested", + "at": 9511, + "time": 1.5732918, + "paused": true, + "skipOffset": 5 + } + ], + "time": 1.8828336, + "paused": false, + "imaVersion": "3.789.0" + }, + "xmlSha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + } + ] + }, + { + "file": "refactor/.cache/vast05-webkit-probe-report/report.json", + "sha256": "a6218a08036214e8c2b94d7ff2c725e9801879276f0f0607d7718e6f0a7a39ae", + "stats": { + "startTime": "2026-09-14T15:51:11.607Z", + "duration": 37577.504, + "expected": 3, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "real IMA candidate / published-1.0.0 plays local VAST media and resumes content", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "state": { + "registration": "ready", + "imaVersion": "3.789.0", + "events": [ + { + "type": "AdContentPauseRequested", + "at": 2789, + "contentTime": 1.6561252, + "contentPaused": true + }, + { + "type": "AdStarted", + "at": 3007, + "contentTime": 1.6561252, + "contentPaused": true + }, + { + "type": "AdComplete", + "at": 7177, + "contentTime": 1.6561252, + "contentPaused": true + }, + { + "type": "MediaResumed", + "at": 7226, + "contentTime": 1.6561252, + "contentPaused": true + }, + { + "type": "AdContentResumeRequested", + "at": 7226, + "contentTime": 1.6561252, + "contentPaused": true + }, + { + "type": "AdAllAdsCompleted", + "at": 7228, + "contentTime": 1.7100002, + "contentPaused": false + } + ], + "mediaSamples": [ + { + "at": 3511, + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "declaredSrc": "http://127.0.0.1:8084/assets/sample/video.mp4", + "width": 640, + "height": 360, + "time": 1.6561252, + "readyState": 4, + "paused": true + }, + { + "src": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "declaredSrc": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "width": 640, + "height": 360, + "time": 0.4703743, + "readyState": 4, + "paused": false + }, + { + "src": "", + "declaredSrc": "", + "width": 0, + "height": 0, + "time": 0, + "readyState": 0, + "paused": true + } + ] + } + ], + "videos": [], + "coreVersion": "5.4.1" + }, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA candidate / published-1.0.0 plays local VAST media and resumes content", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "state": { + "registration": "ready", + "imaVersion": "3.789.0", + "events": [ + { + "type": "AdContentPauseRequested", + "at": 2843, + "contentTime": 1.6087085, + "contentPaused": true + }, + { + "type": "AdStarted", + "at": 3050, + "contentTime": 1.6087085, + "contentPaused": true + }, + { + "type": "AdComplete", + "at": 7192, + "contentTime": 1.6087085, + "contentPaused": true + }, + { + "type": "MediaResumed", + "at": 7239, + "contentTime": 1.6087085, + "contentPaused": true + }, + { + "type": "AdContentResumeRequested", + "at": 7239, + "contentTime": 1.6087085, + "contentPaused": true + }, + { + "type": "AdAllAdsCompleted", + "at": 7241, + "contentTime": 1.6600002, + "contentPaused": false + } + ], + "mediaSamples": [ + { + "at": 3552, + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "declaredSrc": "http://127.0.0.1:8084/assets/sample/video.mp4", + "width": 640, + "height": 360, + "time": 1.6087085, + "readyState": 4, + "paused": true + }, + { + "src": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "declaredSrc": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "width": 640, + "height": 360, + "time": 0.4867701, + "readyState": 4, + "paused": false + }, + { + "src": "", + "declaredSrc": "", + "width": 0, + "height": 0, + "time": 0, + "readyState": 0, + "paused": true + } + ] + } + ], + "videos": [], + "coreVersion": "5.4.1" + }, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "real IMA candidate / published-1.0.0 plays local VAST media and resumes content", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "state": { + "registration": "ready", + "imaVersion": "3.789.0", + "events": [ + { + "type": "AdContentPauseRequested", + "at": 2577, + "contentTime": 1.5936669, + "contentPaused": true + }, + { + "type": "AdStarted", + "at": 2772, + "contentTime": 1.5936669, + "contentPaused": true + }, + { + "type": "AdComplete", + "at": 6934, + "contentTime": 1.5936669, + "contentPaused": true + }, + { + "type": "MediaResumed", + "at": 6980, + "contentTime": 1.5936669, + "contentPaused": true + }, + { + "type": "AdContentResumeRequested", + "at": 6980, + "contentTime": 1.5936669, + "contentPaused": true + }, + { + "type": "AdAllAdsCompleted", + "at": 6982, + "contentTime": 1.6200002, + "contentPaused": false + } + ], + "mediaSamples": [ + { + "at": 3258, + "videos": [ + { + "src": "http://127.0.0.1:8084/assets/sample/video.mp4", + "declaredSrc": "http://127.0.0.1:8084/assets/sample/video.mp4", + "width": 640, + "height": 360, + "time": 1.5936669, + "readyState": 4, + "paused": true + }, + { + "src": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "declaredSrc": "http://127.0.0.1:8084/assets/vast/linear-video.mp4", + "width": 640, + "height": 360, + "time": 0.4528326, + "readyState": 4, + "paused": false + }, + { + "src": "", + "declaredSrc": "", + "width": 0, + "height": 0, + "time": 0, + "readyState": 0, + "paused": true + } + ] + } + ], + "videos": [], + "coreVersion": "5.4.1" + }, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + } + ] + }, + { + "file": "refactor/.cache/vast05-server-regression-report/report.json", + "sha256": "3a0fa9c8fd175c534172db9eadc49920c61776bda59c792fa9cc65f52301a3c1", + "stats": { + "startTime": "2026-09-14T15:52:29.830Z", + "duration": 16864.015, + "expected": 12, + "skipped": 0, + "unexpected": 0, + "flaky": 0 + }, + "cases": [ + { + "title": "published: real playback, pause, seek, switch and destroy", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "candidate: real playback, pause, seek, switch and destroy", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "controlled HTTP media failure emits a real video error", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "Range, media identity and docs candidate routes", + "browser": "chromium", + "browserVersion": "153.0.8010.12", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "published: real playback, pause, seek, switch and destroy", + "browser": "firefox", + "browserVersion": "155.0", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "candidate: real playback, pause, seek, switch and destroy", + "browser": "firefox", + "browserVersion": "155.0", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "controlled HTTP media failure emits a real video error", + "browser": "firefox", + "browserVersion": "155.0", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "Range, media identity and docs candidate routes", + "browser": "firefox", + "browserVersion": "155.0", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "published: real playback, pause, seek, switch and destroy", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "candidate: real playback, pause, seek, switch and destroy", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "controlled HTTP media failure emits a real video error", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + }, + { + "title": "Range, media identity and docs candidate routes", + "browser": "webkit", + "browserVersion": "26.6", + "status": "passed", + "retry": 0, + "errors": [], + "consoleErrors": [], + "fixture": { + "source": { + "kind": "local-vast-fixture", + "file": "test/browser/vast-fixture.mjs" + }, + "sha256": "f810be089e762ea77c08a1f736781fa8f07f5746bab6ec59819bd03d36a46baf", + "bytes": 760 + } + } + ] + } + ], + "files": { + "test/browser/vast-fixture.mjs": "87427a98f815fd70f12ee2399e68360536f1b625c49a2798c357f81537179682", + "test/browser/vast-skip.native.js": "c95060b38e675cf1cb7a0e8ad0a3b322b49d24803cd2644c0aa21e7e54da463a", + "test/browser/vast.native.js": "f2155b9d5a3a1a840cbec7d79d19c1589a5ddbdd5d7cbb5e4c28ca54847b6e6a", + "test/browser/server.mjs": "17b7ea3db2c8e21115346d62f307d45253599307bf7f68052f41af864ab8ebe5", + "playwright.vast-native.config.js": "eebfca76d96cc9d84f8d9ece20aee32d1dd4826373e18ed31ae80a98f3173474", + "test/browser/media/pattern.mp4": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233" + }, + "historicalWebkit": { + "previousFailureRetained": true, + "trace": "refactor/.cache/vast05-native-final-report/results/vast.native.js-real-IMA-ca-70929-T-media-and-resumes-content-webkit/trace.zip", + "image": "refactor/.cache/vast05-old-webkit-ad.jpg", + "imageSha256": "4f6f2b540152d0a9a0d3dcf36bd259b9bc1ffd0d47142ac509128a4da7bc5a99", + "observed": "Trace screenshot shows Linear Ad at00:00:00,12. Actual AdComplete/content resume events occurred, while the prior video polling predicate never became true. Three fixed diagnostic repetitions passed with unchanged predicate and additional raw media samples; root cause remains unproven." + }, + "remaining": [ + "Keep prior SDK9000/late load and historical failures open; this is not a green full matrix.", + "Real physical iOS/Android evidence remains required.", + "Exact native package/distribution validation remains task06.", + "Short4-second ad with1-second skipoffset did not expose skip state; do not generalize the8-second/5-second case to every ad." + ] +} diff --git a/refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md b/refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md new file mode 100644 index 000000000..3fbc3c8cb --- /dev/null +++ b/refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md @@ -0,0 +1,56 @@ +# PKG-VAST-05:playUrl、真实跳过按钮和失败分层 + +前置HEAD75da50a45。本次继续05,保持doing;生产源码、公开类型、依赖和产物没有 +变化,不把测试夹具修正包装成播放器缺陷修复。全部原报告和新证据见 +[vast-skip-validation.json](../baselines/vast-skip-validation.json)。 + +## 新增的实际覆盖 + +`test/browser/vast-skip.native.js`在候选核心的两种VAST模式下调用playUrl,真实IMA +iframe通过HTTP读取广告XML,核对200、MIME、CORS与响应原文;广告实际解码后, +等待AdSkippableStateChanged和可用的SDK按钮,真实点击,再验证AdSkipped、主片 +恢复及销毁。没有调用模拟事件、强制隐藏广告、伪造时间或通过skipAd替代UI。 + +[Google AdsManager接口](https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/reference/interface/google.ima.AdsManager#skip) +明确限制:当IMA自己显示跳过按钮时,程序skip方法不负责跳过。因此本项直接测试 +SDK按钮。观察到的SDK为3.789.0,浏览器与前检查点相同。 + +`vast-fixture.mjs`生成8秒pattern.mp4广告、5秒跳过点、独立本地跟踪图片; +`server.mjs`增加一个测试XML映射及XML MIME。仅这个资源允许明确的http/https +imasdk.googleapis.com来源并返回凭据CORS和Vary:Origin,不给其他路由统一加CORS。 +正常PR套件只服务该本地资源,不自动运行外部SDK测试。没有新增依赖或脚本。 + +## 实际结果与失败记录 + +| 批次 | 结果 | 原因或实际范围 | +| --- | --- | --- | +| 4秒素材,首次HTTP标签 | 0/2 | 测试服务缺少CORS,真实iframe请求被拒绝;主片继续播放 | +| 补CORS,仍用4秒/1秒跳过点 | 0/2 | 广告真实完成但没有跳过状态/按钮;没有推断所有短广告都不支持跳过 | +| 8秒/5秒素材,旧按钮等待 | 0/2 | 1项SDK9000/标签等待超时;1项在倒计时已暴露的Skip Ad控件上过早点击 | +| 等待真实可跳过事件和可用按钮 | 5/6 | 三引擎两模式;Chromium默认项收到9000后虽完成跳过/恢复仍判失败 | +| WebKit旧插件媒体采样诊断 | 3/3 | 预先固定3次执行、无重试;未修改原首帧断言,不关闭历史失败 | +| 既有播放/切源/错误/Range回归 | 12/12 | 三引擎、新旧核心;验证共享测试服务未破坏已有路径 | + +所有浏览器进程退出后才读取最终JSON/归档,记录实际退出码。SDK加载和广告请求 +超时仍保持失败,没有提高SDK默认5秒/10秒预算;HTTP响应等待由默认整例30秒 +收紧到7秒以更快暴露资源读取失败。首三轮失败用于修正可定位的夹具问题,不作为 +成功证据;未反复运行相同断言直到变绿。最终5/6不是完整兼容通过。 + +## 旧WebKit首帧问题的新增证据 + +上个检查点的失败trace里,主脚本和bridge都返回200,AdStarted之后有实际 +“Linear Ad”画面(画面时钟00:00:00,12),随后AdComplete/主片恢复也发生了。 +它不能再被表述为没有播放广告;但当时首帧video属性断言为何始终不成立仍未确定。 +`vast.native.js`现在保存每次采样的currentSrc/src、尺寸、时间、readyState和paused, +原判定不变。固定三次诊断都通过,不能覆盖旧失败或宣称间歇问题已修复。 + +## 后续与回退 + +专项只读lint、plan/risk-register生成与检查通过;风险/浏览器runner工程测试6通过, +固定Node24.21.0/Yarn1.22.22严格工具链检查通过。git diff --check通过。没有为 +该测试改动重建生产包;上一步运行时、类型与分发文件保持原样。 + +更新包架构、维护说明和风险/进度,05仍doing。下一步是SDK9000后迟到广告的可控 +真实SDK复现与处置、历史首帧观测差异、物理iOS/Android验证及06分发。不能把这些 +环境门槛拖延其他依赖已满足的源码任务。没有修改用户VPN/浏览器保护,也未使用 +VAST脚本加载豁免。该检查点可独立回退;未push、未发布npm。 diff --git a/refactor/plan.md b/refactor/plan.md index 64452b02f..72d9a7604 100644 --- a/refactor/plan.md +++ b/refactor/plan.md @@ -602,7 +602,7 @@ - PKG-VAST-02: [记录](changes/2026-09-12-PKG-VAST-02-tests.md) [记录](baselines/vast-behavior-validation.json) [记录](baselines/vast-core.json) [记录](vast-validation.md) - PKG-VAST-03: [记录](changes/2026-09-12-PKG-VAST-03-lifecycle.md) [记录](baselines/vast-lifecycle-validation.json) [记录](vast-compatibility-decision.md) [记录](changes/2026-09-14-PKG-VAST-03-compatibility.md) [记录](baselines/vast-compatibility-validation.json) - PKG-VAST-04: [记录](changes/2026-09-14-PKG-VAST-04-types.md) [记录](baselines/vast-types-validation.json) [记录](vast-type-decision.md) [记录](vast-validation.md) -- PKG-VAST-05: [记录](changes/2026-09-14-PKG-VAST-05-native-checkpoint.md) [记录](baselines/vast-native-validation.json) +- PKG-VAST-05: [记录](changes/2026-09-14-PKG-VAST-05-native-checkpoint.md) [记录](baselines/vast-native-validation.json) [记录](changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md) [记录](baselines/vast-skip-validation.json) - PKG-CAST-01: [记录](baselines/chromecast-release.json) [记录](baselines/chromecast-contract.md) [记录](baselines/chromecast-contract-validation.json) [记录](changes/2026-09-13-PKG-CAST-01-contracts.md) - PKG-CAST-02: [记录](changes/2026-09-13-PKG-CAST-02-failures.md) [记录](baselines/chromecast-failures-validation.json) - PKG-CAST-03: [记录](changes/2026-09-13-PKG-CAST-03-runtime.md) [记录](baselines/chromecast-runtime-validation.json) diff --git a/refactor/progress.md b/refactor/progress.md index 8b325ab7e..4ffa14203 100644 --- a/refactor/progress.md +++ b/refactor/progress.md @@ -1,5 +1,14 @@ # 进度与证据 +## PKG-VAST-05 增补真实playUrl和SDK跳过按钮 + +真实HTTP标签、广告解码、跳过事件/按钮点击及主片恢复最终5/6通过;Chromium默认 +项虽完成跳过/恢复,因先出现SDK9000仍判失败。修复测试资源CORS与倒计时按钮 +误判,保留三轮失败,不修改生产代码。共享服务播放/切源/错误/Range回归12/12。 +旧WebKit失败trace确有广告画面;新增原始video采样后固定3次诊断通过,原失败 +仍未关闭。见[记录](changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md)和 +[证据](baselines/vast-skip-validation.json)。05保持doing,任务总数和完成数不变。 + ## PKG-VAST-05 真实 IMA 桌面验证检查点,仍进行中 新增独立`test:vast-native`,真实远端IMA3.789.0与本地广告媒体,不替换SDK。 diff --git a/refactor/risks.json b/refactor/risks.json index af8b0da85..828d4b841 100644 --- a/refactor/risks.json +++ b/refactor/risks.json @@ -777,11 +777,13 @@ "refactor/vast-validation.md", "refactor/baselines/vast-behavior-validation.json", "refactor/changes/2026-09-14-PKG-VAST-05-native-checkpoint.md", - "refactor/baselines/vast-native-validation.json" + "refactor/baselines/vast-native-validation.json", + "refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md", + "refactor/baselines/vast-skip-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/外部资源版本和环境;成功/失败/切换/销毁有真实证据,设备和网络限制逐项记录。", - "workspaceState": "Real IMA3.789.0 executes with immutable npm1.0.0/candidate bundles: final27 desktop combinations22 passed5 failed; error303 recovery/recreation/active-ad destruction6 passed. Initial27 were23 passed4 failed and are preserved. SDK9000/late loading and historical WebKit ad-frame failures remain open. IAB SDK200 but bridge document ERR_BLOCKED_BY_CLIENT; physical devices/playUrl/skip not accepted. No VPN exception or timeout widening/retry-to-green." + "workspaceState": "Real IMA3.789.0 executes with immutable npm1.0.0/candidate bundles: final27 desktop combinations22 passed5 failed; error303 recovery/recreation/active-ad destruction6 passed. Initial27 were23 passed4 failed and are preserved. SDK9000/late loading and historical WebKit ad-frame failures remain open. IAB SDK200 but bridge document ERR_BLOCKED_BY_CLIENT; physical devices/playUrl/skip not accepted. No VPN exception or timeout widening/retry-to-green. Native playUrl/IMA skip UI adds5/6 passes (one9000 failure despite later skip), shared server12/12 passes. Historical WebKit trace contains real ad frames; three fixed raw-media diagnostic runs passed without changing the assertion, so the original intermittent failure is not closed." }, { "id": "SDK-08", @@ -3335,11 +3337,13 @@ "refactor/changes/2026-09-14-PKG-VAST-03-compatibility.md", "refactor/baselines/vast-compatibility-validation.json", "refactor/changes/2026-09-14-PKG-VAST-05-native-checkpoint.md", - "refactor/baselines/vast-native-validation.json" + "refactor/baselines/vast-native-validation.json", + "refactor/changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md", + "refactor/baselines/vast-skip-validation.json" ], "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.", - "workspaceState": "Task03 controlled82 Node/162 browser checks remain valid; task05 adds6 real IMA303 recovery/recreation/active-ad core destruction passes across three engines/two modes. Native full combination failures, devices and remaining ad lifecycle paths keep this risk open; no claim of complete vendor/device acceptance." + "workspaceState": "Task03 controlled82 Node/162 browser checks remain valid; task05 adds6 real IMA303 recovery/recreation/active-ad core destruction passes across three engines/two modes. Native full combination failures, devices and remaining ad lifecycle paths keep this risk open; no claim of complete vendor/device acceptance. Native playUrl/IMA skip UI adds5/6 passes (one9000 failure despite later skip), shared server12/12 passes. Historical WebKit trace contains real ad frames; three fixed raw-media diagnostic runs passed without changing the assertion, so the original intermittent failure is not closed." }, { "id": "VAST-DIST-01", diff --git a/refactor/tasks.json b/refactor/tasks.json index 4af82baf5..e73352405 100644 --- a/refactor/tasks.json +++ b/refactor/tasks.json @@ -3140,7 +3140,9 @@ "acceptance": "最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成", "evidence": [ "changes/2026-09-14-PKG-VAST-05-native-checkpoint.md", - "baselines/vast-native-validation.json" + "baselines/vast-native-validation.json", + "changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md", + "baselines/vast-skip-validation.json" ] }, { diff --git a/refactor/vast-validation.md b/refactor/vast-validation.md index 8bb1d348d..a333b43d3 100644 --- a/refactor/vast-validation.md +++ b/refactor/vast-validation.md @@ -117,3 +117,11 @@ SDK清理由夹具在证据后显式补做,不能据DOM被核心移除就认 广告播放中核心销毁。当前结果见[05检查点](changes/2026-09-14-PKG-VAST-05-native-checkpoint.md): 最终组合22/27,恢复6/6;SDK超时/迟到、旧插件首帧及真机未关闭。独立入口失败应 保留为失败,不能用默认受控套件通过抵消,也不能为稳定CI添加宽泛skip。 + +`vast-skip.native.js`经真实HTTP调用playUrl,只允许IMA来源读取测试XML,验证响应 +原文/MIME/CORS及广告解码;等待SDK可跳过事件和可用按钮后点击,断言AdSkipped与 +主片恢复。SDK倒计时控件提前有Skip Ad名称,不能只靠名称和visible就点击。 +8秒生成视频配5秒跳过点;4秒/1秒素材未出现跳过状态的结果独立保留,不泛化。 +最终5/6(另1项9000失败)、共享服务12/12及固定WebKit诊断3/3见 +[增补记录](changes/2026-09-14-PKG-VAST-05-skip-checkpoint.md)。媒体采样仅用于 +记录,未放宽原首帧断言;三次通过不能覆盖先前间歇失败。 diff --git a/test/browser/server.mjs b/test/browser/server.mjs index ee5457b41..fa8ff1114 100644 --- a/test/browser/server.mjs +++ b/test/browser/server.mjs @@ -11,6 +11,7 @@ import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases import { performanceHtml, performanceScript } from '../../scripts/performance-fixture.mjs' import { getEntryFile } from '../../scripts/projects.js' import { getGlobalName, getViteBuildConfig } from '../../scripts/utils.js' +import { skippableVast } from './vast-fixture.mjs' const workspace = fileURLToPath(new URL('../../', import.meta.url)) @@ -94,7 +95,8 @@ add('/test/audio-tone.m4a', fs.readFileSync(path.join(workspace, 'test/browser/m add('/test/thumbnail-pattern.mp4', fs.readFileSync(path.join(workspace, 'test/browser/media/thumbnail-pattern.mp4')), { kind: 'generated-media', file: 'test/browser/media/thumbnail-pattern.mp4' }) add('/test/auto-thumbnail-timeline.mp4', fs.readFileSync(path.join(workspace, 'test/browser/media/auto-thumbnail-timeline.mp4')), { kind: 'generated-media', file: 'test/browser/media/auto-thumbnail-timeline.mp4' }) -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', '.wasm': 'application/wasm', '.woff2': 'font/woff2' } +const mime = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.json': 'application/json', '.xml': 'application/xml', '.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', '.wasm': 'application/wasm', '.woff2': 'font/woff2' } +add('/test/vast-skippable.xml', Buffer.from(skippableVast(`http://127.0.0.1:${port}`)), { kind: 'local-vast-fixture', file: 'test/browser/vast-fixture.mjs' }) add('/test/declaration-cues.vtt', fs.readFileSync(path.join(workspace, 'test/browser/media/declaration-cues.vtt')), { kind: 'test-subtitles', file: 'test/browser/media/declaration-cues.vtt' }) add('/test/thumbnail-grid.svg', fs.readFileSync(path.join(workspace, 'test/browser/media/thumbnail-grid.svg')), { kind: 'test-thumbnail-grid', file: 'test/browser/media/thumbnail-grid.svg' }) add('/test/legacy-safe-area.js', fs.readFileSync(path.join(workspace, 'test/helpers/legacy-safe-area.js')), { kind: 'frozen-own-source', file: 'test/helpers/legacy-safe-area.js', commit: 'ccf77c4e' }) @@ -170,6 +172,11 @@ const server = http.createServer((req, res) => { return } if (files.has(url.pathname)) { + if (url.pathname === '/test/vast-skippable.xml' && ['http://imasdk.googleapis.com', 'https://imasdk.googleapis.com'].includes(req.headers.origin)) { + res.setHeader('Access-Control-Allow-Origin', req.headers.origin) + res.setHeader('Access-Control-Allow-Credentials', 'true') + res.setHeader('Vary', 'Origin') + } send(req, res, files.get(url.pathname), url.pathname) return } diff --git a/test/browser/vast-fixture.mjs b/test/browser/vast-fixture.mjs new file mode 100644 index 000000000..8801dfaf5 --- /dev/null +++ b/test/browser/vast-fixture.mjs @@ -0,0 +1,10 @@ +export function skippableVast(origin) { + return ` + +ArtPlayer test fixtureLocal skippable ad + +00:00:08 + + +` +} diff --git a/test/browser/vast-skip.native.js b/test/browser/vast-skip.native.js new file mode 100644 index 000000000..a24cf8532 --- /dev/null +++ b/test/browser/vast-skip.native.js @@ -0,0 +1,78 @@ +import fs from 'node:fs' +import { hash } from '../../refactor/scripts/releases.mjs' +import { compilePackage } from '../helpers/load.js' +import { expect, test } from './fixtures.js' +import { skippableVast } from './vast-fixture.mjs' + +// eslint-disable-next-line antfu/no-top-level-await -- Native SDK bundle is shared by test discovery. +const code = await compilePackage('artplayer-plugin-vast', 'umd') + +for (const compatibility of [undefined, 'workspace-1.2']) { + test(`real IMA ${compatibility || 'npm-default'} requests playUrl and skips through the SDK button`, async ({ page }, testInfo) => { + await page.goto('/test/player.html?core=candidate') + await page.addScriptTag({ content: code }) + await page.evaluate(async (compatibility) => { + window.createPlayer('/assets/sample/video.mp4') + window.skipEvents = [] + await window.art.plugins.add(window.artplayerPluginVast((context) => { + window.skipContext = context + const player = context.init() + for (const type of ['AdStarted', 'AdComplete', 'AdSkipped', 'AdSkippableStateChanged', 'AdContentPauseRequested', 'AdContentResumeRequested', 'AdError']) { + player.addEventListener(type, event => window.skipEvents.push({ type, at: performance.now(), time: window.art.currentTime, paused: window.art.template.$video.paused, skipOffset: event.detail?.ad?.getSkipTimeOffset(), error: event.detail?.error?.message })) + } + }, compatibility ? { compatibility } : undefined)) + }, compatibility) + await page.locator('#play').click() + await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.5) + await page.evaluate(() => { + document.querySelector('#play').onclick = () => window.skipContext.playUrl(`${location.origin}/test/vast-skippable.xml`) + }) + const beforeAd = await page.evaluate(() => window.art.currentTime) + const responsePromise = page.waitForResponse(response => response.url().endsWith('/test/vast-skippable.xml'), { timeout: 7000 }) + await page.locator('#play').click() + const response = await responsePromise + expect(response.status()).toBe(200) + expect(response.headers()['content-type']).toBe('application/xml') + expect(['http://imasdk.googleapis.com', 'https://imasdk.googleapis.com']).toContain(response.headers()['access-control-allow-origin']) + const xml = await response.text() + expect(xml).toBe(skippableVast(new URL(page.url()).origin)) + await testInfo.attach('native-vast-skip.xml', { contentType: 'application/xml', body: xml }) + await expect.poll(() => page.evaluate(() => window.skipEvents.some(event => event.type === 'AdStarted'))).toBe(true) + await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('video')].some(video => video.currentSrc.includes('/test/pattern.mp4?skippable=1') && video.videoWidth > 0 && video.currentTime > 0))).toBe(true) + expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(true) + await testInfo.attach('native-vast-before-skip.png', { contentType: 'image/png', body: await page.screenshot() }) + await expect.poll(() => page.evaluate(() => window.skipEvents.some(event => event.type === 'AdSkippableStateChanged'))).toBe(true) + let skipButton + await expect.poll(async () => { + for (const frame of page.frames()) { + const button = frame.getByRole('button', { name: /skip ad/i }) + if (await button.count() === 1 && await button.isVisible() && await button.isEnabled()) { + skipButton = button + return true + } + } + return false + }).toBe(true) + await testInfo.attach('native-vast-skip-button.png', { contentType: 'image/png', body: await page.screenshot() }) + await skipButton.click() + await expect.poll(() => page.evaluate(() => window.skipEvents.filter(event => event.type === 'AdSkipped').length)).toBe(1) + await expect.poll(() => page.evaluate(() => window.skipEvents.some(event => event.type === 'AdContentResumeRequested'))).toBe(true) + await expect(page.locator('[id^="art-"]')).toBeHidden() + await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(beforeAd + 0.2) + const state = await page.evaluate(() => ({ events: window.skipEvents, time: window.art.currentTime, paused: window.art.template.$video.paused, imaVersion: window.google.ima.VERSION })) + expect(state.paused).toBe(false) + expect(state.events.filter(event => event.type === 'AdError' || event.type === 'AdComplete')).toEqual([]) + await testInfo.attach('native-vast-skip', { contentType: 'application/json', body: JSON.stringify({ sourceSha256: hash(code), xmlSha256: hash(xml), mediaSha256: hash(fs.readFileSync('test/browser/media/pattern.mp4')), compatibility: compatibility || 'npm-default', beforeAd, ...state }) }) + await page.evaluate(() => window.art.destroy()) + await expect(page.locator('[id^="art-"]')).toHaveCount(0) + }) +} + +test.afterEach(async ({ page }, testInfo) => { + await testInfo.attach('native-vast-skip-final', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(() => ({ events: window.skipEvents, imaVersion: window.google?.ima?.VERSION, videos: [...document.querySelectorAll('video')].map(video => ({ src: video.currentSrc, time: video.currentTime, width: video.videoWidth, paused: video.paused })) }))) }) + for (const [index, frame] of page.frames().entries()) { + if (frame !== page.mainFrame()) + await testInfo.attach(`native-vast-frame-${index}`, { contentType: 'text/plain', body: await frame.locator('body').ariaSnapshot().catch(error => String(error)) }) + } + await page.evaluate(() => window.art?.destroy()) +}) diff --git a/test/browser/vast.native.js b/test/browser/vast.native.js index 7536d888e..e5f23b716 100644 --- a/test/browser/vast.native.js +++ b/test/browser/vast.native.js @@ -17,6 +17,7 @@ test.afterEach(async ({ page }, testInfo) => { error: window.nativeVastError, imaVersion: window.google?.ima?.VERSION, events: window.nativeVastEvents, + mediaSamples: window.nativeVastMediaSamples, videos: [...document.querySelectorAll('video')].map(video => ({ src: video.currentSrc, time: video.currentTime, duration: video.duration, width: video.videoWidth, height: video.videoHeight, paused: video.paused, ended: video.ended })), containerConnected: window.nativeVastContext?.container?.isConnected, coreVersion: window.Artplayer?.version, @@ -41,6 +42,7 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) { await page.evaluate(({ compatibility, historical }) => { window.nativeVastHistorical = historical window.nativeVastEvents = [] + window.nativeVastMediaSamples = [] window.nativeVastRegistration = 'pending' window.createPlayer('/assets/sample/video.mp4') const factory = window.artplayerPluginVast.default || window.artplayerPluginVast @@ -81,7 +83,11 @@ for (const core of ['published-5.1.7', 'published', 'candidate']) { await page.locator('#play').click() await expect.poll(() => page.evaluate(() => window.nativeVastEvents.some(event => event.type === 'AdStarted'))).toBe(true) await expect(page.locator('[id^="art-"]')).toBeVisible() - await expect.poll(() => page.evaluate(() => [...document.querySelectorAll('video')].some(video => video.currentSrc.includes('/assets/vast/linear-video.mp4') && video.videoWidth > 0 && video.videoHeight > 0 && video.currentTime > 0))).toBe(true) + await expect.poll(() => page.evaluate(() => { + const videos = [...document.querySelectorAll('video')].map(video => ({ src: video.currentSrc, declaredSrc: video.src, width: video.videoWidth, height: video.videoHeight, time: video.currentTime, readyState: video.readyState, paused: video.paused })) + window.nativeVastMediaSamples.push({ at: performance.now(), videos }) + return videos.some(video => video.src.includes('/assets/vast/linear-video.mp4') && video.width > 0 && video.height > 0 && video.time > 0) + })).toBe(true) const decodedAd = await page.evaluate(() => [...document.querySelectorAll('video')].map(video => ({ src: video.currentSrc, width: video.videoWidth, height: video.videoHeight, time: video.currentTime })).filter(video => video.src.includes('/assets/vast/linear-video.mp4'))) expect(decodedAd.some(video => video.width > 0 && video.height > 0)).toBe(true) expect(await page.evaluate(() => window.art.template.$video.paused)).toBe(true)