Skip to content

Commit ecce2e0

Browse files
committed
Clipboard support for E2E tests
1 parent d5efd42 commit ecce2e0

File tree

7 files changed

+233
-22
lines changed

7 files changed

+233
-22
lines changed

package-lock.json

Lines changed: 92 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/quill/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@babel/core": "^7.24.0",
1818
"@babel/preset-env": "^7.24.0",
1919
"@babel/preset-typescript": "^7.23.3",
20-
"@playwright/test": "1.38.1",
20+
"@playwright/test": "1.44.1",
2121
"@types/highlight.js": "^9.12.4",
2222
"@types/lodash-es": "^4.17.12",
2323
"@types/node": "^20.10.0",
@@ -36,6 +36,7 @@
3636
"eslint-plugin-jsx-a11y": "^6.8.0",
3737
"eslint-plugin-prettier": "^5.1.3",
3838
"eslint-plugin-require-extensions": "^0.1.3",
39+
"glob": "10.4.2",
3940
"highlight.js": "^9.18.1",
4041
"html-loader": "^4.2.0",
4142
"html-webpack-plugin": "^5.5.3",

packages/quill/playwright.config.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,15 @@ export default defineConfig({
2121
ignoreHTTPSErrors: true,
2222
},
2323
projects: [
24-
{ name: 'Chrome', use: { ...devices['Desktop Chrome'] } },
24+
{
25+
name: 'Chrome',
26+
use: {
27+
...devices['Desktop Chrome'],
28+
contextOptions: {
29+
permissions: ['clipboard-read', 'clipboard-write'],
30+
},
31+
},
32+
},
2533
{ name: 'Firefox', use: { ...devices['Desktop Firefox'] } },
2634
{ name: 'Safari', use: { ...devices['Desktop Safari'] } },
2735
],
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import type { Page } from '@playwright/test';
2+
import { SHORTKEY } from '../utils/index.js';
3+
4+
class Clipboard {
5+
constructor(private page: Page) {}
6+
7+
async copy() {
8+
await this.page.keyboard.press(`${SHORTKEY}+c`);
9+
}
10+
11+
async cut() {
12+
await this.page.keyboard.press(`${SHORTKEY}+x`);
13+
}
14+
15+
async paste() {
16+
await this.page.keyboard.press(`${SHORTKEY}+v`);
17+
}
18+
19+
async writeText(value: string) {
20+
await this.write(value, 'text/plain');
21+
}
22+
23+
async writeHTML(value: string) {
24+
return this.write(value, 'text/html');
25+
}
26+
27+
async readText() {
28+
return this.read('text/plain');
29+
}
30+
31+
async readHTML() {
32+
const html = await this.read('text/html');
33+
return html.replace(/<meta[^>]*>/g, '');
34+
}
35+
36+
private async read(type: string) {
37+
const isHTML = type === 'text/html';
38+
await this.page.evaluate((isHTML) => {
39+
const dataContainer = document.createElement(isHTML ? 'div' : 'textarea');
40+
if (isHTML) dataContainer.setAttribute('contenteditable', 'true');
41+
dataContainer.id = '_readClipboard';
42+
document.body.appendChild(dataContainer);
43+
dataContainer.focus();
44+
return dataContainer;
45+
}, isHTML);
46+
await this.paste();
47+
const locator = this.page.locator('#_readClipboard');
48+
const data = await (isHTML ? locator.innerHTML() : locator.inputValue());
49+
await locator.evaluate((node) => node.remove());
50+
return data;
51+
}
52+
53+
private async write(data: string, type: string) {
54+
await this.page.evaluate(
55+
async ({ data, type }) => {
56+
if (type === 'text/html') {
57+
await navigator.clipboard.write([
58+
new ClipboardItem({
59+
'text/html': new Blob([data], { type: 'text/html' }),
60+
}),
61+
]);
62+
} else {
63+
await navigator.clipboard.writeText(data);
64+
}
65+
},
66+
{ data, type },
67+
);
68+
}
69+
}
70+
71+
export default Clipboard;

packages/quill/test/e2e/fixtures/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { test as base } from '@playwright/test';
22
import EditorPage from '../pageobjects/EditorPage.js';
33
import Composition from './Composition.js';
4+
import Locker from './utils/Locker.js';
5+
import Clipboard from './Clipboard.js';
46

57
export const test = base.extend<{
68
editorPage: EditorPage;
@@ -18,6 +20,15 @@ export const test = base.extend<{
1820

1921
use(new Composition(page, browserName));
2022
},
23+
clipboard: [
24+
async ({ page }, use) => {
25+
const locker = new Locker('clipboard');
26+
await locker.lock();
27+
await use(new Clipboard(page));
28+
await locker.release();
29+
},
30+
{ timeout: 30000 },
31+
],
2132
});
2233

2334
export const CHAPTER = 'Chapter 1. Loomings.';
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { unlink, writeFile } from 'fs/promises';
2+
import { unlinkSync } from 'fs';
3+
import { tmpdir } from 'os';
4+
import { join } from 'path';
5+
import { globSync } from 'glob';
6+
7+
const sleep = (ms: number) =>
8+
new Promise((resolve) => {
9+
setTimeout(resolve, ms);
10+
});
11+
12+
const PREFIX = 'playwright_locker_';
13+
14+
class Locker {
15+
public static clearAll() {
16+
globSync(join(tmpdir(), `${PREFIX}*.txt`)).forEach(unlinkSync);
17+
}
18+
19+
constructor(private key: string) {}
20+
21+
private get filePath() {
22+
return join(tmpdir(), `${PREFIX}${this.key}.txt`);
23+
}
24+
25+
async lock() {
26+
try {
27+
await writeFile(this.filePath, '', { flag: 'wx' });
28+
} catch {
29+
await sleep(50);
30+
await this.lock();
31+
}
32+
}
33+
34+
async release() {
35+
await unlink(this.filePath);
36+
}
37+
}
38+
39+
export default Locker;

packages/quill/test/e2e/history.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from '@playwright/test';
22
import type { Page } from '@playwright/test';
33
import { test } from './fixtures/index.js';
44
import { SHORTKEY } from './utils/index.js';
5+
import { cli } from 'webpack';
56

67
const undo = (page: Page) => page.keyboard.press(`${SHORTKEY}+z`);
78
const redo = (page: Page) => page.keyboard.press(`${SHORTKEY}+Shift+z`);
@@ -38,6 +39,14 @@ test.describe('history', () => {
3839
expect(await editorPage.getContents()).toEqual([{ insert: '1234\n' }]);
3940
});
4041

42+
test('clipboard', async ({ clipboard, page, editorPage }) => {
43+
await editorPage.moveCursorAfterText('2');
44+
await clipboard.writeText('a');
45+
await clipboard.paste();
46+
await undo(page);
47+
expect(await editorPage.getContents()).toEqual([{ insert: '1234\n' }]);
48+
});
49+
4150
test.describe('selection', () => {
4251
test('typing', async ({ page, editorPage }) => {
4352
await editorPage.moveCursorAfterText('2');

0 commit comments

Comments
 (0)