// DoD scenario e2e_import_errors_actionable (SD-0002 §6.8): row-level errors are // actionable — line, column, message — at the preview gate AND on the run report // card (PUC-5), while the good rows still import. import { expect, test } from "@playwright/test"; import { gotoProducts, signUpWithStorefront, uploadFixture } from "../helpers"; test("e2e_import_errors_actionable", async ({ page }) => { await signUpWithStorefront(page); await gotoProducts(page); await uploadFixture(page, "mixed-errors.csv"); // Preview tiles: two good products, one errored row. await expect( page.getByRole("heading", { name: "Import preview — mixed-errors.csv" }), ).toBeVisible(); await expect(page.getByRole("button", { name: "2 to add" })).toBeVisible(); await page.getByRole("button", { name: "1 errors" }).click(); // The error table names the line, the column, and the problem (actionable, PUC-5). const previewRow = page.locator(".errortable tbody tr"); await expect(previewRow).toHaveCount(1); await expect(previewRow.locator("td").nth(0)).toHaveText("3"); await expect(previewRow.locator("td").nth(1)).toHaveText("Variant Price"); await expect(previewRow.locator("td").nth(2)).toContainText("is not a price"); // Good rows still import. await page.getByRole("button", { name: "Import 2 products" }).click(); // Run detail: counts include the errored row, and the same error row persists. await expect(page.getByRole("heading", { level: 1, name: "mixed-errors.csv" })).toBeVisible(); await expect(page.getByText("2 added · 0 updated · 1 rows in error")).toBeVisible(); const runRow = page.locator(".errortable tbody tr"); await expect(runRow).toHaveCount(1); await expect(runRow.locator("td").nth(0)).toHaveText("3"); await expect(runRow.locator("td").nth(1)).toHaveText("Variant Price"); await expect(runRow.locator("td").nth(2)).toContainText("is not a price"); // The catalog gained the two good products. await gotoProducts(page); await expect(page.getByRole("heading", { level: 1, name: "Products · 2" })).toBeVisible(); });