153 lines
6.3 KiB
TypeScript
153 lines
6.3 KiB
TypeScript
/**
|
|
* E2E — Manager approval workflow.
|
|
* Covers: M-01 (approvals queue), M-02 (approve / approve+note),
|
|
* M-03 (reject with reason), M-04 (request edits, flag vendor ID).
|
|
*/
|
|
import { test, expect, type Page } from "@playwright/test";
|
|
import { fillFirstLineItem, fillPoHeader } from "./helpers/login";
|
|
|
|
const TECH = { email: "tech@pelagia.local", password: "tech1234" };
|
|
const MGR = { email: "manager@pelagia.local", password: "manager1234" };
|
|
|
|
async function login(page: Page, creds: typeof TECH) {
|
|
await page.goto("/login");
|
|
await page.getByLabel(/email/i).fill(creds.email);
|
|
await page.getByLabel(/password/i).fill(creds.password);
|
|
await page.getByRole("button", { name: /sign in/i }).click();
|
|
await expect(page).not.toHaveURL(/\/login/, { timeout: 20_000 });
|
|
}
|
|
|
|
/** Create + submit a PO as tech user, return the PO URL */
|
|
async function submitPoAsTech(page: Page, title: string): Promise<string> {
|
|
await login(page, TECH);
|
|
await page.goto("/po/new");
|
|
await fillPoHeader(page, title);
|
|
await fillFirstLineItem(page, {
|
|
name: "Engine filter",
|
|
quantity: "2",
|
|
unitPrice: "500",
|
|
});
|
|
await page.getByRole("button", { name: /submit for approval/i }).click();
|
|
await expect(page).toHaveURL(/\/po\//);
|
|
return page.url();
|
|
}
|
|
|
|
// ── M-01: Approvals queue ────────────────────────────────────────────────────
|
|
|
|
test("M-01 — manager sees approvals queue with pending POs", async ({ page }) => {
|
|
await login(page, MGR);
|
|
await page.goto("/approvals");
|
|
await expect(page.getByRole("heading", { name: /approval/i })).toBeVisible();
|
|
// Should have table or list of POs
|
|
await expect(page.locator("table, [role='list']")).toBeVisible();
|
|
});
|
|
|
|
test("M-01 — manager can search approvals queue by PO number", async ({ page }) => {
|
|
await login(page, MGR);
|
|
await page.goto("/approvals");
|
|
const searchInput = page.getByPlaceholder(/search|po number/i);
|
|
if (await searchInput.isVisible()) {
|
|
await searchInput.fill("PO-");
|
|
await expect(searchInput).toHaveValue("PO-");
|
|
}
|
|
});
|
|
|
|
// ── M-02: Approve ─────────────────────────────────────────────────────────────
|
|
|
|
test("M-02 — manager can approve a submitted PO", async ({ page, context }) => {
|
|
const title = `E2E_MGR_APPROVE_${Date.now()}`;
|
|
await submitPoAsTech(page, title);
|
|
const poUrl = page.url();
|
|
|
|
// Switch to manager session
|
|
await context.clearCookies();
|
|
await login(page, MGR);
|
|
await page.goto(poUrl);
|
|
|
|
await expect(page.getByText(/under review/i)).toBeVisible();
|
|
await page.getByRole("button", { name: /^approve$/i }).click();
|
|
await expect(page.getByText(/approved/i)).toBeVisible();
|
|
});
|
|
|
|
test("M-02 — manager can approve with a note", async ({ page, context }) => {
|
|
const title = `E2E_MGR_APPRNOTE_${Date.now()}`;
|
|
await submitPoAsTech(page, title);
|
|
const poUrl = page.url();
|
|
|
|
await context.clearCookies();
|
|
await login(page, MGR);
|
|
await page.goto(poUrl);
|
|
|
|
await page.getByRole("button", { name: /approve.*note/i }).click();
|
|
const noteInput = page.getByPlaceholder(/note|comment/i).first();
|
|
await noteInput.fill("Approved — please expedite delivery");
|
|
await page.getByRole("button", { name: /^approve$/i }).last().click();
|
|
await expect(page.getByText(/approved/i)).toBeVisible();
|
|
});
|
|
|
|
// ── M-03: Reject ─────────────────────────────────────────────────────────────
|
|
|
|
test("M-03 — manager can reject a PO with a reason", async ({ page, context }) => {
|
|
const title = `E2E_MGR_REJECT_${Date.now()}`;
|
|
await submitPoAsTech(page, title);
|
|
const poUrl = page.url();
|
|
|
|
await context.clearCookies();
|
|
await login(page, MGR);
|
|
await page.goto(poUrl);
|
|
|
|
await page.getByRole("button", { name: /reject/i }).click();
|
|
const noteInput = page.getByPlaceholder(/reason|note/i).first();
|
|
await noteInput.fill("Budget not available for this quarter");
|
|
await page.getByRole("button", { name: /^reject$/i }).last().click();
|
|
await expect(page.getByText(/rejected/i)).toBeVisible();
|
|
});
|
|
|
|
// ── M-04: Request edits ──────────────────────────────────────────────────────
|
|
|
|
test("M-04 — manager can request edits with a note", async ({ page, context }) => {
|
|
const title = `E2E_MGR_EDITS_${Date.now()}`;
|
|
await submitPoAsTech(page, title);
|
|
const poUrl = page.url();
|
|
|
|
await context.clearCookies();
|
|
await login(page, MGR);
|
|
await page.goto(poUrl);
|
|
|
|
await page.getByRole("button", { name: /request edits/i }).click();
|
|
const noteInput = page.getByPlaceholder(/reason|note/i).first();
|
|
await noteInput.fill("Please add vendor ID and update quantity");
|
|
await page.getByRole("button", { name: /request edits/i }).last().click();
|
|
await expect(page.getByText(/edits requested/i)).toBeVisible();
|
|
});
|
|
|
|
// ── M-04: Flag vendor ID ──────────────────────────────────────────────────────
|
|
|
|
test("M-04 — manager can flag a PO for vendor ID verification", async ({ page, context }) => {
|
|
const title = `E2E_MGR_VENDOR_${Date.now()}`;
|
|
await submitPoAsTech(page, title);
|
|
const poUrl = page.url();
|
|
|
|
await context.clearCookies();
|
|
await login(page, MGR);
|
|
await page.goto(poUrl);
|
|
|
|
const vendorIdBtn = page.getByRole("button", { name: /request vendor id|vendor id/i });
|
|
await expect(vendorIdBtn).toBeVisible();
|
|
await vendorIdBtn.click();
|
|
await expect(page.getByText(/vendor id pending/i)).toBeVisible();
|
|
});
|
|
|
|
// ── Manager dashboard ─────────────────────────────────────────────────────────
|
|
|
|
test("manager dashboard shows approvals queue link", async ({ page }) => {
|
|
await login(page, MGR);
|
|
await expect(page.getByRole("link", { name: /approval/i })).toBeVisible();
|
|
});
|
|
|
|
test("manager cannot create a new PO (no create_po permission)", async ({ page }) => {
|
|
await login(page, MGR);
|
|
await page.goto("/po/new");
|
|
// Should redirect away — managers can't create POs
|
|
await expect(page).not.toHaveURL(/\/po\/new/);
|
|
});
|