Guide for testing EDS blocks using test.html files and the development server. Covers test file structure, EDS core integration, testing patterns, and debugging workflows for Adobe Edge Delivery Services blocks.
Guide developers through testing Adobe Edge Delivery Services (EDS) blocks using test.html files, the development server, and proper EDS integration patterns.
Automatically activates when:
test.html files in block directoriesEvery block should have a test.html file in its directory:
blocks/your-block/
├── your-block.js
├── your-block.css
├── README.md
├── EXAMPLE.md
└── test.html ← Create this file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Block Test - EDS Native Pattern</title>
<!-- EDS Core Styles -->
<link rel="stylesheet" href="/styles/styles.css">
<link rel="stylesheet" href="/styles/fonts.css">
<link rel="stylesheet" href="/styles/lazy-styles.css">
<!-- Note: Block CSS is loaded automatically by EDS -->
<style>
/* Test-specific styling */
body {
padding: 2rem;
background: var(--light-color);
}
.test-content {
max-width: 1200px;
margin: 0 auto;
background: var(--background-color);
padding: 2rem;
border-radius: 8px;
}
.test-section {
margin: 2rem 0;
padding: 1rem;
border: 1px solid var(--dark-color);
border-radius: 4px;
}
/* EDS pattern - ensure body appears */
body.appear {
display: block;
}
</style>
</head>
<body>
<div class="test-content">
<h1>Your Block Test Page</h1>
<div class="test-section">
<h2>Test Case 1: Basic Usage</h2>
<!-- Your block with test content (note: only block name class, .block is added by script) -->
<div class="your-block">
<div>
<div>Title 1</div>
<div>Description 1</div>
</div>
<div>
<div>Title 2</div>
<div>Description 2</div>
</div>
</div>
</div>
<div class="test-section">
<h2>Test Case 2: With Images</h2>
<div class="your-block">
<div>
<div>
<picture>
<img src="/images/test-image.jpg" alt="Test">
</picture>
</div>
<div>Content with image</div>
</div>
</div>
</div>
</div>
<!-- EDS Core Scripts -->
<script type="module">
import {
sampleRUM,
loadBlock,
loadCSS
} from '/scripts/aem.js';
// Initialize RUM (optional for testing)
sampleRUM('top');
window.addEventListener('load', () => sampleRUM('load'));
// CRITICAL: Add body.appear class FIRST (before loadBlock)
// EDS global styles hide body by default to prevent FOUC
// This makes the page visible before blocks load
document.body.classList.add('appear');
// Load all blocks on the page
// Mimic EDS behavior: find by block name, add .block class automatically
const blocks = document.querySelectorAll('.your-block');
for (const block of blocks) {
try {
// Add .block class like EDS decorateBlock() does
block.classList.add('block');
await loadBlock(block);
console.log(`✅ Block loaded: ${block.className}`);
} catch (error) {
console.error(`❌ Block failed: ${block.className}`, error);
}
}
</script>
</body>
</html>
Important Notes:
document.body.classList.add('appear') line is required and must be called before loadBlock(). EDS hides the body by default (body { display: none; } in styles/styles.css) to prevent Flash of Unstyled Content (FOUC). Adding the appear class makes the page visible. In production, EDS adds this automatically, but test files must add it manually..block class is automatically added by the script (line block.classList.add('block')) to mimic EDS production behavior. In your HTML, only use the block name class (e.g., class="your-block").decorateBlock() adds the .block class automatically.npm run debug
The server starts on http://localhost:3000 with:
http://localhost:3000/blocks/your-block/test.html
<div class="your-block">
<!-- Row 1 -->
<div>
<div>Column 1 Content</div>
<div>Column 2 Content</div>
</div>
<!-- Row 2 -->
<div>
<div>Column 1 Content</div>
<div>Column 2 Content</div>
</div>
</div>
Important:
.block class is automatically added by EDS's decorateBlock() function in production.block manually: <div class="your-block block"><div class="your-block">
<div>
<div>
<picture>
<source type="image/webp" srcset="/image.webp">
<img src="/image.jpg" alt="Description" loading="lazy">
</picture>
</div>
<div>Text content</div>
</div>
</div>
<div class="your-block">
<div>
<div>
<a href="https://example.com">Link Text</a>
</div>
<div>Description</div>
</div>
</div>
<div class="your-block"
data-layout="grid"
data-columns="3"
data-autoplay="true">
<!-- Block content -->
</div>
Note: Examples show blocks without the .block class. Your test script should add it automatically to mimic EDS production behavior.
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
await loadBlock(block);
</script>
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const blocks = document.querySelectorAll('.block');
// Load blocks sequentially
for (const block of blocks) {
await loadBlock(block);
}
// Or load in parallel
await Promise.all(
Array.from(blocks).map(block => loadBlock(block))
);
</script>
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const blocks = document.querySelectorAll('.block');
for (const block of blocks) {
try {
await loadBlock(block);
console.log(`✅ Loaded: ${block.className}`);
} catch (error) {
console.error(`❌ Failed to load: ${block.className}`, error);
block.innerHTML = '<p class="error">Failed to load block</p>';
}
}
</script>
<div class="test-section">
<h2>Test Case: Empty Content</h2>
<div class="your-block block">
<!-- No content - test error handling -->
</div>
</div>
<div class="test-section">
<h2>Test Case: Invalid Structure</h2>
<div class="your-block block">
<div>
<div>Only One Column</div>
<!-- Missing second column -->
</div>
</div>
</div>
<div class="test-section">
<h2>Test Case: Very Long Content</h2>
<div class="your-block block">
<div>
<div>Short title</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit... (very long text)</div>
</div>
</div>
</div>
<style>
.test-mobile {
max-width: 375px;
margin: 0 auto;
}
.test-tablet {
max-width: 768px;
margin: 0 auto;
}
</style>
<div class="test-section test-mobile">
<h2>Test Case: Mobile View (375px)</h2>
<div class="your-block block">
<!-- Content -->
</div>
</div>
<div class="test-section test-tablet">
<h2>Test Case: Tablet View (768px)</h2>
<div class="your-block block">
<!-- Content -->
</div>
</div>
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
console.group('Block Loading');
console.log('Block element:', block);
console.log('Block classes:', block.className);
console.log('Block content before:', block.innerHTML);
await loadBlock(block);
console.log('Block content after:', block.innerHTML);
console.groupEnd();
</script>
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
console.time('Block Load Time');
await loadBlock(block);
console.timeEnd('Block Load Time');
</script>
Open Chrome DevTools → Network tab to see:
Symptoms:
Root Cause: CSS class name conflicts with EDS reserved names
Solution: Never use these class patterns in your CSS or JavaScript:
.{blockname}-container - EDS adds to parent <section> elements.{blockname}-wrapper - EDS adds to block parent <div> wrappers.block - EDS adds to all block elements (avoid styling globally).section - EDS adds to all sections (avoid styling globally).button-container - EDS adds to button parent elements.default-content-wrapper - EDS adds to default content wrappersExample of the bug:
/* ❌ BAD - Will be applied to parent section, not your modal */
.overlay-container {
position: fixed;
z-index: 999;
opacity: 0; /* Makes entire page invisible! */
}
/* ✅ GOOD - Use different suffix */
.overlay-backdrop {
position: fixed;
z-index: 999;
opacity: 0; /* Only affects your backdrop element */
}
Why this happens:
decorateBlock() adds .{blockname}-container to parent sections (aem.js:684).{blockname}-container for your componentposition: fixed; opacity: 0 making page invisibleOther global classes that can cause issues:
/* ❌ DANGER - These affect ALL blocks/sections if styled incorrectly */
.block { position: fixed; } /* Breaks ALL blocks */
.section { display: none; } /* Hides ALL sections */
.button-container { overflow: hidden; } /* Breaks ALL buttons */
How to debug:
document.querySelector('section').className{blockname}-container in the class list, rename your CSS classes.block or .section with layout propertiesSolution: Ensure your CSS file has the exact same name as your JS file:
blocks/your-block/
├── your-block.js ✅ Matches
├── your-block.css ✅ Matches
└── test.html
Check:
decorate function is exported as defaultSolution: Use absolute paths from project root:
<!-- ❌ BAD -->
<img src="image.jpg">
<!-- ✅ GOOD -->
<img src="/images/image.jpg">
<div class="test-controls">
<button onclick="reloadBlock()">Reload Block</button>
<button onclick="clearBlock()">Clear Block</button>
<button onclick="logBlockState()">Log State</button>
</div>
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
window.reloadBlock = async () => {
console.log('Reloading block...');
block.innerHTML = originalHTML; // Save original
await loadBlock(block);
};
window.clearBlock = () => {
block.innerHTML = '';
};
window.logBlockState = () => {
console.log('Block state:', {
className: block.className,
children: block.children.length,
innerHTML: block.innerHTML
});
};
// Save original HTML
const originalHTML = block.innerHTML;
await loadBlock(block);
</script>
<div class="test-controls">
<label>
Layout:
<select onchange="updateLayout(this.value)">
<option value="grid">Grid</option>
<option value="list">List</option>
<option value="carousel">Carousel</option>
</select>
</label>
</div>
<div class="your-block block" data-layout="grid">
<!-- Content -->
</div>
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
const originalHTML = block.innerHTML;
window.updateLayout = async (layout) => {
block.innerHTML = originalHTML;
block.dataset.layout = layout;
await loadBlock(block);
};
await loadBlock(block);
</script>
<div class="test-section">
<h2>Accessibility Test</h2>
<p>Use Tab to navigate, Enter/Space to activate</p>
<div class="your-block block">
<!-- Interactive content -->
</div>
</div>
<script type="module">
// Log keyboard events for testing
document.addEventListener('keydown', (e) => {
console.log('Key pressed:', e.key, 'on:', e.target);
});
</script>
Use Chrome DevTools:
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
// Mark start time
performance.mark('block-load-start');
await loadBlock(block);
// Mark end time
performance.mark('block-load-end');
// Measure duration
performance.measure(
'block-load-time',
'block-load-start',
'block-load-end'
);
const measure = performance.getEntriesByName('block-load-time')[0];
console.log(`Block loaded in ${measure.duration.toFixed(2)}ms`);
</script>
<script type="module">
import { loadBlock } from '/scripts/aem.js';
const block = document.querySelector('.your-block');
// Load and unload multiple times
for (let i = 0; i < 100; i++) {
const originalHTML = block.innerHTML;
await loadBlock(block);
// Reset for next iteration
block.innerHTML = originalHTML;
}
console.log('Memory test complete - check DevTools Memory tab');
</script>
Before considering your block complete, test:
npm run debugRemember: Proper testing ensures your block works correctly in all scenarios and provides a great user experience on the production site!
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer