mcpField
Q
qe-wms-testing-patterns

qe-wms-testing-patterns

github.com/proffesor-for-testing测试与质量51983 次安装今天更新
代码仓库

关于 qe-wms-testing-patterns

qe-wms-testing-patterns 汇集仓储管理系统(WMS)的测试模式,供测试人员验证库存、履约流程、EDI 和系统集成。内容涵盖常见场景、异常路径及 Agent 协作建议。

主要功能

  1. 01验证收货、上架、盘点与库存调整
  2. 02覆盖波次拣选、打包和发运流程
  3. 03测试 EDI 文档及业务规则
  4. 04检查 RF 与条码扫描流程
  5. 05验证分配、补货及 WMS-ERP 同步

适用场景

  1. 01排查库存差异并验证调整逻辑
  2. 02测试订单从波次到发运的全流程
  3. 03验证 EDI 文档和确认消息
  4. 04模拟扫描设备故障及系统集成异常

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/proffesor-for-testing/agentic-qe --skill qe-wms-testing-patterns

安装后在 Claude Code 中输入/qe-wms-testing-patterns确认已加载。

详细介绍

WMS 测试模式

<default_to_action> 测试仓库管理系统时:

  1. 验证库存准确性(收货、上架、循环盘点、调整)
  2. 端到端测试拣货/包装/发运工作流(波次发布 -> 发运确认)
  3. 验证 EDI 消息处理(856 ASN、940 订单、945 确认、943/944 库存)
  4. 断言 RF/条码扫描流程(扫描 -> 验证 -> 更新 -> 确认)
  5. 检验分配和补货逻辑(FIFO、FEFO、批次控制)
  6. 测试 WMS-ERP 集成(库存同步、订单状态、货物收货)
  7. 验证波次管理(波次计划、发布、缺货拣选处理)

快速模式选择:

  • 库存差异 -> 循环盘点和调整测试
  • 订单履约问题 -> 拣货/包装/发运工作流测试
  • EDI 故障 -> 消息格式和确认测试
  • 扫描问题 -> RF 设备模拟测试
  • 分配错误 -> 批次控制和 FIFO/FEFO 测试
  • 集成缺口 -> WMS-ERP 同步边界测试

关键成功因素:

  • WMS 准确性会直接影响客户体验和财务报告
  • 始终测试正常流程和异常流程(缺货拣选、货物损坏、退货)
  • EDI 测试必须涵盖语法验证和业务规则验证 </default_to_action>

快速参考卡

适用场景
  • 测试 WMS 平台(Blue Yonder/JDA、Manhattan Associates、SAP EWM、Oracle WMS)
  • 验证库存交易准确性(收货、拣货、调整)
  • 测试 EDI 单据交换(X12 856/940/945/943/944、EDIFACT DESADV/ORDERS)
  • 验证 RF/移动端扫描工作流
  • 测试波次管理和分配逻辑
  • 验证 WMS 与 ERP 集成(SAP MM/WM、Oracle)
测试级别
级别用途依赖项速度
单元逻辑分配/补货规则无快
API 契约WMS REST/SOAP 端点契约API 存根快
EDI 验证单据语法 + 业务规则EDI 解析器中
集成WMS-ERP 库存同步完整技术栈较慢
端到端工作流完整订单履约周期WMS + ERP + TMS慢
关键测试场景
场景必测内容示例
收货入库准确性PO 收货超收/少收容差
上架库位分配根据产品属性进行定向上架
拣货波次/批次拣货通过优先级分配处理多订单波次
包装包装验证纸箱化及重量/尺寸验证
发运承运商分配运费比价和标签生成
循环盘点库存准确性盲盘与引导式盘点对账
退货逆向物流RMA 收货、检验、处置
EDI 856ASN 生成发运确认会触发向客户发送正确的 ASN
EDI 940仓库订单入库订单创建正确的作业单
缺货拣选异常处理部分分配并创建欠单
工具
  • WMS 平台:Blue Yonder WMS、Manhattan WMOS、SAP EWM、Oracle WMS Cloud
  • EDI 测试:Bots EDI、SPS Commerce Test、Cleo Clarify
  • RF 模拟:自定义 RF 模拟器、Zebra SDK 测试框架
  • 集成:SAP PI/PO、MuleSoft、Dell Boomi
  • 监控:Splunk、WMS 仪表板、EDI 跟踪门户
