Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7c5ff43
test(playwright): wait for editor editability in goToNewPad/goToPad
JohnMcLear Apr 29, 2026
8372e6d
test(playwright): un-skip bold.spec under WITH_PLUGINS
JohnMcLear Apr 29, 2026
cd0f6e1
test(playwright): make bold.spec robust to Firefox + WITH_PLUGINS
JohnMcLear Apr 29, 2026
336751e
test(playwright): un-skip 4 writeToPad-only specs under WITH_PLUGINS
JohnMcLear Apr 29, 2026
f0cd2ae
test(playwright): un-skip page_up_down and timeslider_line_numbers un…
JohnMcLear Apr 29, 2026
6d01e9e
test(playwright): un-skip chat/list_wrap/clear_authorship; re-skip un…
JohnMcLear Apr 29, 2026
25b1bd7
test(playwright): un-skip alphabet/delete/select_focus_restore under …
JohnMcLear Apr 29, 2026
1de450c
test(playwright): un-skip bold_paste + undo_redo_scroll under WITH_PL…
JohnMcLear Apr 29, 2026
9fdbe8f
test(playwright): un-skip unordered_list 'enter for the new line' und…
JohnMcLear Apr 29, 2026
fe3144c
test(playwright): un-skip all 4 ordered_list tests under WITH_PLUGINS
JohnMcLear Apr 29, 2026
277ff5f
test(playwright): un-skip all 4 indentation tests under WITH_PLUGINS
JohnMcLear Apr 29, 2026
a34ab00
test(playwright): un-skip enter.spec 'enter is always visible' under …
JohnMcLear Apr 29, 2026
e9b55da
test(playwright): un-skip undo_clear_authorship under WITH_PLUGINS
JohnMcLear Apr 29, 2026
084d82f
test(playwright): un-skip collab_client 'bug #4978 regression test' u…
JohnMcLear Apr 29, 2026
92aebf9
test(playwright): use stable l10n selector for OL toolbar button
JohnMcLear Apr 29, 2026
cdf88ad
test(playwright): tighten writeToPad Enter delivery + fix toolbar ove…
JohnMcLear Apr 29, 2026
4f79dd4
test(playwright): revert writeToPad per-Enter retry — overshoots caus…
JohnMcLear Apr 29, 2026
9381490
test(playwright): re-skip 8 tests that need deeper rework to un-skip
JohnMcLear Apr 29, 2026
fb20b0f
DO-NOT-MERGE bisect plugins: Firefox×HALF-A + Firefox×HALF-B
JohnMcLear Apr 29, 2026
701cf47
DO-NOT-MERGE bisect plugins iter 2: A1 (align,author_hover) vs A2 (cu…
JohnMcLear Apr 29, 2026
db6675e
DO-NOT-MERGE bisect plugins iter 3: A2a (cursortrace) vs A2b (font_si…
JohnMcLear Apr 29, 2026
1e80b3a
DO-NOT-MERGE bisect plugins iter 4 (confirm): all-minus-cursortrace
JohnMcLear Apr 29, 2026
3fe6d3a
ci(frontend-tests): exclude ep_cursortrace from with-plugins set
JohnMcLear Apr 29, 2026
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
26 changes: 18 additions & 8 deletions .github/workflows/frontend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,25 @@ jobs:
- name: Install all dependencies and symlink for ep_etherpad-lite
run: pnpm install --frozen-lockfile
- name: Install Etherpad plugins
# Same plugin set as backend-tests.yml's withpluginsLinux job.
# Same plugin set as backend-tests.yml's withpluginsLinux job,
# MINUS ep_cursortrace.
#
# ep_cursortrace's `aceEditEvent` hook fires on every keyboard
# event (handleClick, handleKeyEvent, idleWorkTimer) and sends a
# cursor-position socket message per call. Under the test
# harness's `writeToPad` bursts (insertText + Enter loops) that
# stream of socket messages saturates the editor's input
# pipeline in Firefox specifically, causing intermittent
# keystroke drops and a long tail of test flakiness.
#
# Bisected via a 4-iteration probe on this branch — see commit
# history of .github/workflows/frontend-tests.yml around the
# PR-7630 timeframe. Tracked for a follow-up fix
# (debounce / throttle in ep_cursortrace's main.js).
run: >
pnpm add -w
ep_align
ep_author_hover
ep_cursortrace
ep_font_size
ep_headings2
ep_markdown
Expand Down Expand Up @@ -235,8 +248,6 @@ jobs:
fi
cd src
pnpm exec playwright install chromium --with-deps
# WITH_PLUGINS skips a small set of specs that fail when the
# /ether plugin set is loaded — tracked for fixup follow-ups.
WITH_PLUGINS=1 pnpm run test-ui --project=chromium
- name: Upload server log on failure
uses: actions/upload-artifact@v7
Expand Down Expand Up @@ -287,12 +298,13 @@ jobs:
- name: Install all dependencies and symlink for ep_etherpad-lite
run: pnpm install --frozen-lockfile
- name: Install Etherpad plugins
# Same plugin set as backend-tests.yml's withpluginsLinux job.
# See sibling Playwright Chrome with plugins job for the full
# rationale on why ep_cursortrace is excluded from the test
# plugin set.
run: >
pnpm add -w
ep_align
ep_author_hover
ep_cursortrace
ep_font_size
ep_headings2
ep_markdown
Expand Down Expand Up @@ -324,8 +336,6 @@ jobs:
fi
cd src
pnpm exec playwright install firefox --with-deps
# WITH_PLUGINS skips a small set of specs that fail when the
# /ether plugin set is loaded — tracked for fixup follow-ups.
WITH_PLUGINS=1 pnpm run test-ui --project=firefox
- name: Upload server log on failure
uses: actions/upload-artifact@v7
Expand Down
26 changes: 21 additions & 5 deletions src/tests/frontend-new/helper/padHelper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Frame, Locator, Page} from "@playwright/test";
import {expect, Frame, Locator, Page} from "@playwright/test";
import {MapArrayType} from "../../../node/types/MapType";
import {randomUUID} from "node:crypto";

Expand Down Expand Up @@ -114,19 +114,35 @@ export const appendQueryParams = async (page: Page, queryParameters: MapArrayTyp
await page.waitForSelector('#editorcontainer.initialized');
}

// Wait until the inner editor body has flipped from
// `class="static" contentEditable="false"` to editable. ace does this
// once padeditor.init resolves; under WITH_PLUGINS load in Firefox the
// flip can lag past `#editorcontainer.initialized`, long enough that
// an immediate click + keyboard.type runs against a still-static body
// and is silently dropped (the body keeps showing the default welcome
// text and never sees the input). Helpers used by every test call
// this so we only have one source of truth for "the editor is ready
// to receive input".
const waitForEditorReady = async (page: Page) => {
await page.waitForSelector('iframe[name="ace_outer"]');
await page.waitForSelector('#editorcontainer.initialized');
await page.frameLocator('iframe[name="ace_outer"]')
.frameLocator('iframe[name="ace_inner"]')
.locator('#innerdocbody[contenteditable="true"]')
.waitFor({state: 'attached'});
};

export const goToNewPad = async (page: Page) => {
// create a new pad before each test run
const padId = "FRONTEND_TESTS"+randomUUID();
await page.goto('http://localhost:9001/p/'+padId);
await page.waitForSelector('iframe[name="ace_outer"]');
await page.waitForSelector('#editorcontainer.initialized');
await waitForEditorReady(page);
return padId;
}

export const goToPad = async (page: Page, padId: string) => {
await page.goto('http://localhost:9001/p/'+padId);
await page.waitForSelector('iframe[name="ace_outer"]');
await page.waitForSelector('#editorcontainer.initialized');
await waitForEditorReady(page);
}


Expand Down
10 changes: 5 additions & 5 deletions src/tests/frontend-new/specs/alphabet.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expect, Page, test} from "@playwright/test";
import {clearPadContent, getPadBody, getPadOuter, goToNewPad} from "../helper/padHelper";
import {clearPadContent, getPadBody, getPadOuter, goToNewPad, writeToPad} from "../helper/padHelper";

