
Testing Blocks
- 1.1k installs
- 158 repo stars
- Updated August 4, 2026
- adobe/skills
testing-blocks is an Adobe AEM Edge Delivery Services skill (v2.0.0) that enforces browser-first validation with Playwright or MCP screenshots, npm linting, and value-based Vitest unit tests before opening pull requests
About
testing-blocks is an Adobe skills package at version 2.0.0 for validating AEM Edge Delivery Services code changes before PRs. building-blocks invokes it at Step 5. The four-step workflow runs npm run lint first, then mandatory browser validation at 375px mobile, 768px tablet, and 1200px desktop viewports with screenshots proving render correctness and zero console errors. Browser testing accepts Playwright MCP, temporary Playwright scripts, or manual devtools. Optional Vitest unit tests target logic-heavy utilities while skipping simple DOM decoration. Step 4 runs npm test to catch regressions. Philosophy: maintain tests only when value exceeds cost; browser proof is never optional. Troubleshooting covers aem up --html-folder drafts and /drafts/tmp/ test URLs. Reach for testing-blocks after block, script, or style changes and before any EDS pull request. Acceptance criteria from content-driven-development Step 2 and design mockups drive viewport screenshot comparisons during browser validation. Troubleshooting references aem up --html-folder drafts, drafts/tmp test URLs, and waitForSelector patterns when blocks load asynchronously in local preview environments.
- Browser tests required before every PR
- Focus testing effort on real-browser validation of blocks
- Temporary tests allowed when value does not justify long-term maintenance
- Validates layout, responsive behavior, DOM, interactions, accessibility, integration and performance
- Catches issues unit tests cannot detect
Testing Blocks by the numbers
- 1,084 all-time installs (skills.sh)
- +67 installs in the week ending Aug 5, 2026 (Skillselion tracking)
- Ranked #516 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/adobe/skills --skill testing-blocksAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.1k |
|---|---|
| repo stars | ★ 158 |
| Security audit | 3 / 3 scanners passed |
| Last updated | August 4, 2026 |
| Repository | adobe/skills ↗ |
How do you test AEM Edge Delivery blocks before PR?
Enforce disciplined browser-first testing that only creates lasting tests when their long-term value exceeds maintenance cost.
Who is it for?
AEM Edge Delivery Services developers finishing block or style changes who must prove browser behavior before opening a pull request.
Skip if: Pre-implementation planning, backend-only services outside EDS, or changes where browser validation proof is intentionally skipped.
When should I use this skill?
User modified AEM EDS blocks or styles and needs lint, browser screenshots, or Vitest validation before PR.
What you get
Lint-clean code, browser screenshots at three viewports, console-error confirmation, and passing npm test suite.
- browser screenshots
- lint pass confirmation
- test suite results
By the numbers
- Adobe skill version 2.0.0 with four-step testing workflow
- Validates three viewport widths: 375px, 768px, and 1200px
- Browser validation is mandatory; unit tests are optional by value/cost
Files
Testing Blocks
This skill guides you through testing code changes in AEM Edge Delivery Services projects. Testing follows a value-versus-cost philosophy: create and maintain tests when the value they bring exceeds the cost of creation and maintenance.
CRITICAL: Browser validation is MANDATORY. You cannot complete this skill without providing proof of functional testing in a real browser environment.
Related Skills
- content-driven-development: Test content created during CDD serves as the basis for testing
- building-blocks: Invokes this skill during Step 5 for comprehensive testing
- block-collection-and-party: May provide reference test patterns from similar blocks
When to Use This Skill
Use this skill:
- ✅ After implementing or modifying blocks
- ✅ After changes to core scripts (scripts.js, delayed.js, aem.js)
- ✅ After style changes (styles.css, lazy-styles.css)
- ✅ After configuration changes that affect functionality
- ✅ Before opening any pull request with code changes
This skill is typically invoked by the building-blocks skill during Step 5 (Test Implementation).
Testing Workflow
Track your progress:
- [ ] Step 1: Run linting and fix issues
- [ ] Step 2: Perform browser validation (MANDATORY)
- [ ] Step 3: Determine if unit tests are needed (optional)
- [ ] Step 4: Run existing tests and verify they pass
Step 1: Run Linting
Run linting first to catch code quality issues:
npm run lintIf linting fails:
npm run lint:fixManually fix remaining issues that auto-fix couldn't handle.
Success criteria:
- ✅ Linting passes with no errors
- ✅ Code follows project standards
Mark complete when: npm run lint passes with no errors
---
Step 2: Browser Validation (MANDATORY)
CRITICAL: You must test in a real browser and provide proof.
What to Test
Load test content URL(s) in browser and validate:
- ✅ Block/functionality renders correctly
- ✅ Responsive behavior (mobile, tablet, desktop viewports)
- ✅ No console errors
- ✅ Visual appearance matches requirements/acceptance criteria
- ✅ Interactive behavior works (if applicable)
- ✅ All variants render correctly (if applicable)
How to Test
Choose the method that makes most sense given your available tools:
Option 1: Browser/Playwright MCP (Recommended)
If you have MCP browser or Playwright tools available, use them directly:
- Navigate to test content URL
- Take accessibility snapshots to inspect rendered content (preferred for interaction)
- Take screenshots at different viewports for visual validation
- Consider both full-page screenshots and element-specific screenshots of the block being tested
- Interact with elements as needed
- Most efficient for agents with tool access
Option 2: Playwright automation
Write one (or more) temporary test scripts to validate functionality with playwright and capture snapshots/screenshots for inspection and validation.
// test-my-block.js (temporary - don't commit)
import { chromium } from 'playwright';
async function test() {
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage();
// Navigate and wait for block
await page.goto('http://localhost:3000/path/to/test');
await page.waitForSelector('.my-block');
// Inspect accessibility tree (useful for validating structure)
const accessibilityTree = await page.accessibility.snapshot();
console.log('Accessibility tree:', JSON.stringify(accessibilityTree, null, 2));
// Optionally save to file for easier analysis
await require('fs').promises.writeFile(
'accessibility-tree.json',
JSON.stringify(accessibilityTree, null, 2)
);
// Test viewports and take screenshots
await page.setViewportSize({ width: 375, height: 667 });
await page.screenshot({ path: 'mobile.png', fullPage: true });
await page.locator('.my-block').screenshot({ path: 'mobile-block.png' });
await page.setViewportSize({ width: 768, height: 1024 });
await page.screenshot({ path: 'tablet.png', fullPage: true });
await page.locator('.my-block').screenshot({ path: 'tablet-block.png' });
await page.setViewportSize({ width: 1200, height: 800 });
await page.screenshot({ path: 'desktop.png', fullPage: true });
await page.locator('.my-block').screenshot({ path: 'desktop-block.png' });
// Check for console errors
page.on('console', msg => console.log('Browser:', msg.text()));
await browser.close();
}
test().catch(console.error);Run: node test-my-block.js then delete the script and analyze the resulting artifacts.
Option 3: Manual browser testing
Use a standard web browser with dev tools: 1. Navigate to test content: http://localhost:3000/path/to/test/content 2. Use browser dev tools responsive mode to test viewports:
- Mobile: <600px (e.g., 375px)
- Tablet: 600-900px (e.g., 768px)
- Desktop: >900px (e.g., 1200px)
3. Check console for errors at each viewport 4. Take screenshots as proof (browser screenshot tool or dev tools)
Validation Against Acceptance Criteria
If acceptance criteria provided (from CDD Step 2):
- Review each criterion
- Test specific scenarios mentioned
- Verify all criteria are met
If design/mockup screenshots provided:
- Compare implementation to design
- Verify visual alignment
- Note any intentional deviations
Proof of Testing
You must provide:
- ✅ Screenshots of test content in browser (at least one viewport)
- ✅ Confirmation no console errors
- ✅ Confirmation acceptance criteria met (if provided)
Success criteria:
- ✅ All test content loads and renders correctly
- ✅ Responsive behavior validated across viewports
- ✅ No console errors
- ✅ Screenshots captured as proof
- ✅ Acceptance criteria validated (if provided)
Mark complete when: Browser testing complete with screenshots as proof
---
Step 3: Unit Tests (Optional)
Determine if unit tests are needed for this change.
Write unit tests when:
- ✅ Logic-heavy functions (calculations, transformations)
- ✅ Utility functions used across multiple blocks
- ✅ Data processing or API integrations
- ✅ Complex business logic
Skip unit tests when:
- ❌ Simple DOM manipulation
- ❌ CSS-only changes
- ❌ Straightforward decoration logic
- ❌ Changes easily validated in browser
For guidance on what to test: See references/testing-philosophy.md
If unit tests needed:
# Verify test setup (see references/vitest-setup.md if not configured)
npm test
# Write test for utility function
# test/utils/my-utility.test.js
import { describe, it, expect } from 'vitest';
import { myUtility } from '../../scripts/utils/my-utility.js';
describe('myUtility', () => {
it('should transform input correctly', () => {
expect(myUtility('input')).toBe('OUTPUT');
});
});For detailed unit testing guidance: See references/unit-testing.md
Success criteria:
- ✅ Unit tests written for logic-heavy code
- ✅ Tests pass:
npm test - ✅ OR determined unit tests not needed
Mark complete when: Unit tests written and passing, or determined not needed
---
Step 4: Run Existing Tests
Verify your changes don't break existing functionality:
npm testIf tests fail: 1. Read error message carefully 2. Run single test to isolate: npm test -- path/to/test.js 3. Fix code or update test if expectations changed 4. Re-run full test suite
Success criteria:
- ✅ All existing tests pass
- ✅ No regressions introduced
Mark complete when: npm test passes with no failures
Troubleshooting
For detailed troubleshooting guide, see references/troubleshooting.md.
Common issues:
Tests fail
- Read error message carefully
- Run single test:
npm test -- path/to/test.js - Fix code or update test
Linting fails
- Run
npm run lint:fix - Manually fix remaining issues
Browser tests fail
- Verify dev server running:
aem up --html-folder drafts - Check test content exists in
drafts/tmp/ - Verify URL uses
/tmp/path:http://localhost:3000/drafts/tmp/my-block - Add waits:
await page.waitForSelector('.block')
Resources
- Unit Testing: references/unit-testing.md - Complete guide to writing and maintaining unit tests
- Troubleshooting: references/troubleshooting.md - Solutions to common testing issues
- Vitest Setup: references/vitest-setup.md - One-time configuration guide
- Testing Philosophy: references/testing-philosophy.md - Guide on what and how to test
Integration with Building Blocks Skill
The building-blocks skill invokes this skill during Step 5 (Test Implementation).
Inputs received from building-blocks:
- Block name being tested
- Test content URL(s) (from CDD Step 4)
- Any variants that need testing
- Screenshots of existing implementation/design/mockup to verify against (if provided)
- Acceptance criteria to verify (from CDD Step 2)
Expected outputs to return to building-blocks:
- ✅ Confirmation all testing steps complete
- ✅ Screenshots from browser testing as proof
- ✅ Confirmation linting passes
- ✅ Confirmation tests pass
- ✅ Any issues discovered and resolved
{"extends": "../../../../../release.config.cjs"}
{
"name": "testing-blocks",
"version": "0.0.0-semantically-released",
"private": true
}
Testing Philosophy
Core Principles:
- Create and maintain tests when the value they bring that exceeds the cost of creation and maintenance. Other things should and must be tested, but these tests can be temporary and not maintained long term
- Browser Tests, even when not maintained long term, are critical to ensuring functionality works before opening a PR
Browser Tests
Most of your testing effort should focus on browser testing. This is where you validate that blocks actually work as intended in a real browser environment.
✅ Always run browser tests for:
- Every block you create or modify
- Visual layout and responsive behavior across breakpoints
- DOM structure and content transformation
- Interactive features (clicks, hovers, keyboard navigation)
- Accessibility (ARIA labels, focus management, screen reader compatibility)
- Integration between blocks and the page
- Performance (LCP, CLS, visual regressions)
Browser tests are required before opening any PR. They catch issues that unit tests cannot: rendering bugs, CSS conflicts, responsive breakdowns, accessibility failures, and real user interaction problems.
Important: Browser tests are considered temporary and should not be committed to source control. They serve their purpose during development and PR validation, but don't need long-term maintenance. Once your PR is merged and the code is in production, these tests have done their job.
Unit Tests
✅ Write unit tests for:
- Logic-heavy utility functions used across multiple blocks
- Data processing and transformation logic
- API integrations and external service interactions
- Complex algorithms or business logic
- Shared libraries and helper functions
These tests provide lasting value because they catch regressions in reused code, serve as living documentation, and are fast and easy to maintain.
❌ Don't write unit tests for:
- Specifc DOM structures or UI Layouts
- Visual appearance validation
- Block-specific decoration logic or rendering behavior
These tests are still critical and must be tested, but we do that with browser testing. We don't want to commit these tests to git or maintain them long term.
Testing Troubleshooting Guide
Common issues encountered during testing and how to resolve them.
Tests Fail After Changes
Unit Tests Fail
Symptoms:
- Test suite runs but some tests fail
- Error messages about unexpected values or behaviors
Diagnosis:
1. Read the error message carefully
- What assertion failed?
- What was expected vs actual?
- Which test file and line number?
2. Determine the cause:
- Did you break existing functionality?
- Did requirements change?
- Is the test incorrect?
Solutions:
If you broke functionality:
# Fix the code, not the test
# Re-run tests
npm testIf requirements changed:
# Update the test to match new requirements
# Ensure the change is intentional
npm testIf testing a specific file:
# Run just one test file for faster feedback
npm test -- test/utils/my-utility.test.jsIf debugging a specific test:
# Use test.only to run just one test
it.only('should do something', () => {
// This test will be the only one that runs
});Browser Tests Fail
Symptoms:
- Browser test script throws errors
- Screenshots not generated
- Timeouts occur
Common Issues and Solutions:
Issue: Dev server not running
# Start the dev server
aem up
# Verify it's accessible
curl http://localhost:3000Issue: Test content doesn't exist
- Verify the URL manually in a browser
- Check that content is published/previewed
- Ensure path in test script is correct
Issue: Block not decorated
// Add wait for specific selector
await page.waitForSelector('.my-block');
// Or wait for network idle
await page.waitForLoadState('networkidle');
// Or increase timeout
await page.waitForSelector('.my-block', { timeout: 10000 });Issue: Timing problems
// Wait for animations to complete
await page.waitForTimeout(500);
// Wait for specific state
await page.waitForFunction(() => {
return document.querySelector('.my-block').classList.contains('loaded');
});Issue: Elements not clickable
// Wait for element to be clickable
await page.locator('.button').click({ force: false });
// Or scroll into view first
await page.locator('.button').scrollIntoViewIfNeeded();
await page.locator('.button').click();Linting Fails
Common Linting Issues
Unused variables
// Error: 'foo' is defined but never used
const foo = 'bar';
// Fix: Remove unused variable
// Or use itMissing semicolons
// Error: Missing semicolon
const foo = 'bar'
// Fix: Add semicolon
const foo = 'bar';Incorrect indentation
# Auto-fix indentation issues
npm run lint:fixConsole.log statements
// Error: Unexpected console statement
console.log('debug info');
// Fix Option 1: Remove it
// (remove the line)
// Fix Option 2: Disable rule for this line
// eslint-disable-next-line no-console
console.log('debug info');Import issues
// Error: Unable to resolve path to module
import { foo } from './utils';
// Fix: Add .js extension
import { foo } from './utils.js';Airbnb style violations
// Error: Expected a line break after this opening brace
import { foo, bar } from './utils.js';
// Fix: Let lint:fix handle it
// Or manually format:
import {
foo,
bar,
} from './utils.js';Quick Fixes
Auto-fix everything possible:
npm run lint:fixCheck what would be fixed:
npm run lint:js -- --fix-dry-runLint specific files:
npm run lint:js -- test/utils/my-test.test.jsGitHub Checks Fail
Checks Don't Run
Issue: PSI checks not running
Cause: Missing test link in PR description
Solution:
## Testing
Preview: https://branch--repo--owner.aem.page/path/to/testThe test link MUST be in the PR description for PSI checks to run.
Issue: Actions workflow disabled
Check repository settings → Actions → ensure workflows are enabled.
PSI Checks Fail
Issue: Poor performance score
Common causes:
- Too much JavaScript loaded eagerly
- CSS blocking render
- Large unoptimized images
- Third-party scripts in eager phase
Solutions:
Move JavaScript to lazy or delayed phase:
// In scripts.js, move non-LCP code to loadLazy() or loadDelayed()Optimize images:
- Use WebP format
- Lazy load below-the-fold images
- Use appropriate dimensions
Defer non-critical CSS:
/* Move non-LCP styles to lazy-styles.css */Review performance report:
# Check details in GitHub PR checks
gh pr checks
# Click on failed check for detailsLinting Checks Fail in CI
Issue: Tests pass locally but fail in CI
Cause: Linting not run locally or different config
Solution:
# Always run lint before committing
npm run lint
# Ensure .eslintrc.js is committed
git add .eslintrc.js
git commit -m "Add eslint config"Tests Pass Locally But Fail in CI
Different Node Version
Symptoms:
- Syntax errors in CI
- Missing features
Solution:
Check CI Node version matches local:
# Check local version
node --version
# Add .nvmrc if needed
echo "20" > .nvmrc
git add .nvmrc
git commit -m "Add nvmrc for consistent Node version"Missing Dependencies
Symptoms:
- Module not found errors in CI
- Dependencies work locally
Solution:
# Ensure package.json is up to date
git add package.json package-lock.json
git commit -m "Update dependencies"
# Or regenerate lock file
rm package-lock.json
npm install
git add package-lock.jsonEnvironment Differences
Symptoms:
- Tests dependent on local files fail
- Absolute paths don't work
Solution:
Use relative paths and environment-agnostic code:
// Bad: Absolute path
const path = '/Users/me/project/file.js';
// Good: Relative path
const path = './file.js';Race Conditions
Symptoms:
- Tests sometimes pass, sometimes fail
- Timing-dependent failures
Solution:
Add proper waits:
// Bad: No wait
it('should update text', async () => {
await updateText();
expect(getText()).toBe('updated');
});
// Good: Wait for update
it('should update text', async () => {
await updateText();
await waitFor(() => getText() === 'updated');
expect(getText()).toBe('updated');
});Vitest-Specific Issues
Module Resolution Errors
Error:
Failed to resolve import './utils.js'Solution:
Check import paths are correct:
// From test/utils/my-test.test.js
// Importing from scripts/utils/my-utility.js
// Correct:
import { myUtility } from '../../scripts/utils/my-utility.js';
// Wrong (missing ../../):
import { myUtility } from 'scripts/utils/my-utility.js';jsdom Errors
Error:
document is not definedSolution:
Ensure vitest.config.js has jsdom environment:
export default defineConfig({
test: {
environment: 'jsdom', // Important!
},
});Coverage Not Generated
Error:
Coverage provider not foundSolution:
Install coverage package:
npm install --save-dev @vitest/coverage-v8Getting Help
If you're stuck:
1. Read the error message completely - Often contains the solution 2. Check the documentation - vitest.dev, playwright.dev 3. Search existing issues - GitHub issues for Vitest/Playwright 4. Simplify - Create minimal reproduction of the issue 5. Ask for help - Include error messages and context
Preventive Measures
To avoid issues:
- ✅ Run
npm testbefore every commit - ✅ Run
npm run lintbefore every commit - ✅ Use
npm run test:watchduring development - ✅ Test in browser manually before opening PR
- ✅ Include test links in PR descriptions
- ✅ Monitor
gh pr checksafter creating PR
Don't:
- ❌ Skip testing locally
- ❌ Commit failing tests
- ❌ Ignore linting errors
- ❌ Force push without re-running tests
- ❌ Merge PRs with failing checks
Quick Diagnostic Commands
# Check Node version
node --version
# Check npm version
npm --version
# Verify Vitest installed
npm list vitest
# Verify test config
cat vitest.config.js
# Check dev server running
curl http://localhost:3000
# Verify git status
git status
# Check GitHub PR status
gh pr checksNext Steps
After resolving issues:
1. Re-run all checks 2. Verify everything passes 3. Commit fixes if needed 4. Update documentation if issue was unclear 5. Help others avoid the same issue
Remember: Every error is a learning opportunity. Take time to understand why something failed, not just how to fix it.
Unit Testing Guide
Unit tests are keeper tests for logic-heavy code that benefits from automated regression testing. This guide covers when to write unit tests, how to structure them, and best practices for maintainable test suites.
When to Write Unit Tests
Write unit tests for:
- Pure functions - Functions with no side effects that transform inputs to outputs
- Utility libraries - Shared helper functions used across blocks
- Data processors - Code that parses, transforms, or validates data
- API integrations - Functions that interact with external services
- Complex algorithms - Business logic, calculations, or conditional flows
Do NOT write unit tests for:
- Block decoration functions (test these in browser)
- DOM manipulation logic (test in browser)
- CSS styles (test in browser)
- Simple getters/setters
- Code that primarily renders UI
Prerequisites
This guide assumes Vitest is already configured in the project. If not, see vitest-setup.md for one-time setup instructions.
Verify test setup exists:
npm test # Should run without errors (even if no tests exist yet)If the command fails or Vitest is not installed, consult vitest-setup.md.
Important: Ensure test files are not served to production by adding them to .hlxignore:
# .hlxignore
test/
*.test.jsThis prevents test files from being accessible on your live site.
Writing Unit Tests
File Location and Naming
Place test files next to the code they test:
scripts/utils/my-utility.js→test/utils/my-utility.test.jsblocks/hero/utils.js→test/blocks/hero/utils.test.js
Naming convention: {filename}.test.js
Test Structure
import { describe, it, expect } from 'vitest';
import { myUtility } from '../../scripts/utils/my-utility.js';
describe('myUtility', () => {
it('should transform input correctly', () => {
const input = { foo: 'bar' };
const result = myUtility(input);
expect(result).toEqual({ foo: 'BAR' });
});
it('should handle edge cases', () => {
expect(myUtility(null)).toBeNull();
expect(myUtility({})).toEqual({});
});
});Running Tests
npm test # Run all tests once
npm run test:watch # Run tests in watch mode
npm run test:ui # Open interactive UI
npm run test:coverage # Generate coverage reportWhat Makes a Good Unit Test
Good unit tests are:
- Fast - Run in milliseconds
- Isolated - Test one function/unit at a time
- Repeatable - Same input always produces same output
- Self-validating - Pass or fail clearly, no manual inspection
- Focused - Test one behavior per test case
Complete Example
Here's an example of a keeper test that's worth maintaining:
// scripts/utils/url-helpers.js
export function normalizeUrl(url, base) {
if (!url) return '';
if (url.startsWith('http://') || url.startsWith('https://')) return url;
if (url.startsWith('/')) return `${base}${url}`;
return `${base}/${url}`;
}
// test/utils/url-helpers.test.js
import { describe, it, expect } from 'vitest';
import { normalizeUrl } from '../../scripts/utils/url-helpers.js';
describe('normalizeUrl', () => {
const base = 'https://example.com';
it('returns empty string for null/undefined', () => {
expect(normalizeUrl(null, base)).toBe('');
expect(normalizeUrl(undefined, base)).toBe('');
});
it('returns absolute URLs unchanged', () => {
expect(normalizeUrl('https://other.com/path', base)).toBe('https://other.com/path');
expect(normalizeUrl('http://other.com/path', base)).toBe('http://other.com/path');
});
it('prepends base to root-relative URLs', () => {
expect(normalizeUrl('/path/to/page', base)).toBe('https://example.com/path/to/page');
});
it('prepends base with slash to relative URLs', () => {
expect(normalizeUrl('path/to/page', base)).toBe('https://example.com/path/to/page');
});
});Why this test is worth maintaining:
- URL normalization is used across many blocks
- Bugs here would break multiple features
- Logic is complex enough to benefit from regression tests
- Test is fast and easy to maintain
Testing with jsdom
When testing DOM-dependent code, jsdom provides a browser-like environment:
import { describe, it, expect, beforeEach } from 'vitest';
import { JSDOM } from 'jsdom';
describe('DOM manipulation', () => {
let document;
beforeEach(() => {
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
document = dom.window.document;
global.document = document;
});
it('should create elements', () => {
const div = document.createElement('div');
div.textContent = 'Hello';
expect(div.textContent).toBe('Hello');
});
});Integration Tests
Integration tests validate that multiple components work together correctly. These fall into the "keeper" category if they test critical workflows.
When to write integration tests:
- Auto-blocking logic that depends on multiple functions
- Complex workflows spanning multiple utilities
- Critical user journeys that depend on multiple blocks
Integration tests use the same Vitest setup as unit tests but test multiple components together.
Example integration test:
// test/integration/auto-blocking.test.js
import { describe, it, expect, beforeEach } from 'vitest';
import { JSDOM } from 'jsdom';
import { buildAutoBlocks } from '../../scripts/scripts.js';
describe('Auto-blocking integration', () => {
let document;
beforeEach(() => {
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
document = dom.window.document;
global.document = document;
});
it('should auto-block hero from first section with image', () => {
const main = document.createElement('main');
const section = document.createElement('div');
const picture = document.createElement('picture');
section.appendChild(picture);
main.appendChild(section);
buildAutoBlocks(main);
const hero = main.querySelector('.hero');
expect(hero).toBeTruthy();
expect(hero.querySelector('picture')).toBeTruthy();
});
});Integration tests are worth maintaining if:
- The workflow is critical to site functionality
- Multiple teams/developers work on related code
- Bugs in this integration would be expensive to fix
Best Practices
1. Test behavior, not implementation - Focus on what the function does, not how it does it 2. Use descriptive test names - Test names should explain what they're testing 3. One assertion per test - Or multiple assertions testing the same behavior 4. Avoid test interdependence - Each test should be able to run independently 5. Keep tests simple - Tests should be easier to understand than the code they test 6. Mock external dependencies - API calls, file system access, etc. 7. Test edge cases - Null, undefined, empty strings, boundary values
Common Patterns
Testing async functions
it('should fetch data asynchronously', async () => {
const result = await fetchData();
expect(result).toBeDefined();
});Using beforeEach for setup
describe('Calculator', () => {
let calculator;
beforeEach(() => {
calculator = new Calculator();
});
it('should add numbers', () => {
expect(calculator.add(2, 3)).toBe(5);
});
});Testing error conditions
it('should throw error for invalid input', () => {
expect(() => {
validateInput('invalid');
}).toThrow('Invalid input');
});Next Steps
Once you've written unit tests: 1. Run tests during development with npm run test:watch 2. Run full test suite before commits with npm test 3. Monitor coverage with npm run test:coverage 4. Keep tests updated as code evolves
Vitest Setup Guide
This guide covers the one-time setup of Vitest for unit testing in AEM Edge Delivery projects. Once configured, you won't need to repeat these steps.
Installation
Install Vitest and required dependencies:
npm install --save-dev vitest @vitest/ui jsdomOptional but recommended for coverage reports:
npm install --save-dev @vitest/coverage-v8Configuration
Create vitest.config.js in the project root:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
globals: true,
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: [
'node_modules/',
'test/',
'**/*.config.js',
],
},
},
});Configuration Options Explained
- environment: 'jsdom' - Provides browser-like environment for testing DOM code
- globals: true - Makes
describe,it,expectavailable without imports - coverage - Configures code coverage reporting (optional but useful)
Package.json Scripts
Add test scripts to your package.json:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
}
}Script Descriptions
- `npm test` - Run all tests once (use in CI or before commits)
- `npm run test:watch` - Run tests in watch mode (use during development)
- `npm run test:ui` - Open interactive web UI for tests
- `npm run test:coverage` - Generate code coverage report
Directory Structure
Create directories for test files:
mkdir -p test/utils
mkdir -p test/blocksRecommended Structure
project-root/
├── scripts/
│ └── utils/
│ └── my-utility.js
├── blocks/
│ └── hero/
│ ├── hero.js
│ ├── hero.css
│ └── utils.js
└── test/
├── utils/
│ └── my-utility.test.js
└── blocks/
└── hero/
└── utils.test.jsTest file naming: {filename}.test.js
Verify Installation
Run tests to verify setup:
npm testIf no tests exist yet, you should see:
No test files found, exiting with code 0Create a simple test to verify everything works:
// test/example.test.js
import { describe, it, expect } from 'vitest';
describe('Vitest setup', () => {
it('should be configured correctly', () => {
expect(true).toBe(true);
});
});Run tests again:
npm testYou should see the test pass. Once verified, delete test/example.test.js.
Usage with jsdom
When testing DOM-dependent code, jsdom provides a browser-like environment:
import { describe, it, expect, beforeEach } from 'vitest';
import { JSDOM } from 'jsdom';
describe('DOM manipulation', () => {
let document;
beforeEach(() => {
const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
document = dom.window.document;
global.document = document;
});
it('should create elements', () => {
const div = document.createElement('div');
div.textContent = 'Hello';
expect(div.textContent).toBe('Hello');
});
});CI Integration
For GitHub Actions or other CI, tests run automatically with:
npm testConsider adding to your CI workflow:
- name: Run tests
run: npm test
- name: Generate coverage
run: npm run test:coverageTroubleshooting
"vitest: command not found"
- Ensure
vitestis indevDependenciesin package.json - Run
npm install
"Cannot find module 'jsdom'"
- Install jsdom:
npm install --save-dev jsdom
Tests not finding imports
- Check import paths are correct relative to test file location
- Ensure files being tested export the functions properly
Coverage not working
- Install coverage provider:
npm install --save-dev @vitest/coverage-v8
Next Steps
Once setup is complete: 1. Write unit tests following the patterns in the main testing-blocks skill 2. Run tests during development with npm run test:watch 3. Run full test suite before commits with npm test 4. Monitor coverage with npm run test:coverage
This is a one-time setup. Once configured, focus on writing valuable tests rather than configuration.
Related skills
How it compares
Use testing-blocks for pre-PR EDS validation; use analyze-and-plan earlier when acceptance criteria are still undefined.
FAQ
Is browser testing optional in testing-blocks?
testing-blocks marks browser validation as mandatory. The skill cannot complete without screenshots proving blocks render at least one viewport, confirmation of no console errors, and validation against provided acceptance criteria.
When does testing-blocks recommend unit tests?
testing-blocks recommends Vitest unit tests for logic-heavy utilities, data processing, and shared helpers. Skip unit tests for simple DOM decoration, CSS-only edits, and changes fully verifiable in the browser.
Is Testing Blocks safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.