aem-eds-kit / End-to-end test

Test run · 19 Oct 2026 · v0.4.44

A full screen, design to EDS blocks

We ran the whole example page (the HTML twin of the Figma example file, 11 blocks) through eds block from-design with a real AI agent, rendered every block in the preview harness and screenshotted it next to the design. Then we ran every option and error path of the CLI, and migrated 163 real AEM components. Nothing below was retouched.

●22 / 22 blocks generated ●18 / 18 clean in eds block check ●10 / 11 within 2 px height at 1440 (scrape) ●$0.48 scrape · $0.24 PNG, whole screen ●65 / 65 CLI cases pass ●151 / 163 AEM components migrated

How it was run

Two design sources, the same 11 blocks each. The agent was Claude Code on its default fast model (Haiku), one run per block, no hand edits.

⌁Stitch / Framer path: scrape

The live page is opened in headless Chromium and its computed layout, type and colours are embedded in the prompt.

$ eds block from-design <page-url> \
    --provider stitch --name hero

◇Figma path: frame exported to PNG

The no-token route: each block frame as an image. The example file is 1:1 with the page, so each section at 1440 px stands in for its Figma frame.

$ eds block from-design ./hero.png \
    --name hero
  1. Generateeds block from-design for each of the 11 blocks; the agent writes JS, CSS, the Universal Editor model and a preview mock.
  2. Rendereds block test --update --widths 390,768,1440 screenshots every block in the preview harness.
  3. Checkeds block check runs the content and accessibility checks on every content block.
  4. CompareEach block is screenshotted in the harness and pixel-diffed (pixelmatch) against the same section of the design.
!

Placeholders are on purpose. The prompt forbids shipping copied text or design-tool image URLs: content comes from authoring. Generated blocks therefore render placeholder copy and grey image boxes, and most of the pixel difference is that. Judge layout, spacing and type, not words.

Whole screen

Generated blocks are stacked in page order. Section backgrounds come from the page, not the blocks, so the harness renders them on white. Scroll inside each column.

Desktop, 1440 px

Design · 1440 px
Example page at 1440 px
Generated · Stitch scrape · 1440 px
Generated blocks from the scrape, stacked, at 1440 px
Generated · Figma PNG export · 1440 px
Generated blocks from PNG exports, stacked, at 1440 px

Mobile, 390 px

Design · 390 px
Example page at 390 px
Generated · Stitch scrape · 390 px
Generated blocks from the scrape, stacked, at 390 px
Generated · Figma PNG export · 390 px
Generated blocks from PNG exports, stacked, at 390 px

Block by block, 1440 px

hero diff at 1440 px: scrape 5.1% · Figma PNG 5.1%

Layout, type scale and spacing land on both paths. On the PNG path the headline reads “h1”: the mock nests one div deeper than decorate() expects, so the heading-level value is taken for the title.

Design
hero section on the example page
Generated · Stitch scrape
hero block generated from the scraped page
Generated · Figma PNG export
hero block generated from a PNG export

stats diff at 1440 px: scrape 1.5% · Figma PNG 2.8%

Desktop matches. At 390 px the design is a 2×2 grid; both generated blocks stack to one column.

Design
stats section on the example page
Generated · Stitch scrape
stats block generated from the scraped page
Generated · Figma PNG export
stats block generated from a PNG export

cards diff at 1440 px: scrape 19.9% · Figma PNG 37.7%

Scrape keeps the section header. The PNG export drops it (eyebrow, title, intro).

Design
cards section on the example page
Generated · Stitch scrape
cards block generated from the scraped page
Generated · Figma PNG export
cards block generated from a PNG export

columns diff at 1440 px: scrape 27.1% · Figma PNG 3.3%

Scrape misses the list bullets; the PNG path keeps them. Most of the scrape diff is the placeholder image.

Design
columns section on the example page
Generated · Stitch scrape
columns block generated from the scraped page
Generated · Figma PNG export
columns block generated from a PNG export

teaser diff at 1440 px: scrape 4.3% · Figma PNG 1.5%

Close on both paths.

Design
teaser section on the example page
Generated · Stitch scrape
teaser block generated from the scraped page
Generated · Figma PNG export
teaser block generated from a PNG export

tabs diff at 1440 px: scrape 1.8% · Figma PNG 5.2%

Scrape is near-identical. The PNG export drops the section header and the code panel.

