220 lines
8.8 KiB
JavaScript
220 lines
8.8 KiB
JavaScript
import { expect, test } from "@playwright/test";
|
|
|
|
const tinyPng = Buffer.from(
|
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=",
|
|
"base64",
|
|
);
|
|
|
|
test("signs in and publishes a listing after adding an inventory image", async ({
|
|
page,
|
|
}) => {
|
|
const email = process.env.INITIAL_ADMIN_EMAIL;
|
|
const password = process.env.INITIAL_ADMIN_PASSWORD;
|
|
|
|
test.skip(
|
|
!email || !password,
|
|
"Set INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD for the local authenticated workflow.",
|
|
);
|
|
|
|
await page.goto("/login");
|
|
await page.getByLabel("Email").fill(email);
|
|
await page.getByLabel("Password").fill(password);
|
|
await page.getByRole("button", { name: "Sign in" }).click();
|
|
await expect(page).toHaveURL(/\/admin$/);
|
|
await expect(
|
|
page.getByRole("heading", { name: "Collector workspace" }),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole("status", { name: "Loading inventory..." }),
|
|
).toHaveCount(0);
|
|
await expect(
|
|
page.getByRole("heading", { name: "At a glance" }),
|
|
).toBeVisible();
|
|
await expect(page.getByText("Active inventory")).toBeVisible();
|
|
await expect(
|
|
page
|
|
.locator(".dashboard-summary__grid div")
|
|
.filter({ hasText: "Missing images" })
|
|
.getByRole("link"),
|
|
).toHaveAttribute("href", "#inventory-title");
|
|
await expect(
|
|
page
|
|
.locator(".dashboard-summary__grid div")
|
|
.filter({ hasText: "Provider failures" })
|
|
.getByRole("link"),
|
|
).toHaveAttribute("href", "#pricing-title");
|
|
|
|
const category = page.locator("#product-category option").nth(1);
|
|
await expect(category).toBeAttached();
|
|
const productName = `Browser test collectible ${Date.now()}`;
|
|
await page.getByLabel("Name").fill(productName);
|
|
await page.getByLabel("Category").selectOption({ index: 1 });
|
|
await page.getByRole("button", { name: "Create product" }).click();
|
|
|
|
await expect(page.locator("#inventory-product option").nth(1)).toBeAttached();
|
|
const createdProductId = await page
|
|
.locator("#inventory-product")
|
|
.locator("option", { hasText: productName })
|
|
.getAttribute("value");
|
|
await page.locator("#mapping-product").selectOption(createdProductId);
|
|
await page.getByLabel("Provider ID").fill("fixture-provider");
|
|
await page.getByLabel("External identifier").fill("fixture-product-1");
|
|
await page.getByRole("button", { name: "Save provider mapping" }).click();
|
|
await expect(
|
|
page.getByRole("region", { name: "Active provider mappings" }),
|
|
).toContainText("fixture-provider");
|
|
await page.locator("#inventory-product").selectOption(createdProductId);
|
|
await page
|
|
.getByLabel("Condition", { exact: true })
|
|
.selectOption({ index: 1 });
|
|
await page.getByRole("button", { name: "Create copy" }).click();
|
|
|
|
const createdInventoryRow = page
|
|
.locator(
|
|
`section[aria-labelledby="inventory-title"] li[data-product-id="${createdProductId}"]`,
|
|
)
|
|
.last();
|
|
const createdInventoryId = await createdInventoryRow
|
|
.locator("a")
|
|
.getAttribute("href")
|
|
.then((href) => href.split("/").at(-2));
|
|
const inventoryLink = page.locator(
|
|
`a[href="/admin/inventory/${createdInventoryId}/images"]`,
|
|
);
|
|
await expect(inventoryLink).toBeVisible();
|
|
await page.locator("#acquisition-inventory").selectOption(createdInventoryId);
|
|
await page.getByLabel("Method").fill("PURCHASE");
|
|
await page.getByLabel("Acquisition date").fill("2026-09-09");
|
|
await page.getByLabel("Cost (cents)").fill("1000");
|
|
await page.getByLabel("Shipping (cents)").fill("100");
|
|
await page.getByLabel("Tax (cents)").fill("50");
|
|
await page.getByRole("button", { name: "Record acquisition" }).click();
|
|
await expect(
|
|
page.locator('section[aria-labelledby="acquisitions-title"] li').first(),
|
|
).toContainText("Basis $11.50");
|
|
await page.locator("#pricing-inventory").selectOption(createdInventoryId);
|
|
await page.getByLabel("Source identifier").fill("browser-price-1");
|
|
await page.getByLabel("Observed price (cents)").fill("2500");
|
|
const [observationResponse, valuationResponse] = await Promise.all([
|
|
page.waitForResponse((response) =>
|
|
response.url().includes("/admin/pricing/observations"),
|
|
),
|
|
page.waitForResponse(
|
|
(response) =>
|
|
response.url().includes("/valuation") &&
|
|
response.request().method() === "POST",
|
|
),
|
|
page.getByRole("button", { name: "Add observation and calculate" }).click(),
|
|
]);
|
|
expect(observationResponse.status()).toBe(201);
|
|
expect(valuationResponse.status()).toBe(200);
|
|
const valuationRow = page
|
|
.getByRole("list", { name: "Market valuations" })
|
|
.getByText(createdInventoryId);
|
|
await expect(valuationRow).toContainText("Market value $25.00");
|
|
await page.getByLabel("Manual value (cents)").fill("2700");
|
|
await page.getByLabel("Override reason").fill("Verified recent sale");
|
|
await page.getByRole("button", { name: "Save manual value" }).click();
|
|
await expect(
|
|
page.getByRole("list", { name: "Market valuations" }),
|
|
).toContainText("Market value $27.00");
|
|
await expect(
|
|
page.getByRole("list", { name: "Market valuations" }),
|
|
).toContainText("OVERRIDE confidence");
|
|
await inventoryLink.click();
|
|
await expect(page).toHaveURL(/\/admin\/inventory\/[^/]+\/images$/);
|
|
await expect(
|
|
page.getByRole("heading", { name: "Manage images" }),
|
|
).toBeVisible();
|
|
await page.getByLabel("Add image").setInputFiles({
|
|
name: "front.png",
|
|
mimeType: "image/png",
|
|
buffer: tinyPng,
|
|
});
|
|
await expect(page.getByLabel("Alt text")).toHaveValue("front.png");
|
|
|
|
await page.goto("/admin");
|
|
await expect(
|
|
page.getByRole("heading", { name: "Create listing draft" }),
|
|
).toBeVisible();
|
|
await page
|
|
.getByLabel("Inventory copy", { exact: true })
|
|
.first()
|
|
.selectOption(createdInventoryId);
|
|
await page.getByLabel("Asking price (cents)").fill("2499");
|
|
await page.getByRole("button", { name: "Create listing" }).click();
|
|
const createdListing = page
|
|
.locator('section[aria-labelledby="listings-title"] article')
|
|
.filter({ hasText: productName })
|
|
.last();
|
|
await expect(
|
|
createdListing.getByText("Add at least one image before publishing."),
|
|
).toHaveCount(0);
|
|
await createdListing.getByRole("button", { name: "Publish" }).click();
|
|
await expect(createdListing).toContainText("AVAILABLE");
|
|
|
|
await page.locator("#sale-inventory").selectOption(createdInventoryId);
|
|
await page.getByLabel("Sale date").fill("2026-09-09");
|
|
await page.getByLabel("Sold price (cents)").fill("2499");
|
|
await page.getByLabel("Cost basis (cents)").fill("1200");
|
|
await page.getByRole("button", { name: "Record sale" }).click();
|
|
await expect(
|
|
page
|
|
.locator('section[aria-labelledby="inventory-title"]')
|
|
.getByText(`${createdInventoryId} · SOLD`),
|
|
).toBeVisible();
|
|
await expect(
|
|
page.getByRole("heading", { name: "Sale history" }),
|
|
).toBeVisible();
|
|
const latestSale = page
|
|
.locator('section[aria-labelledby="sales-title"] li')
|
|
.first();
|
|
await expect(latestSale).toContainText("Profit $12.99");
|
|
await latestSale.getByLabel("Void reason").fill("Browser test correction");
|
|
await latestSale.getByRole("button", { name: "Void sale" }).click();
|
|
await expect(latestSale).toContainText("VOIDED");
|
|
await expect(
|
|
page
|
|
.locator('section[aria-labelledby="inventory-title"]')
|
|
.getByText(`${createdInventoryId} · ACTIVE`),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("manages settings, saved views, and export controls", async ({ page }) => {
|
|
const email = process.env.INITIAL_ADMIN_EMAIL;
|
|
const password = process.env.INITIAL_ADMIN_PASSWORD;
|
|
|
|
test.skip(
|
|
!email || !password,
|
|
"Set INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD for the local authenticated workflow.",
|
|
);
|
|
|
|
await page.goto("/login");
|
|
await page.getByLabel("Email").fill(email);
|
|
await page.getByLabel("Password").fill(password);
|
|
await page.getByRole("button", { name: "Sign in" }).click();
|
|
await expect(page).toHaveURL(/\/admin$/);
|
|
await expect(
|
|
page.getByRole("link", { name: "Download inventory JSON" }),
|
|
).toHaveAttribute("download", "inventory.json");
|
|
|
|
await page.goto("/admin/settings");
|
|
await expect(page.getByRole("heading", { name: "Settings" })).toBeVisible();
|
|
await page.getByLabel("Maximum images per item").fill("12");
|
|
await page.getByRole("button", { name: "Save settings" }).click();
|
|
await expect(page.getByRole("status")).toContainText("Settings saved.");
|
|
|
|
await page.goto("/admin/saved-views");
|
|
await expect(
|
|
page.getByRole("heading", { name: "Saved views", exact: true }),
|
|
).toBeVisible();
|
|
const viewName = `Browser view ${Date.now()}`;
|
|
await page.getByLabel("Name").fill(viewName);
|
|
await page.getByLabel("Filters (JSON)").fill('{"status":"ACTIVE"}');
|
|
await page.getByRole("button", { name: "Save view" }).click();
|
|
const savedView = page.getByRole("listitem").filter({ hasText: viewName });
|
|
await expect(savedView).toBeVisible();
|
|
await savedView.getByRole("button", { name: "Delete" }).click();
|
|
await expect(savedView).toHaveCount(0);
|
|
});
|