Skip to content
This repository was archived by the owner on May 29, 2026. It is now read-only.

test(e2e): Phase 4 — stability / perf / a11y - #238

Merged
Jocs merged 3 commits into
masterfrom
test/e2e-phase-4-stability-perf-a11y
May 21, 2026
Merged

test(e2e): Phase 4 — stability / perf / a11y#238
Jocs merged 3 commits into
masterfrom
test/e2e-phase-4-stability-perf-a11y

Conversation

@Jocs

@Jocs Jocs commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

Lands the full Phase 4 BACKLOG of the muya-e2e suite: 28 new Playwright specs across stability, perf, accessibility, option matrix, edge inputs, and the static MarkdownToHtml export pipeline. Total test count grows from 54 passed / 1 skipped → 83 passed / 1 skipped.

  • Stability (2 specs): PR-17 listener-leak regression (50× destroy/rebuild loop asserting EventCenter array bounds), 10k-paragraph setContent perf smoke (tagged @perf).
  • Accessibility (6 specs): @axe-core/playwright scans of the clean host plus IFT, slash menu, link tools, image tools, and table tools open states. Fails on critical only; non-critical violations logged for Phase 5 triage.
  • Option matrix (10 specs): full on/off coverage for autoPairBracket / autoPairMarkdownSyntax / autoPairQuote, plus focusMode, spellcheckEnabled, and disableHtml.
  • Edges (3 specs): empty document, single-character document, 10× rapid setContent without awaits.
  • Static export (4 specs): window.MarkdownToHtml presence, generate shape (heading / list / code-block / KaTeX / mermaid), and the script-injection sanitization test that Phase 3 PR test(e2e): Phase 3 — render depth + remaining blocks + sanitize #237 deferred — DOMPurify strips <script> and mounting the output in the DOM does not execute the payload.
  • Infrastructure: host exposes window.MarkdownToHtml and window.__e2e.rebuildMuya(opts) so option-matrix specs can rebuild Muya with different IMuyaOptions. Types added to e2e/types.d.ts — no (window as any) anywhere.

Caveats / known deferrals (captured in e2e/BACKLOG.md)

  • Perf budget: 60 s against the unbundled Vite dev server (~20 s observed locally). Brief asked for 5 s; that's only realistic against a production bundle. Phase 5 idea: wire a vite build + preview lane for @perf.
  • focusMode: option round-trips through the constructor, but the spec doesn't assert a visual marker — MU_FOCUS_MODE class is declared in core but no render path applies it today. Spec will tighten once the render path lands.
  • a11y bar: critical only for Phase 4. Non-critical findings (landmark-one-main, region, scrollable-region-focusable, page-has-heading-one, color-contrast) are surfaced via console.log for Phase 5 triage.
  • MutationObserver leak guard: the listener-leak spec covers EventCenter only; muya doesn't expose MutationObserver registration through the public API yet.

Test plan

  • pnpm e2e83 passed, 1 skipped (Phase 2 clipboard test.fixme).
  • pnpm test386 passed (unit suite untouched).
  • pnpm exec eslint e2e — clean.
  • CI 🎭 CI E2E workflow green on the PR.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 21, 2026 03:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds Phase 4 coverage to the muya-e2e Playwright suite, expanding stability/perf/a11y guardrails, option-matrix coverage, edge-input regressions, and static MarkdownToHtml export validation. This extends the repo’s automated end-to-end verification surface while adding minimal host plumbing to support the new scenarios.

Changes:

  • Add @axe-core/playwright and introduce critical-only axe scans for the host + key floating UI states.
  • Add new e2e specs for stability (listener leak, perf smoke), options (auto-pair matrix, focusMode/spellcheck/disableHtml), edge inputs, and MarkdownToHtml export + XSS sanitization.
  • Extend the e2e host with window.MarkdownToHtml and window.__e2e.rebuildMuya(opts) to support option-matrix and export tests; update BACKLOG status.

Reviewed changes