Agent 协作
  • qe-middleware-validator:WMS-ERP 集成流程和消息转换验证
  • qe-contract-validator:EDI 单据契约和 WMS API 契约
  • qe-sap-idoc-tester:WMS-SAP IDoc 验证(WMMBID01、SHPCON)
  • qe-odata-contract-tester:SAP EWM OData 服务测试

库存交易测试

收货和上架
describe('Inbound Receipt - PO with Lot Control', () => {
  it('receives goods against PO with lot tracking', async () => {
    const receipt = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 100,
      lot: 'LOT-2026-001',
      expiryDate: '2027-06-30',
      uom: 'EA'
    });

    expect(receipt.status).toBe('RECEIVED');
    expect(receipt.quantityReceived).toBe(100);
    expect(receipt.lot).toBe('LOT-2026-001');

    // Verify putaway task was created
    const putawayTask = await wms.getTask(receipt.putawayTaskId);
    expect(putawayTask.type).toBe('DIRECTED_PUTAWAY');
    expect(putawayTask.suggestedLocation).toMatch(/^BIN-/);
  });

  it('rejects over-receipt beyond tolerance', async () => {
    // PO line is for 100 EA, tolerance is 10%
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 115, // 15% over, exceeds 10% tolerance
      lot: 'LOT-2026-002'
    });

    expect(result.status).toBe('REJECTED');
    expect(result.reason).toContain('Over-receipt tolerance exceeded');
    expect(result.maxAllowed).toBe(110);
  });

  it('accepts under-receipt and creates remaining open quantity', async () => {
    const result = await wms.receive({
      po: 'PO-4500001234',
      item: 'MAT-100',
      quantity: 80,
      lot: 'LOT-2026-003'
    });

    expect(result.status).toBe('PARTIAL_RECEIPT');
    expect(result.quantityReceived).toBe(80);
    expect(result.remainingOpen).toBe(20);
  });
});

describe('Directed Putaway', () => {
  it('assigns location based on product attributes', async () => {
    // Hazmat product should go to hazmat zone
    const putaway = await wms.executePutaway({
      item: 'MAT-HAZ-001',
      attributes: { hazmat: true, temperature: 'ambient' },
      quantity: 50,
      lot: 'LOT-HAZ-001'
    });

    expect(putaway.location).toMatch(/^HAZ-/); // Hazmat zone prefix
    expect(putaway.status).toBe('PUTAWAY_COMPLETE');
  });

  it('falls back to overflow when primary zone is full', async () => {
    // Fill the primary zone first
    await wms.fillZone('ZONE-A', 100); // 100% capacity

    const putaway = await wms.executePutaway({
      item: 'MAT-200',
      quantity: 10,
      preferredZone: 'ZONE-A'
    });

    expect(putaway.location).toMatch(/^OVF-/); // Overflow zone
    expect(putaway.overflowReason).toBe('PRIMARY_ZONE_FULL');
  });
});
循环盘点和调整
describe('Cycle Count - Blind Count', () => {
  it('auto-approves variance within threshold', async () => {
    // System shows 100 EA, physical count is 98
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-01',
      item: 'MAT-100',
      countedQuantity: 98,
      countType: 'BLIND', // Counter does not see system quantity
      varianceThreshold: 5 // percent
    });

    expect(count.systemQuantity).toBe(100);
    expect(count.variance).toBe(-2);
    expect(count.variancePercent).toBeCloseTo(2.0);
    expect(count.autoApproved).toBe(true);
    expect(count.adjustmentPosted).toBe(true);
  });

  it('requires supervisor approval when variance exceeds threshold', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-02',
      item: 'MAT-200',
      countedQuantity: 80,
      countType: 'BLIND',
      varianceThreshold: 5 // 20% variance exceeds 5% threshold
    });

    expect(count.variancePercent).toBeCloseTo(20.0);
    expect(count.autoApproved).toBe(false);
    expect(count.adjustmentPosted).toBe(false);
    expect(count.status).toBe('PENDING_SUPERVISOR_APPROVAL');
  });

  it('triggers recount when variance is critical', async () => {
    const count = await wms.submitCycleCount({
      location: 'BIN-A-01-03',
      item: 'MAT-300',
      countedQuantity: 0, // System shows 50
      countType: 'BLIND',
      varianceThreshold: 5,
      criticalThreshold: 50
    });

    expect(count.variancePercent).toBe(100);
    expect(count.status).toBe('RECOUNT_REQUIRED');
    expect(count.recountAssigned).toBe(true);
    expect(count.recountAssignee).not.toBe(count.originalCounter);
  });
});

