All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 1m10s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 3s
Build & Deploy Costco Grocery List / deploy (push) Successful in 11s
Build & Deploy Costco Grocery List / notify (push) Successful in 1s
273 lines
8.3 KiB
TypeScript
273 lines
8.3 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
function seedAuthStorage(page: import("@playwright/test").Page) {
|
|
return page.addInitScript(() => {
|
|
localStorage.setItem("token", "test-token");
|
|
localStorage.setItem("userId", "1");
|
|
localStorage.setItem("role", "admin");
|
|
localStorage.setItem("username", "toast-user");
|
|
});
|
|
}
|
|
|
|
async function mockConfig(page: import("@playwright/test").Page) {
|
|
await page.route("**/config", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
maxFileSizeMB: 20,
|
|
maxImageDimension: 800,
|
|
imageQuality: 85,
|
|
}),
|
|
});
|
|
});
|
|
}
|
|
|
|
test("login failure shows inline error and error toast", async ({ page }) => {
|
|
await mockConfig(page);
|
|
await page.route("**/auth/login", async (route) => {
|
|
await route.fulfill({
|
|
status: 401,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ message: "Invalid credentials" }),
|
|
});
|
|
});
|
|
|
|
await page.goto("/login");
|
|
await page.getByPlaceholder("Username").fill("bad-user");
|
|
await page.getByPlaceholder("Password").fill("bad-password");
|
|
await page.getByRole("button", { name: "Login" }).click();
|
|
|
|
await expect(page.getByText("Invalid credentials")).toBeVisible();
|
|
await expect(page.locator(".action-toast.action-toast-error")).toContainText("Login failed");
|
|
await expect(page.locator(".action-toast.action-toast-error")).toContainText("Invalid credentials");
|
|
});
|
|
|
|
test("manage stores add success shows success toast", async ({ page }) => {
|
|
await seedAuthStorage(page);
|
|
await mockConfig(page);
|
|
|
|
let linkedStoreIds = [10];
|
|
const allStores = [
|
|
{ id: 10, name: "Costco North", location: "North", is_default: true },
|
|
{ id: 11, name: "Costco South", location: "South", is_default: false },
|
|
];
|
|
|
|
await page.route("**/households", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([{ id: 1, name: "Toast Home", role: "admin", invite_code: "ABCD1234" }]),
|
|
});
|
|
});
|
|
|
|
await page.route("**/stores/household/1", async (route) => {
|
|
const request = route.request();
|
|
if (request.method() === "GET") {
|
|
const payload = linkedStoreIds.map((id, index) => {
|
|
const store = allStores.find((candidate) => candidate.id === id);
|
|
return {
|
|
...store,
|
|
is_default: index === 0,
|
|
};
|
|
});
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(payload),
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (request.method() === "POST") {
|
|
const body = request.postDataJSON() as { storeId?: number };
|
|
if (body.storeId && !linkedStoreIds.includes(body.storeId)) {
|
|
linkedStoreIds = [...linkedStoreIds, body.storeId];
|
|
}
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({ ok: true }),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fallback();
|
|
});
|
|
|
|
await page.route("**/stores", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(allStores),
|
|
});
|
|
});
|
|
|
|
await page.goto("/manage?tab=stores");
|
|
await page.getByRole("button", { name: "+ Add Store" }).click();
|
|
await page.locator(".available-store-card").filter({ hasText: "Costco South" }).getByRole("button", { name: "Add" }).click();
|
|
|
|
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Added store");
|
|
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Costco South");
|
|
});
|
|
|
|
test("manage stores add failure shows normalized error toast", async ({ page }) => {
|
|
await seedAuthStorage(page);
|
|
await mockConfig(page);
|
|
|
|
const allStores = [
|
|
{ id: 10, name: "Costco North", location: "North", is_default: true },
|
|
{ id: 11, name: "Costco South", location: "South", is_default: false },
|
|
];
|
|
|
|
await page.route("**/households", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([{ id: 1, name: "Toast Home", role: "admin", invite_code: "ABCD1234" }]),
|
|
});
|
|
});
|
|
|
|
await page.route("**/stores/household/1", async (route) => {
|
|
const request = route.request();
|
|
if (request.method() === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([{ id: 10, name: "Costco North", location: "North", is_default: true }]),
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (request.method() === "POST") {
|
|
await route.fulfill({
|
|
status: 400,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
error: { message: "Store already linked to household" },
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fallback();
|
|
});
|
|
|
|
await page.route("**/stores", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify(allStores),
|
|
});
|
|
});
|
|
|
|
await page.goto("/manage?tab=stores");
|
|
await page.getByRole("button", { name: "+ Add Store" }).click();
|
|
await page.locator(".available-store-card").filter({ hasText: "Costco South" }).getByRole("button", { name: "Add" }).click();
|
|
|
|
await expect(page.locator(".action-toast.action-toast-error")).toContainText("Add store failed");
|
|
await expect(page.locator(".action-toast.action-toast-error")).toContainText("Store already linked to household");
|
|
});
|
|
|
|
test("invite accept JOINED shows success toast", async ({ page }) => {
|
|
await seedAuthStorage(page);
|
|
await mockConfig(page);
|
|
|
|
await page.route("**/api/invite-links/toast-token", async (route) => {
|
|
const request = route.request();
|
|
|
|
if (request.method() === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
link: {
|
|
token: "toast-token",
|
|
status: "ACTIVE",
|
|
viewerStatus: null,
|
|
active_policy: "AUTO_ACCEPT",
|
|
group_name: "Toast Group",
|
|
},
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
result: {
|
|
status: "JOINED",
|
|
group: { name: "Toast Group" },
|
|
},
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.route("**/households", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([{ id: 1, name: "Toast Home", role: "member", invite_code: "ABCD1234" }]),
|
|
});
|
|
});
|
|
|
|
await page.goto("/invite/toast-token");
|
|
await page.getByRole("button", { name: "Join Group" }).click();
|
|
|
|
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Joined group");
|
|
await expect(page.locator(".action-toast.action-toast-success")).toContainText("Toast Group");
|
|
});
|
|
|
|
test("invite accept PENDING shows info toast", async ({ page }) => {
|
|
await seedAuthStorage(page);
|
|
await mockConfig(page);
|
|
|
|
await page.route("**/api/invite-links/pending-token", async (route) => {
|
|
const request = route.request();
|
|
|
|
if (request.method() === "GET") {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
link: {
|
|
token: "pending-token",
|
|
status: "ACTIVE",
|
|
viewerStatus: null,
|
|
active_policy: "APPROVAL_REQUIRED",
|
|
group_name: "Pending Group",
|
|
},
|
|
}),
|
|
});
|
|
return;
|
|
}
|
|
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
result: {
|
|
status: "PENDING",
|
|
group: { name: "Pending Group" },
|
|
},
|
|
}),
|
|
});
|
|
});
|
|
|
|
await page.route("**/households", async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify([{ id: 1, name: "Toast Home", role: "member", invite_code: "ABCD1234" }]),
|
|
});
|
|
});
|
|
|
|
await page.goto("/invite/pending-token");
|
|
await page.getByRole("button", { name: "Join Group" }).click();
|
|
|
|
await expect(page.locator(".action-toast.action-toast-info")).toContainText("Join request sent");
|
|
await expect(page.locator(".action-toast.action-toast-info")).toContainText("Pending Group");
|
|
});
|