@zenuml/core 3.46.4 → 3.46.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.claude/skills/babysit-pr/SKILL.md +203 -0
  2. package/.claude/skills/babysit-pr/agents/openai.yaml +7 -0
  3. package/.claude/skills/dia-scoring/SKILL.md +1 -1
  4. package/.claude/skills/propagate-core-release/SKILL.md +200 -0
  5. package/.claude/skills/propagate-core-release/agents/openai.yaml +7 -0
  6. package/.claude/skills/propagate-core-release/references/downstreams.md +41 -0
  7. package/dist/stats.html +1 -1
  8. package/dist/zenuml.esm.mjs +3 -3
  9. package/dist/zenuml.js +3 -3
  10. package/docs/superpowers/plans/2026-03-27-e2e-test-reorg.md +698 -0
  11. package/{cy → e2e/data}/compare-cases.js +70 -37
  12. package/{cy/smoke-editable-label.html → e2e/fixtures/editable-label.html} +1 -1
  13. package/{cy/editable-span-test.html → e2e/fixtures/editable-span.html} +1 -1
  14. package/e2e/fixtures/fixture.html +31 -0
  15. package/{cy → e2e/tools}/canonical-history.html +1 -1
  16. package/{cy → e2e/tools}/compare-case.html +3 -3
  17. package/{cy → e2e/tools}/compare.html +2 -2
  18. package/{cy → e2e/tools}/native-diff-ext/content.js +2 -2
  19. package/firebase-debug.log +108 -0
  20. package/index.html +2 -2
  21. package/mermaid-zenuml-async-spa-auth.png +0 -0
  22. package/mermaid-zenuml-async-spa-auth.snapshot.md +96 -0
  23. package/package.json +1 -1
  24. package/scripts/analyze-compare-case/collect-data.mjs +1 -1
  25. package/scripts/analyze-compare-case.mjs +1 -1
  26. package/skills/dia-scoring/SKILL.md +1 -1
  27. package/vite.config.ts +5 -5
  28. package/cy/async-message-1.html +0 -32
  29. package/cy/async-message-2.html +0 -46
  30. package/cy/async-message-3.html +0 -41
  31. package/cy/creation-rtl.html +0 -28
  32. package/cy/defect-406-alt-under-creation.html +0 -40
  33. package/cy/demo1.html +0 -28
  34. package/cy/demo3.html +0 -28
  35. package/cy/demo4.html +0 -28
  36. package/cy/element-report.html +0 -705
  37. package/cy/fragments-with-return.html +0 -35
  38. package/cy/icons-test.html +0 -29
  39. package/cy/if-fragment.html +0 -28
  40. package/cy/legacy-vs-html.html +0 -291
  41. package/cy/named-parameters.html +0 -30
  42. package/cy/nested-interaction-with-fragment.html +0 -34
  43. package/cy/nested-interaction-with-outbound.html +0 -34
  44. package/cy/parity-test.html +0 -122
  45. package/cy/return-in-nested-if.html +0 -29
  46. package/cy/return.html +0 -38
  47. package/cy/self-sync-message-at-root.html +0 -28
  48. package/cy/smoke-creation.html +0 -26
  49. package/cy/smoke-fragment-issue.html +0 -36
  50. package/cy/smoke-fragment.html +0 -42
  51. package/cy/smoke-interaction.html +0 -34
  52. package/cy/smoke.html +0 -40
  53. package/cy/theme-default-test.html +0 -28
  54. package/cy/vertical-1.html +0 -25
  55. package/cy/vertical-10.html +0 -33
  56. package/cy/vertical-11.html +0 -29
  57. package/cy/vertical-2.html +0 -23
  58. package/cy/vertical-3.html +0 -37
  59. package/cy/vertical-4.html +0 -42
  60. package/cy/vertical-5.html +0 -40
  61. package/cy/vertical-6.html +0 -29
  62. package/cy/vertical-7.html +0 -27
  63. package/cy/vertical-8.html +0 -32
  64. package/cy/vertical-9.html +0 -25
  65. package/cy/xss.html +0 -21
  66. /package/{cy → e2e/data}/diff-algorithm.js +0 -0
  67. /package/{cy → e2e/fixtures}/svg-test.html +0 -0
  68. /package/{cy → e2e/tools}/native-diff-ext/background.js +0 -0
  69. /package/{cy → e2e/tools}/native-diff-ext/bridge.js +0 -0
  70. /package/{cy → e2e/tools}/svg-preview.html +0 -0
