|
| 1 | +import path from "path"; |
1 | 2 | import { test, expect } from "@mendix/run-e2e/fixtures"; |
2 | 3 | import { waitForWidget, waitFrames } from "@mendix/run-e2e/mendix-helpers"; |
3 | 4 |
|
@@ -111,25 +112,45 @@ test.describe("External video", () => { |
111 | 112 | }); |
112 | 113 |
|
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. |
| 117 | + await page.route("**/*.mp4", route => |
| 118 | + route.fulfill({ |
| 119 | + status: 200, |
| 120 | + contentType: "video/mp4", |
| 121 | + path: path.join(import.meta.dirname, "fixtures/stub.mp4") |
| 122 | + }) |
| 123 | + ); |
| 124 | + // Reload so the intercepted route applies to the video element fetch. |
| 125 | + await page.reload(); |
| 126 | + |
114 | 127 | const widget = page.locator(".widget-video-player"); |
115 | 128 | const videoLocator = page.locator(".widget-video-player video"); |
116 | 129 | await widget.scrollIntoViewIfNeeded(); |
117 | 130 | await expect(widget).toBeVisible(); |
118 | 131 | await expect(videoLocator).toHaveAttribute("poster", /.+/); |
119 | | - // Wait for poster image to decode in-page before screenshotting. |
120 | | - // page.evaluate with a separate Image() is unreliable — the promise can |
121 | | - // resolve before the <video> element itself has rendered the poster frame. |
122 | | - await videoLocator.evaluate(el => |
123 | | - el.poster |
124 | | - ? new Promise(r => { |
125 | | - const i = new Image(); |
126 | | - i.onload = i.onerror = r; |
127 | | - i.src = el.poster; |
128 | | - if (i.complete) r(); |
129 | | - }) |
130 | | - : Promise.resolve() |
131 | | - ); |
132 | | - // Wait two animation frames so the browser flushes layout and paints the poster frame. |
| 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 } |
| 150 | + ); |
| 151 | + }); |
| 152 | + }); |
| 153 | + // Flush layout and paint after metadata is ready. |
133 | 154 | await waitFrames(page, 2); |
134 | 155 | await expect(widget).toHaveScreenshot("videoPlayerExternalPoster.png"); |
135 | 156 | }); |
|
0 commit comments