Design
tabs section on the example page
Generated · Stitch scrape
tabs block generated from the scraped page
Generated · Figma PNG export
tabs block generated from a PNG export

accordion diff at 1440 px: scrape 1.4% · Figma PNG 1.9%

Close on both paths, including the open first item.

Design
accordion section on the example page
Generated · Stitch scrape
accordion block generated from the scraped page
Generated · Figma PNG export
accordion block generated from a PNG export

quote diff at 1440 px: scrape 1.3% · Figma PNG 1.2%

Close on both paths.

Design
quote section on the example page
Generated · Stitch scrape
quote block generated from the scraped page
Generated · Figma PNG export
quote block generated from a PNG export

cta-banner diff at 1440 px: scrape 2.6% · Figma PNG 2.7%

Close on desktop; the higher mobile diff comes from placeholder copy wrapping differently.

Design
cta-banner section on the example page
Generated · Stitch scrape
cta-banner block generated from the scraped page
Generated · Figma PNG export
cta-banner block generated from a PNG export

Every case

65 cases, each in a fresh project, each checking the exit code and what the CLI prints (and files it writes, where that matters). The scrape cases run real headless Chromium against the example page.

Design source 14 / 14

Every way a design gets in: host detection, the real Chromium scrape, cache and --refresh, local PNG / SVG exports, and the Figma URL paths (MCP, REST with fallback, cached snapshot, whole file, legacy links).

#CaseCommandResult
A01Stitch URL is detected from the hosteds block from-design https://stitch.withgoogle.com/projects/123 --name hero --dry-runpass
A02Framer URL is detected from the hosteds block from-design https://acme.framer.website/pricing --name hero --dry-runpass
A03Any page scraped with --provider stitch (real Chromium)eds block from-design <site>/example/ --provider stitch --name hero --agent nonepass
A04Any page scraped with --provider framereds block from-design <site>/example/ --provider framer --name hero --agent nonepass
A05Second scrape reuses the cacheeds block from-design <site>/example/ --provider stitch --name hero --agent nonepass
A06--refresh re-scrapes past the cacheeds block from-design <site>/example/ --provider stitch --name hero --agent none --refreshpass
A07Local PNG export (Figma frame)eds block from-design hero.png --name hero --dry-runpass
A08Local SVG export (bundled --sample)eds block from-design --sample --dry-runpass
A09Figma URL, no token → MCP, fetched once and cachededs block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-runpass
A10Figma URL, bad token → REST fails, falls back to MCPeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --agent nonepass
A11Figma URL, token + --mcp → MCP, no REST calleds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-run --mcppass
A12Figma URL with a cached snapshot → embedded, no MCPeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-runpass
A13Figma URL with no node → whole fileeds block from-design https://www.figma.com/design/AbCdEf123/Kit --name hero --dry-runpass
A14Legacy /file/ Figma URLeds block from-design https://www.figma.com/file/AbCdEf123/Kit?node-id=12%3A345 --name hero --dry-runpass

Options 13 / 13

Content sources, UE model, screenshot call, block-name inference, and saved-session scraping (--auth).

#CaseCommandResult
B01--source document (default): no content-source sectioneds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-runpass
B02--source ue: Universal Editor model requirededs block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-run --source uepass
B03--source cf: Content Fragment guidanceeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-run --source cfpass
B04--source mixed: CF + UE guidanceeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-run --source mixedpass
B05--source with an unknown value is rejectededs block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-run --source sharepointpass
B06--no-ue-model drops the UE stepeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-run --no-ue-modelpass
B07--screenshot adds one get_screenshot calleds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-run --screenshotpass
B08Block name from a local file nameeds block from-design Hero Banner.png --dry-runpass
B09Block name from the page URLeds block from-design <site>/example/ --provider stitch --dry-runpass
B10Figma node selected without --name → warns the name came from the fileeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --dry-runpass
B11--auth with a saved session reuses it (headless)eds block from-design <site>/example/ --provider stitch --name hero --agent none --authpass
B12--auth-refresh with no display fails cleanlyeds block from-design <site>/example/ --provider stitch --name hero --agent none --auth-refreshpass
B13--auth is ignored on a dry runeds block from-design <site>/example/ --provider stitch --name hero --dry-run --authpass

Agent hand-off 14 / 14

What happens after the agent runs. These use a stand-in claude that writes known files, so each outcome is forced: a good block, nothing, a broken export, a missing model, a crash.

