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`);
|
|
}
|
|
});
|
|
});
|