Marketing website for Scanopy — SvelteKit + Tailwind, fully prerendered static site (@sveltejs/adapter-static). The docs sub-site in docs/ is built separately and copied into build/docs by npm run build. Deploys on push to main.
npm install # also installs docs/ dependencies
npm run dev # dev server
npm run build # full production build (site + docs)The public lead-capture forms are the inbound channel for the highest-value deals and have silently broken in production more than once (most recently June 2026: the /commercial "Request a Quote" form did nothing on click, with no error shown). A weekly Playwright suite submits each form on the live site end to end so a broken form is caught within a week instead of by a frustrated prospect.
- Specs live in
e2e/, shared helpers ine2e/helpers.ts, config inplaywright.config.ts. - CI:
.github/workflows/form-monitor.yml— every Monday 06:00 UTC, plus manual runs via the Actions tab (Form Monitor → Run workflow). - On failure the workflow goes red (GitHub emails the repo owner) and opens a GitHub issue labeled
form-monitor(or comments on the existing open one). The Playwright report with screenshots and traces is attached to the run as an artifact.
Every form found on the site is listed here. Both forms submit to Brevo (sibforms.com) via src/lib/brevo.ts — there is no server-side form handling in this repo.
| Form | Page tested | Spec | Real submission? |
|---|---|---|---|
| Contact modal — "Request a Quote" (the June 2026 failure) | /commercial |
e2e/contact-modal.spec.ts |
yes |
| Contact modal — Enterprise "Request Information" | /pricing |
e2e/contact-modal.spec.ts |
yes |
| Contact modal — Self-Hosted "Contact Us" | / (home) |
e2e/contact-modal.spec.ts |
yes |
| Newsletter signup (footer, sitewide) | / (home) |
e2e/newsletter.spec.ts |
yes |
The contact modal is one component with three separate page-level trigger wirings; a page-specific JS error can break one page while the others keep working, so each path submits for real.
Each weekly run creates 3 real Brevo contact inquiries and 1 newsletter subscription (double that in the worst case, since CI retries a failed test once).
reCAPTCHA history: the forms originally attached reCAPTCHA v3 tokens, and the newsletter form enforced them in Brevo — which blocked this monitor, since automated browsers always score too low to pass v3. Enforcement was turned off in Brevo in July 2026 and the client-side integration was commented out in src/lib/brevo.ts (spam protection is now the honeypot field plus newsletter double opt-in). If the newsletter test ever fails with BREVO REJECTED SUBMISSION mentioning a captcha error, someone re-enabled enforcement in Brevo — either turn it back off, or accept that the newsletter form can only be monitored up to Brevo's bot-protection boundary (the pre-July-2026 version of e2e/newsletter.spec.ts in git history did exactly that).
Every submission the suite makes is marked so it can be filtered everywhere downstream. Downstream filters key on these exact values — don't change them casually (they live in e2e/helpers.ts):
- Email:
formtest+YYYYMMDD@scanopy.net(e.g.formtest+20260705@scanopy.net) - First name / last name / company:
AUTOMATED TEST - Use-case message:
AUTOMATED TEST - weekly production form monitor. Please ignore.
Suggested filters:
- Brevo: a segment or automation matching EMAIL contains
formtest+(or FIRSTNAME equalsAUTOMATED TEST) → auto-blocklist or periodically bulk-delete. This also keeps test contacts out of any workflows/sequences. - Gmail: filter
to:(formtest+)→ skip inbox, archive. Newsletter double-opt-in confirmations go to whereverformtest@scanopy.netroutes — make sure that alias exists (or accept the bounce).
Brevo hosted forms have no test mode, so real submissions are intentional: the point is exercising the production code path end to end.
npm run test:forms # headless
npm run test:forms:headed # watch it happenWarning: local runs submit real (sentinel-marked) data to production Brevo, exactly like CI.
Adding a form to the site should always come with a spec here:
- Create
e2e/<form-name>.spec.ts(one spec file per form). - Use the helpers:
seedCookieConsent(keeps the cookie banner from intercepting clicks and analytics from recording the run),gotoHydrated(the site is prerendered — clicking before hydration silently does nothing), sentinel values fromhelpers.ts, andsubmitAndExpectBrevoSuccess(asserts the POST fires, returns 2xx, andsuccess:true). For a non-Brevo endpoint, add a sibling helper that matches that endpoint's URL and success shape. - Assert the user-visible success state, not just the network response.
- Add a row to the coverage table above.
NewsletterCTA.svelte— dead component, rendered by zero routes (the footer is the only live newsletter instance). Nothing to test in production; if it ever gets wired into a page, add a spec.- "Talk to Sales" / demo buttons — external Cal.com scheduling links, not forms.
mailto:links (licensing@, etc.) — no form pipeline to break.- Cookie consent banner, service-catalog search box — no network submission.