拣货/包装/发运工作流测试

波次管理
describe('Wave Planning and Release', () => {
  it('creates wave from eligible orders with batch-pick strategy', async () => {
    // Create 5 orders for wave planning
    const orders = await Promise.all(
      Array.from({ length: 5 }, (_, i) => wms.createOrder({
        orderId: `SO-100${i}`,
        lines: [
          { item: 'MAT-100', qty: 10 },
          { item: 'MAT-200', qty: 5 }
        ],
        priority: i === 0 ? 'RUSH' : 'STANDARD',
        shipBy: '2026-02-05'
      }))
    );

    const wave = await wms.planWave({
      strategy: 'BATCH_PICK',
      maxOrders: 10,
      cutoffTime: '2026-02-05T14:00:00Z'
    });

    expect(wave.orderCount).toBe(5);
    expect(wave.pickTasks).toBeGreaterThan(0);
    expect(wave.status).toBe('PLANNED');

    // Rush order should be first in pick sequence
    const firstTask = wave.pickTasks[0];
    expect(firstTask.orderId).toBe('SO-1000');
    expect(firstTask.priority).toBe('RUSH');
  });

  it('handles short-pick by creating backorder', async () => {
    // Only 5 EA available, order needs 10
    await wms.setInventory('BIN-B-01-01', 'MAT-500', 5);

    const order = await wms.createOrder({
      orderId: 'SO-SHORT',
      lines: [{ item: 'MAT-500', qty: 10 }]
    });

    const wave = await wms.planWave({ strategy: 'SINGLE_ORDER' });
    await wms.releaseWave(wave.id);

    // Attempt to pick
    const pickResult = await wms.confirmPick({
      taskId: wave.pickTasks[0].id,
      pickedQty: 5 // Only 5 available
    });

    expect(pickResult.status).toBe('SHORT_PICK');
    expect(pickResult.pickedQuantity).toBe(5);
    expect(pickResult.shortQuantity).toBe(5);
    expect(pickResult.backorderCreated).toBe(true);
    expect(pickResult.backorderId).toBeDefined();
  });
});
分配逻辑
describe('FIFO Allocation', () => {
  it('allocates oldest lot first', async () => {
    // Set up two lots with different receipt dates
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-OLD', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-FIFO', 50, { lot: 'LOT-NEW', receiptDate: '2026-02-01' });

    const allocation = await wms.allocate({
      item: 'MAT-FIFO',
      quantity: 30,
      method: 'FIFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-OLD');
    expect(allocation.quantity).toBe(30);
  });

  it('splits allocation across lots when oldest is insufficient', async () => {
    await wms.receiveInventory('MAT-SPLIT', 20, { lot: 'LOT-A', receiptDate: '2026-01-01' });
    await wms.receiveInventory('MAT-SPLIT', 50, { lot: 'LOT-B', receiptDate: '2026-01-15' });

    const allocation = await wms.allocate({
      item: 'MAT-SPLIT',
      quantity: 40,
      method: 'FIFO'
    });

    expect(allocation.splits).toHaveLength(2);
    expect(allocation.splits[0]).toEqual({ lot: 'LOT-A', quantity: 20 });
    expect(allocation.splits[1]).toEqual({ lot: 'LOT-B', quantity: 20 });
  });
});

