c31e23c2b9
- 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.
126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
import { test, expect, APIRequestContext } from "@playwright/test"
|
|
import { ApiSeeder, newApiContext, Vendor, Warehouse, Item, Uom } from "../support/api"
|
|
import { GrnNewPage, GrnDetailPage } from "../pages/GrnPages"
|
|
|
|
// GRN receiving flow (Backend/ERPCore/Controllers/GrnsController.cs, Frontend
|
|
// app/dashboard/receiving/grn/*). Covers direct + against-PO receipts, confirm posting to
|
|
// the stock ledger, and per-line hold-status actions.
|
|
test.describe("GRN", () => {
|
|
let api: APIRequestContext
|
|
let seeder: ApiSeeder
|
|
let warehouse: Warehouse
|
|
let vendor: Vendor
|
|
let uom: Uom
|
|
let item: Item
|
|
|
|
test.beforeAll(async () => {
|
|
api = await newApiContext()
|
|
seeder = new ApiSeeder(api)
|
|
warehouse = await seeder.firstWarehouse()
|
|
uom = await seeder.firstUom()
|
|
vendor = await seeder.createVendor()
|
|
item = await seeder.createItem({ namePrefix: "GRN Test Item" })
|
|
})
|
|
|
|
test.afterAll(async () => {
|
|
await api.dispose()
|
|
})
|
|
|
|
test("direct receipt creates a Draft GRN, confirm posts stock", async ({ page }) => {
|
|
const grnNew = new GrnNewPage(page)
|
|
await grnNew.goto()
|
|
await grnNew.useDirectReceipt()
|
|
await grnNew.selectVendor(vendor.name)
|
|
await grnNew.selectWarehouse(warehouse.name)
|
|
await grnNew.fillFirstLine({ item: item.name, uom: uom.name, qty: 10, unitCost: 50 })
|
|
await grnNew.submit()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/receiving\/grn\/\d+/)
|
|
const grnDetail = new GrnDetailPage(page)
|
|
await grnDetail.expectStatus("Draft")
|
|
|
|
const before = await seeder.stockOnHand(item.itemId, warehouse.warehouseId)
|
|
|
|
await grnDetail.confirm()
|
|
await grnDetail.expectStatus("Confirmed")
|
|
|
|
const after = await seeder.stockOnHand(item.itemId, warehouse.warehouseId)
|
|
expect(after.onHand).toBeCloseTo(before.onHand + 10, 4)
|
|
})
|
|
|
|
test("against-PO receipt pre-fills vendor/warehouse from the PO", async ({ page }) => {
|
|
const po = await seeder.createPurchaseOrder({
|
|
vendorId: vendor.vendorId,
|
|
warehouseId: warehouse.warehouseId,
|
|
itemId: item.itemId,
|
|
uomId: uom.uomId,
|
|
qty: 5,
|
|
unitPrice: 40,
|
|
})
|
|
|
|
const grnNew = new GrnNewPage(page)
|
|
await grnNew.goto()
|
|
await grnNew.useAgainstPo()
|
|
await grnNew.selectPurchaseOrder(po.docNo)
|
|
await grnNew.fillFirstLine({ item: item.name, qty: 5 })
|
|
await grnNew.submit()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/receiving\/grn\/\d+/)
|
|
const grnDetail = new GrnDetailPage(page)
|
|
await grnDetail.expectStatus("Draft")
|
|
})
|
|
|
|
test("rejecting a Draft GRN's blocked submit (missing warehouse) keeps the user on the form", async ({ page }) => {
|
|
const grnNew = new GrnNewPage(page)
|
|
await grnNew.goto()
|
|
await grnNew.useDirectReceipt()
|
|
await grnNew.selectVendor(vendor.name)
|
|
// Warehouse intentionally left unselected.
|
|
await grnNew.fillFirstLine({ item: item.name, uom: uom.name, qty: 1, unitCost: 10 })
|
|
await grnNew.submit()
|
|
|
|
await expect(page).toHaveURL(/\/dashboard\/receiving\/grn\/new/)
|
|
})
|
|
|
|
test("releasing an on-hold line clears the hold and makes stock available", async ({ page }) => {
|
|
const grn = await seeder.receiveStockOnHold({
|
|
warehouseId: warehouse.warehouseId,
|
|
vendorId: vendor.vendorId,
|
|
itemId: item.itemId,
|
|
uomId: uom.uomId,
|
|
qty: 8,
|
|
unitCost: 12,
|
|
})
|
|
|
|
const grnDetail = new GrnDetailPage(page)
|
|
await grnDetail.gotoById(grn.grnId)
|
|
await grnDetail.expectStatus("Confirmed")
|
|
|
|
const before = await seeder.stockOnHand(item.itemId, warehouse.warehouseId)
|
|
expect(before.available).toBeLessThan(before.onHand) // held stock is on-hand but not available
|
|
|
|
await grnDetail.releaseFirstOnHoldLine()
|
|
|
|
const after = await seeder.stockOnHand(item.itemId, warehouse.warehouseId)
|
|
expect(after.available).toBeCloseTo(before.available + 8, 4)
|
|
})
|
|
|
|
test("rejecting an on-hold line surfaces a Create Return link", async ({ page }) => {
|
|
const grn = await seeder.receiveStockOnHold({
|
|
warehouseId: warehouse.warehouseId,
|
|
vendorId: vendor.vendorId,
|
|
itemId: item.itemId,
|
|
uomId: uom.uomId,
|
|
qty: 3,
|
|
unitCost: 12,
|
|
})
|
|
|
|
const grnDetail = new GrnDetailPage(page)
|
|
await grnDetail.gotoById(grn.grnId)
|
|
await grnDetail.expectStatus("Confirmed")
|
|
|
|
await grnDetail.rejectFirstOnHoldLine()
|
|
await grnDetail.expectCreateReturnLink()
|
|
})
|
|
})
|