#CaseCommandResult
C01Agent writes a valid block → verified, meta savededs block from-design <site>/example/ --provider stitch --name hero --agent claude --yespass
C02--widths overrides the agent's widthseds block from-design <site>/example/ --provider stitch --name hero --agent claude --yes --widths 390,768,1440pass
C03Prompt reaches the agent on stdin (146 KB page)eds block from-design <site>/example/ --provider stitch --name hero --agent claude --yespass
C04Agent writes nothing → clear failureeds block from-design <site>/example/ --provider stitch --name hero --agent claude --yespass
C05Agent writes nothing, no Figma MCP configured → says soeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --agent claude --yespass
C14--source cf: block with no CF field or fetch is flaggededs block from-design <site>/example/ --provider stitch --name hero --agent claude --yes --source cfpass
C06JS without a default export fails verificationeds block from-design <site>/example/ --provider stitch --name hero --agent claude --yespass
C07Repeatable model but decorate() never iterates → warnededs block from-design <site>/example/ --provider stitch --name hero --agent claude --yespass
C08Missing UE model → warned; fine with --no-ue-modeleds block from-design <site>/example/ --provider stitch --name hero --agent claude --yes --no-ue-modelpass
C09Missing UE model without --no-ue-model → warnededs block from-design <site>/example/ --provider stitch --name hero --agent claude --yespass
C10Agent exits non-zero → failureeds block from-design <site>/example/ --provider stitch --name hero --agent claude --yespass
C11No agent installed → install hint, prompt kepteds block from-design <site>/example/ --provider stitch --name hero --yespass
C12--agent cursor not installed → warns, uses the agent foundeds block from-design <site>/example/ --provider stitch --name hero --agent cursor --yespass
C13--serve starts the preview after generatingeds block from-design <site>/example/ --provider stitch --name hero --agent claude --yes --servepass

Bad input 8 / 8

Wrong flags, wrong URLs, unreachable and gated pages, and running outside a project.

#CaseCommandResult
D01Unknown --providereds block from-design <site>/example/ --provider canva --name hero --dry-runpass
D02Block name not kebab-caseeds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name Hero_Banner --dry-runpass
D03Figma URL without a file keyeds block from-design https://www.figma.com/community/plugins --name hero --dry-runpass
D04Local design file that doesn't existeds block from-design ./missing.png --name hero --dry-runpass
D05Unreachable page → scrape failededs block from-design http://localhost:1/ --provider stitch --name hero --agent nonepass
D06404 page → warns the page looks like an error shelleds block from-design <site>/nope/ --provider stitch --name hero --agent nonepass
D07Login page → warns and points at --autheds block from-design <site>/__login/ --provider stitch --name hero --agent nonepass
D08Outside an EDS projecteds block from-design https://www.figma.com/design/AbCdEf123/Kit-Example?node-id=12-345 --name hero --dry-runpass

Test, check, preview 16 / 16

eds block test baselines, diffs, thresholds and updates; eds block check errors, warnings and --strict; the preview servers.

#CaseCommandResult
E01First run creates the baselineseds block test --widths 390,1440pass
E02Unchanged blocks passeds block test --widths 390,1440pass
E03A CSS change fails with a diff imageeds block test --widths 390,1440pass
E04--threshold lets a change througheds block test stats --threshold 1 --widths 390,1440pass
E05--update accepts the changeeds block test stats --update --widths 390,1440pass
E06…and the next run passeseds block test stats --widths 390,1440pass
E07A different block height fails as a size changeeds block test quote --widths 390,1440pass
E08--design without a Figma source in metaeds block test hero --designpass
E09Unknown block nameeds block test nope --widths 390,1440pass
E10Content check on clean blockseds block checkpass
E11Content check flags a11y errorseds block check badpass
E12Warnings pass by default…eds block check badpass
E13…and fail with --stricteds block check bad --strictpass
E14A mock nested one div too deep is flaggededs block check nestpass
E15Single-block preview serves the harnesseds block preview hero --no-open --port 8795pass
E16Gallery preview serves every blockeds block preview --no-open --port 8796pass

Other sources with the real agent

RunSourceResult
Framer--provider framer, the example pagehero generated, clean in check
SVG sample--sample (Kiri Bloom, a Figma SVG export)hero generated with its real copy and artwork
CF--source cf, cardsgenerated, but no Content Fragment field or fetch (EDS-019)
Mixed--source mixed, teasersame as CF (EDS-019); now flagged by verification (EDS-011)
No UE model--no-ue-model, quotegenerated without _quote.json, as asked
Figma, no MCPa Figma URL, no token, no Figma MCPagent can't read the design and writes nothing; the CLI now says why (EDS-010)
Framer path · hero
Hero block generated from the page scraped as Framer
SVG sample · Kiri Bloom hero
Kiri Bloom hero block generated from the bundled SVG

Migrating AEM components: AEMaaCS and AEM 6.5

eds migrate component was run on every component with a dialog in four public projects, plus every proxy component. Each migrated block was then loaded in the preview and run through eds block check.

ProjectPlatformComponentsMigratedFields
Core ComponentsAEMaaCS and AEM 6.56463434
WKNDAEMaaCS (30 proxy components)3534214
We.RetailAEM 6.3–6.5 (21 proxies, Coral 2 dialogs)3828155
ACS CommonsAEM 6.x2626127

Before the fixes, proxy components (the usual AEMaaCS setup: no dialog of their own, only sling:resourceSuperType) all failed, the Core Components page picker was dropped from 17 dialogs, and 57 choice fields came out with no options, which Universal Editor rejects. Now all 930 fields are valid, and all 151 blocks render without a script error and pass eds block check. The 12 that don't migrate have no authorable fields (panelcontainer) or point at product components that exist only in /libs (Commerce, Social, Experience Fragment pages).

$ eds migrate component ui.apps/…/apps/wknd/components/teaser \
    --super-root aem-core-wcm-components/content/src/content/jcr_root
  ↳ Dialog inherited from core/wcm/components/teaser/v2/teaser
  ▊ Mapped fields  14 from dialog

Second round: fixing the open issues

The gaps left open above were each fixed through their own branch and pull request, then tested the same way.

IssueBeforeAfter
EDS-017A 134 KB prompt (a 33-section page) passed to Cursor / Codex as one argumentPassed as a 257-byte pointer to the prompt file; 70 KB prompts unchanged
EDS-018Classic UI dialog.xml (AEM 6.5): "no Touch UI dialog"ACS Commons 3.14: 21 of 22 Classic UI dialogs migrate, 63 valid fields, all clean in eds block check
EDS-032A checkbox with value="true" came out ticked by defaultThe default comes from checked
EDS-019--source cf / mixed: no CF field, no fetchCF field and an async, fetching decorate() in both (Sonnet by default for CF; about $0.35 per block)
EDS-021Header baseline 1440×900, mostly white1440×80; older baselines get a clear "re-run with --update"
EDS-020REST path covered only up to the requestAgainst a stand-in API serving the example hero frame: fetch, cache, 403 / 404 hints, a real agent run from the REST spec, and block test --design (~3.2% diff)
--source cf · cards (fetch fails in preview, falls back to authored content)
Cards block generated as a Content Fragment block
--source mixed · teaser
Teaser block generated with mixed UE and Content Fragment sources

Bugs found, tracked as issues

Every bug and gap found here is a GitHub issue. Each fix lands through its own bug/EDS-NNN or feature/EDS-NNN branch and pull request, and merging it closes the issue.

IssueWhatStatus
EDS-001Agent never started on a full-page scrape (E2BIG)fixed
EDS-002block test --design always crashedfixed
EDS-003Scraped frame widths saved as default widthsfixed
EDS-005Proxy components couldn't be migratedfixed
EDS-006AEMaaCS link, XF and CF pickers droppedfixed
EDS-007Invalid UE models (empty selects, duplicate names)fixed
EDS-008Unknown --source treated as mixedfixed
EDS-009Figma community URL taken as a filefixed
EDS-010Misleading MCP-limit hint with no MCPfixed
EDS-011No check that CF blocks use a fragmentfixed
EDS-012img-cls check never firedfixed
EDS-013Mock nested deeper than EDS markup not flaggedfixed
EDS-015Guide pages scrolled sideways on phonesfixed
EDS-017Cursor / Codex still get the prompt as an argumentfixed
EDS-018Classic UI dialogs (AEM 6.5) not migratedfixed
EDS-019Default model ignores Content Fragment guidancefixed
EDS-020Figma REST / MCP not yet tested on a real fileREST tested on a stand-in API; real file open
EDS-021Test baselines padded to 900 pxfixed
{I(32)}Migrated checkboxes defaulted to tickedfixed

The example page →   The Figma example file →   Docs →