How to Test Product Configurators and Custom Builds
Short answer
Configurators sell valid BOM combinations at correct dynamic prices. E2E must probe config hash rejection for incompatible options and price recalc after each toggle—not screenshot comparisons. Use compatibility matrix seeds, poll async pricing APIs, and assert checkout sends canonical config id to inventory fulfillment.
Part of Testing Guides by business flow.
Who this is for
Manufacturing, furniture, PC builder, and print-on-demand teams with interactive configurators (React, Three.js, or CPQ integrations).
Why testing configurators matters
- Fulfillment — unbuildable configs ship; returns spike.
- Revenue — stale price after option change undercharges.
- UX — saved builds lost on share link.
Complexity map
| Scenario | Edge case | Why tests break | Approach |
|---|---|---|---|
| Invalid combo | Red + XL disallowed | Still purchasable | Probe rejects config hash |
| Dependency | B requires A | Orphan selection | Probe validation on toggle |
| Price recalc | Async API 2s | Stale total checkout | Poll price probe after toggle |
| Default options | Hidden defaults | Wrong SKU | Probe resolved BOM |
| 3D/canvas | WebGL headless | Crash skipped | Separate visual job; probe price |
| Saved configuration | Share URL | Wrong hash load | Seed build id; probe options |
| Min/max constraints | Qty 0 | Checkout allowed | Probe min qty |
| Out of stock option | Option disabled late | Race | Seed stock 0 on option sku |
| Add-on bundle | Optional warranty | Price add-on | Probe line items |
| Export to PDF | Quote flow | Not order | Probe quote vs order mode |
| Localization | Option labels | Price same | Probe sku not label |
Invalid combination
await page.goto(`/configure/chair?runId=${runId}`);
await page.getByRole('button', { name: 'Red' }).click();
await page.getByRole('button', { name: 'XL' }).click();
const validation = await request.post('/api/test/probes/config/validate', {
data: { runId, productId: 'chair', options: { color: 'red', size: 'xl' } },
}).then(r => r.json());
expect(validation.valid).toBe(false);
await expect(page.getByRole('button', { name: 'Add to cart' })).toBeDisabled();
Async price recalc
await page.getByRole('button', { name: 'Leather upgrade' }).click();
await expect.poll(async () => {
const res = await request.get(`/api/test/probes/config/price/${runId}`);
return (await res.json()).totalCents;
}, { timeout: 10_000 }).toBe(129900);
await page.getByRole('button', { name: 'Add to cart' }).click();
const cart = await request.get(`/api/test/probes/cart/${runId}`).then(r => r.json());
expect(cart.lines[0].totalCents).toBe(129900);
Saved build share link
const { buildId } = await request.post('/api/test/probes/config/save', {
data: { runId, options: { color: 'blue', size: 'M' } },
}).then(r => r.json());
await page.goto(`/configure/chair?build=${buildId}`);
const loaded = await request.get(`/api/test/probes/config/load/${buildId}`).then(r => r.json());
expect(loaded.options).toMatchObject({ color: 'blue', size: 'M' });
Canvas / visual configurators
For canvas interactions, run visual smoke separately; E2E on every PR should still probe BOM and price after programmatic option API if UI is heavy.
Checkout with config id
Ensure cart POST sends canonical configId your warehouse reads:
await page.getByRole('button', { name: 'Add to cart' }).click();
const cart = await request.get(`/api/test/probes/cart/${runId}`).then(r => r.json());
expect(cart.lines[0].configId).toMatch(/^cfg_/);
expect(cart.lines[0].optionsHash).toBe(
(await request.get(`/api/test/probes/config/price/${runId}`).then(r => r.json())).optionsHash,
);
CI checklist
- Compatibility matrix loaded from same source as prod CPQ rules
- Price probe polled after every option toggle—no fixed sleeps
- Invalid combo negative test on every PR
- Visual/WebGL job separate from BOM probe job if needed
- Config seeds scoped to
runIdwhen configs persist to database
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| Screenshot price | Font/theme flake | Probe cents |
| Only happy path | Invalid combos ship | Matrix negatives |
| No post-toggle wait | Race on price | expect.poll |
| UI disabled only | API accepts invalid | Probe validate endpoint |
Example scenario
Situation: User selects incompatible color and size then attempts checkout.
Expected outcome: Checkout blocked; config validation fails server-side.
Why UI-only automation breaks: Submit disabled in UI but POST /cart accepts invalid hash.
- Arrange: Load configurator with compatibility matrix seed.
- Act: Select disallowed pair; attempt add to cart via API if UI disabled.
- Assert: Probe valid=false; cart empty; optional UI disabled state.
TestChimp workflow: Track config_options_hash popularity in TrueCoverage for rare combos in prod.
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
- Canvas interactions — WebGL configurators
- Ecommerce checkout — cart → pay
- Inventory — component SKUs
External references
Frequently asked questions
How do I test invalid configuration combinations?
Select disallowed option pair per compatibility rules, assert checkout blocked via probe or disabled submit—do not only check error toast.
Pricing API is slow—how to avoid flaky tests?
Use expect.poll on price probe after each toggle with 10–15s timeout; never fixed sleep.
Should I test 3D preview rendering in CI?
Probe BOM and price every PR; visual/WebGL smoke on separate job if needed—headless WebGL varies by runner.
How do saved configuration links work in tests?
Save config via API, navigate share URL, probe loaded options match saved hash.
Add-to-cart sends options or config id?
Assert probe on cart line stores canonical config_id your fulfillment system reads—not ad-hoc JSON from client only.
Out-of-stock option mid-session?
Seed option sku qty=0 after page load; probe toggle disabled and validate fails on submit.
Rare option combos popular in prod—how to cover?
TrueCoverage on config_options_hash. Run /testchimp evolve to add scenarios for top untested hashes with // @Scenario: tags.
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.