test(e2e): harden PO form selectors

This commit is contained in:
Hardik 2026-05-22 17:15:17 +05:30
parent 32ea27331c
commit 934979750f
7 changed files with 92 additions and 56 deletions

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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");
});
});

View file

@ -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 ───────────────────────────────────────────────