From d16e3aefd3a58e57a851b3853b9c6adc45350578 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 31 Mar 2026 22:15:15 +0100 Subject: [PATCH 1/7] Fix frontend test failures across all browsers - Fix home button using fragile relative URL (window.location.href + "/../..") that WebKit doesn't resolve correctly. Use window.location.origin instead. - Wait for #editorcontainer.initialized in goToNewPad/goToPad/ appendQueryParams so toolbar, chat, and cookie handlers are fully set up before tests interact with them. - Clear cookies in chat test beforeEach to prevent chatAndUsers cookie from prior tests disabling the sticky chat checkbox. - Wait for navigation to complete in editbar home button test. Fixes #7405 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/static/js/pad_editbar.ts | 2 +- src/tests/frontend-new/helper/padHelper.ts | 3 +++ src/tests/frontend-new/specs/chat.spec.ts | 3 ++- src/tests/frontend-new/specs/editbar.spec.ts | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/static/js/pad_editbar.ts b/src/static/js/pad_editbar.ts index 1a5ee1dc04b..b3873490480 100644 --- a/src/static/js/pad_editbar.ts +++ b/src/static/js/pad_editbar.ts @@ -356,7 +356,7 @@ exports.padeditbar = new class { this.registerDropdownCommand('import_export'); this.registerDropdownCommand('embed'); this.registerCommand('home', ()=>{ - window.location.href = window.location.href + "/../.." + window.location.href = window.location.origin + '/' }) this.registerCommand('settings', () => { diff --git a/src/tests/frontend-new/helper/padHelper.ts b/src/tests/frontend-new/helper/padHelper.ts index f52cd0a356e..1be987d2316 100644 --- a/src/tests/frontend-new/helper/padHelper.ts +++ b/src/tests/frontend-new/helper/padHelper.ts @@ -111,6 +111,7 @@ export const appendQueryParams = async (page: Page, queryParameters: MapArrayTyp }); await page.goto(page.url()+"?"+ searchParams.toString()); await page.waitForSelector('iframe[name="ace_outer"]'); + await page.waitForSelector('#editorcontainer.initialized'); } export const goToNewPad = async (page: Page) => { @@ -118,12 +119,14 @@ export const goToNewPad = async (page: Page) => { 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'); 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'); } diff --git a/src/tests/frontend-new/specs/chat.spec.ts b/src/tests/frontend-new/specs/chat.spec.ts index 4d4f1bd1c44..fac0e915dcb 100644 --- a/src/tests/frontend-new/specs/chat.spec.ts +++ b/src/tests/frontend-new/specs/chat.spec.ts @@ -14,7 +14,8 @@ import { import {disableStickyChat, enableStickyChatviaSettings, hideSettings, showSettings} from "../helper/settingsHelper"; -test.beforeEach(async ({ page })=>{ +test.beforeEach(async ({ page, context })=>{ + await context.clearCookies(); await goToNewPad(page); }) diff --git a/src/tests/frontend-new/specs/editbar.spec.ts b/src/tests/frontend-new/specs/editbar.spec.ts index c2dc56fda88..154d79180e4 100644 --- a/src/tests/frontend-new/specs/editbar.spec.ts +++ b/src/tests/frontend-new/specs/editbar.spec.ts @@ -12,6 +12,7 @@ test('should go to home on pad', async ({page}) => { expect(attribute).toBe('pad.toolbar.home.title'); await homeButton.click(); + await page.waitForURL((url) => !url.pathname.includes('/p/')); const url = page.url(); expect(url).not.toContain('/p/'); }) From a01e3df8b717ab6d92d657a2911e57978fe19978 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 31 Mar 2026 22:42:43 +0100 Subject: [PATCH 2/7] Run frontend tests on pull requests Playwright runs locally and doesn't need Sauce Labs secrets, so there's no reason to limit frontend tests to push events only. Also remove stale Sauce Labs references from workflow names/comments. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/frontend-admin-tests.yml | 4 +++- .github/workflows/frontend-tests.yml | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index 45e56963f84..8b83d0e4cf5 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -1,10 +1,12 @@ -# Leave the powered by Sauce Labs bit in as this means we get additional concurrency name: "Frontend admin tests" on: push: paths-ignore: - 'doc/**' + pull_request: + paths-ignore: + - 'doc/**' permissions: contents: read # to fetch code (actions/checkout) diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index a99118cb67a..817aae2abcb 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -1,10 +1,12 @@ -# Leave the powered by Sauce Labs bit in as this means we get additional concurrency -name: "Frontend tests powered by Sauce Labs" +name: "Frontend tests" on: push: paths-ignore: - 'doc/**' + pull_request: + paths-ignore: + - 'doc/**' permissions: contents: read # to fetch code (actions/checkout) From 60ddbef971c5af1b15bd2172d664d034dfa43d1b Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 31 Mar 2026 22:51:28 +0100 Subject: [PATCH 3/7] Fix sticky chat test: use click() instead of check()/uncheck() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stickToScreen() handler manages checkbox state internally with its own toggle logic and a setTimeout. Playwright's check()/uncheck() methods verify state after clicking, but race with the async toggle, causing "Clicking the checkbox did not change its state" errors. Using click() avoids this — the waitForSelector calls already verify the final state. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/tests/frontend-new/helper/settingsHelper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/frontend-new/helper/settingsHelper.ts b/src/tests/frontend-new/helper/settingsHelper.ts index 729dd48f6b6..c91c91f19bb 100644 --- a/src/tests/frontend-new/helper/settingsHelper.ts +++ b/src/tests/frontend-new/helper/settingsHelper.ts @@ -22,7 +22,7 @@ export const enableStickyChatviaSettings = async (page: Page) => { const stickyChat = page.locator('#options-stickychat') const checked = await stickyChat.isChecked() if(checked) return - await stickyChat.check({force: true}) + await stickyChat.click({force: true}) await page.waitForSelector('#options-stickychat:checked') } @@ -30,6 +30,6 @@ export const disableStickyChat = async (page: Page) => { const stickyChat = page.locator('#options-stickychat') const checked = await stickyChat.isChecked() if(!checked) return - await stickyChat.uncheck({force: true}) + await stickyChat.click({force: true}) await page.waitForSelector('#options-stickychat:not(:checked)') } From de73edfd48249fe5e4d6dbbd6ce5acc4afae1b30 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 31 Mar 2026 23:02:59 +0100 Subject: [PATCH 4/7] Fix sticky chat handler and reduce parallel workers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove force:true from sticky chat checkbox clicks — it can bypass jQuery event handlers preventing stickToScreen() from firing. - Wait for chatbox stickyChat class instead of checkbox state, since stickToScreen() manages the checkbox asynchronously via setTimeout. - Reduce workers from 5 to 2 to avoid overloading the single Etherpad server instance, which causes goToNewPad timeouts on CI. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/playwright.config.ts | 2 +- src/tests/frontend-new/helper/settingsHelper.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/playwright.config.ts b/src/playwright.config.ts index 583c4f920ee..003645f7bff 100644 --- a/src/playwright.config.ts +++ b/src/playwright.config.ts @@ -17,7 +17,7 @@ export default defineConfig({ expect: { timeout: defaultExpectTimeout }, timeout: defaultTestTimeout, retries: 0, - workers: 5, + workers: 2, /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ diff --git a/src/tests/frontend-new/helper/settingsHelper.ts b/src/tests/frontend-new/helper/settingsHelper.ts index c91c91f19bb..de5541e8e98 100644 --- a/src/tests/frontend-new/helper/settingsHelper.ts +++ b/src/tests/frontend-new/helper/settingsHelper.ts @@ -22,14 +22,14 @@ export const enableStickyChatviaSettings = async (page: Page) => { const stickyChat = page.locator('#options-stickychat') const checked = await stickyChat.isChecked() if(checked) return - await stickyChat.click({force: true}) - await page.waitForSelector('#options-stickychat:checked') + await stickyChat.click() + await page.waitForFunction(() => document.querySelector('#chatbox')?.classList.contains('stickyChat')) } export const disableStickyChat = async (page: Page) => { const stickyChat = page.locator('#options-stickychat') const checked = await stickyChat.isChecked() if(!checked) return - await stickyChat.click({force: true}) - await page.waitForSelector('#options-stickychat:not(:checked)') + await stickyChat.click() + await page.waitForFunction(() => !document.querySelector('#chatbox')?.classList.contains('stickyChat')) } From 3c2a1a115587478a22e4cda4ecb4fe0657f41a59 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 31 Mar 2026 23:04:16 +0100 Subject: [PATCH 5/7] Clean up workflows: remove Sauce Labs, load test push-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove all Sauce Labs references (steps, comments, secrets) from frontend test workflows — Playwright replaced Sauce Labs - Remove unused set-output steps and GIT_HASH exports - Remove stale commented-out code from admin tests - Restrict load test to push events only (no need on PRs) - Fix artifact names to not reference undefined matrix.node Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/frontend-admin-tests.yml | 57 +--------------------- .github/workflows/frontend-tests.yml | 39 ++------------- .github/workflows/load-test.yml | 4 -- 3 files changed, 5 insertions(+), 95 deletions(-) diff --git a/.github/workflows/frontend-admin-tests.yml b/.github/workflows/frontend-admin-tests.yml index 8b83d0e4cf5..f28483325d2 100644 --- a/.github/workflows/frontend-admin-tests.yml +++ b/.github/workflows/frontend-admin-tests.yml @@ -9,7 +9,7 @@ on: - 'doc/**' permissions: - contents: read # to fetch code (actions/checkout) + contents: read jobs: withplugins: @@ -24,12 +24,6 @@ jobs: node: [20, 22, 24] steps: - - - name: Generate Sauce Labs strings - id: sauce_strings - run: | - printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }} - Node ${{ matrix.node }}' - printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}-node${{ matrix.node }}' - name: Checkout repository uses: actions/checkout@v6 @@ -56,31 +50,9 @@ jobs: path: | ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} - #- - # name: Install etherpad plugins - # # We intentionally install an old ep_align version to test upgrades to - # # the minor version number. The --legacy-peer-deps flag is required to - # # work around a bug in npm v7: https://github.com/npm/cli/issues/2199 - # run: pnpm install --workspace-root ep_align@0.2.27 - # Etherpad core dependencies must be installed after installing the - # plugin's dependencies, otherwise npm will try to hoist common - # dependencies by removing them from src/node_modules and installing them - # in the top-level node_modules. As of v6.14.10, npm's hoist logic appears - # to be buggy, because it sometimes removes dependencies from - # src/node_modules but fails to add them to the top-level node_modules. - # Even if npm correctly hoists the dependencies, the hoisting seems to - # confuse tools such as `npm outdated`, `npm update`, and some ESLint - # rules. - name: Install all dependencies and symlink for ep_etherpad-lite run: gnpm i --runtimeVersion="${{ matrix.node }}" - #- - # name: Install etherpad plugins - # run: rm -Rf node_modules/ep_align/static/tests/* - - - name: export GIT_HASH to env - id: environment - run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})" - name: Create settings.json run: cp settings.json.template settings.json @@ -98,33 +70,6 @@ jobs: working-directory: admin run: | gnpm run build --runtimeVersion="${{ matrix.node }}" - # name: Run the frontend admin tests - # shell: bash - # env: - # SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} - # SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} - # SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }} - # TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }} - # GIT_HASH: ${{ steps.environment.outputs.sha_short }} - # run: | - # src/tests/frontend/travis/adminrunner.sh - #- - # uses: saucelabs/sauce-connect-action@v2.3.6 - # with: - # username: ${{ secrets.SAUCE_USERNAME }} - # accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} - # tunnelIdentifier: ${{ steps.sauce_strings.outputs.tunnel_id }} - #- - # name: Run the frontend admin tests - # shell: bash - # env: - # SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} - # SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} - # SAUCE_NAME: ${{ steps.sauce_strings.outputs.name }} - # TRAVIS_JOB_NUMBER: ${{ steps.sauce_strings.outputs.tunnel_id }} - # GIT_HASH: ${{ steps.environment.outputs.sha_short }} - # run: | - # src/tests/frontend/travis/adminrunner.sh - name: Run the frontend admin tests shell: bash run: | diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index 817aae2abcb..da091923ec9 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -9,7 +9,7 @@ on: - 'doc/**' permissions: - contents: read # to fetch code (actions/checkout) + contents: read jobs: playwright-chrome: @@ -18,12 +18,6 @@ jobs: name: Playwright Chrome runs-on: ubuntu-latest steps: - - - name: Generate Sauce Labs strings - id: sauce_strings - run: | - printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}' - printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}' - name: Checkout repository uses: actions/checkout@v6 @@ -47,10 +41,6 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: gnpm install --frozen-lockfile - - - name: export GIT_HASH to env - id: environment - run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})" - name: Create settings.json run: cp ./src/tests/settings.json settings.json @@ -74,7 +64,7 @@ jobs: - uses: actions/upload-artifact@v7 if: always() with: - name: playwright-report-${{ matrix.node }}-chrome + name: playwright-report-chrome path: src/playwright-report/ retention-days: 30 playwright-firefox: @@ -83,11 +73,6 @@ jobs: name: Playwright Firefox runs-on: ubuntu-latest steps: - - name: Generate Sauce Labs strings - id: sauce_strings - run: | - printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}' - printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}' - name: Checkout repository uses: actions/checkout@v6 - uses: actions/cache@v5 @@ -109,9 +94,6 @@ jobs: version: 0.0.12 - name: Install all dependencies and symlink for ep_etherpad-lite run: gnpm install --frozen-lockfile - - name: export GIT_HASH to env - id: environment - run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})" - name: Create settings.json run: cp ./src/tests/settings.json settings.json - name: Run the frontend tests @@ -134,7 +116,7 @@ jobs: - uses: actions/upload-artifact@v7 if: always() with: - name: playwright-report-${{ matrix.node }}-firefox + name: playwright-report-firefox path: src/playwright-report/ retention-days: 30 playwright-webkit: @@ -143,12 +125,6 @@ jobs: env: PNPM_HOME: ~/.pnpm-store steps: - - - name: Generate Sauce Labs strings - id: sauce_strings - run: | - printf %s\\n '::set-output name=name::${{ github.workflow }} - ${{ github.job }}' - printf %s\\n '::set-output name=tunnel_id::${{ github.run_id }}-${{ github.run_number }}-${{ github.job }}' - name: Checkout repository uses: actions/checkout@v6 @@ -171,10 +147,6 @@ jobs: - name: Install all dependencies and symlink for ep_etherpad-lite run: gnpm install --frozen-lockfile - - - name: export GIT_HASH to env - id: environment - run: echo "::set-output name=sha_short::$(git rev-parse --short ${{ github.sha }})" - name: Create settings.json run: cp ./src/tests/settings.json settings.json @@ -198,9 +170,6 @@ jobs: - uses: actions/upload-artifact@v7 if: always() with: - name: playwright-report-${{ matrix.node }}-webkit + name: playwright-report-webkit path: src/playwright-report/ retention-days: 30 - - - diff --git a/.github/workflows/load-test.yml b/.github/workflows/load-test.yml index 12bacdcf8e3..f590dd9311e 100644 --- a/.github/workflows/load-test.yml +++ b/.github/workflows/load-test.yml @@ -1,13 +1,9 @@ name: "Loadtest" -# any branch is useful for testing before a PR is submitted on: push: paths-ignore: - "doc/**" - pull_request: - paths-ignore: - - "doc/**" permissions: contents: read From d1b7d23c57492d4f9f1a32de8db5ee4a15ff1799 Mon Sep 17 00:00:00 2001 From: John McLear Date: Tue, 31 Mar 2026 23:13:14 +0100 Subject: [PATCH 6/7] Fix sticky chat test: click label instead of checkbox The label element intercepts pointer events on the checkbox (reported by Webkit). On Chrome/Firefox the checkbox is "not stable" due to animations. Clicking the label is how a real user interacts with it and properly triggers the jQuery click handler. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/tests/frontend-new/helper/settingsHelper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/frontend-new/helper/settingsHelper.ts b/src/tests/frontend-new/helper/settingsHelper.ts index de5541e8e98..c35ccf3467f 100644 --- a/src/tests/frontend-new/helper/settingsHelper.ts +++ b/src/tests/frontend-new/helper/settingsHelper.ts @@ -22,7 +22,7 @@ export const enableStickyChatviaSettings = async (page: Page) => { const stickyChat = page.locator('#options-stickychat') const checked = await stickyChat.isChecked() if(checked) return - await stickyChat.click() + await page.locator('label[for="options-stickychat"]').click() await page.waitForFunction(() => document.querySelector('#chatbox')?.classList.contains('stickyChat')) } @@ -30,6 +30,6 @@ export const disableStickyChat = async (page: Page) => { const stickyChat = page.locator('#options-stickychat') const checked = await stickyChat.isChecked() if(!checked) return - await stickyChat.click() + await page.locator('label[for="options-stickychat"]').click() await page.waitForFunction(() => !document.querySelector('#chatbox')?.classList.contains('stickyChat')) } From 19ae6829d32f711715331cab3bbca8f57e2162ef Mon Sep 17 00:00:00 2001 From: John McLear Date: Wed, 1 Apr 2026 08:54:25 +0100 Subject: [PATCH 7/7] Fix home button to preserve subpath installations Use URL API to resolve '../..' relative to current URL instead of hardcoding origin + '/'. This preserves any configured base path (e.g. /etherpad) for reverse-proxy installations. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/static/js/pad_editbar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/pad_editbar.ts b/src/static/js/pad_editbar.ts index b3873490480..cb8dae2a781 100644 --- a/src/static/js/pad_editbar.ts +++ b/src/static/js/pad_editbar.ts @@ -356,7 +356,7 @@ exports.padeditbar = new class { this.registerDropdownCommand('import_export'); this.registerDropdownCommand('embed'); this.registerCommand('home', ()=>{ - window.location.href = window.location.origin + '/' + window.location.href = new URL('../..', window.location.href).href }) this.registerCommand('settings', () => {