Skip to main content

Testing

getbased uses two test layers:
  • Vitest wraps the node-side helper fixtures and fast logic checks.
  • Playwright runs the browser suite against the live app in headless Chrome.
Routstr/Cashu wallet work has an extra release gate because it touches bearer money. Before changing wallet funding, token export/import, seed restore, node deposit/top-up, refunds, or Cashu TS migration code, read Routstr/Cashu safety. Some browser fixtures are still self-executing IIFEs with local assert() helpers; Playwright executes those through tests/playwright/browser-script-runner.js so they run in the same browser suite as native Playwright specs.

The assert pattern

Browser-script fixture files define a local assert helper and collect results:
(async () => {
  const results = [];
  let passed = 0, failed = 0;

  function assert(name, condition, detail = '') {
    if (condition) {
      results.push({ ok: true, name });
      passed++;
    } else {
      results.push({ ok: false, name, detail });
      failed++;
    }
  }

  // --- tests ---

  assert('state object exists', typeof window._labState === 'object');
  assert('importedData has entries array',
    Array.isArray(window._labState.importedData.entries));

  // --- report ---
  console.log(`Tests: ${passed} passed, ${failed} failed`);
  results.filter(r => !r.ok).forEach(r =>
    console.error(`FAIL: ${r.name}`, r.detail)
  );
  return { passed, failed, results };
})();
The detail argument appears in the failure output — use it to print the actual value that caused the failure.

The test files