Copilot reviewed 13 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
pnpm-lock.yaml Locks @axe-core/playwright and its transitive axe-core dependency.
e2e/package.json Adds @axe-core/playwright dev dependency for accessibility scans.
e2e/types.d.ts Extends Window typings for MarkdownToHtml and __e2e.rebuildMuya used by new specs.
e2e/host/main.ts Exposes window.MarkdownToHtml and adds a rebuild helper to recreate Muya with different options.
e2e/tests/stability/perf.spec.ts Adds a 10k-paragraph setContent perf smoke + scroll reachability check (@Perf).
e2e/tests/stability/listener-leak.spec.ts Adds rebuild-loop regression test to ensure EventCenter listeners/dom events stay bounded.
e2e/tests/options/autopair.spec.ts Adds on/off matrix tests for auto-pair options using rebuild helper.
e2e/tests/options/focus-mode.spec.ts Validates focusMode option round-trip and editor usability after rebuild.
e2e/tests/options/spellcheck.spec.ts Asserts editor root spellcheck attribute reflects option.
e2e/tests/options/disable-html.spec.ts Asserts disableHtml prevents live HTML rendering in html-block preview.
e2e/tests/helpers/selectors.ts Adds selector for the disable-html wrapper class.
e2e/tests/export/markdown-to-html.spec.ts Adds export pipeline shape assertions and script-injection sanitization verification.
e2e/tests/edges/empty-and-tiny.spec.ts Adds edge-case setContent tests (empty, 1-char, rapid setContent loop).
e2e/tests/a11y/host-scan.spec.ts Adds axe scans for clean host and various UI states; fails on critical only.
e2e/BACKLOG.md Marks Phase 4 items landed and documents deferred follow-ups.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread e2e/tests/stability/perf.spec.ts Outdated
Comment on lines +18 to +29
* Budget = 60_000ms (= the per-test timeout in playwright.config). The
* goal is to catch a 5-10× regression on this path, not to pin a number.
* A future Phase-5 nightly job can tighten this against a production
* build.
*
* Tagged @perf so a future Phase-2 CI config can `--grep-invert "@perf"`
* for the PR-time runs and keep this in a nightly schedule.
*/
test.describe('stability / perf smoke @perf', () => {
// The setContent of 10k paragraphs alone routinely takes 15-25s on the
// Vite dev server, which exceeds the default 30s suite timeout. Bump
// the per-test timeout for this spec only.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair point — the comment conflated three different numbers. Fixed in 360cf35: rewrote the header to list each timeout separately and explain why they differ:

  • playwright.config default timeout: 30_000 — used by every other spec
  • test.setTimeout(120_000) (this spec only) — wide ceiling so regressions surface as a meaningful expect failure rather than a Playwright stack-trace timeout
  • expect(result.ms).toBeLessThan(60_000) — the actual setContent assertion budget; this is the one that catches a 5-10× regression

Also expanded the inline comment above setTimeout to call out that 120s is intentionally 4× the assertion budget.

@Jocs
Jocs force-pushed the test/e2e-phase-4-stability-perf-a11y branch from aceb6aa to 360cf35 Compare May 21, 2026 04:03
Jocs and others added 3 commits May 21, 2026 12:34
Adds 28 new Playwright specs across five buckets, landing the full
Phase 4 BACKLOG scope on the muya-e2e suite. Total test count grows
from 54 passed / 1 skipped to 83 passed / 1 skipped.

Stability
- stability/listener-leak.spec.ts: direct regression for PR-17
  (commit 39852a6). 50× setContent/locale/destroy/rebuild loop;
  asserts EventCenter `events` (DOM) and `listeners` (pub/sub)
  arrays stay within ±5 across rebuild cycles.
- stability/perf.spec.ts: 10k-paragraph setContent timed via
  performance.now(); 60s budget against the unbundled Vite dev
  server (~20s observed locally). Tagged @Perf for future
  PR-time / nightly split.

Accessibility
- a11y/host-scan.spec.ts: @axe-core/playwright scan of the clean
  host plus IFT, slash menu, link tools, image tools, and table
  tools open states. Fails on `critical` only; non-critical
  violations logged for Phase 5 triage. Excludes `.tools` (host
  test-harness toolbar, not muya's a11y surface).

Option matrix
- options/autopair.spec.ts: full on/off matrix for
  autoPairBracket / autoPairMarkdownSyntax / autoPairQuote plus
  all-off combo.
- options/focus-mode.spec.ts: option round-trips through the
  constructor (no visual marker assertion — focusMode is
  currently a no-op in the render path; tightened spec deferred
  to Phase 5).
- options/spellcheck.spec.ts: asserts `spellcheck` attribute on
  `.mu-editor` root for both states.
- options/disable-html.spec.ts: asserts `.mu-disable-html-render`
  class and that raw HTML stays as escaped source vs. live DOM.

Edges
- edges/empty-and-tiny.spec.ts: setContent(''), setContent('a'),
  10× rapid setContent without awaits. Cursor placement and
  final-state correctness.

Static export
- export/markdown-to-html.spec.ts: heading/list/code-block/KaTeX
  shape; mermaid container; script-injection sanitised away
  (Phase 3 deferred this — landed here). Includes the
  payload-execution sentinel that Phase 3 PR #237 marked for
  Phase 4.

Infrastructure
- e2e/host/main.ts: exposes window.MarkdownToHtml and
  window.__e2e.rebuildMuya(opts) helper for option-matrix specs.
- e2e/types.d.ts: typed augmentation of Window for the new globals.
- e2e/package.json: adds @axe-core/playwright devDep.
- e2e/tests/helpers/selectors.ts: adds htmlDisabled and htmlPreview
  selectors; documents the .mu-disable-html-render flag class.

Phase 5 follow-ups captured in e2e/BACKLOG.md: triage non-critical
axe violations, label the host toolbar so the scan exclusion can
drop, tighten the focusMode spec once the render path applies the
marker class, add a MutationObserver leak guard, and wire a perf
lane against a production bundle.

Validation:
- pnpm e2e — 83 passed, 1 skipped (Phase 2 clipboard fixme).
- pnpm test — 386 passed (unit baseline untouched).
- pnpm exec eslint e2e — clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The header said "Budget = 60_000ms (= the per-test timeout in
playwright.config)" — wrong on both counts: the config default is
30_000ms, the per-spec override is 120_000ms, and the 60_000ms is the
setContent assertion budget (not the test timeout). Rewrote the header
to list the three numbers separately and explain why they differ, plus
expanded the inline comment above setTimeout to note it's a 4× ceiling
above the assertion budget so regressions surface as a meaningful
expect failure, not a stack-trace timeout.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's bundled Chromium-for-Testing fails 7/7 autopair tests because the
sync barrier — click empty paragraph then waitForFunction
activeContentBlock != null — never resolves. Root cause: clicking an
*empty* `.mu-paragraph` lands the browser selection anchor on the
paragraph element itself rather than on a text node inside it.
muya/editor/index.ts dispatchEvents then sees no anchorBlock from
selection.getSelection() and explicitly sets activeContentBlock = null
before returning, so the wait times out at 30s.

Replaced the click+wait pattern with:
1. window.muya.focus() — uses muya's API to setCursor(0, 0, …) on the
   first leaf block, establishing a real Range with a text-node anchor.
2. window.muya.domNode.focus() — ensures DOM activeElement is the
   contenteditable so subsequent page.keyboard.type() lands there.

No behavior change locally (7/7 still pass via system Chrome). Fixes
CI failures on bundled Chromium.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Jocs
Jocs force-pushed the test/e2e-phase-4-stability-perf-a11y branch from ef255a3 to 26f235f Compare May 21, 2026 04:34
@Jocs
Jocs merged commit bbcea8c into master May 21, 2026
6 checks passed
@Jocs
Jocs deleted the test/e2e-phase-4-stability-perf-a11y branch May 21, 2026 04:37
Jocs added a commit that referenced this pull request May 21, 2026
* test(e2e): skip 10k-paragraph perf budget on webkit and firefox

The 60s budget added in #238 was calibrated against chromium (~20s
observed locally) before the cross-browser matrix landed in #239.
WebKit on ubuntu-latest against the Vite dev server consistently runs
108-120s; firefox 62-68s. Both blow the budget on every run, and the
3-attempt retry cycle has been pushing the e2e job past its 15min cap
(#240 was cancelled by the GHA timeout for this reason).

Skip the perf assertion on non-chromium browsers as a short-term fix
so PR CI can stay green. The longer-term plan, already documented in
the file header, is to move @Perf tests to a nightly schedule against
a production bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): exclude @Perf tests from PR-time e2e job

The per-browser skip added in the first iteration of this PR turned
out to be incomplete: chromium on GHA ubuntu-latest also overshoots
the 60s budget (~70s observed) once running against the Vite dev
server, not just the webkit ~115s and firefox ~65s seen in the prior
runs. All three browsers blow the budget; only the local M-class
macOS Chromium baseline (~20s) actually fits.

Switch to the project's already-documented plan (see file header):
exclude @perf-tagged tests from PR-time CI via --grep-invert. Future
@Perf tests automatically pick up the same treatment, and the test
file stays free of skip logic. The follow-up to move @Perf onto a
nightly schedule against a production bundle becomes a small
workflow addition rather than a rewrite.

Revert the per-browser test.skip in perf.spec.ts so the local
`pnpm e2e` invocation still runs the regression guard on all three
browsers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants