Covers 21 user story groups extracted from the last 15+ feature commits: - rebrand.spec.ts — PPMS branding on login page, sidebar, and tab title - dashboard/po-status-badges.js — color-coded status badges for submitter & manager - po-submit-button.spec.ts — Submit for Approval button visibility on DRAFT POs - notification-bell.spec.ts — in-app bell icon, unread badge, and panel open - export-gate.spec.ts — export buttons gated on MGR_APPROVED+ status; 403 on pre-approval - payment-history.spec.ts — /payments/history accessible to ACCOUNTS; redirects others - partial-receipt.spec.ts — per-item delivery tracking UI on paid POs - vendor-auto-verify.spec.ts — vendor verification status visible in admin - admin-bordered-buttons.spec.ts — Edit/Deactivate/Delete have border classes on admin pages - profile.spec.ts — profile page loads for all roles; signature section for MANAGER/SUPERUSER - inventory/items-tags.spec.ts — Cheapest/Closest tags and auto-sort by distance - inventory/cart-icon.spec.ts — cart header icon with badge; item/vendor detail pages - mobile/desktop-required.spec.ts — Desktop Required overlay for non-mobile roles + sign-out - mobile/manager-approvals.spec.ts — mobile card layout; edit form hidden; action buttons visible - mobile/accounts-payments.spec.ts — ACCOUNTS payments queue and buttons on mobile - mobile/bottom-nav.spec.ts — Home/Approvals/Profile tabs for MANAGER; Home/Payments/Profile for ACCOUNTS - approvals-edit-highlight.spec.ts — diff indicators on resubmitted POs Also adds shared helpers/login.ts with USERS constants, login(), createDraftPo(), and submitPo() — all using name-attribute selectors since PO form labels have no htmlFor binding. Playwright config updated: workers capped at 2 locally (was unlimited) to prevent auth concurrency failures, and retries set to 1 locally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
128 lines
4.6 KiB
TypeScript
128 lines
4.6 KiB
TypeScript
/**
|
|
* User stories covered: Feature 10 — Admin bordered buttons
|
|
* - On /admin/vendors, Edit and Delete buttons have visible borders (not plain text links)
|
|
* - Same checks apply to /admin/users, /admin/vessels, /admin/accounts, /admin/products
|
|
*
|
|
* The fix replaced text-link style buttons with bordered buttons. We check that action
|
|
* buttons in admin tables do NOT have link-only styling (no underline-only appearance)
|
|
* and DO have a border or background CSS class.
|
|
*
|
|
* Selector strategy: Admin action buttons are rendered by EditVendorButton, ConfirmDeleteButton.
|
|
* We look for <button> or <a> elements in the last table column that have class attributes
|
|
* containing "border" — this is the key indicator of the style fix.
|
|
*
|
|
* Created: 2026-05-17
|
|
*/
|
|
import { test, expect } from "@playwright/test";
|
|
import { login, USERS } from "./helpers/login";
|
|
|
|
/** Assert that action buttons in the last column of an admin table have border classes. */
|
|
async function assertBorderedButtons(
|
|
page: import("@playwright/test").Page,
|
|
url: string
|
|
): Promise<void> {
|
|
await page.goto(url);
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// Find all action cells in the last column of the table body
|
|
// The admin pages use a consistent table layout where the last <td> holds action buttons
|
|
const actionCells = page.locator("table tbody tr td:last-child");
|
|
const cellCount = await actionCells.count();
|
|
|
|
if (cellCount === 0) {
|
|
console.log(` No table rows found on ${url} — skipping button check`);
|
|
return;
|
|
}
|
|
|
|
// Get all buttons/links within the action cells
|
|
const actionButtons = actionCells.locator("button, a");
|
|
const btnCount = await actionButtons.count();
|
|
|
|
if (btnCount === 0) {
|
|
console.log(` No action buttons found on ${url}`);
|
|
return;
|
|
}
|
|
|
|
// Verify at least one button has border-related class (not a plain text link)
|
|
let foundBorderedButton = false;
|
|
for (let i = 0; i < Math.min(btnCount, 10); i++) {
|
|
const cls = (await actionButtons.nth(i).getAttribute("class")) ?? "";
|
|
if (
|
|
cls.includes("border") ||
|
|
cls.includes("bg-") ||
|
|
cls.includes("btn") ||
|
|
cls.includes("rounded")
|
|
) {
|
|
foundBorderedButton = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect(foundBorderedButton).toBeTruthy();
|
|
console.log(`✓ Bordered/styled action buttons found on ${url}`);
|
|
}
|
|
|
|
test.describe("Feature 10 — Admin bordered buttons", () => {
|
|
test("US-10a: /admin/vendors action buttons have border classes", async ({
|
|
page,
|
|
}) => {
|
|
await login(page, USERS.ADMIN);
|
|
await assertBorderedButtons(page, "/admin/vendors");
|
|
});
|
|
|
|
test("US-10a: /admin/users action buttons have border classes", async ({
|
|
page,
|
|
}) => {
|
|
await login(page, USERS.ADMIN);
|
|
await assertBorderedButtons(page, "/admin/users");
|
|
});
|
|
|
|
test("US-10a: /admin/vessels action buttons have border classes", async ({
|
|
page,
|
|
}) => {
|
|
await login(page, USERS.ADMIN);
|
|
await assertBorderedButtons(page, "/admin/vessels");
|
|
});
|
|
|
|
test("US-10a: /admin/accounts action buttons have border classes", async ({
|
|
page,
|
|
}) => {
|
|
await login(page, USERS.ADMIN);
|
|
await assertBorderedButtons(page, "/admin/accounts");
|
|
});
|
|
|
|
test("US-10a: /admin/products action buttons have border classes", async ({
|
|
page,
|
|
}) => {
|
|
await login(page, USERS.ADMIN);
|
|
await assertBorderedButtons(page, "/admin/products");
|
|
});
|
|
|
|
test("US-10a: /admin/vendors Edit button is visually distinct from plain text", async ({
|
|
page,
|
|
}) => {
|
|
await login(page, USERS.ADMIN);
|
|
await page.goto("/admin/vendors");
|
|
await page.waitForLoadState("networkidle");
|
|
|
|
// The EditVendorButton renders as a <button> with some visual styling
|
|
// After the fix it should have border or background — not just text color
|
|
const editBtns = page.locator("table tbody tr td:last-child button").first();
|
|
if (await editBtns.isVisible()) {
|
|
const cls = (await editBtns.getAttribute("class")) ?? "";
|
|
// Must NOT be an unstyled anchor-look-alike; must have border or bg
|
|
const hasVisualStyle =
|
|
cls.includes("border") || cls.includes("bg-") || cls.includes("rounded");
|
|
expect(hasVisualStyle).toBeTruthy();
|
|
console.log(`✓ Edit button class: ${cls.slice(0, 80)}...`);
|
|
} else {
|
|
// Vendors table might use <span> wrapping both buttons
|
|
const spanBtn = page.locator("table tbody tr td:last-child span button").first();
|
|
const spanCls = (await spanBtn.getAttribute("class")) ?? "";
|
|
expect(
|
|
spanCls.includes("border") || spanCls.includes("bg-") || spanCls.includes("rounded")
|
|
).toBeTruthy();
|
|
console.log(`✓ Vendor edit button has visual styling`);
|
|
}
|
|
});
|
|
});
|