describe('FEFO Allocation (First Expired, First Out)', () => {
  it('allocates lot with earliest expiry first', async () => {
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-LATE', expiry: '2027-12-31' });
    await wms.receiveInventory('MAT-FEFO', 50, { lot: 'LOT-EXP-SOON', expiry: '2026-06-30' });

    const allocation = await wms.allocate({
      item: 'MAT-FEFO',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-EXP-SOON');
  });

  it('excludes expired lots from allocation', async () => {
    await wms.receiveInventory('MAT-EXPIRED', 100, { lot: 'LOT-PAST', expiry: '2025-12-31' });
    await wms.receiveInventory('MAT-EXPIRED', 50, { lot: 'LOT-VALID', expiry: '2027-12-31' });

    const allocation = await wms.allocate({
      item: 'MAT-EXPIRED',
      quantity: 30,
      method: 'FEFO'
    });

    expect(allocation.allocatedLot).toBe('LOT-VALID');
    expect(allocation.excludedLots).toContain('LOT-PAST');
    expect(allocation.excludeReason).toBe('EXPIRED');
  });
});
包装和发运
describe('Pack Verification', () => {
  it('verifies pack contents match pick list', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-001',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 },
        { barcode: 'MAT-200-LOT002', qty: 5 }
      ]
    });

    expect(packResult.status).toBe('VERIFIED');
    expect(packResult.allItemsScanned).toBe(true);
    expect(packResult.weightValidation).toBe('WITHIN_TOLERANCE');
  });

  it('rejects pack with missing items', async () => {
    const packResult = await wms.packVerify({
      orderId: 'SO-1001',
      cartonId: 'CTN-002',
      scannedItems: [
        { barcode: 'MAT-100-LOT001', qty: 10 }
        // Missing MAT-200
      ]
    });

    expect(packResult.status).toBe('INCOMPLETE');
    expect(packResult.missingItems).toContainEqual({ item: 'MAT-200', expectedQty: 5 });
  });
});

describe('Shipping and Carrier Assignment', () => {
  it('rate-shops across carriers and selects cheapest', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1001',
      cartons: ['CTN-001'],
      carrierSelection: 'RATE_SHOP',
      eligibleCarriers: ['UPS', 'FEDEX', 'USPS']
    });

    expect(shipment.status).toBe('SHIPPED');
    expect(shipment.carrier).toBeDefined();
    expect(shipment.trackingNumber).toBeDefined();
    expect(shipment.rateShopResults).toHaveLength(3);
    expect(shipment.selectedRate).toBeLessThanOrEqual(
      Math.min(...shipment.rateShopResults.map(r => r.rate))
    );
  });

  it('generates shipping label and triggers EDI 856', async () => {
    const shipment = await wms.shipConfirm({
      orderId: 'SO-1002',
      cartons: ['CTN-003'],
      carrier: 'UPS',
      service: 'GROUND'
    });

    expect(shipment.label).toBeDefined();
    expect(shipment.label.format).toBe('ZPL');
    expect(shipment.edi856Generated).toBe(true);
    expect(shipment.edi856.transactionSetId).toMatch(/^856/);
  });
});

EDI 单据测试

