Skip to main content

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

ScenarioEdge caseWhy tests breakApproach
Invalid comboRed + XL disallowedStill purchasableProbe rejects config hash
DependencyB requires AOrphan selectionProbe validation on toggle
Price recalcAsync API 2sStale total checkoutPoll price probe after toggle
Default optionsHidden defaultsWrong SKUProbe resolved BOM
3D/canvasWebGL headlessCrash skippedSeparate visual job; probe price
Saved configurationShare URLWrong hash loadSeed build id; probe options
Min/max constraintsQty 0Checkout allowedProbe min qty
Out of stock optionOption disabled lateRaceSeed stock 0 on option sku
Add-on bundleOptional warrantyPrice add-onProbe line items
Export to PDFQuote flowNot orderProbe quote vs order mode
LocalizationOption labelsPrice sameProbe 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);
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

  1. Compatibility matrix loaded from same source as prod CPQ rules
  2. Price probe polled after every option toggle—no fixed sleeps
  3. Invalid combo negative test on every PR
  4. Visual/WebGL job separate from BOM probe job if needed
  5. Config seeds scoped to runId when configs persist to database

Anti-patterns

Anti-patternWhy it failsBetter approach
Screenshot priceFont/theme flakeProbe cents
Only happy pathInvalid combos shipMatrix negatives
No post-toggle waitRace on priceexpect.poll
UI disabled onlyAPI accepts invalidProbe 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.

  1. Arrange: Load configurator with compatibility matrix seed.
  2. Act: Select disallowed pair; attempt add to cart via API if UI disabled.
  3. 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).

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.

Start free on TestChimp · Book a demo