Skip to content

test: add unit and E2E testing infrastructure with Vitest and Playwright#193

Merged
vancura merged 12 commits intomainfrom
al-216-testing-unit-and-e2e-coverage-for-critical-flows
Feb 13, 2026
Merged

test: add unit and E2E testing infrastructure with Vitest and Playwright#193
vancura merged 12 commits intomainfrom
al-216-testing-unit-and-e2e-coverage-for-critical-flows

Conversation

@vancura
Copy link
Copy Markdown
Collaborator

@vancura vancura commented Feb 10, 2026

Linear

AL-216: Testing: Unit and E2E Coverage for Critical Flows

Summary

Adds comprehensive testing infrastructure for the Ambilab website:

  • Unit tests (Vitest): formatDate, debounce, safe-execute, dom utilities
  • E2E tests (Playwright): newsletter subscription, locale switching, theme switching
  • Translation validation: Script to ensure en/cs key completeness

Changes

  • Vitest: Already configured; added safe-execute.test.ts and dom.test.ts (formatDate and debounce already had tests)
  • Playwright: Installed and configured with webServer (pnpm preview), playwright.config.ts
  • E2E tests: tests/e2e/newsletter.spec.ts, locale-switching.spec.ts, theme-switching.spec.ts
  • Translation validation: src/scripts/validate-translations.ts
  • NewsletterForm: Added to Footer so newsletter E2E flow is testable
  • Scripts: test:e2e, validate:translations in package.json
  • Docs: README updated with testing section

How to run

  • Unit tests: pnpm test:run
  • E2E tests: pnpm build && pnpm test:e2e
  • Translation validation: pnpm validate:translations

Acceptance criteria

  • Vitest and Playwright installed and configured
  • At least 4 unit tests created (formatDate, debounce, safe-execute, dom)
  • At least 3 E2E tests created (newsletter, locale, theme)
  • Translation validation script working
  • All unit tests pass
  • Documentation updated

Made with Cursor

Testing Infrastructure Implementation

This PR adds comprehensive testing support: Vitest for unit tests, Playwright for E2E tests, and a translation completeness validator.