EDI 856 - 高级发运通知
describe('EDI 856 ASN Generation', () => {
  it('generates valid 856 with correct hierarchical levels', async () => {
    const asn = await edi.generate856({
      shipmentId: 'SHP-001',
      carrier: { scac: 'UPSN', proNumber: 'PRO-12345' },
      orders: [{
        orderId: 'SO-1001',
        items: [
          { sku: 'MAT-100', qty: 10, lot: 'LOT-001', upc: '012345678901' }
        ]
      }]
    });

    // Validate hierarchical structure: Shipment -> Order -> Item
    expect(asn.segments.filter(s => s.id === 'HL')).toHaveLength(3);
    expect(asn.getSegment('BSN', 'BSN01')).toBe('00'); // Original
    expect(asn.getSegment('TD5', 'TD504')).toBe('UPSN');
    expect(asn.getSegment('SN1', 'SN102')).toBe('10');

    // Validate segment count and control numbers
    expect(asn.getSegment('CTT', 'CTT01')).toBe('1'); // 1 transaction
    expect(asn.getSegment('SE', 'SE01')).toBeDefined(); // Segment count
  });

  it('validates 997 functional acknowledgment for sent 856', async () => {
    const asn = await edi.send856('SHP-001');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.transactionSetId).toBe('997');
    expect(ack.acknowledgmentCode).toBe('A'); // Accepted
    expect(ack.referencedTransactionSet).toBe('856');
  });

  it('handles 997 rejection and triggers resend', async () => {
    const asn = await edi.send856('SHP-INVALID');
    const ack = await edi.receive997({ timeout: 30000 });

    expect(ack.acknowledgmentCode).toBe('R'); // Rejected
    expect(ack.errors).toContainEqual(
      expect.objectContaining({ segmentId: 'SN1', errorCode: '7' })
    );

    // Verify resend was triggered
    const resendLog = await edi.getResendLog('SHP-INVALID');
    expect(resendLog.attempts).toBeGreaterThanOrEqual(1);
  });
});
EDI 940 - 仓库发货订单
describe('EDI 940 Inbound Processing', () => {
  it('creates WMS order from valid 940 document', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(true);
    expect(result.wmsOrderId).toBeDefined();
    expect(result.lineCount).toBe(5);
    expect(result.allocationTriggered).toBe(true);
    expect(result.priorityMapped).toBe('STANDARD');
  });

  it('rejects 940 with unknown SKU and generates 997 rejection', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-invalid-sku.edi');
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.errors).toContainEqual(
      expect.objectContaining({ segment: 'LIN', error: 'Unknown SKU: INVALID-SKU' })
    );
    expect(result.ack997.code).toBe('R');
  });

  it('detects duplicate 940 by BSN reference number', async () => {
    const edi940 = loadEdiDocument('testdata/edi-940-standard.edi');

    // Process first time - success
    await edi.process940(edi940);

    // Process duplicate - should detect
    const result = await edi.process940(edi940);

    expect(result.orderCreated).toBe(false);
    expect(result.duplicate).toBe(true);
    expect(result.originalOrderId).toBeDefined();
  });
});
EDI 945 - 仓库发货通知
describe('EDI 945 Outbound Generation', () => {
  it('generates 945 on shipment confirmation', async () => {
    await wms.shipConfirm({ orderId: 'SO-1005', carrier: 'FEDEX' });

    const edi945 = await edi.getLatest945('SO-1005');

    expect(edi945.transactionSetId).toBe('945');
    expect(edi945.getSegment('W06', 'W0601')).toBe('N'); // Normal shipment
    expect(edi945.getSegment('W12', 'W1202')).toBeDefined(); // Quantity shipped
    expect(edi945.getSegment('N1', 'N102')).toBeDefined(); // Ship-to name
  });
});

RF/条码扫描流程测试

describe('RF Directed Pick Workflow', () => {
  let rfSession;

  beforeEach(async () => {
    rfSession = await rfSimulator.createSession({
      device: 'Zebra-MC9300',
      mode: 'directed-pick'
    });
  });

  it('completes directed pick with valid scans', async () => {
    // Step 1: Login
    const login = await rfSession.scan('badge', 'USER-PICKER-01');
    expect(login.screen).toBe('MAIN_MENU');

    // Step 2: Select pick task
    const taskSelect = await rfSession.selectMenu('PICK');
    expect(taskSelect.screen).toBe('PICK_TASK_ASSIGNED');
    expect(taskSelect.taskId).toBeDefined();

    // Step 3: Scan source location
    const locationScan = await rfSession.scan('location', 'BIN-A-01-01');
    expect(locationScan.screen).toBe('SCAN_ITEM');
    expect(locationScan.expectedItem).toBe('MAT-100');

    // Step 4: Scan item barcode
    const itemScan = await rfSession.scan('item', 'MAT-100-LOT001');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');
    expect(itemScan.maxQty).toBe(10);

    // Step 5: Enter quantity
    const qtyConfirm = await rfSession.enterQuantity(10);
    expect(qtyConfirm.screen).toBe('SCAN_DESTINATION');

    // Step 6: Scan staging lane
    const destScan = await rfSession.scan('location', 'STAGE-LANE-01');
    expect(destScan.screen).toBe('PICK_COMPLETE');
    expect(destScan.taskStatus).toBe('COMPLETED');
  });

  it('rejects wrong item scan with mismatch alert', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');

    const wrongItem = await rfSession.scan('item', 'MAT-999-WRONG');

    expect(wrongItem.screen).toBe('ITEM_MISMATCH');
    expect(wrongItem.alert).toBe('MISMATCH');
    expect(wrongItem.expectedItem).toBe('MAT-100');
    expect(wrongItem.scannedItem).toBe('MAT-999');
  });

  it('warns on over-pick quantity', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');
    await rfSession.scan('location', 'BIN-A-01-01');
    await rfSession.scan('item', 'MAT-100-LOT001');

    const overPick = await rfSession.enterQuantity(999);

    expect(overPick.screen).toBe('OVER_PICK_WARNING');
    expect(overPick.maxAllowed).toBe(10);
    expect(overPick.requiresSupervisor).toBe(true);
  });

  it('handles wrong location scan gracefully', async () => {
    await rfSession.scan('badge', 'USER-PICKER-01');
    await rfSession.selectMenu('PICK');

    const wrongLocation = await rfSession.scan('location', 'BIN-Z-99-99');

    expect(wrongLocation.screen).toBe('WRONG_LOCATION');
    expect(wrongLocation.expectedLocation).toBe('BIN-A-01-01');
    expect(wrongLocation.action).toBe('RESCAN');
  });
});