@@ -0,0 +1,698 @@
1
+ # E2E Test Reorganization Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Eliminate massive duplication in E2E tests by consolidating DSL data into a single source of truth, making repetitive tests data-driven, and organizing tests into logical subdirectories.
6
+
7
+ **Architecture:** Replace 37 near-identical HTML fixture files with a single parameterized template (`e2e/fixtures/fixture.html`) that reads DSL from a query parameter or shared data module. Consolidate all DSL test data into `e2e/data/compare-cases.js` (already exists, already has ~50 cases). Convert repetitive spec files (vertical, creation, fragment, etc.) into data-driven loops like `svg-parity.spec.ts` already does. Group spec files into subdirectories by test type.
8
+
9
+ **Tech Stack:** Playwright, TypeScript, Vite dev server, existing `cy/` HTML infrastructure
10
+
11
+ ---
12
+
13
+ ## Current State Analysis
14
+
15
+ ### Problems
16
+
17
+ 1. **37 near-identical HTML files** — Each `e2e/**/*.html` is the same boilerplate with different DSL in a `<pre>` tag. Example: `vertical-1.html` through `vertical-11.html` differ only by 2-3 lines of DSL code.
18
+
19
+ 2. **DSL duplicated 3 times** — The same diagram code exists in:
20
+ - `e2e/**/*.html` files (for HTML renderer tests)
21
+ - `svg-parity.spec.ts` inline array (for SVG parity tests)
22
+ - `e2e/data/compare-cases.js` (for the compare-case viewer)
23
+
24
+ 3. **Copy-paste spec files** — `vertical.spec.ts` has 11 identical test blocks differing only by number. `creation.spec.ts`, `fragment.spec.ts`, `return.spec.ts`, etc. are all the same 15-line pattern.
25
+
26
+ 4. **Flat directory** — All 21 spec files sit in `tests/` with no grouping. Visual regression tests, interaction tests, and parity tests are mixed together.
27
+
28
+ 5. **Inconsistent `describe` names** — "Smoke test", "Rendering", "SVG Parity Tests" — no convention.
29
+
30
+ ### What Works Well (Keep)
31
+
32
+ - `svg-parity.spec.ts` — already data-driven with an inline array. Good pattern to extend.
33
+ - `fixtures.ts` — clean custom fixture with console logging. Keep as-is.
34
+ - `compare-cases.js` — central case registry. Promote to single source of truth.
35
+ - `editable-label.spec.ts`, `editable-span-escape.spec.ts` — interaction tests with unique logic. These stay as individual files.
36
+ - `width-provider-comparison.spec.ts` — measurement test with unique logic. Stays as-is.
37
+
38
+ ## Target State
39
+
40
+ ```
41
+ tests/
42
+ ├── fixtures.ts # Shared fixture (unchanged)
43
+ ├── test-cases.ts # Re-exports from e2e/data/compare-cases.js with types
44
+ ├── visual/
45
+ │ ├── html-rendering.spec.ts # Data-driven: all HTML renderer screenshot tests
46
+ │ └── svg-parity.spec.ts # Data-driven: all SVG renderer screenshot tests (exists, move)
47
+ ├── interaction/
48
+ │ ├── editable-label.spec.ts # Keep as-is (move)
49
+ │ └── editable-span-escape.spec.ts # Keep as-is (move)
50
+ ├── regression/
51
+ │ └── defect-406.spec.ts # Keep as-is (move)
52
+ ├── measurement/
53
+ │ └── width-provider-comparison.spec.ts # Keep as-is (move)
54
+ └── *.spec.ts-snapshots/ # Snapshot dirs (Playwright manages these)
55
+
56
+ cy/
57
+ ├── fixture.html # Single parameterized HTML template
58
+ ├── compare-cases.js # Single source of truth for ALL DSL cases
59
+ ├── compare-case.html # Keep (compare viewer)
60
+ ├── compare.html # Keep (index page)
61
+ ├── svg-test.html # Keep (SVG render harness)
62
+ ├── editable-span-test.html # Keep (interaction test page)
63
+ ├── smoke-editable-label.html # Keep (interaction test page)
64
+ ├── element-report.html # Keep (dev tool)
65
+ ├── parity-test.html # Keep (dev tool)
66
+ ├── legacy-vs-html.html # Keep (dev tool)
67
+ ├── canonical-history.html # Keep (dev tool)
68
+ ├── svg-preview.html # Keep (dev tool)
69
+ ├── icons-test.html # Keep (dev tool)
70
+ ├── theme-default-test.html # Keep (dev tool)
71
+ └── diff-algorithm.js # Keep (utility)
72
+ ```
73
+
74
+ **Deleted** (replaced by `fixture.html`): `smoke.html`, `smoke-creation.html`, `smoke-fragment.html`, `smoke-fragment-issue.html`, `smoke-interaction.html`, `creation-rtl.html`, `defect-406-alt-under-creation.html`, `fragment.html`, `fragments-with-return.html`, `if-fragment.html`, `return.html`, `self-sync-message-at-root.html`, `named-parameters.html`, `nested-interaction-with-fragment.html`, `nested-interaction-with-outbound.html`, `vertical-1.html` through `vertical-11.html`, `async-message-1.html` through `async-message-3.html`, `demo1.html`, `demo3.html`, `demo4.html`, `return-in-nested-if.html`.
75
+
76
+ **Deleted spec files** (merged into `html-rendering.spec.ts`): `smoke.spec.ts`, `creation.spec.ts`, `creation-rtl.spec.ts`, `fragment.spec.ts`, `fragments-with-return.spec.ts`, `if-fragment.spec.ts`, `interaction.spec.ts`, `nested-interactions.spec.ts`, `named-parameters.spec.ts`, `return.spec.ts`, `return-in-nested-if.spec.ts`, `self-sync-message-at-root.spec.ts`, `vertical.spec.ts`, `async-message.spec.ts`, `demo.spec.ts`, `style-panel.spec.ts`.
77
+
78
+ ---
79
+
80
+ ### Task 1: Create parameterized HTML fixture template
81
+
82
+ **Files:**
83
+ - Create: `e2e/fixtures/fixture.html`
84
+ - Reference: `cy/smoke.html` (pattern to generalize)
85
+
86
+ The new template reads a `?case=<name>` query parameter and looks up the DSL from the `compare-cases.js` registry.
87
+
88
+ - [ ] **Step 1: Create `e2e/fixtures/fixture.html`**
89
+
90
+ ```html
91
+ <!doctype html>
92
+ <html>
93
+ <head>
94
+ <meta charset="utf-8" />
95
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
96
+ <meta name="viewport" content="width=device-width,initial-scale=1.0" />
97
+ <title>ZenUML Test Fixture</title>
98
+ <style>
99
+ body {
100
+ margin: 0;
101
+ }
102
+ </style>
103
+ </head>
104
+ <body>
105
+ <div id="diagram" class="diagram">
106
+ <pre class="zenuml" style="margin: 0" id="zenuml-code"></pre>
107
+ </div>
108
+ <script type="module">
109
+ import { CASES } from "./compare-cases.js";
110
+ const params = new URLSearchParams(window.location.search);
111
+ const caseName = params.get("case");
112
+ if (caseName && CASES[caseName]) {
113
+ document.getElementById("zenuml-code").textContent = CASES[caseName];
114
+ } else {
115
+ document.getElementById("zenuml-code").textContent =
116
+ "// Case not found: " + caseName;
117
+ }
118
+ </script>
119
+ <script type="module" src="/src/main-cy.ts"></script>
120
+ </body>
121
+ </html>
122
+ ```
123
+
124
+ - [ ] **Step 2: Verify the template loads correctly**
125
+
126
+ Start the dev server (`bun dev`) and open `http://localhost:8080/cy/fixture.html?case=smoke` in a browser. Verify the diagram renders identically to `http://localhost:8080/cy/smoke.html`.
127
+
128
+ - [ ] **Step 3: Commit**
129
+
130
+ ```bash
131
+ git add cy/fixture.html
132
+ git commit -m "Add parameterized HTML fixture template for E2E tests"
133
+ ```
134
+
135
+ ---
136
+
137
+ ### Task 2: Ensure all current HTML fixture DSL is in compare-cases.js
138
+
139
+ **Files:**
140
+ - Modify: `e2e/data/compare-cases.js`
141
+ - Reference: All `e2e/**/*.html` files (to extract DSL)
142
+
143
+ Some HTML fixture files have DSL that isn't in `compare-cases.js`. We need to add any missing cases.
144
+
145
+ - [ ] **Step 1: Audit missing cases**
146
+
147
+ Write a script or manually compare. The following cases from HTML fixtures must exist in `compare-cases.js`:
148
+
149
+ Check these exist (by name): `smoke`, `creation` (from `smoke-creation.html`), `creation-rtl`, `defect-406` (from `defect-406-alt-under-creation.html`), `fragment` (from `smoke-fragment.html`), `fragment-issue` (from `smoke-fragment-issue.html`), `if-fragment`, `fragments-return` (from `fragments-with-return.html`), `interaction` (from `smoke-interaction.html`), `async-1`, `async-2`, `async-3`, `return`, `self-sync` (from `self-sync-message-at-root.html`), `named-params` (from `named-parameters.html`), `nested-fragment` (from `nested-interaction-with-fragment.html`), `nested-outbound` (from `nested-interaction-with-outbound.html`), `vertical-1` through `vertical-11`, `demo1-smoke` (from `demo1.html`), `demo3-nested-fragments` (from `demo3.html`), `demo4-fragment-span` (from `demo4.html`), `return-in-nested-if`, `style-panel` (from `smoke-fragment.html` used by `style-panel.spec.ts`).
150
+
151
+ - [ ] **Step 2: Add any missing cases to `compare-cases.js`**
152
+
153
+ Extract the DSL from each HTML file's `<pre class="zenuml">` block. Add to the `CASES` object using the same name used in `svg-parity.spec.ts` where possible (for consistency).
154
+
155
+ For each missing case, add it in the appropriate category section with a comment:
156
+
157
+ ```javascript
158
+ // --- Category ---
159
+ "case-name": `DSL code here`,
160
+ ```
161
+
162
+ - [ ] **Step 3: Verify fixture.html works for all added cases**
163
+
164
+ Spot-check 3-4 newly added cases by loading `fixture.html?case=<name>` and comparing to the original HTML file.
165
+
166
+ - [ ] **Step 4: Commit**
167
+
168
+ ```bash
169
+ git add e2e/data/compare-cases.js
170
+ git commit -m "Add all HTML fixture DSL cases to compare-cases.js"
171
+ ```
172
+
173
+ ---
174
+
175
+ ### Task 3: Create typed test-cases module
176
+
177
+ **Files:**
178
+ - Create: `tests/test-cases.ts`
179
+
180
+ A thin TypeScript wrapper that imports from `compare-cases.js` and provides typed access for spec files.
181
+
182
+ - [ ] **Step 1: Create `tests/test-cases.ts`**
183
+
184
+ ```typescript
185
+ // Re-export compare-cases with type safety for use in spec files.
186
+ // compare-cases.js is the single source of truth for all DSL test data.
187
+
188
+ // @ts-expect-error — JS module without types
189
+ import { CASES } from "../e2e/data/compare-cases.js";
190
+
191
+ export const TEST_CASES: Record<string, string> = CASES;
192
+
193
+ /**
194
+ * Subset of cases used by the HTML renderer visual regression tests.
195
+ * Each entry maps to a fixture.html?case=<key> URL.
196
+ */
197
+ export const HTML_VISUAL_CASES: { name: string; threshold?: number }[] = [
198
+ // Smoke / basics
199
+ { name: "smoke", threshold: 0.012 },
200
+ { name: "creation" },
201
+ { name: "creation-rtl" },
202
+ { name: "defect-406" },
203
+
204
+ // Fragments
205
+ { name: "fragment" },
206
+ { name: "fragment-issue" },
207
+ { name: "if-fragment" },
208
+ { name: "fragments-return" },
209
+
210
+ // Interactions
211
+ { name: "interaction" },
212
+ { name: "nested-fragment" },
213
+ { name: "nested-outbound" },
214
+
215
+ // Async messages
216
+ { name: "async-1" },
217
+ { name: "async-2" },
218
+ { name: "async-3" },
219
+
220
+ // Returns
221
+ { name: "return" },
222
+ { name: "return-in-nested-if" },
223
+
224
+ // Self-calls
225
+ { name: "self-sync" },
226
+
227
+ // Named parameters
228
+ { name: "named-params" },
229
+
230
+ // Vertical layout
231
+ { name: "vertical-1" },
232
+ { name: "vertical-2" },
233
+ { name: "vertical-3" },
234
+ { name: "vertical-4" },
235
+ { name: "vertical-5" },
236
+ { name: "vertical-6" },
237
+ { name: "vertical-7" },
238
+ { name: "vertical-8" },
239
+ { name: "vertical-9" },
240
+ { name: "vertical-10" },
241
+ { name: "vertical-11" },
242
+
243
+ // Demos
244
+ { name: "demo1-smoke" },
245
+ { name: "demo3-nested-fragments" },
246
+ { name: "demo4-fragment-span" },
247
+ ];
248
+
249
+ /**
250
+ * Default screenshot threshold for visual tests.
251
+ */
252
+ export const DEFAULT_THRESHOLD = 0.02;
253
+ ```
254
+
255
+ - [ ] **Step 2: Verify import works**
256
+
257
+ ```bash
258
+ cd /Users/pengxiao/workspaces/zenuml/native-svg-renderer/zenuml-core
259
+ bun -e "import { TEST_CASES, HTML_VISUAL_CASES } from './tests/test-cases.ts'; console.log(Object.keys(TEST_CASES).length, 'cases'); console.log(HTML_VISUAL_CASES.length, 'html visual cases')"
260
+ ```
261
+
262
+ Expected: prints case counts without errors.
263
+
264
+ - [ ] **Step 3: Commit**
265
+
266
+ ```bash
267
+ git add tests/test-cases.ts
268
+ git commit -m "Add typed test-cases module wrapping compare-cases.js"
269
+ ```
270
+
271
+ ---
272
+
273
+ ### Task 4: Create directory structure and move unique test files
274
+
275
+ **Files:**
276
+ - Create dirs: `tests/visual/`, `tests/interaction/`, `tests/regression/`, `tests/measurement/`
277
+ - Move: `tests/editable-label.spec.ts` → `tests/interaction/editable-label.spec.ts`
278
+ - Move: `tests/editable-span-escape.spec.ts` → `tests/interaction/editable-span-escape.spec.ts`
279
+ - Move: `tests/defect-406.spec.ts` → `tests/regression/defect-406.spec.ts`
280
+ - Move: `tests/width-provider-comparison.spec.ts` → `tests/measurement/width-provider-comparison.spec.ts`
281
+ - Move: `tests/svg-parity.spec.ts` → `tests/visual/svg-parity.spec.ts`
282
+ - Modify: `playwright.config.ts` (update testDir if needed)
283
+
284
+ - [ ] **Step 1: Create directories**
285
+
286
+ ```bash
287
+ mkdir -p tests/visual tests/interaction tests/regression tests/measurement
288
+ ```
289
+
290
+ - [ ] **Step 2: Move unique test files**
291
+
292
+ ```bash
293
+ git mv tests/editable-label.spec.ts tests/interaction/editable-label.spec.ts
294
+ git mv tests/editable-span-escape.spec.ts tests/interaction/editable-span-escape.spec.ts
295
+ git mv tests/defect-406.spec.ts tests/regression/defect-406.spec.ts
296
+ git mv tests/width-provider-comparison.spec.ts tests/measurement/width-provider-comparison.spec.ts
297
+ git mv tests/svg-parity.spec.ts tests/visual/svg-parity.spec.ts
298
+ ```
299
+
300
+ - [ ] **Step 3: Update fixture imports in moved files**
301
+
302
+ Each moved file imports from `./fixtures`. Update to `../fixtures`:
303
+
304
+ In `tests/interaction/editable-label.spec.ts`:
305
+ ```typescript
306
+ import { test, expect } from "../fixtures";
307
+ ```
308
+
309
+ In `tests/interaction/editable-span-escape.spec.ts`:
310
+ ```typescript
311
+ import { test, expect } from "../fixtures";
312
+ ```
313
+
314
+ In `tests/regression/defect-406.spec.ts`:
315
+ ```typescript
316
+ import { test, expect } from "../fixtures";
317
+ ```
318
+
319
+ In `tests/measurement/width-provider-comparison.spec.ts`:
320
+ ```typescript
321
+ import { test, expect } from "../fixtures";
322
+ ```
323
+
324
+ In `tests/visual/svg-parity.spec.ts`:
325
+ ```typescript
326
+ import { test, expect } from "../fixtures";
327
+ ```
328
+
329
+ - [ ] **Step 4: Move snapshot directories alongside their spec files**
330
+
331
+ ```bash
332
+ # Move snapshot dirs to match new spec locations
333
+ git mv tests/editable-label.spec.ts-snapshots tests/interaction/editable-label.spec.ts-snapshots
334
+ git mv tests/defect-406.spec.ts-snapshots tests/regression/defect-406.spec.ts-snapshots
335
+ git mv tests/svg-parity.spec.ts-snapshots tests/visual/svg-parity.spec.ts-snapshots
336
+ ```
337
+
338
+ Note: `editable-span-escape.spec.ts` and `width-provider-comparison.spec.ts` don't have snapshot dirs (they don't use `toHaveScreenshot`).
339
+
340
+ - [ ] **Step 5: Verify Playwright finds all tests**
341
+
342
+ ```bash
343
+ bun pw --list
344
+ ```
345
+
346
+ Expected: All moved tests appear with their new paths. The `testDir: "./tests"` in `playwright.config.ts` already recurses into subdirectories by default, so no config change needed.
347
+
348
+ - [ ] **Step 6: Run the moved tests to verify snapshots still match**
349
+
350
+ ```bash
351
+ bun pw tests/interaction/ tests/regression/ tests/measurement/ tests/visual/svg-parity.spec.ts
352
+ ```
353
+
354
+ Expected: All pass (snapshots found in new locations).
355
+
356
+ - [ ] **Step 7: Commit**
357
+
358
+ ```bash
359
+ git add -A tests/
360
+ git commit -m "Organize E2E tests into subdirectories by type"
361
+ ```
362
+
363
+ ---
364
+
365
+ ### Task 5: Create data-driven HTML rendering test
366
+
367
+ **Files:**
368
+ - Create: `tests/visual/html-rendering.spec.ts`
369
+
370
+ This single file replaces 16 separate spec files by iterating over `HTML_VISUAL_CASES`.
371
+
372
+ - [ ] **Step 1: Create `tests/visual/html-rendering.spec.ts`**
373
+
374
+ ```typescript
375
+ import { test, expect } from "../fixtures";
376
+ import { HTML_VISUAL_CASES, DEFAULT_THRESHOLD } from "../test-cases";
377
+
378
+ test.describe("HTML Rendering", () => {
379
+ for (const { name, threshold } of HTML_VISUAL_CASES) {
380
+ test(name, async ({ page }) => {
381
+ await page.goto(`/cy/fixture.html?case=${name}`);
382
+
383
+ // Wait for diagram to render
384
+ await expect(page.locator(".privacy>span>svg")).toBeVisible({
385
+ timeout: 5000,
386
+ });
387
+
388
+ await expect(page).toHaveScreenshot(`${name}.png`, {
389
+ threshold: threshold ?? DEFAULT_THRESHOLD,
390
+ fullPage: true,
391
+ });
392
+ });
393
+ }
394
+ });
395
+ ```
396
+
397
+ - [ ] **Step 2: Run the new test to generate initial snapshots**
398
+
399
+ ```bash
400
+ bun pw tests/visual/html-rendering.spec.ts --update-snapshots
401
+ ```
402
+
403
+ Expected: All cases generate snapshots. Verify a few match the old snapshots visually.
404
+
405
+ - [ ] **Step 3: Run without --update-snapshots to verify stability**
406
+
407
+ ```bash
408
+ bun pw tests/visual/html-rendering.spec.ts
409
+ ```
410
+
411
+ Expected: All pass.
412
+
413
+ - [ ] **Step 4: Commit**
414
+
415
+ ```bash
416
+ git add tests/visual/html-rendering.spec.ts tests/visual/html-rendering.spec.ts-snapshots/
417
+ git commit -m "Add data-driven HTML rendering visual test"
418
+ ```
419
+
420
+ ---
421
+
422
+ ### Task 6: Refactor svg-parity.spec.ts to use shared test data
423
+
424
+ **Files:**
425
+ - Modify: `tests/visual/svg-parity.spec.ts`
426
+
427
+ Remove the 540-line inline `SVG_PARITY_CASES` array and replace with imports from `test-cases.ts`. The SVG parity test uses the same case names but renders through `renderToSvg()` instead of the HTML renderer.
428
+
429
+ - [ ] **Step 1: Add SVG parity case list to `tests/test-cases.ts`**
430
+
431
+ Add after `HTML_VISUAL_CASES`:
432
+
433
+ ```typescript
434
+ /**
435
+ * Subset of cases used by the SVG parity visual regression tests.
436
+ * Each entry renders through renderToSvg() on svg-test.html.
437
+ */
438
+ export const SVG_PARITY_CASES: string[] = [
439
+ "smoke",
440
+ "creation",
441
+ "creation-rtl",
442
+ "defect-406",
443
+ "fragment",
444
+ "fragment-issue",
445
+ "if-fragment",
446
+ "fragments-return",
447
+ "interaction",
448
+ "async-1",
449
+ "async-2",
450
+ "async-3",
451
+ "return",
452
+ "self-sync",
453
+ "nested-fragment",
454
+ "nested-outbound",
455
+ "named-params",
456
+ "vertical-1",
457
+ "vertical-2",
458
+ "vertical-3",
459
+ "vertical-4",
460
+ "vertical-5",
461
+ "vertical-6",
462
+ "vertical-7",
463
+ "vertical-8",
464
+ "vertical-9",
465
+ "vertical-10",
466
+ "vertical-11",
467
+ "demo1-smoke",
468
+ "demo3-nested-fragments",
469
+ "demo4-fragment-span",
470
+ "demo5-self-named",
471
+ "demo6-async-styled",
472
+ ];
473
+ ```
474
+
475
+ - [ ] **Step 2: Rewrite `tests/visual/svg-parity.spec.ts`**
476
+
477
+ ```typescript
478
+ import { test, expect } from "../fixtures";
479
+ import { TEST_CASES, SVG_PARITY_CASES } from "../test-cases";
480
+
481
+ /**
482
+ * SVG Parity Tests
483
+ *
484
+ * Renders every visual test fixture through renderToSvg() and captures
485
+ * screenshot baselines. These verify structural/layout parity with the
486
+ * React/HTML renderer — same elements, positions, and reading order.
487
+ */
488
+ test.describe("SVG Parity Tests", () => {
489
+ for (const name of SVG_PARITY_CASES) {
490
+ test(`svg-${name}`, async ({ page }) => {
491
+ const code = TEST_CASES[name];
492
+ if (!code) throw new Error(`Missing test case: ${name}`);
493
+
494
+ await page.goto("/cy/svg-test.html");
495
+ await page.evaluate((c) => (window as any).__renderSvg(c), code);
496
+ await expect(page.locator("#svg-output > svg")).toBeVisible({ timeout: 5000 });
497
+ await expect(page).toHaveScreenshot(`svg-${name}.png`, {
498
+ threshold: 0.02,
499
+ fullPage: true,
500
+ });
501
+ });
502
+ }
503
+ });
504
+ ```
505
+
506
+ - [ ] **Step 3: Run to verify snapshots still match**
507
+
508
+ ```bash
509
+ bun pw tests/visual/svg-parity.spec.ts
510
+ ```
511
+
512
+ Expected: All pass with existing snapshots (screenshot names unchanged).
513
+
514
+ - [ ] **Step 4: Commit**
515
+
516
+ ```bash
517
+ git add tests/visual/svg-parity.spec.ts tests/test-cases.ts
518
+ git commit -m "Refactor svg-parity.spec.ts to use shared test-cases module"
519
+ ```
520
+
521
+ ---
522
+
523
+ ### Task 7: Delete replaced spec files and HTML fixtures
524
+
525
+ **Files:**
526
+ - Delete: 16 spec files (replaced by `html-rendering.spec.ts`)
527
+ - Delete: ~27 HTML fixture files (replaced by `fixture.html`)
528
+
529
+ - [ ] **Step 1: Verify the new data-driven tests cover all old tests**
530
+
531
+ Run both old and new tests, compare test counts:
532
+
533
+ ```bash
534
+ # Count old individual tests
535
+ bun pw tests/smoke.spec.ts tests/creation.spec.ts tests/creation-rtl.spec.ts tests/fragment.spec.ts tests/fragments-with-return.spec.ts tests/if-fragment.spec.ts tests/interaction.spec.ts tests/nested-interactions.spec.ts tests/named-parameters.spec.ts tests/return.spec.ts tests/return-in-nested-if.spec.ts tests/self-sync-message-at-root.spec.ts tests/vertical.spec.ts tests/async-message.spec.ts tests/demo.spec.ts tests/style-panel.spec.ts --list 2>/dev/null | wc -l
536
+
537
+ # Count new data-driven tests
538
+ bun pw tests/visual/html-rendering.spec.ts --list 2>/dev/null | wc -l
539
+ ```
540
+
541
+ The new test count should be >= old test count.
542
+
543
+ - [ ] **Step 2: Delete old spec files**
544
+
545
+ ```bash
546
+ git rm tests/smoke.spec.ts
547
+ git rm tests/creation.spec.ts
548
+ git rm tests/creation-rtl.spec.ts
549
+ git rm tests/fragment.spec.ts
550
+ git rm tests/fragments-with-return.spec.ts
551
+ git rm tests/if-fragment.spec.ts
552
+ git rm tests/interaction.spec.ts
553
+ git rm tests/nested-interactions.spec.ts
554
+ git rm tests/named-parameters.spec.ts
555
+ git rm tests/return.spec.ts
556
+ git rm tests/return-in-nested-if.spec.ts
557
+ git rm tests/self-sync-message-at-root.spec.ts
558
+ git rm tests/vertical.spec.ts
559
+ git rm tests/async-message.spec.ts
560
+ git rm tests/demo.spec.ts
561
+ git rm tests/style-panel.spec.ts
562
+ ```
563
+
564
+ - [ ] **Step 3: Delete old snapshot directories for removed specs**
565
+
566
+ ```bash
567
+ git rm -r tests/smoke.spec.ts-snapshots
568
+ git rm -r tests/creation.spec.ts-snapshots
569
+ git rm -r tests/creation-rtl.spec.ts-snapshots
570
+ git rm -r tests/fragment.spec.ts-snapshots
571
+ git rm -r tests/fragments-with-return.spec.ts-snapshots
572
+ git rm -r tests/if-fragment.spec.ts-snapshots
573
+ git rm -r tests/interaction.spec.ts-snapshots
574
+ git rm -r tests/nested-interactions.spec.ts-snapshots
575
+ git rm -r tests/named-parameters.spec.ts-snapshots
576
+ git rm -r tests/return.spec.ts-snapshots
577
+ git rm -r tests/return-in-nested-if.spec.ts-snapshots
578
+ git rm -r tests/self-sync-message-at-root.spec.ts-snapshots
579
+ git rm -r tests/vertical.spec.ts-snapshots
580
+ git rm -r tests/async-message.spec.ts-snapshots
581
+ git rm -r tests/demo.spec.ts-snapshots
582
+ git rm -r tests/style-panel.spec.ts-snapshots
583
+ ```
584
+
585
+ - [ ] **Step 4: Delete replaced HTML fixtures**
586
+
587
+ ```bash
588
+ git rm cy/smoke.html
589
+ git rm cy/smoke-creation.html
590
+ git rm cy/smoke-fragment.html
591
+ git rm cy/smoke-fragment-issue.html
592
+ git rm cy/smoke-interaction.html
593
+ git rm cy/creation-rtl.html
594
+ git rm cy/defect-406-alt-under-creation.html
595
+ git rm cy/fragment.html
596
+ git rm cy/fragments-with-return.html
597
+ git rm cy/if-fragment.html
598
+ git rm cy/return.html
599
+ git rm cy/return-in-nested-if.html
600
+ git rm cy/self-sync-message-at-root.html
601
+ git rm cy/named-parameters.html
602
+ git rm cy/nested-interaction-with-fragment.html
603
+ git rm cy/nested-interaction-with-outbound.html
604
+ git rm cy/vertical-1.html cy/vertical-2.html cy/vertical-3.html cy/vertical-4.html
605
+ git rm cy/vertical-5.html cy/vertical-6.html cy/vertical-7.html cy/vertical-8.html
606
+ git rm cy/vertical-9.html cy/vertical-10.html cy/vertical-11.html
607
+ git rm cy/async-message-1.html cy/async-message-2.html cy/async-message-3.html
608
+ git rm cy/demo1.html cy/demo3.html cy/demo4.html
609
+ ```
610
+
611
+ - [ ] **Step 5: Run full test suite to verify nothing is broken**
612
+
613
+ ```bash
614
+ bun pw
615
+ ```
616
+
617
+ Expected: All tests pass. No tests reference deleted files.
618
+
619
+ - [ ] **Step 6: Commit**
620
+
621
+ ```bash
622
+ git add -A
623
+ git commit -m "Remove replaced spec files and HTML fixtures"
624
+ ```
625
+
626
+ ---
627
+
628
+ ### Task 8: Final verification and cleanup
629
+
630
+ **Files:**
631
+ - Verify: All tests pass
632
+ - Verify: No dead references
633
+
634
+ - [ ] **Step 1: Run the full test suite**
635
+
636
+ ```bash
637
+ bun pw --reporter=list
638
+ ```
639
+
640
+ Expected: All tests pass.
641
+
642
+ - [ ] **Step 2: Verify test count**
643
+
644
+ ```bash
645
+ bun pw --list | wc -l
646
+ ```
647
+
648
+ Expected: Should be roughly the same as before (the data-driven tests cover all the old individual tests plus SVG parity tests).
649
+
650
+ - [ ] **Step 3: Check for dead HTML references**
651
+
652
+ ```bash
653
+ grep -r "cy/smoke\.html\|cy/vertical-\|cy/creation-rtl\|cy/fragment\.html\|cy/demo[134]\.html" tests/
654
+ ```
655
+
656
+ Expected: No matches (no spec files reference deleted HTML fixtures).
657
+
658
+ - [ ] **Step 4: Verify directory structure matches target**
659
+
660
+ ```bash
661
+ find tests -name "*.spec.ts" | sort
662
+ ```
663
+
664
+ Expected output:
665
+ ```
666
+ tests/interaction/editable-label.spec.ts
667
+ tests/interaction/editable-span-escape.spec.ts
668
+ tests/measurement/width-provider-comparison.spec.ts
669
+ tests/regression/defect-406.spec.ts
670
+ tests/visual/html-rendering.spec.ts
671
+ tests/visual/svg-parity.spec.ts
672
+ ```
673
+
674
+ - [ ] **Step 5: Commit (if any cleanup needed)**
675
+
676
+ ```bash
677
+ git add -A
678
+ git commit -m "Final cleanup after E2E test reorganization"
679
+ ```
680
+
681
+ ---
682
+
683
+ ## Summary of Changes
684
+
685
+ | Metric | Before | After |
686
+ |--------|--------|-------|
687
+ | Spec files | 21 | 6 |
688
+ | HTML fixtures | 37 | ~14 (unique pages only) |
689
+ | Lines in `vertical.spec.ts` | 147 (11 copy-paste blocks) | 0 (absorbed into html-rendering.spec.ts) |
690
+ | Lines in `svg-parity.spec.ts` | 553 (inline DSL) | ~25 (imports from test-cases) |
691
+ | DSL source of truth | 3 places | 1 (`compare-cases.js`) |
692
+ | Test organization | Flat | 4 subdirectories by type |
693
+
694
+ ## Risk Mitigation
695
+
696
+ - **Snapshot names change** — The new `html-rendering.spec.ts` uses `${name}.png` naming. Old snapshots used various names like `should-load-the-home-page.png`, `creation.png`, etc. We generate fresh snapshots in Task 5 and verify they match visually.
697
+ - **fixture.html rendering differs from dedicated HTML** — The `main-cy.ts` script renders `<pre class="zenuml">` content. The parameterized template sets `textContent` before `main-cy.ts` runs (script is `type="module"`, deferred). Test in Task 1 confirms this.
698
+ - **CI snapshot mismatch** — Linux snapshots will need regenerating. Run `bun pw:update` in CI or update manually after merge.