Files
ERP-core/Testing/e2e/pages/StockPages.ts
T
ImanThiyanga 722b1e78ed feat(e2e): add Playwright end-to-end tests for authentication, GRN, production, stock transfers, and adjustments
- Introduced Playwright configuration for e2e testing.
- Implemented authentication tests to validate login functionality.
- Created tests for GRN (Goods Receipt Note) to ensure proper stock handling.
- Developed production run tests to verify lifecycle and stock posting.
- Added stock transfer tests to check movement between warehouses.
- Implemented stock adjustment tests for positive and negative adjustments.
- Established API seeder for test data setup and verification.
- Enhanced utility functions for UI interactions and response handling.
2026-08-05 13:23:07 +05:30

83 lines
3.3 KiB
TypeScript

import { Page, expect } from "@playwright/test"
import { selectOption, comboboxByLabel } from "../support/ui"
// Locators verified against Frontend/erp-system/app/dashboard/stock/transfers/new/page.tsx,
// .../transfers/[id]/page.tsx, and .../stock/adjustments/new/page.tsx.
// Every page here intermittently throws a real React hydration error (#418) that blanks a
// random subset of Select triggers' placeholder text for that page's lifetime, without
// affecting their sibling <Label> or role="combobox" attribute (support/ui.ts has the full
// writeup). Triggers are located via comboboxByLabel() (label + role, no name lookup) instead
// of getByRole("combobox", { name }); the row-scoped item combo has no adjacent label and is
// instead found by position (it's the first combobox in the row).
export class StockTransferNewPage {
constructor(private readonly page: Page) {}
async goto() {
await this.page.goto("/dashboard/stock/transfers/new")
}
async fill(opts: { fromWarehouse: string; toWarehouse: string; item: string; qty: number }) {
await selectOption(this.page, comboboxByLabel(this.page, "From warehouse"), opts.fromWarehouse)
await selectOption(this.page, comboboxByLabel(this.page, "To warehouse"), opts.toWarehouse)
const row = this.page.locator("table tbody tr").first()
await selectOption(this.page, row.getByRole("combobox").first(), opts.item)
// The Qty <Input type="number"> carries no accessible name - it's the only number input in the row.
await row.locator('input[type="number"]').fill(String(opts.qty))
}
async submit() {
await this.page.getByRole("button", { name: /create transfer/i }).click()
}
}
export class StockTransferDetailPage {
constructor(private readonly page: Page) {}
async gotoById(transferId: number) {
await this.page.goto(`/dashboard/stock/transfers/${transferId}`)
}
/**
* TransferStatusBadge renders the raw enum literal ("Draft" | "InTransit" | "Received") -
* exact match, since "Received" is also a substring of the post-receive success panel's
* heading ("Received — destination layers created").
*/
async expectStatus(status: "Draft" | "InTransit" | "Received") {
await expect(this.page.getByText(status, { exact: true })).toBeVisible()
}
async dispatch() {
await this.page.getByRole("button", { name: /^dispatch$/i }).click()
}
async receive() {
await this.page.getByRole("button", { name: /^receive$/i }).click()
}
}
export class StockAdjustmentNewPage {
constructor(private readonly page: Page) {}
async goto() {
await this.page.goto("/dashboard/stock/adjustments/new")
}
async fill(opts: { warehouse: string; reasonCode: string; item: string; qtyDelta: number }) {
await selectOption(this.page, comboboxByLabel(this.page, "Warehouse"), opts.warehouse)
await selectOption(this.page, comboboxByLabel(this.page, "Reason code"), opts.reasonCode)
const row = this.page.locator("table tbody tr").first()
await selectOption(this.page, row.getByRole("combobox").first(), opts.item)
await row.getByPlaceholder(/e\.g\. -15 or 50/i).fill(String(opts.qtyDelta))
}
async submit() {
await this.page.getByRole("button", { name: /post adjustment/i }).click()
}
async expectPosted() {
await expect(this.page.getByRole("button", { name: /new adjustment/i })).toBeVisible()
}
}