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 annotations 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.