describe('RF Receiving Workflow', () => {
  it('receives goods by scanning PO and items', async () => {
    const rfSession = await rfSimulator.createSession({ mode: 'receiving' });

    await rfSession.scan('badge', 'USER-RECEIVER-01');
    const poScan = await rfSession.scan('document', 'PO-4500001234');
    expect(poScan.screen).toBe('SCAN_ITEM');
    expect(poScan.expectedItems).toHaveLength(3);

    const itemScan = await rfSession.scan('item', 'MAT-100');
    expect(itemScan.screen).toBe('ENTER_QUANTITY');

    const receipt = await rfSession.enterQuantity(100);
    expect(receipt.screen).toBe('SCAN_LOT');

    const lotScan = await rfSession.scan('lot', 'LOT-2026-050');
    expect(lotScan.screen).toBe('RECEIPT_CONFIRMED');
    expect(lotScan.quantityReceived).toBe(100);
  });
});

WMS-ERP 集成测试

SAP EWM 到 S/4HANA 同步
describe('WMS-ERP Goods Receipt Sync', () => {
  it('syncs goods receipt from EWM to S/4HANA within SLA', async () => {
    const startTime = Date.now();

    // Confirm inbound delivery in EWM
    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-001',
      items: [{ material: 'MAT-100', quantity: 100, batch: 'BATCH-001' }]
    });

    // Wait for sync to S/4HANA
    const materialDoc = await s4hana.waitForMaterialDocument({
      reference: 'IBD-001',
      timeout: 30000
    });

    const elapsed = Date.now() - startTime;

    expect(materialDoc.created).toBe(true);
    expect(materialDoc.type).toBe('WE'); // Goods receipt
    expect(materialDoc.quantity).toBe(100);
    expect(materialDoc.batch).toBe('BATCH-001');
    expect(materialDoc.plant).toBe('1000');
    expect(elapsed).toBeLessThan(30000); // 30s SLA
  });

  it('validates inventory quantities match between EWM and S/4HANA', async () => {
    const ewmStock = await ewm.getStock('MAT-100', 'WH-01');
    const s4Stock = await s4hana.getStock('MAT-100', '1000', '0001');

    expect(ewmStock.available).toBe(s4Stock.unrestricted);
    expect(ewmStock.allocated).toBe(s4Stock.reserved);
    expect(ewmStock.blocked).toBe(s4Stock.qualityInspection);
  });

  it('handles sync failure with IDoc error and retry', async () => {
    // Simulate S/4HANA unavailability
    await s4hana.simulateDowntime(10000);

    await ewm.confirmInboundDelivery({
      deliveryId: 'IBD-FAIL',
      items: [{ material: 'MAT-200', quantity: 50 }]
    });

    // Check IDoc status
    const idoc = await sap.getIdoc({ reference: 'IBD-FAIL', type: 'WMMBID01' });
    expect(idoc.status).toBe('03'); // Error status

    // Restore S/4HANA and verify auto-retry
    await s4hana.restoreService();
    const retried = await sap.waitForIdocStatus(idoc.id, '53', { timeout: 60000 });
    expect(retried.status).toBe('53'); // Successfully processed
  });
});
Manhattan WMOS 到 Oracle EBS 同步
describe('Manhattan WMOS to Oracle EBS', () => {
  it('posts inventory adjustment to Oracle via REST API', async () => {
    const adjustment = await wmos.postAdjustment({
      item: 'ITEM-500',
      location: 'LOC-A-01',
      adjustmentQty: -5,
      reason: 'DAMAGED'
    });

    const oracleTransaction = await oracle.waitForTransaction({
      reference: adjustment.transactionId,
      timeout: 15000
    });

    expect(oracleTransaction.transactionType).toBe('ADJUSTMENT');
    expect(oracleTransaction.quantity).toBe(-5);
    expect(oracleTransaction.reasonCode).toBe('DAMAGED');
    expect(oracleTransaction.accountPosted).toBe(true);
  });
});

