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.
This commit is contained in:
2026-08-05 13:23:07 +05:30
parent 8e24ed6375
commit c31e23c2b9
36 changed files with 20627 additions and 137 deletions
+90
View File
@@ -0,0 +1,90 @@
import { test, expect, APIRequestContext } from "@playwright/test"
import { ApiSeeder, newApiContext, Warehouse, Item, Uom, Vendor } from "../support/api"
import { StockTransferNewPage, StockTransferDetailPage } from "../pages/StockPages"
// Stock transfer flow (Backend/ERPCore/Controllers/StockTransfersController.cs, Frontend
// app/dashboard/stock/transfers/*): Draft -> Dispatch -> Receive, moving FIFO layers
// between warehouses.
test.describe("Stock transfers", () => {
let api: APIRequestContext
let seeder: ApiSeeder
let srcWarehouse: Warehouse
let destWarehouse: Warehouse
let vendor: Vendor
let uom: Uom
let item: Item
test.beforeAll(async () => {
api = await newApiContext()
seeder = new ApiSeeder(api)
srcWarehouse = await seeder.firstWarehouse()
destWarehouse = await seeder.secondWarehouse()
uom = await seeder.firstUom()
vendor = await seeder.createVendor()
item = await seeder.createItem({ namePrefix: "Transfer Test Item" })
await seeder.receiveStock({
warehouseId: srcWarehouse.warehouseId,
vendorId: vendor.vendorId,
itemId: item.itemId,
uomId: uom.uomId,
qty: 50,
unitCost: 15,
})
})
test.afterAll(async () => {
await api.dispose()
})
test("create -> dispatch -> receive moves stock between warehouses", async ({ page }) => {
const before = await seeder.stockOnHand(item.itemId, srcWarehouse.warehouseId)
const newPage = new StockTransferNewPage(page)
await newPage.goto()
await newPage.fill({ fromWarehouse: srcWarehouse.name, toWarehouse: destWarehouse.name, item: item.name, qty: 10 })
await newPage.submit()
await expect(page).toHaveURL(/\/dashboard\/stock\/transfers\/\d+/)
const detail = new StockTransferDetailPage(page)
await detail.expectStatus("Draft")
await detail.dispatch()
await detail.expectStatus("InTransit")
const afterDispatch = await seeder.stockOnHand(item.itemId, srcWarehouse.warehouseId)
// Dispatch consumes the source FIFO layers immediately (Backend/ERPCore/Services/Stock/
// StockService.cs: "Dispatch already consumed the source layers, so this stock has left
// onHand") - inTransit is reported for visibility only, not held back from onHand.
expect(afterDispatch.onHand).toBeCloseTo(before.onHand - 10, 4)
await detail.receive()
await detail.expectStatus("Received")
const destAfter = await seeder.stockOnHand(item.itemId, destWarehouse.warehouseId)
expect(destAfter.onHand).toBeGreaterThanOrEqual(10)
})
test("dispatch fails with insufficient stock and the transfer stays Draft", async ({ page }) => {
const newPage = new StockTransferNewPage(page)
await newPage.goto()
await newPage.fill({
fromWarehouse: srcWarehouse.name,
toWarehouse: destWarehouse.name,
item: item.name,
qty: 999_999,
})
await newPage.submit()
await expect(page).toHaveURL(/\/dashboard\/stock\/transfers\/\d+/)
const detail = new StockTransferDetailPage(page)
await detail.expectStatus("Draft")
await detail.dispatch()
// FifoCostingService rejects with 409 STOCK_NEGATIVE_BLOCKED - the frontend surfaces the
// error and leaves the transfer in Draft rather than advancing it.
await detail.expectStatus("Draft")
await expect(page.getByRole("button", { name: /^dispatch$/i })).toBeVisible()
})
})