|
1 | | -import path from "path"; |
2 | 1 | import { test, expect } from "@mendix/run-e2e/fixtures"; |
3 | 2 | import { waitForWidget, waitFrames } from "@mendix/run-e2e/mendix-helpers"; |
4 | 3 |
|
@@ -107,49 +106,38 @@ test.describe("Error page", () => { |
107 | 106 | }); |
108 | 107 |
|
109 | 108 | test.describe("External video", () => { |
110 | | - test.beforeEach(async ({ page }) => { |
111 | | - await page.goto("/p/external"); |
112 | | - }); |
113 | | - |
114 | | - test("renders a poster", async ({ page }) => { |
115 | | - // Intercept the external video URL and serve a local stub so the test |
116 | | - // doesn't depend on third-party CDN availability in CI. |
| 109 | + test("renders a poster", async ({ page }, testInfo) => { |
| 110 | + // Register the route before navigation so the mp4 fetch is intercepted |
| 111 | + // from the start. The beforeEach can't be used here because it runs |
| 112 | + // before the test body, after which it's too late to register the route. |
| 113 | + const stubPath = testInfo.file.replace("VideoPlayer.spec.js", "fixtures/stub.mp4"); |
117 | 114 | await page.route("**/*.mp4", route => |
118 | 115 | route.fulfill({ |
119 | 116 | status: 200, |
120 | 117 | contentType: "video/mp4", |
121 | | - path: path.join(import.meta.dirname, "fixtures/stub.mp4") |
| 118 | + path: stubPath |
122 | 119 | }) |
123 | 120 | ); |
124 | | - // Reload so the intercepted route applies to the video element fetch. |
125 | | - await page.reload(); |
| 121 | + await page.goto("/p/external"); |
126 | 122 |
|
127 | 123 | const widget = page.locator(".widget-video-player"); |
128 | 124 | const videoLocator = page.locator(".widget-video-player video"); |
129 | 125 | await widget.scrollIntoViewIfNeeded(); |
130 | 126 | await expect(widget).toBeVisible(); |
131 | 127 | await expect(videoLocator).toHaveAttribute("poster", /.+/); |
132 | | - // Wait for the video element to reach HAVE_METADATA (readyState >= 1). |
133 | | - // Until metadata loads, Chrome renders the <video> without its native chrome |
134 | | - // (controls bar, dark background) — producing a screenshot that doesn't match |
135 | | - // the baseline. Guard against a pre-existing error to avoid waiting forever |
136 | | - // when onerror already fired before we attach the listener. |
137 | | - await videoLocator.evaluate(el => { |
138 | | - if (el.readyState >= 1) { |
139 | | - return Promise.resolve(); |
140 | | - } |
141 | | - if (el.error !== null) { |
142 | | - return Promise.reject(new Error("Video failed to load metadata before screenshot")); |
143 | | - } |
144 | | - return new Promise((resolve, reject) => { |
145 | | - el.addEventListener("loadedmetadata", resolve, { once: true }); |
146 | | - el.addEventListener( |
147 | | - "error", |
148 | | - () => reject(new Error("Video failed to load metadata before screenshot")), |
149 | | - { once: true } |
| 128 | + // Poll for a terminal state: metadata loaded OR all sources failed (networkState 3). |
| 129 | + // Polling avoids the race where loadedmetadata/error events fire on <source> before |
| 130 | + // we can attach listeners on the <video> element inside evaluate(). |
| 131 | + await page.waitForFunction( |
| 132 | + () => { |
| 133 | + const el = document.querySelector(".widget-video-player video"); |
| 134 | + return ( |
| 135 | + el !== null && |
| 136 | + (el.readyState >= 1 || el.networkState === HTMLMediaElement.NETWORK_NO_SOURCE || el.error !== null) |
150 | 137 | ); |
151 | | - }); |
152 | | - }); |
| 138 | + }, |
| 139 | + { timeout: 8000 } |
| 140 | + ); |
153 | 141 | // Flush layout and paint after metadata is ready. |
154 | 142 | await waitFrames(page, 2); |
155 | 143 | await expect(widget).toHaveScreenshot("videoPlayerExternalPoster.png"); |
|
0 commit comments