Test Configuration

  • Playwright config: playwright.config.ts configured to run tests from ./tests/e2e against a preview web server (pnpm preview → http://localhost:4321/), Chromium project, HTML reporter with trace on first retry, CI-specific retries=2 and workers=1.

Unit Tests

  • New/updated unit tests under src/utils:
    • src/utils/safe-execute.test.ts — safeExecute tests (success, fallback, logging, non-Error throws, return propagation).
    • src/utils/dom.test.ts — toggleDarkMode tests (class toggling, localStorage persistence, error resilience).
    • Additional existing tests present: debounce.test.ts, errors.test.ts, formatDate.test.ts, logger.test.ts.
  • Vitest scripts in package.json:
    • test, test:run, test:coverage, test:ui configured; run unit tests with pnpm test:run.

End-to-End Tests

  • Playwright E2E tests added under tests/e2e:
    • tests/e2e/newsletter.spec.ts — newsletter form rendering, invalid/valid submission flows and assertions on outcomes.
    • tests/e2e/locale-switching.spec.ts — locale switcher visibility, EN↔CS switching, html lang attribute checks, locale cookie persistence.
    • tests/e2e/theme-switching.spec.ts — theme toggle visibility, dark class toggling, localStorage persistence, idempotent toggling.
  • E2E script in package.json: test:e2e → playwright test. Recommended run: pnpm build && pnpm test:e2e.

Translation Validation

  • src/scripts/validate-translations.ts — script to recursively extract keys and compare translations (base locale: en) against LOCALES; prints details for missing/extra keys and exits with code 1 on mismatch.
  • package.json script: validate:translations and validate updated to include it. Run with pnpm validate:translations.

Component & Integration Changes to Support Tests

  • src/components/svelte/NewsletterForm.svelte:
    • Added data-testid attributes: newsletter-heading, newsletter-email, newsletter-submit.
    • Honeypot input class adjusted to size-px.
    • Newsletter form included in pages/layouts so E2E flows are reachable.
  • src/components/svelte/Button.svelte:
    • Added optional data-testid prop and renders it on both and .
  • NewsletterForm is rendered in NewsPostLayout and on index route (src/pages/[...slug].astro) to make newsletter flow testable.

Package & Tooling Updates

  • package.json:
    • Dev dependencies added: @playwright/test and playwright.
    • Scripts added/updated: test:e2e, validate:translations, test, test:run, test:coverage, etc.
    • preview script uses wrangler pages dev dist --port=4321 to match Playwright webServer.
  • knip.json and cspell.json updated:
    • NewsletterForm is no longer ignored by knip analysis.
    • cspell.json dictionary updated with Czech words and test-related entries.

Documentation

  • README.md updated with a Testing section documenting:
    • Unit tests: pnpm test or pnpm test:run
    • E2E tests: pnpm build && pnpm test:e2e
    • Translation validation: pnpm validate:translations

How to run

  • Unit tests: pnpm test:run
  • E2E tests (recommended): pnpm build && pnpm test:e2e
  • Translation validation: pnpm validate:translations

Acceptance criteria (covered)

  • Vitest and Playwright configured.
  • Multiple unit tests added/available (safe-execute, dom, formatDate, debounce, logger, errors).
  • At least three E2E tests covering newsletter, locale switching, and theme switching.
  • Translation validation script added and wired to validate script.
  • Documentation updated with testing commands.

@linear
Copy link
Copy Markdown

linear bot commented Feb 10, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

Adds testing infrastructure: Playwright E2E config and suites, Vitest unit tests for utilities, a translation validation script, package.json test scripts/devDependencies, testids on components, and README testing docs; also removes NewsletterForm from knip ignore and minor doc whitespace fix.

Changes

Cohort / File(s) Summary
Testing config & scripts
playwright.config.ts, package.json
New Playwright config targeting preview server (http://localhost:4321) with CI-aware retries/workers and webServer; package.json adds Playwright devDeps, test:e2e and validate:translations scripts, and updates validate.
E2E test suites
tests/e2e/locale-switching.spec.ts, tests/e2e/newsletter.spec.ts, tests/e2e/theme-switching.spec.ts
New Playwright E2E tests covering locale switching, newsletter form flows, and theme toggling with persistence and assertions.
Unit tests
src/utils/dom.test.ts, src/utils/safe-execute.test.ts
Vitest unit tests added for DOM utilities (toggleDarkMode with localStorage mocks) and safeExecute (fallbacks, error handling, non-Error throws).
Translation validation
src/scripts/validate-translations.ts
New script that recursively compares translation keys against en to report missing/extra keys and exit non‑zero on mismatch.
Component test support
src/components/svelte/NewsletterForm.svelte, src/components/svelte/Button.svelte
Added data-testid attributes for E2E selectors and adjusted honeypot input sizing; Button component accepts and renders data-testid prop.
Component integration
src/components/astro/NewsPostLayout.astro, src/pages/[...slug].astro
NewsletterForm imported and rendered in post layout and index route with client:idle and locale prop.
Auxiliary config & docs
knip.json, README.md, ai-rules/astro-svelte.mdc, cspell.json
Removed NewsletterForm from knip ignore, added "Testing" section to README, minor docs whitespace fix, and added Czech words to cspell dictionary.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Dev/CI
participant Build as "Build (pnpm build)"
participant Preview as "Preview Server\n(pnpm preview @ localhost:4321)"
participant Playwright as "Playwright Test Runner"
Dev/CI->>Build: run production build
Build->>Preview: start preview server
Playwright->>Preview: hit baseURL (http://localhost:4321) for tests
Playwright->>Preview: run E2E scenarios (locale, newsletter, theme)
Playwright-->>Dev/CI: test results (reporter, traces on retry)
Preview-->>Build: shutdown/reuse based on CI flag

Possibly related issues

  • AL-216 — Implements the testing infra, unit/E2E coverage, and translation validation requested by this issue.
  • AL-237 — Overlaps in expanding testing configuration, test scripts, and knip.json adjustments.

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (9 files):

⚔️ README.md (content)
⚔️ ai-rules/astro-svelte.mdc (content)
⚔️ cspell.json (content)
⚔️ knip.json (content)
⚔️ package.json (content)
⚔️ pnpm-lock.yaml (content)
⚔️ src/components/astro/NewsPostLayout.astro (content)
⚔️ src/components/svelte/Button.svelte (content)
⚔️ src/components/svelte/NewsletterForm.svelte (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the main change: adding testing infrastructure (Vitest and Playwright) for unit and E2E tests.
Linked Issues check ✅ Passed PR addresses all major AL-216 requirements: unit tests for utilities (safe-execute, dom), E2E tests (newsletter, locale-switching, theme-switching), translation validation script, Vitest/Playwright configuration, and documentation updates.
Out of Scope Changes check ✅ Passed All changes align with AL-216 scope. Minor additions (Button.svelte data-testid prop, NewsletterForm integration, cspell.json updates, knip.json adjustment) directly support E2E testing requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch al-216-testing-unit-and-e2e-coverage-for-critical-flows
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch al-216-testing-unit-and-e2e-coverage-for-critical-flows
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

No actionable comments were generated in the recent review. 🎉


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/utils/safe-execute.test.ts`:
- Around line 28-35: The test name claims to verify logger behavior for
safeExecute but only checks the returned result; either change the test to
assert the logger call or rename the test to match its assertion. To verify
logging, import or mock the logger used by safeExecute (spy on logger.error),
call safeExecute(throwFn, null, 'Custom error message'), and assert logger.error
was called with the custom message and/or the thrown Error; alternatively,
rename the test string to something like "should return fallback when fn throws
with custom error message" to reflect the current assertion. Ensure you
reference the safeExecute function and the throwFn used in the test when making
changes.

In `@tests/e2e/locale-switching.spec.ts`:
- Around line 32-44: The test "should persist locale via cookie after switch"
currently only asserts the cookie contains a valid locale; instead determine the
expected locale after clicking the localeButton and assert localeCookie.value
equals that expected value. After clicking localeButton (variable localeButton)
and awaiting networkidle, derive the expectedLocale from the UI change you
trigger (e.g., check the new label/text/aria of localeButton or another element
that reflects the selected language) and then replace the loose assertion on
localeCookie.value with a strict equality check (localeCookie?.value ===
expectedLocale) using context.cookies() and localeCookie as currently
implemented.
- Around line 16-30: The test "should switch from English to Czech" currently
only checks post-click state and uses a loose OR that can pass if page starts in
Czech; first assert the initial locale is English by inspecting htmlLang
(htmlLang === 'en') or bodyText includes English labels like 'Home' or
'Projects' before clicking the localeButton, then click localeButton, wait for
networkidle, and assert the locale actually transitioned by checking htmlLang
=== 'cs' and bodyText includes Czech strings like 'Domů' and/or 'Projekty'
(replace the single OR assertion with two explicit expects: one for initial
state and one for post-click state); refer to the localeButton variable,
htmlLang and bodyText locators and the test block name to find the code to
change.

In `@tests/e2e/newsletter.spec.ts`:
- Around line 11-17: The test 'should display newsletter form on homepage' uses
hardcoded English strings in its selectors (getByRole heading name 'Subscribe to
Our Newsletter', getByPlaceholder 'Enter your email', button name 'Subscribe')
which breaks under other locales; update the NewsletterForm (or template) to add
stable data-testid attributes for the heading, input and button (e.g.,
data-testid="newsletter-heading", "newsletter-email", "newsletter-submit") and
then change the test to use page.getByTestId(...) for those elements, or
alternatively replace the string matchers with locale-agnostic regexes (e.g.,
/Subscribe|Odebírat/i) to cover both English and Czech; ensure selectors
reference the new test IDs or regexes in the test named 'should display
newsletter form on homepage'.
🧹 Nitpick comments (3)
src/utils/safe-execute.test.ts (1)

45-50: Test name is confusing.

The phrase "async-like synchronous return values" is unclear. The test verifies that object references are preserved (identity check via toBe), which is straightforward. Consider a clearer name like "should preserve object reference on success".

Suggested rename
-    it('should propagate async-like synchronous return values', () => {
+    it('should preserve object reference on success', () => {
         const obj = { key: 'value' };
         const result = safeExecute(() => obj, {});

         expect(result).toBe(obj);
     });
tests/e2e/theme-switching.spec.ts (1)

30-39: Consider asserting the specific theme value.

Similar to the locale tests, the assertion at line 38 only verifies the theme is valid ('light' or 'dark'), not that it reflects the change from clicking the toggle. However, since the toggle behavior is verified in the adjacent test (lines 16-28), this is acceptable for a persistence check.

Optional: more precise assertion
     test('should persist theme preference in localStorage', async ({ page }) => {
         await page.goto('/');

+        const hadDarkBefore = await page.evaluate(() => document.documentElement.classList.contains('dark'));
         const themeButton = page.getByRole('button', { name: /Toggle theme|Přepnout motiv/i });
         await themeButton.click();

         const theme = await page.evaluate(() => localStorage.getItem('theme'));

-        expect(theme === 'light' || theme === 'dark').toBeTruthy();
+        expect(theme).toBe(hadDarkBefore ? 'light' : 'dark');
     });
src/utils/dom.test.ts (1)

49-55: Consider consolidating with the first test case.

This test duplicates the localStorage assertion from 'should add dark class when not present'. While explicit persistence tests can improve clarity, this specific test doesn't add new coverage.

That said, keeping it separate does make the test suite more readable for someone scanning test names, so this is a minor observation rather than a blocker.

vancura and others added 3 commits February 10, 2026 19:15
Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-authored-by: Cursor <cursoragent@cursor.com>
…e sizes

- Add AVIF support documentation (Sharp supports it; use format="avif" on Image)
- Add priority prop and decoding attribute to ResponsiveImage (sync for LCP, async for lazy)
- Change GoToTop hydration from client:idle to client:visible
- Fine-tune getResponsiveSizes for mobile/tablet (100vw, 95vw, 85vw, 75vw breakpoints)
- Document image strategy in ai-rules (AVIF, priority, decoding, responsive sizes)

Closes AL-217

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add safe-execute.test.ts and dom.test.ts unit tests
- Add E2E tests for newsletter, locale switching, theme switching
- Add validate-translations.ts script for key completeness
- Add NewsletterForm to Footer for newsletter E2E flow
- Add playwright.config.ts with webServer for preview
- Update package.json with test:e2e and validate:translations scripts
- Update README with testing documentation
- Remove NewsletterForm from knip ignore (now used in Footer)

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-authored-by: Cursor <cursoragent@cursor.com>
@vancura vancura force-pushed the al-216-testing-unit-and-e2e-coverage-for-critical-flows branch from 4df1fd7 to ab6e27c Compare February 10, 2026 18:15
@vancura
Copy link
Copy Markdown
Collaborator Author

vancura commented Feb 10, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 10, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

vancura and others added 3 commits February 13, 2026 17:39
- Import additional testing utilities from Vitest
- Add console error spying to capture and verify
  error messages during tests
- Update tests to check for correct error logging
  when exceptions are thrown

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Vaclav Vancura <commit@vancura.dev>
- Add logger verification to safe-execute test by mocking console.error
  and asserting it's called with custom error message and Error object
- Add initial state verification to locale-switching E2E test to ensure
  page starts in English before clicking locale switcher
- Replace loose OR assertion with explicit pre-click and post-click
  state checks to verify actual locale transition from EN to CS
- Use separate variables for before/after state to improve test clarity

These changes ensure tests verify actual behavior rather than just
checking post-action state, making them more robust and easier to debug.

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Vaclav Vancura <commit@vancura.dev>
- Add console.error spy to safe-execute test to verify logger is called
  with custom error message and Error object when function throws
- Add initial English state verification to locale-switching E2E test
  before clicking switcher to ensure proper transition baseline
- Replace loose OR assertion with explicit before/after state checks
  to verify actual EN→CS locale transition occurred
- Update cookie persistence test to derive expected locale from html
  lang attribute and assert exact match instead of loose OR check

These changes ensure tests verify complete behavior flows rather than
just final states, making them more robust and better at catching
regressions in error logging, locale transitions, and cookie sync.

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Vaclav Vancura <commit@vancura.dev>
vancura and others added 5 commits February 13, 2026 17:50
- Introduced 'data-testid' attributes in Button and NewsletterForm
  components to enhance accessibility for testing.
- Updated e2e tests to utilize these attributes for selecting elements,
  improving the reliability of the tests.

These changes facilitate better integration with testing frameworks
and ensure that UI components can be easily targeted in automated tests.

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Vaclav Vancura <commit@vancura.dev>
…nd-e2e-coverage-for-critical-flows

# Conflicts:
#	ai-rules/astro-svelte.mdc
#	src/components/astro/Footer.astro
#	src/components/svelte/NewsletterForm.svelte

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Signed-off-by: Vaclav Vancura <commit@vancura.dev>
… and dynamic pages

- Removed the NewsletterForm component from the Footer.astro file.
- Added the NewsletterForm component to the NewsPostLayout.astro file
  and dynamic pages for better integration and visibility.

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/utils/dom.test.ts`:
- Around line 6-28: The tests mutate document.documentElement classes but don't
restore them; capture the initial className in beforeEach (e.g., save
document.documentElement.className into a variable alongside
mockDocumentElement) and then restore it in afterEach before calling
vi.unstubAllGlobals by setting document.documentElement.className back to the
saved value; update the existing beforeEach/afterEach that reference
mockDocumentElement and vi.unstubAllGlobals to use this saved initialClass so
the `dark` class (or any other mutation) doesn't leak between tests.

- Added logic to preserve the initial class name
  of the document element in the dom tests.
- Ensured that the class name is reset after
  each test to maintain test isolation.

Signed-off-by: Vaclav Vancura <commit@vancura.dev>
Co-Authored-By: Claude <noreply@anthropic.com>
@vancura
Copy link
Copy Markdown
Collaborator Author

vancura commented Feb 13, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vancura
Copy link
Copy Markdown
Collaborator Author

vancura commented Feb 13, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@vancura vancura merged commit 32e0c55 into main Feb 13, 2026
6 checks passed
@vancura vancura deleted the al-216-testing-unit-and-e2e-coverage-for-critical-flows branch February 13, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant