test(e2e): e2e_image_outcomes — fixture image host, per-image outcomes + notice band (SD-0002 §6.8)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 00:41:39 -07:00
parent d81215be1d
commit 0775537d4c
8 changed files with 61 additions and 0 deletions
+1
View File
@@ -1,4 +1,5 @@
node_modules/
.backend.log
.objectstore/
test-results/
playwright-report/
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 B

+4
View File
@@ -0,0 +1,4 @@
Handle,Title,Image Src
good-lamp,Good Lamp,http://127.0.0.1:8799/good.png
tiny-lamp,Tiny Lamp,http://127.0.0.1:8799/tiny.png
gone-lamp,Gone Lamp,http://127.0.0.1:8799/missing.png
1 Handle Title Image Src
2 good-lamp Good Lamp http://127.0.0.1:8799/good.png
3 tiny-lamp Tiny Lamp http://127.0.0.1:8799/tiny.png
4 gone-lamp Gone Lamp http://127.0.0.1:8799/missing.png
+10
View File
@@ -83,6 +83,16 @@ export async function importGoodCsv(page: Page) {
await expect(page.getByRole("heading", { level: 1, name: "good.csv" })).toBeVisible();
}
// Import with-images.csv (3 products, each with one Image Src) and confirm it.
// Mirrors importGoodCsv; callers continue from the run-detail screen, where the
// background image fetch progresses fetching_images → terminal.
export async function importWithImages(page: Page) {
await uploadFixture(page, "with-images.csv");
await expect(page.getByRole("heading", { name: "Import preview — with-images.csv" })).toBeVisible();
await page.getByRole("button", { name: "Import 3 products" }).click();
await expect(page.getByRole("heading", { level: 1, name: "with-images.csv" })).toBeVisible();
}
// Click an Export status option and capture the downloaded CSV's text + path.
export async function exportCatalog(page: Page, label: string): Promise<{ text: string; path: string }> {
// Open the <details> menu, then click the status option, capturing the download.
+19
View File
@@ -0,0 +1,19 @@
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const dir = join(dirname(fileURLToPath(import.meta.url)), "fixtures", "images");
createServer(async (req, res) => {
const name = (req.url || "").replace(/^\//, "").split("?")[0];
if (name === "good.png" || name === "tiny.png") {
try {
const buf = await readFile(join(dir, name));
res.writeHead(200, { "Content-Type": "image/png" });
res.end(buf);
return;
} catch { /* fallthrough */ }
}
res.writeHead(404);
res.end();
}).listen(8799, "127.0.0.1");
+11
View File
@@ -18,6 +18,17 @@ PY
export ECOMM_DATABASE_URL="postgresql://ecomm:ecomm@localhost:5432/ecomm_e2e"
export ECOMM_MAILER=log
# Image pipeline (SLICE-7): local objectstore + a fixture image host on :8799.
# ECOMM_IMAGE_FETCH_ALLOW_PRIVATE lets the SSRF guard reach 127.0.0.1 — TEST-ONLY,
# never set in the deployed overlay. The &-backgrounded node host is a child of
# this serve process; Playwright tears the whole webServer group down between runs.
export ECOMM_OBJECTSTORE_KIND=local
export ECOMM_OBJECTSTORE_DIR="$repo_root/e2e/.objectstore"
export ECOMM_IMAGE_FETCH_ALLOW_PRIVATE=1
rm -rf "$repo_root/e2e/.objectstore"
node "$repo_root/e2e/image-host.mjs" &
cd "$repo_root/backend"
exec "$repo_root/.venv/bin/python" -m uvicorn app.main:app --port 8765 \
> "$repo_root/e2e/.backend.log" 2>&1
+16
View File
@@ -0,0 +1,16 @@
import { expect, test } from "@playwright/test";
import { gotoProducts, importWithImages, signUpWithStorefront } from "../helpers";
test("e2e_image_outcomes: per-image outcomes, problem rows, notice band", async ({ page }) => {
await signUpWithStorefront(page);
await gotoProducts(page);
await importWithImages(page);
// Run detail polls fetching_images → terminal; wait for the outcome summary.
await expect(page.getByText("1 fetched · 1 rejected · 1 failed")).toBeVisible({ timeout: 30_000 });
// Problem images listed in the outcomes table.
await expect(page.getByText("tiny-lamp")).toBeVisible();
await expect(page.getByText("gone-lamp")).toBeVisible();
// Products page surfaces the aggregate notice.
await gotoProducts(page);
await expect(page.getByText(/products have image problems/)).toBeVisible();
});