722b1e78ed
- 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.
83 lines
3.3 KiB
TypeScript
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()
|
|
}
|
|
}
|