test(e2e): harden PO form selectors
This commit is contained in:
parent
32ea27331c
commit
934979750f
7 changed files with 92 additions and 56 deletions
|
|
@ -3,6 +3,7 @@
|
|||
* Covers: A-01 (view payment queue), A-02 (mark PO as paid with reference).
|
||||
*/
|
||||
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" };
|
||||
|
|
@ -13,7 +14,7 @@ async function login(page: Page, creds: typeof TECH) {
|
|||
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/);
|
||||
await expect(page).not.toHaveURL(/\/login/, { timeout: 20_000 });
|
||||
}
|
||||
|
||||
/** Full flow: create PO as tech → approve as manager → return PO URL */
|
||||
|
|
@ -21,12 +22,12 @@ async function createApprovedPo(page: Page, context: import("@playwright/test").
|
|||
// Step 1: create + submit as tech
|
||||
await login(page, TECH);
|
||||
await page.goto("/po/new");
|
||||
await page.getByLabel(/title/i).fill(title);
|
||||
await page.getByLabel(/vessel/i).selectOption({ index: 1 });
|
||||
await page.getByLabel(/account/i).selectOption({ index: 1 });
|
||||
await page.getByPlaceholder("Item description").fill("Deck paint");
|
||||
await page.getByRole("spinbutton").first().fill("3");
|
||||
await page.locator("input[placeholder='0.00']").fill("800");
|
||||
await fillPoHeader(page, title);
|
||||
await fillFirstLineItem(page, {
|
||||
name: "Deck paint",
|
||||
quantity: "3",
|
||||
unitPrice: "800",
|
||||
});
|
||||
await page.getByRole("button", { name: /submit for approval/i }).click();
|
||||
await expect(page).toHaveURL(/\/po\//);
|
||||
const poUrl = page.url();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
* Created: 2026-05-17
|
||||
*/
|
||||
import { test, expect, type BrowserContext } from "@playwright/test";
|
||||
import { login, USERS } from "./helpers/login";
|
||||
import { fillFirstLineItem, fillPoHeader, login, USERS } from "./helpers/login";
|
||||
|
||||
async function driveEditHighlightFlow(
|
||||
page: import("@playwright/test").Page,
|
||||
|
|
@ -29,12 +29,12 @@ async function driveEditHighlightFlow(
|
|||
// Step 1: Submit as tech
|
||||
await login(page, USERS.TECH);
|
||||
await page.goto("/po/new");
|
||||
await page.getByLabel(/title/i).fill(title);
|
||||
await page.getByLabel(/vessel/i).selectOption({ index: 1 });
|
||||
await page.getByLabel(/account/i).selectOption({ index: 1 });
|
||||
await page.getByPlaceholder("Item description").fill("Original item");
|
||||
await page.getByRole("spinbutton").first().fill("2");
|
||||
await page.locator("input[placeholder='0.00']").fill("150");
|
||||
await fillPoHeader(page, title);
|
||||
await fillFirstLineItem(page, {
|
||||
name: "Original item",
|
||||
quantity: "2",
|
||||
unitPrice: "150",
|
||||
});
|
||||
await page.getByRole("button", { name: /submit for approval/i }).click();
|
||||
await expect(page).toHaveURL(/\/po\//, { timeout: 15_000 });
|
||||
const poUrl = page.url();
|
||||
|
|
|
|||
|
|
@ -28,20 +28,52 @@ export async function login(page: Page, creds: Credentials): Promise<void> {
|
|||
console.log(`✓ Logged in as ${creds.email}`);
|
||||
}
|
||||
|
||||
export async function fillPoHeader(page: Page, title: string): Promise<void> {
|
||||
await page.waitForSelector('input[name="title"]', { timeout: 15_000 });
|
||||
await page.locator('input[name="title"]').fill(title);
|
||||
await page.locator('select[name="vesselId"]').selectOption({ index: 1 });
|
||||
await page.locator('select[name="accountId"]').selectOption({ index: 1 });
|
||||
}
|
||||
|
||||
export async function fillFirstLineItem(
|
||||
page: Page,
|
||||
{
|
||||
name,
|
||||
quantity,
|
||||
unitPrice,
|
||||
description,
|
||||
}: {
|
||||
name: string;
|
||||
quantity: string;
|
||||
unitPrice: string;
|
||||
description?: string;
|
||||
}
|
||||
): Promise<void> {
|
||||
const lineItems = page.locator("section").filter({
|
||||
has: page.getByRole("heading", { name: /line items/i }),
|
||||
});
|
||||
const firstRow = lineItems.locator("tbody tr").first();
|
||||
|
||||
await firstRow.getByPlaceholder("Item name *").fill(name);
|
||||
if (description) {
|
||||
await firstRow.getByPlaceholder("Description (optional)").fill(description);
|
||||
}
|
||||
await firstRow.locator('input[type="number"]').first().fill(quantity);
|
||||
await firstRow.locator('input[placeholder="0.00"]').fill(unitPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a minimal draft PO and return the absolute PO URL.
|
||||
* Uses name-based selectors since the PO form labels have no htmlFor binding.
|
||||
*/
|
||||
export async function createDraftPo(page: Page, title: string): Promise<string> {
|
||||
await page.goto("/po/new");
|
||||
// Wait for the form to be ready
|
||||
await page.waitForSelector('input[name="title"]', { timeout: 15_000 });
|
||||
await page.locator('input[name="title"]').fill(title);
|
||||
await page.locator('select[name="vesselId"]').selectOption({ index: 1 });
|
||||
await page.locator('select[name="accountId"]').selectOption({ index: 1 });
|
||||
await page.getByPlaceholder("Item description").fill("Test item");
|
||||
await page.getByRole("spinbutton").first().fill("1");
|
||||
await page.locator("input[placeholder='0.00']").first().fill("100");
|
||||
await fillPoHeader(page, title);
|
||||
await fillFirstLineItem(page, {
|
||||
name: "Test item",
|
||||
quantity: "1",
|
||||
unitPrice: "100",
|
||||
});
|
||||
await page.getByRole("button", { name: /save as draft/i }).click();
|
||||
await expect(page).toHaveURL(/\/po\//, { timeout: 20_000 });
|
||||
return page.url();
|
||||
|
|
@ -50,13 +82,12 @@ export async function createDraftPo(page: Page, title: string): Promise<string>
|
|||
/** Create a PO and submit it for approval; returns the PO URL. */
|
||||
export async function submitPo(page: Page, title: string): Promise<string> {
|
||||
await page.goto("/po/new");
|
||||
await page.waitForSelector('input[name="title"]', { timeout: 15_000 });
|
||||
await page.locator('input[name="title"]').fill(title);
|
||||
await page.locator('select[name="vesselId"]').selectOption({ index: 1 });
|
||||
await page.locator('select[name="accountId"]').selectOption({ index: 1 });
|
||||
await page.getByPlaceholder("Item description").fill("Engine gasket");
|
||||
await page.getByRole("spinbutton").first().fill("2");
|
||||
await page.locator("input[placeholder='0.00']").first().fill("250");
|
||||
await fillPoHeader(page, title);
|
||||
await fillFirstLineItem(page, {
|
||||
name: "Engine gasket",
|
||||
quantity: "2",
|
||||
unitPrice: "250",
|
||||
});
|
||||
await page.getByRole("button", { name: /submit for approval/i }).click();
|
||||
await expect(page).toHaveURL(/\/po\//, { timeout: 20_000 });
|
||||
return page.url();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* 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" };
|
||||
|
|
@ -13,19 +14,19 @@ async function login(page: Page, creds: typeof TECH) {
|
|||
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/);
|
||||
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 page.getByLabel(/title/i).fill(title);
|
||||
await page.getByLabel(/vessel/i).selectOption({ index: 1 });
|
||||
await page.getByLabel(/account/i).selectOption({ index: 1 });
|
||||
await page.getByPlaceholder("Item description").fill("Engine filter");
|
||||
await page.getByRole("spinbutton").first().fill("2");
|
||||
await page.locator("input[placeholder='0.00']").fill("500");
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* Verifies export buttons are present and the export endpoints respond correctly.
|
||||
*/
|
||||
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" };
|
||||
|
|
@ -12,17 +13,17 @@ async function login(page: Page, creds: typeof TECH) {
|
|||
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/);
|
||||
await expect(page).not.toHaveURL(/\/login/, { timeout: 20_000 });
|
||||
}
|
||||
|
||||
async function createDraftPo(page: Page, title: string): Promise<string> {
|
||||
await page.goto("/po/new");
|
||||
await page.getByLabel(/title/i).fill(title);
|
||||
await page.getByLabel(/vessel/i).selectOption({ index: 1 });
|
||||
await page.getByLabel(/account/i).selectOption({ index: 1 });
|
||||
await page.getByPlaceholder("Item description").fill("Test item for export");
|
||||
await page.getByRole("spinbutton").first().fill("1");
|
||||
await page.locator("input[placeholder='0.00']").fill("100");
|
||||
await fillPoHeader(page, title);
|
||||
await fillFirstLineItem(page, {
|
||||
name: "Test item for export",
|
||||
quantity: "1",
|
||||
unitPrice: "100",
|
||||
});
|
||||
await page.getByRole("button", { name: /save as draft/i }).click();
|
||||
await expect(page).toHaveURL(/\/po\//);
|
||||
return page.url();
|
||||
|
|
|
|||
|
|
@ -83,7 +83,9 @@ test.describe("Feature 11 — User profile page & manager signature", () => {
|
|||
await login(page, USERS.TECH);
|
||||
await page.goto("/profile");
|
||||
|
||||
await expect(page.getByText(/change password/i)).toBeVisible();
|
||||
await expect(
|
||||
page.locator("section h2").filter({ hasText: /change password/i })
|
||||
).toBeVisible();
|
||||
console.log("✓ Change Password section visible on profile page");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* S-05 (view status), S-07 (edit and resubmit), S-08 (confirm receipt page visible).
|
||||
*/
|
||||
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" };
|
||||
|
|
@ -13,18 +14,17 @@ async function login(page: Page, creds: typeof TECH) {
|
|||
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/);
|
||||
await expect(page).not.toHaveURL(/\/login/, { timeout: 20_000 });
|
||||
}
|
||||
|
||||
async function fillNewPoForm(page: Page, title: string) {
|
||||
await page.goto("/po/new");
|
||||
await page.getByLabel(/title/i).fill(title);
|
||||
await page.getByLabel(/vessel/i).selectOption({ index: 1 });
|
||||
await page.getByLabel(/account/i).selectOption({ index: 1 });
|
||||
// Line item
|
||||
await page.getByPlaceholder("Item description").fill("Test marine part");
|
||||
await page.getByRole("spinbutton").first().fill("5");
|
||||
await page.locator("input[placeholder='0.00']").fill("100");
|
||||
await fillPoHeader(page, title);
|
||||
await fillFirstLineItem(page, {
|
||||
name: "Test marine part",
|
||||
quantity: "5",
|
||||
unitPrice: "100",
|
||||
});
|
||||
}
|
||||
|
||||
// ── S-02: Save as draft ──────────────────────────────────────────────────────
|
||||
|
|
@ -67,8 +67,8 @@ test("S-01 — new PO form shows structured T&C section with fixed first line",
|
|||
await login(page, TECH);
|
||||
await page.goto("/po/new");
|
||||
await expect(page.getByText(/please quote this purchase order/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/delivery/i).first()).toBeVisible();
|
||||
await expect(page.getByLabel(/payment terms/i)).toBeVisible();
|
||||
await expect(page.locator('input[name="tcDelivery"]')).toBeVisible();
|
||||
await expect(page.locator('input[name="tcPaymentTerms"]')).toBeVisible();
|
||||
});
|
||||
|
||||
// ── S-05: View status and activity ──────────────────────────────────────────
|
||||
|
|
@ -100,8 +100,8 @@ test("S-07 — submitter sees edit form pre-populated with existing values", asy
|
|||
await page.getByRole("link", { name: /edit/i }).click();
|
||||
await expect(page).toHaveURL(/\/edit$/);
|
||||
// Form should be pre-populated
|
||||
await expect(page.getByLabel(/title/i)).toHaveValue(title);
|
||||
await expect(page.getByPlaceholder("Item description")).toHaveValue("Test marine part");
|
||||
await expect(page.locator('input[name="title"]')).toHaveValue(title);
|
||||
await expect(page.getByPlaceholder("Item name *")).toHaveValue("Test marine part");
|
||||
});
|
||||
|
||||
// ── S-08: Confirm receipt page ───────────────────────────────────────────────
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue