Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,24 +90,31 @@ jobs:
test-e2e-web:
needs: [changes]
if: needs.changes.outputs.web == 'true'
runs-on: macos-26
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup
uses: ./.github/actions/setup

- name: Cache Playwright browsers
- name: Cache Playwright Docker image
id: playwright-image-cache
uses: actions/cache@v4
with:
path: ~/Library/Caches/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-playwright-
path: ${{ runner.temp }}/playwright-image.tar
key: ${{ runner.os }}-${{ runner.arch }}-playwright-image-${{ hashFiles('.playwright/docker-image') }}

- name: Install Playwright browsers
run: yarn playwright install chromium
- name: Load Playwright Docker image
if: steps.playwright-image-cache.outputs.cache-hit == 'true'
run: docker load -i "${{ runner.temp }}/playwright-image.tar"

- name: Pull Playwright Docker image
if: steps.playwright-image-cache.outputs.cache-hit != 'true'
run: |
IMAGE="$(cat .playwright/docker-image)"
docker pull "$IMAGE"
docker save "$IMAGE" -o "${{ runner.temp }}/playwright-image.tar"

- name: Run web e2e tests
run: yarn test:e2e:web
Expand All @@ -117,7 +124,9 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: .playwright/playwright-report/
path: |
.playwright/playwright-report/
.playwright/test-results/
retention-days: 7

build-library:
Expand Down
1 change: 1 addition & 0 deletions .playwright/docker-image
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mcr.microsoft.com/playwright:v1.58.2-noble
30 changes: 30 additions & 0 deletions .playwright/fonts.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<!--
Mounted into the Playwright Docker container as /etc/fonts/local.conf.
Without it every generic family falls back to the CJK font WenQuanYi Zen Hei,
which e.g. renders the ellipsis character vertically centered. Liberation
fonts are metric-compatible with Helvetica/Arial/Courier.
-->
<fontconfig>
<alias binding="strong">
<family>sans-serif</family>
<prefer><family>Liberation Sans</family></prefer>
</alias>
<alias binding="strong">
<family>system-ui</family>
<prefer><family>Liberation Sans</family></prefer>
</alias>
<alias binding="strong">
<family>Helvetica Neue</family>
<prefer><family>Liberation Sans</family></prefer>
</alias>
<alias binding="strong">
<family>serif</family>
<prefer><family>Liberation Serif</family></prefer>
</alias>
<alias binding="strong">
<family>monospace</family>
<prefer><family>Liberation Mono</family></prefer>
</alias>
</fontconfig>
13 changes: 13 additions & 0 deletions .playwright/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@ import { defineConfig, devices } from '@playwright/test';

const port = process.env.PW_PORT ?? '5173';

// By default, the browser runs in the official Playwright Linux container
// (started by scripts/run-tests.sh) so screenshots render identically on every
// machine and in CI. Set PW_LOCAL_BROWSER=1 to use a locally installed browser
// instead, e.g. for headed debugging - screenshot assertions won't match then.
const connectOptions = process.env.PW_LOCAL_BROWSER
? undefined
: {
wsEndpoint: process.env.PW_WS_ENDPOINT ?? 'ws://127.0.0.1:3000/',
// Lets the containerised browser reach the Vite server on host localhost.
exposeNetwork: '<loopback>',
};

export default defineConfig({
testDir: './tests',
outputDir: './test-results',
Expand All @@ -27,6 +39,7 @@ export default defineConfig({
screenshot: 'only-on-failure',
video: 'retain-on-failure',
permissions: ['clipboard-read', 'clipboard-write'],
connectOptions,
},

webServer: {
Expand Down
Binary file modified .playwright/screenshots/alignment-mixed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/display-alignment-blockquotes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/display-alignment-checkbox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/display-alignment-headings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/display-alignment-mixed-lists.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/display-alignment-ol.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/display-alignment-paragraphs.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/display-alignment-ul.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/ellipsize-between-empty-paragraphs-clip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/ellipsize-between-empty-paragraphs-tail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/ellipsize-checkbox-list-clip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/ellipsize-checkbox-list-head.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/ellipsize-checkbox-list-tail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified .playwright/screenshots/ellipsize-empty-in-the-middle-clip.png
Binary file modified .playwright/screenshots/ellipsize-empty-in-the-middle-head.png
Binary file modified .playwright/screenshots/ellipsize-empty-in-the-middle-tail.png
Binary file modified .playwright/screenshots/ellipsize-empty-lines-middle.png
Binary file modified .playwright/screenshots/ellipsize-inline-images-clip.png
Binary file modified .playwright/screenshots/ellipsize-inline-images-head.png
Binary file modified .playwright/screenshots/ellipsize-inline-images-tail.png
Binary file modified .playwright/screenshots/ellipsize-one-word-wrapping-clip.png
Binary file modified .playwright/screenshots/ellipsize-one-word-wrapping-head.png
Binary file modified .playwright/screenshots/ellipsize-one-word-wrapping-tail.png
Binary file modified .playwright/screenshots/ellipsize-ordered-list-clip.png
Binary file modified .playwright/screenshots/ellipsize-ordered-list-head.png
Binary file modified .playwright/screenshots/ellipsize-ordered-list-only-empty-clip.png
Binary file modified .playwright/screenshots/ellipsize-ordered-list-tail.png
Binary file modified .playwright/screenshots/ellipsize-ordered-list-with-empty-head.png
Binary file modified .playwright/screenshots/ellipsize-paragraph-fit-clip.png
Binary file modified .playwright/screenshots/ellipsize-paragraph-fit-head.png
Binary file modified .playwright/screenshots/ellipsize-paragraph-fit-tail.png
Binary file modified .playwright/screenshots/ellipsize-paragraph-styles-mix-clip.png
Binary file modified .playwright/screenshots/ellipsize-paragraph-styles-mix-head.png
Binary file modified .playwright/screenshots/ellipsize-paragraph-styles-mix-tail.png
Binary file modified .playwright/screenshots/ellipsize-single-word-clip.png
Binary file modified .playwright/screenshots/ellipsize-single-word-head.png
Binary file modified .playwright/screenshots/ellipsize-single-word-tail.png
Binary file modified .playwright/screenshots/ellipsize-style-fontsize-recompute.png
Binary file modified .playwright/screenshots/ellipsize-unordered-list-clip.png
Binary file modified .playwright/screenshots/ellipsize-unordered-list-head.png
Binary file modified .playwright/screenshots/ellipsize-unordered-list-tail.png
Binary file modified .playwright/screenshots/ellipsize-very-long-paragraph-clip.png
Binary file modified .playwright/screenshots/ellipsize-very-long-paragraph-head.png
Binary file modified .playwright/screenshots/ellipsize-very-long-paragraph-tail.png
Binary file modified .playwright/screenshots/enriched-text-all-headings.png
Binary file modified .playwright/screenshots/enriched-text-all-list-types.png
Binary file modified .playwright/screenshots/enriched-text-blockquote-code-codeblock.png
Binary file modified .playwright/screenshots/enriched-text-checkbox-list-checked.png
Binary file modified .playwright/screenshots/enriched-text-checkbox-list-empty-items.png
Binary file modified .playwright/screenshots/enriched-text-checkbox-list-unchecked.png
Binary file modified .playwright/screenshots/enriched-text-checkbox-list-wrapped.png
Binary file modified .playwright/screenshots/enriched-text-images-inline.png
Binary file modified .playwright/screenshots/enriched-text-images-inside-list.png
Binary file modified .playwright/screenshots/enriched-text-images-placeholder-list.png
Binary file modified .playwright/screenshots/enriched-text-line-wrapping.png
Binary file modified .playwright/screenshots/enriched-text-mentions.png
Binary file modified .playwright/screenshots/enriched-text-newlines-spaces.png
Binary file modified .playwright/screenshots/enriched-text-ordered-list-empty-items.png
Binary file modified .playwright/screenshots/enriched-text-ordered-list-wrapped.png
Binary file modified .playwright/screenshots/enriched-text-ordered-list.png
Binary file modified .playwright/screenshots/enriched-text-rich-text.png
Binary file modified .playwright/screenshots/enriched-text-unordered-list-wrapped.png
Binary file modified .playwright/screenshots/enriched-text-unordered-list.png
Binary file modified .playwright/screenshots/images-blockquote-placeholder.png
Binary file modified .playwright/screenshots/images-checkbox-list-placeholder.png
Binary file modified .playwright/screenshots/images-loaded-in-list.png
Binary file modified .playwright/screenshots/images-ol-placeholder.png
Binary file modified .playwright/screenshots/images-paragraph-placeholder.png
Binary file modified .playwright/screenshots/images-placeholder-empty-src.png
Binary file modified .playwright/screenshots/images-ul-placeholder.png
Binary file modified .playwright/screenshots/inline-styles.png
Binary file modified .playwright/screenshots/links-display.png
Binary file modified .playwright/screenshots/list-keyboard-enter-bullet.png
Binary file modified .playwright/screenshots/list-keyboard-enter-checkbox.png
Binary file modified .playwright/screenshots/list-keyboard-enter-ordered.png
Binary file modified .playwright/screenshots/list-nested-html-ol-in-ul.png
Binary file modified .playwright/screenshots/list-nested-html-triple-nested-ul.png
Binary file modified .playwright/screenshots/list-nested-html-ul-in-ol.png
Binary file modified .playwright/screenshots/list-nested-html-ul-in-ul.png
Binary file modified .playwright/screenshots/list-wrap-selection-round-trip.png
Binary file modified .playwright/screenshots/mention-visual.png
Binary file modified .playwright/screenshots/paragraph-styles-visual-blockquote.png
Binary file modified .playwright/screenshots/paragraph-styles-visual-codeblock.png
Binary file modified .playwright/screenshots/paragraph-styles-visual-headings.png
Binary file modified .playwright/screenshots/paragraph-styles-visual-ordered-list.png
Binary file modified .playwright/screenshots/paragraph-styles-visual-unordered-list.png
Binary file modified .playwright/screenshots/wrapped-block-keyboard-enter-codeblock.png
55 changes: 55 additions & 0 deletions .playwright/scripts/run-tests.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
#!/bin/bash
# run-tests.sh - Run Playwright tests.
#
# The browser runs inside the official Playwright Docker image pinned in
# .playwright/docker-image (must match the installed @playwright/test version), so screenshots are identical on macOS,
# Linux and CI. The test runner and Vite dev server stay on the host.
#
# Usage:
# ./run-tests.sh [--update-screenshots] [playwright-args...]
#
# Options:
# --update-screenshots Update screenshot baselines (maps to --update-snapshots).
#
# Environment:
# PW_LOCAL_BROWSER=1 Use a locally installed browser instead of Docker
# (screenshot assertions won't match the baselines).
# PW_WS_ENDPOINT Connect to an already running Playwright server.
# PW_SERVER_PORT Host port for the Playwright server (default: 3000).

set -euo pipefail

Expand All @@ -22,5 +32,50 @@ while [ $# -gt 0 ]; do
done

cd "$REPO_ROOT"

if [ -z "${PW_LOCAL_BROWSER:-}" ] && [ -z "${PW_WS_ENDPOINT:-}" ]; then
if ! docker info > /dev/null 2>&1; then
echo "error: Docker is not running. Web e2e tests run the browser in the Playwright Docker image." >&2
echo "Start Docker, or set PW_LOCAL_BROWSER=1 to use a local browser (screenshots won't match)." >&2
exit 1
fi

IMAGE="$(tr -d '[:space:]' < "$REPO_ROOT/.playwright/docker-image")"
PW_VERSION="$(node -p "require('@playwright/test/package.json').version")"
PW_SERVER_PORT="${PW_SERVER_PORT:-3000}"

if [[ "$IMAGE" != *":v${PW_VERSION}-"* ]]; then
echo "error: .playwright/docker-image ($IMAGE) does not match @playwright/test $PW_VERSION." >&2
echo "Update the image tag (and the screenshot baselines) when upgrading Playwright." >&2
exit 1
fi

echo "Starting Playwright server ($IMAGE) on port $PW_SERVER_PORT..."
CONTAINER_ID="$(docker run -d --rm --init --ipc=host \
-p "127.0.0.1:${PW_SERVER_PORT}:3000" \
-v "$REPO_ROOT/.playwright/fonts.conf:/etc/fonts/local.conf:ro" \
"$IMAGE" \
/bin/sh -c "npx -y playwright@${PW_VERSION} run-server --port 3000 --host 0.0.0.0")"
trap 'docker stop "$CONTAINER_ID" > /dev/null 2>&1 || true' EXIT

for _ in $(seq 1 60); do
if curl -s -o /dev/null "http://127.0.0.1:${PW_SERVER_PORT}/"; then
break
fi
if ! docker ps -q --no-trunc | grep -q "$CONTAINER_ID"; then
echo "error: Playwright server container exited unexpectedly." >&2
exit 1
fi
sleep 1
done
if ! curl -s -o /dev/null "http://127.0.0.1:${PW_SERVER_PORT}/"; then
echo "error: Playwright server did not start in time." >&2
docker logs "$CONTAINER_ID" >&2 || true
exit 1
fi

export PW_WS_ENDPOINT="ws://127.0.0.1:${PW_SERVER_PORT}/"
fi

# shellcheck disable=SC2086
playwright test --config .playwright/playwright.config.ts $EXTRA
4 changes: 2 additions & 2 deletions .playwright/tests/applyWrappingListSelection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ for (const { label, toolbarTestId } of LIST_VARIANTS) {
const editor = editorLocator(page);
await editor.click();

await expect(editor).toHaveScreenshot(ROUND_TRIP_SNAPSHOT);

await selectParagraphTextInclusive(page, 1, 3);

await expect(editor).toHaveScreenshot(ROUND_TRIP_SNAPSHOT);

const listBtn = toolbarButton(page, toolbarTestId);
await listBtn.click();
await listBtn.click();
Expand Down
2 changes: 1 addition & 1 deletion .playwright/tests/images.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ test.describe('images', () => {
for (const key of toolbarOrder) {
const editor = await focusEnrichedEditable(page);
await page.waitForTimeout(10);
await editor.press('Meta+A');
await editor.press('ControlOrMeta+A');
await toolbarButton(page, key).click();
await expect
.poll(
Expand Down
2 changes: 1 addition & 1 deletion .playwright/tests/strictMarks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ test.describe('strict marks', () => {

await typeBoldText(page, 'hello world');

await editor.press('Meta+A');
await editor.press('ControlOrMeta+A');
await editor.press('Backspace');

await expect(toolbarButton(page, 'bold')).not.toHaveClass(
Expand Down
10 changes: 4 additions & 6 deletions .playwright/tests/undoRedo.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ import { selectRange } from '../helpers/selection';
// entries that can be undone independently.
const HISTORY_GROUP_DELAY = 600;

const MOD = process.platform === 'darwin' ? 'Meta' : 'Control';

async function pressUndo(page: Page) {
await page.keyboard.press(`${MOD}+z`);
await page.keyboard.press('ControlOrMeta+z');
}

async function pressRedo(page: Page) {
await page.keyboard.press(`${MOD}+Shift+z`);
await page.keyboard.press('ControlOrMeta+Shift+z');
}

test.describe('undo / redo', () => {
Expand Down Expand Up @@ -74,7 +72,7 @@ test.describe('undo / redo', () => {
await page.keyboard.type('Hello World');
await page.waitForTimeout(HISTORY_GROUP_DELAY);

await page.keyboard.press(`${MOD}+a`);
await page.keyboard.press('ControlOrMeta+a');
await toolbarButton(page, 'bold').click();

expect(await getSerializedHtml(page)).toEqual(
Expand All @@ -93,7 +91,7 @@ test.describe('undo / redo', () => {
await page.keyboard.type('Hello World!');
await page.waitForTimeout(HISTORY_GROUP_DELAY);

await page.keyboard.press(`${MOD}+a`);
await page.keyboard.press('ControlOrMeta+a');
await toolbarButton(page, 'bold').click();
await page.waitForTimeout(HISTORY_GROUP_DELAY);
await selectRange(page, 6, 11);
Expand Down
5 changes: 1 addition & 4 deletions .playwright/tests/wrappedBlockKeyboard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,7 @@ for (const { label, tag, wrapperSelector, toolbarTestId } of WRAPPED_BLOCKS) {

const secondP = paragraphsInWrapper.nth(1);
await secondP.click();
await editor.press('End');
for (let i = 0; i < 'second'.length; i++) {
await editor.press('ArrowLeft', { delay: 60 });
}
await editor.press('Home');

await editor.press('Backspace', { delay: 60 });

Expand Down
19 changes: 14 additions & 5 deletions docs/docs/misc/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,11 @@ to re-enable it afterwards with `-bool NO`.
### Web (Playwright)

We use [Playwright](https://playwright.dev/) for end-to-end testing of the web
example. Tests live in `.playwright/tests/`. Install the browser binaries once:

```sh
yarn playwright install
```
example. Tests live in `.playwright/tests/`. The browser runs inside the
official [Playwright Docker image](https://playwright.dev/docs/docker), pinned
in `.playwright/docker-image` (bump it together with `@playwright/test`), so screenshots render identically on
every machine and in CI. Make sure [Docker](https://www.docker.com/) is running
before you start the tests.

Run the suite from the root directory:

Expand All @@ -238,6 +238,15 @@ Append `--ui` for Playwright's
inspect traces). The Playwright config starts the Vite dev server
automatically, so you don't need to run `yarn example-web dev` separately.

To debug in a locally installed browser (e.g. with `--headed`), install it once
with `yarn playwright install chromium` and set `PW_LOCAL_BROWSER=1`. Screenshot
assertions won't match the baselines in this mode, so always update baselines
through the default Docker setup.

```sh
PW_LOCAL_BROWSER=1 yarn test:e2e:web --headed
```

## Documentation

The site you're reading is built with [Docusaurus](https://docusaurus.io/) and
Expand Down
Loading