Skip to content
Merged
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
103 changes: 103 additions & 0 deletions doc/PLUGIN_FRONTEND_TESTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Plugin frontend tests

Etherpad core's Playwright runner discovers plugin frontend specs from
the conventional path:

```
node_modules/ep_<plugin>/static/tests/frontend-new/specs/**/*.spec.ts
```

When the plugin is installed alongside core (e.g. via `pnpm add -w
ep_<plugin>` or in a `with-plugins` CI variant), the plugin's specs
run as part of `pnpm run test-ui`. Same pattern backend tests already
use (`mocha ... ../node_modules/ep_*/static/tests/backend/specs/**`).

This re-enables coverage that was lost in commit `cc80db2d3` (2023-07)
when the legacy jQuery test runner (`static/tests/frontend/specs/test.js`
+ in-page mocha+helper) was removed without a Playwright replacement.
See [#7622](https://github.com/ether/etherpad/issues/7622).

## Layout in your plugin

```
ep_yourplugin/
├── ep.json
├── package.json
├── static/
│ └── tests/
│ └── frontend-new/
│ └── specs/
│ └── yourplugin.spec.ts
└── ...
```

A spec is a normal Playwright test file. Import shared helpers from the
core package — `ep_etherpad-lite` is symlinked into `node_modules` by
the workspace, so this resolves anywhere the plugin is installed
alongside core:

```ts
import {expect, test} from '@playwright/test';
import {clearPadContent, getPadBody, goToNewPad, writeToPad}
from 'ep_etherpad-lite/tests/frontend-new/helper/padHelper';

test.beforeEach(async ({page}) => {
await goToNewPad(page);
});

test.describe('ep_yourplugin', () => {
test('does the thing', async ({page}) => {
const padBody = await getPadBody(page);
await padBody.click();
await clearPadContent(page);
await writeToPad(page, 'hello');
// …assertions…
await expect(padBody.locator('div').first()).toHaveText('hello');
});
});
```

## Migrating from the legacy `static/tests/frontend/specs/test.js`

The old format used mocha + a jQuery `helper` global:

```js
// Legacy — does not run anywhere any more.
describe('ep_yourplugin', function () {
beforeEach(function (cb) { helper.newPad(cb); });
it('does the thing', async function () {
const chrome$ = helper.padChrome$;
const inner$ = helper.padInner$;
expect(chrome$('#yourbutton').length).to.be.greaterThan(0);
});
});
```

Translation table:

| Legacy (mocha + helper) | Playwright |
|---|---|
| `describe(...)` / `it(...)` | `test.describe(...)` / `test(...)` |
| `helper.newPad(cb)` | `await goToNewPad(page)` |
| `helper.padChrome$('#x')` | `page.locator('#x')` |
| `helper.padInner$('div')` | `(await getPadBody(page)).locator('div')` |
| `expect(x).to.equal(y)` | `expect(x).toBe(y)` (Playwright's expect) |
| `expect($el.length).to.be.greaterThan(0)` | `await expect(page.locator('#x')).toBeVisible()` |
| `$el.sendkeys('text')` | `await page.keyboard.type('text')` |
| `$el.simulate('click')` | `await page.locator(...).click()` |

Most legacy specs translate ~mechanically. After migrating, **delete
the legacy file** so the plugin can't accidentally ship stale tests
that nothing executes.

## Running them

```sh
# Inside core, with the plugin installed:
pnpm run test-ui --project=chromium
# Or via core's with-plugins CI job (see frontend-tests.yml).
```

`pnpm run test-ui` automatically picks up plugin specs from any
installed `ep_*` package. To gate per-plugin: use playwright's
`--grep` against your plugin's describe name.
8 changes: 4 additions & 4 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@
"prod": "cross-env NODE_ENV=production node --require tsx/cjs node/server.ts",
"ts-check": "tsc --noEmit",
"ts-check:watch": "tsc --noEmit --watch",
"test-ui": "cross-env NODE_ENV=production npx playwright test tests/frontend-new/specs",
"test-ui:ui": "cross-env NODE_ENV=production npx playwright test tests/frontend-new/specs --ui",
"test-admin": "cross-env NODE_ENV=production npx playwright test tests/frontend-new/admin-spec --workers 1 --project=chromium",
"test-admin:ui": "cross-env NODE_ENV=production npx playwright test tests/frontend-new/admin-spec --ui --workers 1",
"test-ui": "cross-env NODE_ENV=production npx playwright test",
"test-ui:ui": "cross-env NODE_ENV=production npx playwright test --ui",
"test-admin": "cross-env NODE_ENV=production npx playwright test --workers 1 --project=chromium-admin",
"test-admin:ui": "cross-env NODE_ENV=production npx playwright test --ui --workers 1 --project=chromium-admin",
"debug:socketio": "cross-env DEBUG=socket.io* node --require tsx/cjs node/server.ts",
"test:vitest": "vitest"
},
Expand Down
141 changes: 85 additions & 56 deletions src/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,98 @@ import {defineConfig, devices, test} from '@playwright/test';
export const defaultExpectTimeout = process.env.CI ? 20 * 1000 : 5000
export const defaultTestTimeout = 90 * 1000

// Mirror of how tests/backend/specs picks up plugin specs from
// `../node_modules/ep_*/static/tests/backend/specs/**`. Plugins that
// ship Playwright frontend tests at the conventional location below
// are discovered automatically when the plugin is installed alongside
// core. See doc/PLUGIN_FRONTEND_TESTS.md.
const CORE_SPECS = 'tests/frontend-new/specs/**/*.spec.ts';
const ADMIN_SPECS = 'tests/frontend-new/admin-spec/**/*.spec.ts';
const PLUGIN_SPECS = [
// Plugins installed via `pnpm add -w ep_*` (CI / dev workspace).
'../node_modules/ep_*/static/tests/frontend-new/specs/**/*.spec.ts',
// Plugins installed via the admin UI / live-plugin-manager land
// here instead of node_modules.
'plugin_packages/ep_*/static/tests/frontend-new/specs/**/*.spec.ts',
];
const FRONTEND_MATCH = [CORE_SPECS, ...PLUGIN_SPECS];

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests/frontend-new/',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? [['github'], ['list']] : 'html',
expect: { timeout: defaultExpectTimeout },
timeout: defaultTestTimeout,
retries: process.env.CI ? 2 : 0,
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('/')`. */
// baseURL: 'http://127.0.0.1:3000',
baseURL: "localhost:9001",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
video: 'on-first-retry',
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// testDir is project-root for src/ so the testMatch globs reach both
// tests under src/tests/... and node_modules/ep_*/... above src/.
testDir: '.',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI ? [['github'], ['list']] : 'html',
expect: { timeout: defaultExpectTimeout },
timeout: defaultTestTimeout,
retries: process.env.CI ? 2 : 0,
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('/')`. */
// baseURL: 'http://127.0.0.1:3000',
baseURL: "localhost:9001",
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
video: 'on-first-retry',
},

{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
// Webkit dropped from CI — see https://github.com/ether/etherpad-lite/issues/XXXX
// Kept chromium and firefox as the supported browsers.
/* Configure projects for major browsers */
projects: [
// Frontend / pad-editor specs (core + plugins).
{
name: 'chromium',
testMatch: FRONTEND_MATCH,
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
testMatch: FRONTEND_MATCH,
use: { ...devices['Desktop Firefox'] },
},

/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
// Admin-UI specs are isolated from the regular frontend run so the
// existing test-admin script + frontend-admin-tests workflow keep
// their own scope (different fixtures, different server state).
{
name: 'chromium-admin',
testMatch: ADMIN_SPECS,
use: { ...devices['Desktop Chrome'] },
},
// Webkit dropped from CI — see https://github.com/ether/etherpad-lite/issues/XXXX
// Kept chromium and firefox as the supported browsers.

/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],

/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});
Loading