All test files live in the tests/ directory. Native browser specs live under tests/playwright/; fixture scripts live as tests/test-*.js. A few tests run node-side (no browser) — pure-helper unit tests + node script guards. Marked node in the table; browser-driven coverage is owned by Playwright specs.
FileWhat it covers
tests/test-a11y-axe.jsRuntime axe-core 4.10 scan across every lens + 6 Settings tabs + EMF editor (14 stops). Baseline-locked CI gate (see Accessibility regression scan)
tests/test-a11y-phase3.jsAccessibility regression pass: keyboard delegation, role=“button” tabindex, focus trapping
tests/test-ai-verdict-engine-instance.jsEngine instance methods (refresh, isAnalyzing, maybeAfterFinish, purgeOrphaned) + the default cfg callbacks the main engine test overrides
tests/test-adapters.jsAdapter registry: structure, fatty acids, OAT, metabolomix, cross-adapter
tests/test-ai-verdict-engine.jsShared js/ai-verdict-engine.js factory: in-flight tracker, watchdog, fingerprint cache, JSON parse guards
tests/test-audit-fixes.jsRegression coverage for the 2026-05-09 audit pass (multi-area follow-ups)
tests/test-audit.jsSecurity audit: XSS escaping, marker-key allowlist guards, innerHTML sanitizer sweep, null/div-by-zero, focus trapping, CSP
tests/test-biometrics.jsBiometrics time-series: weight, BP, pulse, BMI auto-calc
tests/test-biostarks-adapter.jsBioStarks adapter: registration, detection, markers, normalization
tests/test-blob-storage.jsIDB-backed key/value store + the localStorage→IDB migration on first read of *-imported
tests/test-calculated-markers.jsCalculated markers: PhenoAge, Bortz Age, Biological Age, BUN/Creatinine, Free Water Deficit, hs-CRP/HDL
tests/test-cashu-wallet.jsCashu wallet: encrypted mnemonic, locks, proofs, recovery, fee split, Nostr discovery, BIP-39 seed
tests/test-change-history.jsChange history: recordChange dedup, snapshot deep-copy, cap, AI context timeline, export/import round-trip
tests/test-changelog.jsWhat’s New modal: version sync, HTML, main.js wiring, settings, forceShow patch-bump override, behavioral idempotency
tests/test-chat-actions.jsChat message action buttons: regenerate, copy, context toggle
tests/test-chat-panel-ux.jsChat panel interactive-while-open + fullscreen layout (v1.3.29 surface)
tests/test-chat-threads.jsChat thread CRUD, auto-naming, migration, encryption patterns, backup inclusion
tests/test-correctness-phase2.jsv1.5.1 correctness pass: per-profile sync debouncer, lab-context fingerprint, lens LRU
tests/test-crypto.jsAES-256-GCM encryption, PBKDF2, passphrase validation (20+ sections)
tests/test-custom-api.jsCustom API provider: 6th provider registration, endpoint config, model fetch
tests/test-custom-lens.jsCustom Knowledge Source (Lens Corpus): backend selection, library CRUD, query routing
tests/test-custom-personality.jsNamed custom personalities: storage, icon picker, generation, dirty state, thread metadata
tests/test-cycle-improvements.jsPhase-aware ranges, cycle iron alerts, perimenopause detection, heavy flow alerts
tests/test-cycle-tour.jsCycle spotlight tour: 8 steps, DOM elements, auto-trigger, storage key
tests/test-dashboard-data-protection.jsData protection CTA + picker on the dashboard (encryption / sync / auto-backup)
tests/test-dashboard-genetics-empty.jsGenetics empty-state CTA (DNA discovery stub) on the dashboard
tests/test-dashboard-knowledge-base.jsKnowledge Base row + Personalize-AI CTA on the dashboard
tests/test-data-merge.jsPer-array union-by-id sync merge: additions, edit-conflict resolution, tombstones, nested
tests/test-data-pipeline.jsCore data pipeline: getActiveData, unit conversion, date filtering, trend detection
tests/test-demo.jsDemo data files: v2 structure, structured context cards, menstrual cycle for Sarah
tests/test-dev-server-helpers.jsnodedev-server.js helper unit tests
tests/test-dev-server-origin.jsnodedev-server.js /api/* same-origin guard rejects forged headers
tests/test-dna-illumina-and-valence.jsIllumina GenomeStudio (DNAEra) + Valence formats; probe-name prefix strip
tests/test-dna-mtdna-subclades.jsmtDNA sub-haplogroup resolution (v1.23.0)
tests/test-dna-recommendations.jsDNA-aware supplement recommendations: snpHints, buildDNAHints, gene-keyword scanner
tests/test-dna.jsDNA import: SNP parsing, APOE haplotype, format detection, dashboard rendering
tests/test-emf.jsEMF assessment: SBM-2015 severity, room CRUD, source/mitigation tags
tests/test-emf-flow.jsEMF module behavioral flow (CRUD + interpret + PDF-import path) — opens the full editor lifecycle that test-emf.js only schema-tests
tests/test-export-import.jsExport/import roundtrip + encrypted-backup re-enumeration: JSON structure, date merge, context field handling
tests/test-family-history.jsMedical History + family-history subsection: relative picker, CRUD, FAMILY_RELATIVES enum
tests/test-folder-backup.jsFolder backup: File System Access API, snapshot format, daily filenames, IndexedDB v2 handle persistence
tests/test-hardware.jsModel Advisor: GPU detection, VRAM badges, model fitness ratings
tests/test-image-utils.jsImage utilities: resize, format, vision content building
tests/test-integration-batch2.jsIntegration: batch import, marker keys, custom markers, adapters
tests/test-lens-local-utils.jsnode — pure helpers from js/lens-local-utils.js: chunking, MMR selection, cosine similarity
tests/test-lens-local-worker.jsFull message-protocol round-trip against lens-local-worker.js with a mocked embedder
tests/test-lens-multi-query.jsMulti-query rewrite + reciprocal-rank-fusion chunk fusion
tests/test-lens-parsers.jsjs/lens-local-parsers.js edge cases: extractFromFile() never throws, returns expected shape
tests/test-light-ai-renders.jsSmoke coverage for the 10 feature-specific Light & Sun AI modules
tests/test-light-device-ai-analysis.jsPer-device-session AI verdict: fingerprint determinism, prompt-context shape (incl. mode resolution + injection guards), render state machine, engine adapter coverage
tests/test-light-devices.jsLight therapy device library + sessions: addDeviceFromPreset, deleteDevice, logDeviceSession
tests/test-light-devices-store.jsLight-device mutation boundary: device CRUD, session lifecycle, dose recompute, sync tombstones
tests/test-light-env.jsLight Environment math + CRUD: rooms, screens, audits, computeRoomSeverity, computeScreenStatus, computeIndoorBurden
tests/test-light-env-store.jsLight Environment mutation boundary: room/screen CRUD, today overrides, room-delete cleanup, sync tombstones
tests/test-light-tools.jsPure helpers re-exported from light-tools.js: computeRowBanding (flicker FFT), saveMeasurement, lockStatusLine
tests/test-light-tools-flow.jsDrives saveMeasurement across all 8 tool types + every camera-bound opener (handles missing getUserMedia cleanly)
tests/test-lighting-hardware-caveats.jsGuard the load-bearing PWM/TRIAC caveat block against silent removal from any AI-analysis prompt
tests/test-manual-entry-flow.jsManual-entry quality-of-life: range sanity check, duplicate-date confirm, Save & Add Another
tests/test-markdown.jsMarkdown rendering + XSS surface assertions for streamed AI responses
tests/test-marker-key-safety.jsnodesafeMarkerId + sanitizeMarkerKey allowlist + proto-pollution rejection (38 assertions)
tests/test-marker-detail-store.jsMarker detail mutation boundary: manual values, mirrored insulin notes, ref overrides, marker notes
tests/test-marker-value-notes.jsPer-value notes on lab markers: schema defaults, profile migration, sync DELTA_MAPS wiring
tests/test-mobile.jsResponsive layout: breakpoints, grid overflow, touch tap targets, safe grid sizing
tests/test-no-native-dialogs.jsnode — guard against window.prompt/confirm/alert regressions across js/
tests/test-normalize-units.jsUnit normalization: SI conversion in the PDF import pipeline
tests/test-openrouter.jsOpenRouter provider: curated model list, pricing cache, exclude blocklist, model fetch
tests/test-phase-ranges.jsPhase-aware reference ranges for estradiol and progesterone aligned with dates
tests/test-pii.jsPII obfuscation: regex patterns, streaming sanitizer
tests/test-prelab.jsPre-lab onboarding: context assembly without data, chat prompts
tests/test-provenance.jsImport provenance: markerSources tracking, PDF/manual source attribution
tests/test-recommendations.jsSupplement recommendations: catalog slots, keyword scanner, safety caveats, disclosure gate
tests/test-schema.jsMARKER_SCHEMA integrity, unit conversions, optimal ranges, phase ranges
tests/test-security-phase1.jsv1.5.0 security pass: pdf.js vendor presence, isEvalSupported, defense-in-depth
tests/test-silhouette-picker.jsBody-region silhouette picker: bindBodySilhouette mounts, every region clickable
tests/test-silhouette-region-map.jsRegion-map correctness: anatomical body-zone definitions and lookups
tests/test-sun-ai-analysis.jsPer-session AI verdict module: fingerprint stability, context-build, render state machine
tests/test-sun-context.jsbuildSunContext({tier}) AI prompt assembly: always/standard/deep tier shaping, deficit detection
tests/test-sun-correlations.jsPearson coefficient + weekly binning + cache invalidation for per-channel × biomarker engine
tests/test-sun-defaults.jsOnboarding defaults: Fitzpatrick mapping, OTT score boundaries, getSunDefaults round-trip
tests/test-sun-spectrum.jsBird-Riordan reconstruction + action-spectrum convolution + vit-D calibration gate
tests/test-sun-ui-flow.jsBehavioral UI flow for the Light & Sun lens: dashboard strip, /light page, session controls
tests/test-sun-uvdata.jsMulti-source UV/ozone client: SSRF guard, manual entry, provider routing, solar-zenith math
tests/test-sun-uvdata-flow.jsBehavioral flow for sun-uvdata.js: cache, provider chain (auto / open-meteo / selfhost / noaa), interpolateAtmosphere bracketing, readStaleCache fallback
tests/test-sun.jsSun session orchestration: lifecycle, hydration, rolling totals, vit-D IU accumulation, MED carry-over
tests/test-supplement-impact.jsSupplement-biomarker impact analysis: batched computation, caching, health dots
tests/test-sync.jsCross-device sync: payload format, AI settings keys, encrypted keys, Evolu integration
tests/test-table-heatmap-empty.jsTable/Heatmap views skip all-null markers; empty category shows hint
tests/test-tour.jsApp tour: 7 steps, spotlight DOM, positioning, escape key, completion flag (154 assertions)
tests/test-trend-alerts.jsTrend detection: sudden change alerts, linear regression, status logic
tests/test-ui-flows.jsBehavioral UI tests: key user flows, rendered output verification
tests/test-unit-import.jsUnit normalization on import: SI conversion, enzyme units, FA adapter safety
tests/test-v1-6-shipped.jsRegression coverage for v1.6.7..v1.6.16 ship arc
tests/test-venice-e2ee.jsVenice E2EE: ECDH key exchange, AES-GCM encryption, TEE headers, model detection
tests/test-vendor-personal-info.jsfetchXxxPersonalInfo + logDebug rails for Fitbit / Ultrahuman / Whoop / Polar (one stubbed /api/proxy response per vendor)
tests/test-coverage-stragglers.jsTargeted probes for the 1-fn gaps left after the AI-verdict + vendor sweeps: image-utils onerror, lens-local-parsers extractDocx, oura-auth json-catch, utils animationend, FileReader stub, AbortSignal.any polyfill, SSE handler, IDB onerror rails (blob / cashu / ws / backup), cashu open onerror, dna worker.onerror
tests/test-wearables-bp-merge.jsBP renders as one paired card (sys/dia): strip-render filter, reorder-mode behavior
tests/test-wearables-fetchers.jsPer-vendor adapter fetchXxxDailyRange against canned proxy responses
tests/test-wearables-manual.jsManual entry as a first-class wearable source: logManualMetric, MANUAL_TAGS whitelist, migration
tests/test-wearables-runtime-config.jsSelf-host OAuth *_CLIENT_ID env override (issue #145)
tests/test-wearables-sync-flow.jsWearable sync orchestration end-to-end with mocked proxy fetch + fake connection record
tests/test-wearables-ui-flows.jsDOM-driven wearable UI flows: detail-modal manual entry, source picker, reorder mode
tests/test-wearables.jsWearable adapter registry + L1 store + L2 summary + AI context + JSZip lazy-load (Apple Health)
The landing page test (test-landing.js) lives in the get-based-site repo.

Run all tests headlessly

./run-tests.sh
The script:
  1. Checks if a server is running on port 8000; starts node dev-server.js if not
  2. Runs the node-side tests first (fast fail on helper regressions, no browser needed)
  3. Runs the dev-server origin guard
  4. Runs the Playwright browser suite
  5. Exits with code 0 if all pass, 1 if any fail
Requires: Node.js and dependencies installed with npm ci.

Coverage status

COVERAGE=1 ./run-tests.sh runs Vitest with V8 coverage enabled, runs the normal Playwright suite with Chromium JS coverage enabled, then merges the Playwright suite shards from tests/.playwright-coverage/ with tests/.vitest-coverage/coverage-final.json. The reporter writes tests/.coverage.json and prints separate Playwright, Vitest/Node, and combined global function/byte coverage percentages for app-source JavaScript. Running node scripts/playwright-coverage.mjs directly still falls back to the legacy high-surface Chromium sampler when no Playwright suite shards are present. The full COVERAGE=1 ./run-tests.sh path requires suite shards so coverage regressions in the Playwright instrumentation fail clearly. Coverage is report-only by default. Set COVERAGE_MIN=90 or another percentage to fail the run when combined global function coverage falls below that floor.

Accessibility regression scan

tests/test-a11y-axe.js loads axe-core 4.10 from cdnjs at runtime and runs axe.run() against the live DOM at 14 stops (every lens + every modal). Severity policy:
  • critical / serious → test fails on regression vs baseline
  • moderate / minor → logged but doesn’t block (axe leans opinionated at those tiers)

Baseline-locked gate

The gate is baseline-relative, not zero-tolerance. Real-world a11y adoption gates on “no regression from current state” — gating on zero violations the first time a codebase adopts axe would block every PR forever. Baseline lives at tests/.a11y-baseline.json:
{
  "_axeVersion": "4.10.0",
  "critical": {},
  "serious": { "color-contrast": 124, "nested-interactive": 40 },
  "moderate": {},
  "minor": {}
}
The test fails when any critical/serious rule’s count exceeds the baseline. New rules with non-zero counts ARE a regression (the suite never saw them before). Improvements (current < baseline) pass and emit a hint to refresh the baseline. _axeVersion pins the runtime axe-core load. Bumping the cdnjs URL without bumping this field would surface rule renames as false regressions; the test prints an info line on mismatch.

Refreshing the baseline

After a wave of fixes that legitimately drops violation counts:
A11Y_REBASELINE=1 ./run-tests.sh
tests/playwright/a11y-axe-browser.spec.js pipes the env var into the page context before loading the fixture. The test prints a ▶ {...} JSON line — copy it over the critical/serious/moderate/minor blocks in tests/.a11y-baseline.json. Don’t lower these numbers without an actual fix; the gate would lock in the new lower bound and silently accept regressions. If the baseline file is missing entirely, the test treats the first run as “establish baseline” and prints the JSON to stdout for paste-back.

Running a single test in the browser

Open the browser console while http://localhost:8000 is running, then:
fetch('tests/test-cycle-improvements.js').then(r => r.text()).then(s => Function(s)())
Results appear in the console. This is useful during development before running the full suite.

Writing new tests

When you add a feature or fix a bug, add assertions to the relevant test file. If none fits, create test-yourfeature.js. What to cover:
  • Source inspection — verify the function or pattern exists in the source:
    const src = await fetch('js/data.js').then(r => r.text());
    assert('getActiveData exported', src.includes('export function getActiveData'));
    
  • DOM state — check that elements render correctly:
    const card = document.querySelector('.context-card[data-key="diet"]');
    assert('diet card rendered', card !== null);
    assert('diet card has edit button', card.querySelector('.ctx-edit-btn') !== null);
    
  • Function behavior — call window-exported functions and check results:
    const data = window.getActiveData ? window.getActiveData() : null;
    assert('getActiveData returns dates array', Array.isArray(data?.dates));
    
  • CSS rules — verify styles are applied (use getComputedStyle or inspect stylesheets):
    const styles = [...document.styleSheets].flatMap(s => {
      try { return [...s.cssRules].map(r => r.cssText); } catch { return []; }
    }).join('\n');
    assert('grid overflow hidden set', styles.includes('min-width: 0'));
    
  • localStorage keys — verify storage conventions:
    assert('correct key format', localStorage.getItem('labcharts-default-imported') !== undefined
      || true); // key may not exist in test env
    

What the headless runner cannot test

  • Drag-and-drop interactions
  • File picker dialogs
  • Actual streaming AI responses (API key required)
  • IndexedDB state across page reloads (the runner resets between files)
For these, test the surrounding logic (e.g., that handleBatchPDFs function exists and has the right signature) rather than the interaction itself.