Testing primer — the Playwright suite, in plain terms
Audience: anyone new to
tests/· Scope: the concepts and vocabulary behind the functional test suite — what kind of tests these are, how they're built, and the words to use · Last reviewed: 2026-07-11Part of Testing. The how-to is guide.md; the reusable state/fixture commands are support-api.md; a catalog of the specs is catalog.md; install & run is
tests/README.md.
What kind of tests are these?
End-to-end (E2E) functional tests, driven through a real browser with Playwright. "End-to-end" because each test exercises the whole stack the way a person would — click in the browser → ConfigBox controller → model → database → the outbound Pipedrive call. We don't test functions in isolation (that would be unit testing); we verify observable behaviour across the system.
In practice they double as integration tests for the Pipedrive sync: the headline assertion is usually "did the right data get sent to Pipedrive?", checked against the intercepted request.
Every test follows Arrange → Act → Assert (AAA):
| Phase | Here it means | Example |
|---|---|---|
| Arrange | Get the system into a known state (a.k.a. fixtures / seeding) | log in as the agent; create a realistic quote |
| Act | Perform one real user action in the browser | mark a quote "Lost"; change a line-item quantity |
| Assert | Check the outcome in two places | the quote row in the DB and the captured Pipedrive payload |
The central trick: intercept the boundary, don't call Pipedrive
There's no Pipedrive sandbox — only production. So we never let a test hit Pipedrive. Every outbound
write funnels through one place (the BcPipedriveApi write wrappers), which in capture mode record
the outgoing request to a file and return a fake success instead of sending it.
In testing vocabulary that interception point is a test double at the integration boundary — specifically a stub (returns a canned response so the flow continues) that is also a spy/recorder (captures the call arguments for later inspection). Asserting on those captured payloads is contract / payload testing: we check the shape and values of what would have been sent.
A second, independent safety layer — the org-6034 guard — is a production guardrail (not a
test concept): outside live, the gate refuses any deal write that isn't for the Pipedrive DEV
organisation. Capture mode means nothing is sent at all; the guard means even a non-capture mistake
can't touch a real deal.
Test data: created through the app, never hand-written SQL
The "Arrange" step uses fixtures (a.k.a. seed data). We build them by driving the real application — e.g. configuring a product and adding it to a quote — rather than inserting rows directly. This "create through the domain, not the database" approach (an application factory) means pricing, BOM, totals and the branch→org link are all correct by construction. Each test (or the seed project) cleans up what it created, so runs are isolated and data doesn't accumulate.
Playwright vocabulary you'll meet
| Term | What it is |
|---|---|
| Spec | A test file (specs/**/*.spec.ts). Holds one or more test(...) blocks. |
| Project | A named run profile: a browser + which specs it runs + what it depends on. We split anon (no login) and agent (logged-in) projects. |
| Setup / teardown project | A project other projects depend on, used to prepare/clean shared state — here: grant cookie consent, log in, seed a quote, delete it afterwards. Wired via dependencies and teardown. |
| storageState | A saved snapshot of cookies + localStorage (e.g. the consent cookie, the logged-in session). Replayed by later projects so we don't repeat login/consent in every test. |
| Fixture | Playwright's term for something injected into a test, like the page (a browser tab). (Not to be confused with test-data fixtures above — same word, different sense.) |
| Locator | A lazy handle to an element (page.locator('…')). |
| Web-first assertion | expect(locator).toBeVisible() etc. — auto-waits and retries until true or it times out, which is why we rarely sleep. |
The support layer (the glue between Node and the app)
Playwright runs in Node; the app is PHP/Joomla. They meet through a secret-gated HTTP endpoint
(ConfigboxControllerBctestsupport → BcTestSupport) that runs the app's own code — so a spec can inspect
and prepare state, and run the deal update in capture mode, against any environment without a local PHP
checkout. Using the app's own code (rather than re-implementing DB access in JS) keeps the tests honest and
avoids duplicating schema knowledge. The full command set is support-api.md.
| Helper | Role |
|---|---|
support/cli.ts | The support.* client — calls the test-support endpoint over HTTP: read a quote's pipedrive_* columns, run the deal update in capture mode, seed stages, mint follow-up tokens, delete a quote, and more. |
support/captures.ts | Reads the (inline-returned) Pipedrive capture payloads, filtered by quote serial. |
support/seedQuote.ts | Drives the configurator to build a realistic, priced quote (the application-factory seed). |
support/followupForm.ts | Race-proof gestures for the follow-up form (chooseOpenAndStage, chooseLost, submitAndConfirm). |
support/{paths,env,loadEnv,seedFile,nav}.ts | Paths/config, .env loading, the seed-serial file, abort-tolerant navigation. |
A test, start to finish (concrete)
specs/agent/change-line-item-quantity.spec.ts:
- Arrange —
seedDecorativeQuote(page)configures the first Decorative product and adds it to a new quote owned by the agent (a priced line item). - Act/Assert (baseline) — run the deal update in capture mode; read the captured
value. - Act — change the line-item quantity to 3 in My Quotes.
- Assert (DB) — the quote is flagged for a Pipedrive update.
- Assert (payload) — run the deal update again; the captured
valueis now higher. - Cleanup — delete the seeded quote.
Glossary (the terms, crisply)
- E2E / functional test — verifies behaviour through the real UI across the whole stack.
- Integration test — verifies two systems work together; here, ConfigBox ↔ Pipedrive (at the payload level).
- AAA — Arrange, Act, Assert: the shape of every test.
- Test double — a stand-in for a real dependency. Flavours: stub (canned response), spy / recorder (captures calls), mock (pre-set expectations). Our capture-mode gate is a stub+spy.
- Capture mode — our gate records outbound Pipedrive payloads and sends nothing.
- Guardrail / guard — the always-on org-6034 safety rule in the app (not a test double).
- Fixture / seed data — the known starting state a test arranges.
- Application factory — building seed data by driving the app's own flows, not raw SQL.
- Flaky test — passes/fails non-deterministically (usually a timing race); we fix, not retry.
- Test isolation — each test sets up and tears down its own data so order doesn't matter.
Where next
- guide.md — the how-to: local stack, projects, accounts, settings, patterns, adding a test.
- support-api.md — the reusable test-support commands.
- catalog.md — every existing spec and what it asserts.
- ../pipedrive/testing.md — the capture / org-6034 guard gate and the deal-payload matrix.
tests/README.md— install and run.