test.beforeEach(async ({ page })=>{
// create a new pad before each test run
Expand All @@ -10,8 +10,6 @@ test.describe('All the alphabet works n stuff', () => {
const expectedString = 'abcdefghijklmnopqrstuvwxyz';

test('when you enter any char it appears right', async ({page}) => {
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');

// get the inner iframe
const innerFrame = await getPadBody(page!);

Expand All @@ -20,8 +18,10 @@ test.describe('All the alphabet works n stuff', () => {
// delete possible old content
await clearPadContent(page!);


await page.keyboard.type(expectedString);
// writeToPad uses keyboard.insertText which is reliable in Firefox
// under WITH_PLUGINS load (per-key keyboard.type races and drops
// characters); see #7625.
await writeToPad(page, expectedString);
const text = await innerFrame.locator('div').innerText();
expect(text).toBe(expectedString);
});
Expand Down
35 changes: 20 additions & 15 deletions src/tests/frontend-new/specs/bold.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import {expect, test} from "@playwright/test";
import {randomInt} from "node:crypto";
import {getPadBody, goToNewPad, selectAllText} from "../helper/padHelper";
import exp from "node:constants";
import {clearPadContent, getPadBody, goToNewPad, selectAllText, writeToPad} from "../helper/padHelper";

test.beforeEach(async ({ page })=>{
await goToNewPad(page);
Expand All @@ -10,33 +8,40 @@ test.beforeEach(async ({ page })=>{
test.describe('bold button', ()=>{

test('makes text bold on click', async ({page}) => {
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
// get the inner iframe
const innerFrame = await getPadBody(page);

await innerFrame.click()
// Select pad text
await selectAllText(page);
await page.keyboard.type("Hi Etherpad");
// clearPadContent + writeToPad replaces the legacy
// selectAllText + keyboard.type pattern: writeToPad delivers the
// string in a single input event (insertText), which Firefox
// under WITH_PLUGINS load handles reliably — per-key keyboard.type
// was racily dropping characters before the selectAllText.
await clearPadContent(page);
await writeToPad(page, "Hi Etherpad");
await selectAllText(page);

// click the bold button
await page.locator("button[data-l10n-id='pad.toolbar.bold.title']").click();
// click the bold button. force:true bypasses the #toolbar-overlay
// div that intercepts pointer events after a text selection (same
// pattern as clearAuthorship in padHelper).
await page.locator("button[data-l10n-id='pad.toolbar.bold.title']")
.click({force: true});


// check if the text is bold
expect(await innerFrame.locator('b').innerText()).toBe('Hi Etherpad');
})

test('makes text bold on keypress', async ({page}) => {
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
// get the inner iframe
const innerFrame = await getPadBody(page);

await innerFrame.click()
// Select pad text
await selectAllText(page);
await page.keyboard.type("Hi Etherpad");
// clearPadContent + writeToPad replaces the legacy
// selectAllText + keyboard.type pattern: writeToPad delivers the
// string in a single input event (insertText), which Firefox
// under WITH_PLUGINS load handles reliably — per-key keyboard.type
// was racily dropping characters before the selectAllText.
await clearPadContent(page);
await writeToPad(page, "Hi Etherpad");
await selectAllText(page);

// Press CTRL + B
Expand Down
6 changes: 3 additions & 3 deletions src/tests/frontend-new/specs/bold_paste.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ test.beforeEach(async ({page}) => {
// Regression test for https://github.com/ether/etherpad-lite/issues/5037
test('bold text retains formatting after copy-paste', async ({page}) => {
// Passes in isolation; fails in the with-plugins suite due to
// suspected clipboard / pad state leakage between specs. Tracked in
// the umbrella issue for plugin-vs-core test breakage (filed in PR).
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
// suspected clipboard / pad state leakage between specs. Tracked
// by #7611 — needs deeper rework (real clipboard or REST-driven
// setup) to un-skip reliably.
const padBody = await getPadBody(page);
await clearPadContent(page);

Expand Down
2 changes: 0 additions & 2 deletions src/tests/frontend-new/specs/chat.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ test("makes sure that an empty message can't be sent", async function ({page}) {
});

test('makes chat stick to right side of the screen via settings, remove sticky via settings, close it', async ({page}) =>{
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
await showSettings(page);

await enableStickyChatviaSettings(page);
Expand Down Expand Up @@ -122,7 +121,6 @@ test('Checks showChat=false URL Parameter hides chat then' +
// visibility via the .visible class — so without an explicit display reset the
// box stays hidden by the lingering inline style. (PR #7597)
test('chat icon click reveals chatbox after a disable → enable cycle', async ({page}) => {
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
await showSettings(page);
await page.locator('label[for="options-disablechat"]').click();
await expect(page.locator('#options-disablechat')).toBeChecked();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ test("clear authorship colors can be undone to restore author colors", async fun

// Test for https://github.com/ether/etherpad-lite/issues/5128
test('clears authorship when first line has line attributes', async function ({page}) {
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
// Make sure there is text with author info. The first line must have a line attribute.
const padBody = await getPadBody(page);
// Accept confirm dialogs before any action that might trigger one
Expand Down
11 changes: 9 additions & 2 deletions src/tests/frontend-new/specs/collab_client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,18 @@ test.describe('Messages in the COLLABROOM', function () {
// simulate key presses to delete content
await div.locator('span').selectText() // select all
await page.keyboard.press('Backspace') // clear the first line
await page.keyboard.type(newText) // insert the string
// insertText (single input event) instead of per-key keyboard.type
// — Firefox + WITH_PLUGINS load races and drops keystrokes; see
// #7625.
await page.keyboard.insertText(newText)
};

test('bug #4978 regression test', async function ({browser}) {
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
// Multi-context test that opens a second browser context and races
// cross-pad propagation. Re-skipped under WITH_PLUGINS — the
// beforeEach burst of 5 writeToPad+Enter sequences leaves the
// pads in too-racy a state for the cross-context assertions to
// settle reliably. Tracked by #7611.
// The bug was triggered by receiving a change from another user while simultaneously composing
// a character and waiting for an acknowledgement of a previously sent change.

Expand Down
8 changes: 5 additions & 3 deletions src/tests/frontend-new/specs/delete.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expect, test} from "@playwright/test";
import {clearPadContent, getPadBody, goToNewPad} from "../helper/padHelper";
import {clearPadContent, getPadBody, goToNewPad, writeToPad} from "../helper/padHelper";

test.beforeEach(async ({ page })=>{
// create a new pad before each test run
Expand All @@ -8,12 +8,14 @@ test.beforeEach(async ({ page })=>{


test('delete keystroke', async ({page}) => {
test.skip(process.env.WITH_PLUGINS === '1', 'flaky in with-plugins suite — see #7611');
const padText = "Hello World this is a test"
const body = await getPadBody(page)
await body.click()
await clearPadContent(page)
await page.keyboard.type(padText)
// writeToPad uses keyboard.insertText (single input event); per-key
// keyboard.type races and drops characters in Firefox under
// WITH_PLUGINS load — see #7625.
await writeToPad(page, padText)
// Navigate to the end of the text
await page.keyboard.press('End');
// Delete the last character
Expand Down
24 changes: 15 additions & 9 deletions src/tests/frontend-new/specs/enter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,28 @@
});

test('enter is always visible after event', async function ({page}) {
test.skip(process.env.WITH_PLUGINS === '1', 'fails with /ether plugin set loaded — see #7611');
// Even with the per-iteration toHaveCount value-wait, this 15-Enter
// loop occasionally misses a line under WITH_PLUGINS load when the
// editor's input pipeline backs up and a press is silently dropped.
// Tracked by #7611 — needs a different drive mechanism (REST API
// or single multi-line write) to un-skip reliably.
const padBody = await getPadBody(page);
const originalLength = await padBody.locator('div').count();
let lastLine = padBody.locator('div').last();

// simulate key presses to enter content
let i = 0;
// Press Enter `numberOfLines` times. Each iteration value-waits
// for the line count to advance before issuing the next press —
// a tight Enter-loop with no per-iteration verify dropped events
// under Firefox + WITH_PLUGINS load (the editor's input pipeline
// can't always keep up with back-to-back keypresses while plugin
// hooks are warming).
const numberOfLines = 15;
while (i < numberOfLines) {
lastLine = padBody.locator('div').last();
for (let i = 0; i < numberOfLines; i++) {
const expectedCount = originalLength + i + 1;
const lastLine = padBody.locator('div').last();
await lastLine.focus();
await page.keyboard.press('End');
await page.keyboard.press('Enter');

// check we can see the caret..
i++;
await expect(padBody.locator('div')).toHaveCount(expectedCount);
}

expect(await padBody.locator('div').count()).toBe(numberOfLines + originalLength);
Expand All @@ -59,6 +65,6 @@
const windowOffset = await scrolledWindow.evaluate(() => window.pageYOffset);
const windowHeight = await scrolledWindow.evaluate(() => window.innerHeight);

expect(windowOffset + windowHeight).toBeGreaterThan(bottomOfLastLine);

Check failure on line 68 in src/tests/frontend-new/specs/enter.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Firefox with plugins

[firefox] › tests/frontend-new/specs/enter.spec.ts:33:7 › enter keystroke › enter is always visible after event

1) [firefox] › tests/frontend-new/specs/enter.spec.ts:33:7 › enter keystroke › enter is always visible after event Error: expect(received).toBeGreaterThan(expected) Expected: > 730 Received: 720 66 | const windowHeight = await scrolledWindow.evaluate(() => window.innerHeight); 67 | > 68 | expect(windowOffset + windowHeight).toBeGreaterThan(bottomOfLastLine); | ^ 69 | }); 70 | }); 71 | at /home/runner/work/etherpad/etherpad/src/tests/frontend-new/specs/enter.spec.ts:68:41

Check failure on line 68 in src/tests/frontend-new/specs/enter.spec.ts

View workflow job for this annotation

GitHub Actions / Playwright Chrome with plugins

[chromium] › tests/frontend-new/specs/enter.spec.ts:33:7 › enter keystroke › enter is always visible after event

1) [chromium] › tests/frontend-new/specs/enter.spec.ts:33:7 › enter keystroke › enter is always visible after event Error: expect(received).toBeGreaterThan(expected) Expected: > 754 Received: 720 66 | const windowHeight = await scrolledWindow.evaluate(() => window.innerHeight); 67 | > 68 | expect(windowOffset + windowHeight).toBeGreaterThan(bottomOfLastLine); | ^ 69 | }); 70 | }); 71 | at /home/runner/work/etherpad/etherpad/src/tests/frontend-new/specs/enter.spec.ts:68:41
});
});
Loading
Loading