How to Test Shipping Rates and Fulfillment Status
Short answer
Shipping miscalculations erode margin; stale fulfillment status drives WISMO tickets. Test rate tables and carrier selection with seeded addresses and cart weights, probe shipping line items at checkout, and assert fulfillment state transitions (allocated → shipped → delivered) via backend probes—not tracking link visibility alone.
Part of Testing Guides by business flow.
Who this is for
Ecommerce teams with zone-based rates, live carrier APIs (UPS, FedEx, Shippo), free-shipping thresholds, or split shipments from inventory nodes.
Why testing shipping matters
- Conversion — surprise surcharges at checkout abandon carts.
- Margin — free-shipping threshold off by $0.01 ships unprofitably.
- Support — "Where is my order?" when status stuck at
processing.
Complexity map
| Scenario | Edge case | Why tests break | Approach |
|---|---|---|---|
| Zone table | Remote postal code | Surcharge surprise | Seed address; probe rate |
| Free shipping threshold | Subtotal $49.99 vs $50 | Wrong free flag | Two seeded cart totals |
| Weight tier | Heavy item | Rate jump | Seed cart weight fixture |
| Carrier API timeout | 30s hang | Spinner forever | Stub carrier; probe fallback |
| Split shipment | Two warehouses | One tracking | Probe packages[] length |
| PO box restriction | Carrier blocks | Checkout allowed | Probe validation error |
| International | Customs not included | Dispute | Probe duties disclaimer flag |
| Pickup in store | Zero shipping | Wrong rate | Probe method=pickup |
| Label purchase | Post-pay async | Status lag | Poll fulfillment probe |
| Address validate | Smarty/USPS | Invalid accepted | Probe normalized address |
| Express vs ground | User selects express | Wrong service code | Probe selected_service |
Free shipping threshold
test('shipping free above threshold', async ({ request, page }) => {
await request.post('/api/test/seed-cart', {
data: { runId, subtotalCents: 4999, thresholdCents: 5000 },
});
await page.goto(`/checkout?runId=${runId}`);
let order = await request.get(`/api/test/probes/checkout-quote/${runId}`).then(r => r.json());
expect(order.shippingCents).toBeGreaterThan(0);
await request.post('/api/test/seed-cart', {
data: { runId, subtotalCents: 5000, thresholdCents: 5000 },
});
await page.reload();
order = await request.get(`/api/test/probes/checkout-quote/${runId}`).then(r => r.json());
expect(order.shippingCents).toBe(0);
});
Remote zone surcharge
await request.post('/api/test/seed-shipping-fixture', {
data: { runId, postalCode: '99501', country: 'US', cartWeightOz: 32 },
});
await page.goto(`/checkout?runId=${runId}`);
const quote = await request.get(`/api/test/probes/shipping-rates/${runId}`).then(r => r.json());
expect(quote.zone).toBe('remote_alaska');
expect(quote.amount).toBe(quote.expectedRemoteRate); // from fixture config
Fulfillment status transitions
const { orderId } = await seedPaidOrder(request, runId);
await expect.poll(async () => {
return (await request.get(`/api/test/probes/fulfillment/${orderId}`).then(r => r.json())).status;
}).toBe('allocated');
await request.post('/api/test/simulate-carrier-event', {
data: { orderId, event: 'shipped', tracking: '1Z999TEST' },
});
await expect.poll(async () => {
return (await request.get(`/api/test/probes/fulfillment/${orderId}`).then(r => r.json())).status;
}).toBe('shipped');
Customer-facing tracking page is optional Assert—probe is authoritative.
Carrier timeout fallback
Stub carrier API in test env to return 504; assert checkout shows degraded message and probe allows standard flat rate fallback if that is prod policy.
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| Assert "$5.99" label | Rate table changes | Probe cents + zone id |
| Single ZIP only | Remote zones untested | Fixture matrix |
| Live carrier every PR | Flaky + slow | Stub in CI; smoke nightly |
| Tracking link exists | Status still processing | Probe fulfillment status |
Example scenario
Situation: Cart subtotal crosses free-shipping threshold after coupon applied.
Expected outcome: Shipping line zero; total reflects discount + free ship consistently.
Why UI-only automation breaks: UI shows FREE shipping but probe charges standard rate on payment intent.
- Arrange: Seed cart at threshold-1 with valid coupon to threshold+.
- Act: Apply coupon at checkout.
- Assert: Probe shippingCents=0 and payment amount matches subtotal after discount.
TestChimp workflow: Track shipping_zone × carrier in TrueCoverage when express carrier use rises.
Same Arrange/Act/Assert pattern as expired-coupon checkout.
Connect scenarios to your QA workflow
Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).
Related scenarios
- Inventory stock — allocate before ship
- Tax regional pricing — tax on shipping
- Marketplace flows — vendor-specific shipping
External references
Frequently asked questions
How do I test free shipping thresholds?
Seed cart subtotal just below and above threshold in separate scenarios; probe shipping line item is zero or non-zero accordingly.
Should I call live carrier APIs in CI?
Stub or record fixtures for PR CI; optional nightly job hits sandbox carrier APIs for integration confidence.
How do I test split shipments?
Seed order lines from two warehouses, complete checkout, probe packages array length=2 with distinct tracking when each ships.
Fulfillment status vs tracking page—which to assert?
Probe backend fulfillment status for Assert; tracking page is secondary UX check.
How do I test remote zone surcharges?
Seed postal codes mapped to remote zones in your rate table fixture; probe zone id and amount—not only UI label.
PO box restrictions—how to test?
Seed PO box address with carrier that blocks; probe checkout validation error before payment.
Express carrier popular in prod but untested—what now?
Use TrueCoverage on shipping_zone × carrier. Run /testchimp evolve to add express scenarios when prod share grows.
Apply these patterns in your repo
Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.