diff --git a/docs-preview-smoke-test/playwright/smoke.spec.ts b/docs-preview-smoke-test/playwright/smoke.spec.ts
index 2779da80aa1..fdd541561d8 100644
--- a/docs-preview-smoke-test/playwright/smoke.spec.ts
+++ b/docs-preview-smoke-test/playwright/smoke.spec.ts
@@ -42,54 +42,95 @@ test.describe("Smoke test: all pages load", () => {
}
});
+/**
+ * Detect whether an image src looks like a leaked filesystem path rather than
+ * a proper server URL. Filesystem paths contain OS-specific patterns that
+ * should never appear in a rendered
src attribute.
+ */
+function looksLikeFilesystemPath(src: string): string | undefined {
+ // file:/// protocol
+ if (/^file:\/\/\//i.test(src)) {
+ return "uses file:/// protocol";
+ }
+
+ // Windows drive letter (e.g., C:/ or C:\)
+ if (/^[a-zA-Z]:[/\\]/.test(src)) {
+ return "contains Windows drive letter";
+ }
+
+ // URL-encoded backslashes (%5C)
+ if (src.includes("%5C")) {
+ return "contains URL-encoded backslashes (%5C)";
+ }
+
+ // Raw backslashes (Windows path separators)
+ if (src.includes("\\")) {
+ return "contains backslashes";
+ }
+
+ // Unresolved file: reference (frontend failed to map to a URL)
+ if (/^file:[0-9a-f-]+$/i.test(src)) {
+ return "contains unresolved file: reference";
+ }
+
+ return undefined;
+}
+
test.describe("Image rendering validation", () => {
- test("GET /welcome should render images that actually load", async ({ page }) => {
+ test("GET /welcome should render images without 404s or filepath leaks", async ({ page }) => {
+ // Track all failed image network requests (4xx/5xx or network errors)
+ const failedImageRequests: { url: string; status: number }[] = [];
+ page.on("response", (response) => {
+ const url = response.url();
+ const contentType = response.headers()["content-type"] ?? "";
+ const isImageRequest =
+ contentType.includes("image") || /\.(png|jpe?g|gif|svg|webp|ico|bmp|avif)(\?|$)/i.test(url);
+ if (isImageRequest && response.status() >= 400) {
+ failedImageRequests.push({ url, status: response.status() });
+ }
+ });
+
+ // Also track requests that fail at the network level (no response at all)
+ const networkFailedImages: string[] = [];
+ page.on("requestfailed", (request) => {
+ const url = request.url();
+ if (/\.(png|jpe?g|gif|svg|webp|ico|bmp|avif)(\?|$)/i.test(url)) {
+ networkFailedImages.push(`${url} (${request.failure()?.errorText ?? "unknown error"})`);
+ }
+ });
+
const response = await page.goto("/welcome", {
- waitUntil: "domcontentloaded",
+ waitUntil: "load",
timeout: 30_000
});
expect(response?.status()).toBe(200);
- // Wait for page to fully render and images to load
+ // Wait for any lazy-loaded images to finish
await page.waitForTimeout(3000);
- // Collect image loading status from the DOM
- const imageResults = await page.evaluate(() => {
- const images = document.querySelectorAll("img");
- return Array.from(images).map((img) => ({
- src: img.getAttribute("src") ?? img.src,
- complete: img.complete,
- naturalWidth: img.naturalWidth,
- naturalHeight: img.naturalHeight
- }));
- });
-
- expect(imageResults.length, "Expected at least one image on /welcome").toBeGreaterThan(0);
-
- for (const img of imageResults) {
- // Image must have finished loading
- expect(img.complete, `Image did not finish loading: ${img.src}`).toBe(true);
+ // --- Network-level check: no image requests should have failed ---
+ expect(
+ failedImageRequests,
+ `Image requests returned errors:\n${failedImageRequests.map((r) => ` ${r.status} ${r.url}`).join("\n")}`
+ ).toHaveLength(0);
- // Image must have rendered with non-zero dimensions (broken/missing images have naturalWidth 0)
- expect(
- img.naturalWidth,
- `Image failed to load (naturalWidth is 0), likely a broken path: ${img.src}`
- ).toBeGreaterThan(0);
+ expect(
+ networkFailedImages,
+ `Image requests failed at network level:\n${networkFailedImages.join("\n")}`
+ ).toHaveLength(0);
- // Must not contain file:/// protocol (broken local path leak)
- expect(img.src, `Image src should not use file:/// protocol: ${img.src}`).not.toMatch(/^file:\/\/\//);
-
- // Must not contain Windows drive letters (e.g., C:/ or C:\)
- expect(img.src, `Image src should not contain Windows drive letter: ${img.src}`).not.toMatch(
- /^[a-zA-Z]:[/\\]/
- );
+ // --- DOM-level check: image src attributes should not contain filesystem paths ---
+ const imageSrcs = await page.evaluate(() => {
+ const images = document.querySelectorAll("img");
+ return Array.from(images).map((img) => img.getAttribute("src") ?? img.src);
+ });
- // Must not contain URL-encoded backslashes (%5C)
- expect(img.src, `Image src should not contain encoded backslashes: ${img.src}`).not.toContain("%5C");
+ expect(imageSrcs.length, "Expected at least one image on /welcome").toBeGreaterThan(0);
- // Must not contain raw backslashes
- expect(img.src, `Image src should not contain backslashes: ${img.src}`).not.toContain("\\");
+ for (const src of imageSrcs) {
+ const pathIssue = looksLikeFilesystemPath(src);
+ expect(pathIssue, `Image src "${src}" ${pathIssue}`).toBeUndefined();
}
});
});