退货与逆向物流

describe('Returns Processing', () => {
  it('processes RMA receipt with inspection and disposition', async () => {
    const rma = await wms.createRMA({
      rmaNumber: 'RMA-001',
      originalOrder: 'SO-1001',
      items: [{ sku: 'MAT-100', qty: 2, reason: 'DEFECTIVE' }]
    });

    // Receive return
    const receipt = await wms.receiveReturn({
      rmaId: rma.id,
      scannedItems: [{ barcode: 'MAT-100', qty: 2 }]
    });

    expect(receipt.status).toBe('RECEIVED_PENDING_INSPECTION');

    // Inspect returned items
    const inspection = await wms.inspectReturn({
      rmaId: rma.id,
      results: [
        { item: 'MAT-100', unit: 1, condition: 'RESTOCK', grade: 'A' },
        { item: 'MAT-100', unit: 2, condition: 'SCRAP', grade: 'F' }
      ]
    });

    expect(inspection.disposition.restock).toBe(1);
    expect(inspection.disposition.scrap).toBe(1);
    expect(inspection.restockLocation).toBeDefined();
  });
});

补货测试

describe('Replenishment Triggers', () => {
  it('triggers min/max replenishment when pick face drops below min', async () => {
    // Set pick face to minimum threshold
    await wms.setInventory('PICK-A-01', 'MAT-100', 5); // Min = 10, Max = 50

    const replenishment = await wms.checkReplenishment('PICK-A-01', 'MAT-100');

    expect(replenishment.triggered).toBe(true);
    expect(replenishment.type).toBe('MIN_MAX');
    expect(replenishment.replenishQty).toBe(45); // Fill to max (50 - 5)
    expect(replenishment.sourceLocation).toMatch(/^BULK-/);
  });

  it('triggers demand-based replenishment from wave allocation', async () => {
    // Wave needs 100 EA, pick face only has 20
    await wms.setInventory('PICK-B-01', 'MAT-200', 20);

    const wave = await wms.planWave({
      strategy: 'SINGLE_ORDER',
      orders: [{ orderId: 'SO-REPLEN', lines: [{ item: 'MAT-200', qty: 100 }] }]
    });

    const replenishment = await wms.getReplenishmentTasks(wave.id);

    expect(replenishment).toHaveLength(1);
    expect(replenishment[0].type).toBe('DEMAND');
    expect(replenishment[0].quantity).toBe(80); // 100 needed - 20 available
    expect(replenishment[0].priority).toBe('URGENT');
  });
});

最佳实践

建议这样做
  • 测试完整生命周期:收货 -> 上架 -> 分配 -> 拣货 -> 包装 -> 发货 -> EDI
  • 在每次状态转换时验证库存准确性(现有库存、已分配、运输中)
  • 测试 WMS 的所有分配方式(FIFO、FEFO、LIFO、区域优先级)
  • 模拟事务处理中途 RF 设备故障(电池耗尽、WiFi 断连、扫描超时)
  • 验证每份出站文档的 EDI 确认(997)
  • 明确测试拣货不足、超量拣货和拣错场景
  • 根据 SLA 阈值验证 WMS-ERP 同步延迟
  • 在多个阈值下测试循环盘点差异(自动批准、主管审核、重新盘点)
避免这样做
  • 仅测试正常收货流程,不测试超差/欠差场景
  • 跳过 RF 错误流程(扫描错误、超时、连接丢失)
  • 忽略 EDI 段级验证(仅相信文档级验证通过)
  • 测试分配逻辑时不考虑已过期批次
  • 假设 WMS-ERP 同步是即时的;始终使用时间断言进行测试
  • 未测试混合优先级订单的波次规划就部署
  • 在包装验证中忽略装箱和重量验证

Agent 辅助的 WMS 测试

// Validate EDI document contracts
await Task("EDI Contract Validation", {
  documents: ['856-ASN', '940-Order', '945-Advice', '943-StockTransfer'],
  validateSegments: true,
  checkAcknowledgments: true,
  testErrorScenarios: ['invalid-sku', 'duplicate-bsn', 'missing-required']
}, "qe-contract-validator");

// Test WMS-ERP integration flows
await Task("WMS-ERP Integration Test", {
  source: 'SAP-EWM',
  target: 'SAP-S4HANA',
  flows: ['goods-receipt', 'goods-issue', 'stock-transfer', 'inventory-adjustment'],
  slaThreshold: 30000,
  validateIdocs: true
}, "qe-middleware-validator");

// Validate SAP IDoc structures
await Task("IDoc Structure Validation", {
  idocTypes: ['WMMBID01', 'SHPCON', 'MBGMCR01'],
  validateSegments: true,
  testMandatoryFields: true,
  crossReferenceWithEWM: true
}, "qe-sap-idoc-tester");

// Test EWM OData services
await Task("EWM OData Service Testing", {
  services: [
    '/sap/opu/odata/sap/API_WAREHOUSE_ORDER',
    '/sap/opu/odata/sap/API_PHYSICAL_INVENTORY_DOC'
  ],
  testCrud: true,
  validateNavigationProperties: true,
  testFilterAndPagination: true
}, "qe-odata-contract-tester");

Agent 协调提示

主要 Agent
  • qe-middleware-validator:WMS-ERP 集成流程测试和消息转换验证
  • qe-contract-validator:EDI 文档契约测试和 WMS API 契约测试
支持 Agent
  • qe-message-broker-tester:WMS 使用消息队列进行异步处理时
  • qe-odata-contract-tester:WMS 暴露 OData 服务(SAP EWM)时
  • qe-sap-idoc-tester:WMS-SAP 集成使用 IDoc(WMMBID01、SHPCON)时
协调模式
1. qe-contract-validator -> Validate EDI document schemas and segments
2. qe-middleware-validator -> Test WMS message flows and transformations
3. qe-message-broker-tester -> Verify async queue processing and DLQ
4. qe-odata-contract-tester -> Test WMS OData APIs (if SAP EWM)
5. qe-sap-idoc-tester -> Validate IDoc structure and content
记忆命名空间
aqe/wms-testing/
  inventory/         - Receipt, putaway, adjustment test results
  fulfillment/       - Pick/pack/ship workflow results
  edi/               - EDI document validation results
  rf-scanning/       - RF workflow simulation results
  allocation/        - FIFO/FEFO/lot control test results
  integration/       - WMS-ERP sync test results
  returns/           - RMA and reverse logistics results
Fleet 协调
const wmsFleet = await FleetManager.coordinate({
  strategy: 'wms-testing',
  agents: [
    'qe-contract-validator',     // EDI contracts and API contracts
    'qe-middleware-validator',    // WMS-ERP message flows
    'qe-sap-idoc-tester',        // IDoc validation
    'qe-odata-contract-tester'   // SAP EWM OData testing
  ],
  topology: 'mesh'
});

await wmsFleet.execute({
  workflows: [
    { name: 'inbound', stages: ['receive', 'putaway', 'inventory-sync'] },
    { name: 'outbound', stages: ['wave', 'pick', 'pack', 'ship', 'edi-856'] },
    { name: 'returns', stages: ['rma-receipt', 'inspection', 'disposition'] }
  ],
  testEdi: true,
  testRfScanning: true,
  testAllocation: true
});
QCSD 集成
  • 构思:HAS_WMS 标记会触发 WMS 专属质量标准(库存准确性、EDI 合规性)
  • 细化:SFDIPOT 纳入仓库运营产品因素(吞吐量、准确性、延迟)
  • 开发:覆盖率分析包括 EDI 文档解析路径和分配算法
  • 验证:流水线关卡包括 WMS 集成健康检查和 EDI 确认验证

相关技能


请记住

仓库管理系统是连接实体与数字世界的桥梁。库存一旦出错,客户就会收到错误的货物,财务报告会不准确,补货也会失败。测试每次库存状态转换、每个 EDI 文档字段,以及每种 RF 扫描异常。2% 的库存差异看似很小,但当它在 50 个仓库的 100,000 个 SKU 中累积时,影响就不容小觑。

**使用 Agent:**Agent 会逐段验证 EDI 文档结构,在 SLA 阈值内测试 WMS-ERP 同步,并大规模模拟 RF 扫描工作流。可使用 Agent 覆盖人工测试无法触及的分配方式、批次控制场景和 EDI 错误条件的组合爆炸空间。

评论

支持 Markdown