From 71390dad9434a97f29cd9cf3899f88db9a50c64f Mon Sep 17 00:00:00 2001 From: Bob Ippolito Date: Mon, 4 Nov 2024 16:07:21 -0800 Subject: [PATCH] wrap most clipboard related e2e tests with withExclusiveClipboardAccess --- .gitignore | 1 + package-lock.json | 59 ++ package.json | 2 + .../__tests__/e2e/CodeActionMenu.spec.mjs | 45 +- .../html/LinksHTMLCopyAndPaste.spec.mjs | 9 +- .../lexical/ContextMenuCopyAndPaste.spec.mjs | 50 +- .../lexical/CopyAndPaste.spec.mjs | 964 +++++++++--------- .../lexical/ListsCopyAndPaste.spec.mjs | 752 +++++++------- .../__tests__/e2e/HorizontalRule.spec.mjs | 53 +- .../__tests__/e2e/Links.spec.mjs | 13 +- .../__tests__/e2e/List.spec.mjs | 13 +- .../__tests__/e2e/Share.spec.mjs | 39 +- .../__tests__/e2e/Tables.spec.mjs | 49 +- .../regression/1384-insert-nodes.spec.mjs | 9 +- .../5251-paste-into-inline-element.spec.mjs | 107 +- .../__tests__/utils/index.mjs | 19 + 16 files changed, 1165 insertions(+), 1019 deletions(-) diff --git a/.gitignore b/.gitignore index 561d9c9edf2..524ab7f7ba5 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ npm .env.production.local .ts-temp .docusaurus +.playwright-clipboard.lock e2e-screenshots test-results playwright-report diff --git a/package-lock.json b/package-lock.json index d230173f961..1913a1c64e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "@types/node": "^17.0.31", "@types/prettier": "^2.7.3", "@types/prismjs": "^1.26.0", + "@types/proper-lockfile": "^4.1.4", "@types/react": "^18.0.8", "@types/react-dom": "^18.0.3", "@types/trusted-types": "^2.0.7", @@ -83,6 +84,7 @@ "prettier-plugin-hermes-parser": "^0.20.1", "prettier-plugin-organize-attributes": "^0.0.5", "prettier-plugin-tailwindcss": "^0.4.1", + "proper-lockfile": "^4.1.2", "react-test-renderer": "^17.0.2", "rollup": "^4.22.4", "size-limit": "^11.1.2", @@ -8238,6 +8240,15 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, + "node_modules/@types/proper-lockfile": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/proper-lockfile/-/proper-lockfile-4.1.4.tgz", + "integrity": "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==", + "dev": true, + "dependencies": { + "@types/retry": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.14", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", @@ -28404,6 +28415,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/proper-lockfile/node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/property-information": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", @@ -42594,6 +42625,15 @@ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" }, + "@types/proper-lockfile": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@types/proper-lockfile/-/proper-lockfile-4.1.4.tgz", + "integrity": "sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==", + "dev": true, + "requires": { + "@types/retry": "*" + } + }, "@types/qs": { "version": "6.9.14", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz", @@ -56745,6 +56785,25 @@ } } }, + "proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + }, + "dependencies": { + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true + } + } + }, "property-information": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", diff --git a/package.json b/package.json index 7c2a5ab57c0..8e1061ae939 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "@types/node": "^17.0.31", "@types/prettier": "^2.7.3", "@types/prismjs": "^1.26.0", + "@types/proper-lockfile": "^4.1.4", "@types/react": "^18.0.8", "@types/react-dom": "^18.0.3", "@types/trusted-types": "^2.0.7", @@ -178,6 +179,7 @@ "prettier-plugin-hermes-parser": "^0.20.1", "prettier-plugin-organize-attributes": "^0.0.5", "prettier-plugin-tailwindcss": "^0.4.1", + "proper-lockfile": "^4.1.2", "react-test-renderer": "^17.0.2", "rollup": "^4.22.4", "size-limit": "^11.1.2", diff --git a/packages/lexical-playground/__tests__/e2e/CodeActionMenu.spec.mjs b/packages/lexical-playground/__tests__/e2e/CodeActionMenu.spec.mjs index 06c414c01dd..19998695f81 100644 --- a/packages/lexical-playground/__tests__/e2e/CodeActionMenu.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CodeActionMenu.spec.mjs @@ -18,6 +18,7 @@ import { pasteFromClipboard, test, waitForSelector, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; test.describe('CodeActionMenu', () => { @@ -84,31 +85,33 @@ test.describe('CodeActionMenu', () => { await mouseMoveToSelector(page, 'code.PlaygroundEditorTheme__code'); - if (browserName === 'chromium') { - await context.grantPermissions(['clipboard-write']); - await click(page, 'button[aria-label=copy]'); - await paste(page); - await context.clearPermissions(); - } else { - await waitForSelector(page, 'button[aria-label=copy]'); + await withExclusiveClipboardAccess(async () => { + if (browserName === 'chromium') { + await context.grantPermissions(['clipboard-write']); + await click(page, 'button[aria-label=copy]'); + await paste(page); + await context.clearPermissions(); + } else { + await waitForSelector(page, 'button[aria-label=copy]'); - const copiedText = await evaluate(page, () => { - let text = null; + const copiedText = await evaluate(page, () => { + let text = null; - navigator.clipboard._writeText = navigator.clipboard.writeText; - navigator.clipboard.writeText = function (data) { - text = data; - this._writeText(data); - }; - document.querySelector('button[aria-label=copy]').click(); + navigator.clipboard._writeText = navigator.clipboard.writeText; + navigator.clipboard.writeText = function (data) { + text = data; + this._writeText(data); + }; + document.querySelector('button[aria-label=copy]').click(); - return text; - }); + return text; + }); - await pasteFromClipboard(page, { - 'text/plain': copiedText, - }); - } + await pasteFromClipboard(page, { + 'text/plain': copiedText, + }); + } + }); await assertHTML( page, diff --git a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs index 395ad3e34f7..efde7a5677d 100644 --- a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/html/LinksHTMLCopyAndPaste.spec.mjs @@ -28,6 +28,7 @@ import { locate, pasteFromClipboard, test, + withExclusiveClipboardAccess, } from '../../../utils/index.mjs'; test.describe('HTML Links CopyAndPaste', () => { @@ -148,9 +149,11 @@ test.describe('HTML Links CopyAndPaste', () => { await page.keyboard.down('Shift'); await moveLeft(page, 2); await page.keyboard.up('Shift'); - const clipboard = await copyToClipboard(page); - await moveToEditorEnd(page); - await pasteFromClipboard(page, clipboard); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); + await moveToEditorEnd(page); + await pasteFromClipboard(page, clipboard); + }); await assertHTML( page, diff --git a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ContextMenuCopyAndPaste.spec.mjs b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ContextMenuCopyAndPaste.spec.mjs index 9fcaeb7cde5..108cd0f0617 100644 --- a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ContextMenuCopyAndPaste.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ContextMenuCopyAndPaste.spec.mjs @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. * */ + import {moveToLineEnd} from '../../../keyboardShortcuts/index.mjs'; import { assertHTML, @@ -15,6 +16,7 @@ import { initialize, pasteFromClipboard, test, + withExclusiveClipboardAccess, } from '../../../utils/index.mjs'; test.describe('ContextMenuCopyAndPaste', () => { @@ -33,13 +35,15 @@ test.describe('ContextMenuCopyAndPaste', () => { await page.pause(); await doubleClick(page, 'div[contenteditable="false"] span'); await page.pause(); - await click(page, 'div[contenteditable="false"] span', {button: 'right'}); - await click(page, '#typeahead-menu [role="option"] :text("Copy")'); + await withExclusiveClipboardAccess(async () => { + await click(page, 'div[contenteditable="false"] span', {button: 'right'}); + await click(page, '#typeahead-menu [role="option"] :text("Copy")'); - await click(page, '.unlock'); - await focusEditor(page); + await click(page, '.unlock'); + await focusEditor(page); - await pasteFromClipboard(page); + await pasteFromClipboard(page); + }); await assertHTML( page, @@ -60,27 +64,29 @@ test.describe('ContextMenuCopyAndPaste', () => { }) => { test.skip(isCollab || isPlainText || browserName !== 'chromium'); - await page - .context() - .grantPermissions(['clipboard-read', 'clipboard-write']); + await withExclusiveClipboardAccess(async () => { + await page + .context() + .grantPermissions(['clipboard-read', 'clipboard-write']); - await click(page, '.font-increment'); - await focusEditor(page); - await page.keyboard.type('MLH Fellowship'); - //await page.pause(); - await moveToLineEnd(page); - await page.keyboard.press('Enter'); - await page.keyboard.type('Fall 2024'); + await click(page, '.font-increment'); + await focusEditor(page); + await page.keyboard.type('MLH Fellowship'); + //await page.pause(); + await moveToLineEnd(page); + await page.keyboard.press('Enter'); + await page.keyboard.type('Fall 2024'); - await click(page, '.lock'); + await click(page, '.lock'); - await doubleClick(page, 'div[contenteditable="false"] span'); - await click(page, 'div[contenteditable="false"] span', {button: 'right'}); - await click(page, '#typeahead-menu [role="option"] :text("Copy")'); + await doubleClick(page, 'div[contenteditable="false"] span'); + await click(page, 'div[contenteditable="false"] span', {button: 'right'}); + await click(page, '#typeahead-menu [role="option"] :text("Copy")'); - await click(page, '.unlock'); - await focusEditor(page); - await pasteFromClipboard(page); + await click(page, '.unlock'); + await focusEditor(page); + await pasteFromClipboard(page); + }); await assertHTML( page, diff --git a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs index 161d5792eb2..c654738d624 100644 --- a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/CopyAndPaste.spec.mjs @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. * */ + import { moveToEditorBeginning, moveToEditorEnd, @@ -24,6 +25,7 @@ import { IS_LINUX, pasteFromClipboard, test, + withExclusiveClipboardAccess, YOUTUBE_SAMPLE_URL, } from '../../../utils/index.mjs'; @@ -148,96 +150,98 @@ test.describe('CopyAndPaste', () => { } // Copy all the text - const clipboard = await copyToClipboard(page); - if (isRichText) { - await assertHTML( - page, - html` -

- Copy + pasting? -

-


-

- Sounds good! -

- `, - ); - } else { - await assertHTML( - page, - html` -

- Copy + pasting? -
-
- Sounds good! -

- `, - ); - } + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); + if (isRichText) { + await assertHTML( + page, + html` +

+ Copy + pasting? +

+


+

+ Sounds good! +

+ `, + ); + } else { + await assertHTML( + page, + html` +

+ Copy + pasting? +
+
+ Sounds good! +

+ `, + ); + } - // Paste after - await page.keyboard.press('ArrowRight'); - await pasteFromClipboard(page, clipboard); - if (isRichText) { - await assertHTML( - page, - html` -

- Copy + pasting? -

-


-

- Sounds good!Copy + pasting? -

-


-

- Sounds good! -

- `, - ); - await assertSelection(page, { - anchorOffset: 12, - anchorPath: [4, 0, 0], - focusOffset: 12, - focusPath: [4, 0, 0], - }); - } else { - await assertHTML( - page, - html` -

- Copy + pasting? -
-
- Sounds good!Copy + pasting? -
-
- Sounds good! -

- `, - ); - await assertSelection(page, { - anchorOffset: 12, - anchorPath: [0, 6, 0], - focusOffset: 12, - focusPath: [0, 6, 0], - }); - } + // Paste after + await page.keyboard.press('ArrowRight'); + await pasteFromClipboard(page, clipboard); + if (isRichText) { + await assertHTML( + page, + html` +

+ Copy + pasting? +

+


+

+ Sounds good!Copy + pasting? +

+


+

+ Sounds good! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 12, + anchorPath: [4, 0, 0], + focusOffset: 12, + focusPath: [4, 0, 0], + }); + } else { + await assertHTML( + page, + html` +

+ Copy + pasting? +
+
+ Sounds good!Copy + pasting? +
+
+ Sounds good! +

+ `, + ); + await assertSelection(page, { + anchorOffset: 12, + anchorPath: [0, 6, 0], + focusOffset: 12, + focusPath: [0, 6, 0], + }); + } + }); }); test(`Copy and paste heading`, async ({ @@ -263,40 +267,42 @@ test.describe('CopyAndPaste', () => { await moveToLineEnd(page); await page.keyboard.up('Shift'); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await moveToEditorEnd(page); - await page.keyboard.press('Enter'); + await moveToEditorEnd(page); + await page.keyboard.press('Enter'); - // Paste the content - await pasteFromClipboard(page, clipboard); + // Paste the content + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - html` -

- Heading -

-

- Some text -

-

- Heading -

- `, - ); + await assertHTML( + page, + html` +

+ Heading +

+

+ Some text +

+

+ Heading +

+ `, + ); - await assertSelection(page, { - anchorOffset: 7, - anchorPath: [2, 0, 0], - focusOffset: 7, - focusPath: [2, 0, 0], + await assertSelection(page, { + anchorOffset: 7, + anchorPath: [2, 0, 0], + focusOffset: 7, + focusPath: [2, 0, 0], + }); }); }); @@ -460,324 +466,326 @@ test.describe('CopyAndPaste', () => { } } - // Copy all the text - let clipboard = await copyToClipboard(page); - await page.keyboard.press('Delete'); - // Paste the content - await pasteFromClipboard(page, clipboard); - - if (isRichText) { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -

-

- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [1, 5, 0], - focusOffset: 4, - focusPath: [1, 5, 0], - }); - } else { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -
- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 12, 0], - focusOffset: 4, - focusPath: [0, 12, 0], - }); - } + await withExclusiveClipboardAccess(async () => { + // Copy all the text + let clipboard = await copyToClipboard(page); + await page.keyboard.press('Delete'); + // Paste the content + await pasteFromClipboard(page, clipboard); + + if (isRichText) { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +

+

+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [1, 5, 0], + focusOffset: 4, + focusPath: [1, 5, 0], + }); + } else { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +
+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 12, 0], + focusOffset: 4, + focusPath: [0, 12, 0], + }); + } - await moveToPrevWord(page); - await page.keyboard.down('Shift'); - await page.keyboard.press('ArrowUp'); - await moveToPrevWord(page); - // Once more for linux on Chromium - if (IS_LINUX && browserName === 'chromium') { await moveToPrevWord(page); - } - await page.keyboard.up('Shift'); - - if (isRichText) { - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [1, 5, 0], - focusOffset: 1, - focusPath: [0, 2, 0], - }); - } else { - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 12, 0], - focusOffset: 1, - focusPath: [0, 2, 0], - }); - } - - // Copy selected text - clipboard = await copyToClipboard(page); - await page.keyboard.press('Delete'); - // Paste the content - await pasteFromClipboard(page, clipboard); - - if (isRichText) { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -

-

- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [1, 5, 0], - focusOffset: 1, - focusPath: [1, 5, 0], - }); - } else { - await assertHTML( - page, - html` -

- Hello world - - #foobar - - test - - #foobar2 - - when - - #not - -
- Next - - #line - - of - - #text - - test - - #foo - -

- `, - ); - await assertSelection(page, { - anchorOffset: 1, - anchorPath: [0, 12, 0], - focusOffset: 1, - focusPath: [0, 12, 0], - }); - } + await page.keyboard.down('Shift'); + await page.keyboard.press('ArrowUp'); + await moveToPrevWord(page); + // Once more for linux on Chromium + if (IS_LINUX && browserName === 'chromium') { + await moveToPrevWord(page); + } + await page.keyboard.up('Shift'); - // Select all the content - await selectAll(page); + if (isRichText) { + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [1, 5, 0], + focusOffset: 1, + focusPath: [0, 2, 0], + }); + } else { + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 12, 0], + focusOffset: 1, + focusPath: [0, 2, 0], + }); + } - if (isRichText) { - if (browserName === 'firefox') { + // Copy selected text + clipboard = await copyToClipboard(page); + await page.keyboard.press('Delete'); + // Paste the content + await pasteFromClipboard(page, clipboard); + + if (isRichText) { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +

+

+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 2, - focusPath: [], + anchorOffset: 1, + anchorPath: [1, 5, 0], + focusOffset: 1, + focusPath: [1, 5, 0], }); } else { + await assertHTML( + page, + html` +

+ Hello world + + #foobar + + test + + #foobar2 + + when + + #not + +
+ Next + + #line + + of + + #text + + test + + #foo + +

+ `, + ); + await assertSelection(page, { + anchorOffset: 1, + anchorPath: [0, 12, 0], + focusOffset: 1, + focusPath: [0, 12, 0], + }); + } + + // Select all the content + await selectAll(page); + + if (isRichText) { if (browserName === 'firefox') { await assertSelection(page, { anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 3, - focusPath: [1, 5, 0], + anchorPath: [], + focusOffset: 2, + focusPath: [], + }); + } else { + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 3, + focusPath: [1, 5, 0], + }); + } else { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 0, 0], + focusOffset: 4, + focusPath: [1, 5, 0], + }); + } + } + } else { + if (browserName === 'firefox') { + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [], + focusOffset: 1, + focusPath: [], }); } else { await assertSelection(page, { anchorOffset: 0, anchorPath: [0, 0, 0], focusOffset: 4, - focusPath: [1, 5, 0], + focusPath: [0, 12, 0], }); } } - } else { - if (browserName === 'firefox') { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [], - focusOffset: 1, - focusPath: [], - }); - } else { - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 0, 0], - focusOffset: 4, - focusPath: [0, 12, 0], - }); - } - } - await page.keyboard.press('Delete'); - await assertHTML( - page, - html` -


- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0], - focusOffset: 0, - focusPath: [0], + await page.keyboard.press('Delete'); + await assertHTML( + page, + html` +


+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0], + focusOffset: 0, + focusPath: [0], + }); }); }); @@ -803,11 +811,13 @@ test.describe('CopyAndPaste', () => { await selectAll(page); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowRight'); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); + }); await assertHTML( page, @@ -863,57 +873,60 @@ test.describe('CopyAndPaste', () => { ); }); - test('Pasting a decorator node on a blank line inserts before the line', async ({ - page, - isCollab, - isPlainText, - }) => { - test.fixme(); // TODO: flaky - test.skip(isPlainText); - - // copying and pasting the node is easier than creating the clipboard data - await focusEditor(page); - await insertYouTubeEmbed(page, YOUTUBE_SAMPLE_URL); - await page.keyboard.press('ArrowLeft'); // this selects the node - const clipboard = await copyToClipboard(page); - await page.keyboard.press('ArrowRight'); // this moves to a new line (empty paragraph node) - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` -


-
-
- -
-
-
-
- -
-
-
- `, - ); - }); + test( + 'Pasting a decorator node on a blank line inserts before the line', + { + tag: '@flaky', + }, + async ({page, isCollab, isPlainText}) => { + test.skip(isPlainText); + + // copying and pasting the node is easier than creating the clipboard data + await focusEditor(page); + await insertYouTubeEmbed(page, YOUTUBE_SAMPLE_URL); + await page.keyboard.press('ArrowLeft'); // this selects the node + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); + await page.keyboard.press('ArrowRight'); // this moves to a new line (empty paragraph node) + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` +


+
+
+ +
+
+
+
+ +
+
+
+ `, + ); + }); + }, + ); test('Copy and paste paragraph into quote', async ({page, isPlainText}) => { test.skip(isPlainText); @@ -925,12 +938,13 @@ test.describe('CopyAndPaste', () => { await selectAll(page); - const clipboard = await copyToClipboard(page); - - await page.keyboard.type('> '); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await pasteFromClipboard(page, clipboard); + await page.keyboard.type('> '); + await pasteFromClipboard(page, clipboard); + }); await assertHTML( page, html` diff --git a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs index 17d628ebff5..973728d73af 100644 --- a/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/CopyAndPaste/lexical/ListsCopyAndPaste.spec.mjs @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. * */ + import { moveLeft, moveToLineBeginning, @@ -23,6 +24,7 @@ import { IS_WINDOWS, pasteFromClipboard, test, + withExclusiveClipboardAccess, } from '../../../utils/index.mjs'; test.describe('Lists CopyAndPaste', () => { @@ -71,40 +73,42 @@ test.describe('Lists CopyAndPaste', () => { focusPath: [0, 2, 0, 0], }); - // Copy the partial list item and paragraph - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + // Copy the partial list item and paragraph + const clipboard = await copyToClipboard(page); - // Select all and remove content - await selectAll(page); - await page.keyboard.press('Backspace'); - await page.keyboard.press('Backspace'); + // Select all and remove content + await selectAll(page); + await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); - await assertHTML( - page, - html` -


- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0], - focusOffset: 0, - focusPath: [0], - }); + await assertHTML( + page, + html` +


+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0], + focusOffset: 0, + focusPath: [0], + }); - // Paste + // Paste - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - '

Some text.

', - ); - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 10, - focusPath: [1, 0, 0], + await assertHTML( + page, + '

Some text.

', + ); + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 10, + focusPath: [1, 0, 0], + }); }); }); @@ -178,109 +182,111 @@ test.describe('Lists CopyAndPaste', () => { focusPath: [0, 2, 0, 0], }); - // Copy the partial list item and paragraph - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + // Copy the partial list item and paragraph + const clipboard = await copyToClipboard(page); - // Select all and remove content - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); - if (!IS_WINDOWS && browserName === 'firefox') { + // Select all and remove content await page.keyboard.press('ArrowUp'); - } - await moveToLineEnd(page); - - await page.keyboard.down('Enter'); - - await assertHTML( - page, - html` - -

- Some text. -

- `, - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 1], - focusOffset: 0, - focusPath: [0, 1], - }); - - await pasteFromClipboard(page, clipboard); - - await assertHTML( - page, - html` - -

- Some text. -

- -

- Some text. -

- `, - ); - await assertSelection(page, { - anchorOffset: 10, - anchorPath: [1, 0, 0], - focusOffset: 10, - focusPath: [1, 0, 0], + await page.keyboard.press('ArrowUp'); + if (!IS_WINDOWS && browserName === 'firefox') { + await page.keyboard.press('ArrowUp'); + } + await moveToLineEnd(page); + + await page.keyboard.down('Enter'); + + await assertHTML( + page, + html` + +

+ Some text. +

+ `, + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 1], + focusOffset: 0, + focusPath: [0, 1], + }); + + await pasteFromClipboard(page, clipboard); + + await assertHTML( + page, + html` + +

+ Some text. +

+ +

+ Some text. +

+ `, + ); + await assertSelection(page, { + anchorOffset: 10, + anchorPath: [1, 0, 0], + focusOffset: 10, + focusPath: [1, 0, 0], + }); }); }, ); @@ -324,32 +330,34 @@ test.describe('Lists CopyAndPaste', () => { focusPath: [0, 3, 0, 0], }); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); - await assertHTML( - page, - '', - ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [0, 2], - focusOffset: 0, - focusPath: [0, 2], - }); + await assertHTML( + page, + '', + ); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [0, 2], + focusOffset: 0, + focusPath: [0, 2], + }); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - '', - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 3, 0, 0], - focusOffset: 4, - focusPath: [0, 3, 0, 0], + await assertHTML( + page, + '', + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 3, 0, 0], + focusOffset: 4, + focusPath: [0, 3, 0, 0], + }); }); }); @@ -413,136 +421,138 @@ test.describe('Lists CopyAndPaste', () => { `, ); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); - await page.keyboard.type('12345'); + await page.keyboard.type('12345'); - await assertHTML( - page, - html` - -

- 12345 -

- `, - ); + await assertHTML( + page, + html` + +

+ 12345 +

+ `, + ); - await page.keyboard.press('ArrowLeft'); - await page.keyboard.press('ArrowLeft'); - await selectCharacters(page, 'left', 1); + await page.keyboard.press('ArrowLeft'); + await page.keyboard.press('ArrowLeft'); + await selectCharacters(page, 'left', 1); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - html` - -

- 12 -

- -

- 45 -

- `, - ); + await assertHTML( + page, + html` + +

+ 12 +

+ +

+ 45 +

+ `, + ); + }); }); test('Copy and paste of list items and paste back into list on an existing item', async ({ @@ -584,32 +594,34 @@ test.describe('Lists CopyAndPaste', () => { focusPath: [0, 3, 0, 0], }); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowRight'); - await assertHTML( - page, - '', - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 3, 0, 0], - focusOffset: 4, - focusPath: [0, 3, 0, 0], - }); + await assertHTML( + page, + '', + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 3, 0, 0], + focusOffset: 4, + focusPath: [0, 3, 0, 0], + }); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - '', - ); - await assertSelection(page, { - anchorOffset: 4, - anchorPath: [0, 5, 0, 0], - focusOffset: 4, - focusPath: [0, 5, 0, 0], + await assertHTML( + page, + '', + ); + await assertSelection(page, { + anchorOffset: 4, + anchorPath: [0, 5, 0, 0], + focusOffset: 4, + focusPath: [0, 5, 0, 0], + }); }); }); @@ -626,50 +638,52 @@ test.describe('Lists CopyAndPaste', () => { await selectAll(page); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + await page.keyboard.press('Enter'); + await page.keyboard.type('five'); - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('ArrowUp'); - await moveToLineBeginning(page); - await page.keyboard.press('ArrowDown'); - await moveToLineEnd(page); - await moveLeft(page, 2); + await moveToLineBeginning(page); + await page.keyboard.press('ArrowDown'); + await moveToLineEnd(page); + await moveLeft(page, 2); - await assertHTML( - page, - '', - ); - await assertSelection(page, { - anchorOffset: 2, - anchorPath: [0, 3, 0, 0], - focusOffset: 2, - focusPath: [0, 3, 0, 0], - }); + await assertHTML( + page, + '', + ); + await assertSelection(page, { + anchorOffset: 2, + anchorPath: [0, 3, 0, 0], + focusOffset: 2, + focusPath: [0, 3, 0, 0], + }); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - '

Worldur

', - ); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [1, 0, 0], - focusOffset: 5, - focusPath: [1, 0, 0], + await assertHTML( + page, + '

Worldur

', + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [1, 0, 0], + focusOffset: 5, + focusPath: [1, 0, 0], + }); }); }); @@ -687,45 +701,47 @@ test.describe('Lists CopyAndPaste', () => { await selectAll(page); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await page.keyboard.press('Backspace'); + await page.keyboard.press('Backspace'); - await page.keyboard.type('- one'); - await page.keyboard.press('Enter'); - await page.keyboard.type('two'); - await page.keyboard.press('Enter'); - await page.keyboard.type('three'); - await page.keyboard.press('Enter'); - await page.keyboard.type('four'); - await page.keyboard.press('Enter'); - await page.keyboard.type('five'); - await page.keyboard.press('Enter'); + await page.keyboard.type('- one'); + await page.keyboard.press('Enter'); + await page.keyboard.type('two'); + await page.keyboard.press('Enter'); + await page.keyboard.type('three'); + await page.keyboard.press('Enter'); + await page.keyboard.type('four'); + await page.keyboard.press('Enter'); + await page.keyboard.type('five'); + await page.keyboard.press('Enter'); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - '

World

', - ); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [1, 0, 0], - focusOffset: 5, - focusPath: [1, 0, 0], - }); + await assertHTML( + page, + '

World

', + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [1, 0, 0], + focusOffset: 5, + focusPath: [1, 0, 0], + }); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - '

WorldHello

World

', - ); - await assertSelection(page, { - anchorOffset: 5, - anchorPath: [2, 0, 0], - focusOffset: 5, - focusPath: [2, 0, 0], + await assertHTML( + page, + '

WorldHello

World

', + ); + await assertSelection(page, { + anchorOffset: 5, + anchorPath: [2, 0, 0], + focusOffset: 5, + focusPath: [2, 0, 0], + }); }); }); }); diff --git a/packages/lexical-playground/__tests__/e2e/HorizontalRule.spec.mjs b/packages/lexical-playground/__tests__/e2e/HorizontalRule.spec.mjs index 37ea391f7f9..25805886a39 100644 --- a/packages/lexical-playground/__tests__/e2e/HorizontalRule.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/HorizontalRule.spec.mjs @@ -23,6 +23,7 @@ import { selectFromInsertDropdown, test, waitForSelector, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; test.describe('HorizontalRule', () => { @@ -307,37 +308,39 @@ test.describe('HorizontalRule', () => { // Select all the text await selectAll(page); - // Copy all the text - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + // Copy all the text + const clipboard = await copyToClipboard(page); - // Delete content - await page.keyboard.press('Backspace'); + // Delete content + await page.keyboard.press('Backspace'); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - html` -


-
-


- `, - ); + await assertHTML( + page, + html` +


+
+


+ `, + ); - await assertSelection(page, { - anchorOffset: 0, - anchorPath: [2], - focusOffset: 0, - focusPath: [2], - }); + await assertSelection(page, { + anchorOffset: 0, + anchorPath: [2], + focusOffset: 0, + focusPath: [2], + }); - await page.keyboard.press('ArrowUp'); - await page.keyboard.press('Backspace'); + await page.keyboard.press('ArrowUp'); + await page.keyboard.press('Backspace'); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); + }); await assertHTML( page, diff --git a/packages/lexical-playground/__tests__/e2e/Links.spec.mjs b/packages/lexical-playground/__tests__/e2e/Links.spec.mjs index d0db3462c25..33d4d5ea89d 100644 --- a/packages/lexical-playground/__tests__/e2e/Links.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Links.spec.mjs @@ -30,6 +30,7 @@ import { keyUpCtrlOrMeta, pasteFromClipboard, test, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; test.beforeEach(({isPlainText}) => { @@ -2093,11 +2094,13 @@ test.describe('Link attributes', () => { await page.keyboard.type('Hello awesome'); await focusEditor(page); await selectAll(page); - await context.grantPermissions(['clipboard-read', 'clipboard-write']); - await page.evaluate(() => - navigator.clipboard.writeText('https://facebook.com'), - ); - await paste(page); + await withExclusiveClipboardAccess(async () => { + await context.grantPermissions(['clipboard-read', 'clipboard-write']); + await page.evaluate(() => + navigator.clipboard.writeText('https://facebook.com'), + ); + await paste(page); + }); await assertHTML( page, html` diff --git a/packages/lexical-playground/__tests__/e2e/List.spec.mjs b/packages/lexical-playground/__tests__/e2e/List.spec.mjs index 26768b19d4f..8c1703c15c9 100644 --- a/packages/lexical-playground/__tests__/e2e/List.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/List.spec.mjs @@ -35,6 +35,7 @@ import { selectFromFormatDropdown, test, waitForSelector, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; async function toggleBulletList(page) { @@ -85,13 +86,15 @@ test.describe.parallel('Nested List', () => { await moveRight(page, 6); await selectCharacters(page, 'right', 11); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - await moveToEditorEnd(page); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); + await moveToEditorEnd(page); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); + }); await assertHTML( page, diff --git a/packages/lexical-playground/__tests__/e2e/Share.spec.mjs b/packages/lexical-playground/__tests__/e2e/Share.spec.mjs index fa5228bcddf..ed3e249d7bb 100644 --- a/packages/lexical-playground/__tests__/e2e/Share.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Share.spec.mjs @@ -15,6 +15,7 @@ import { html, initialize, test, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; test.use({ @@ -46,24 +47,26 @@ test.describe('Share', () => { await page.keyboard.type('foo'); await assertHTML(page, fooHTML); - if (browserName === 'chromium') { - await page - .context() - .grantPermissions(['clipboard-read', 'clipboard-write']); - } - expect(page.url()).not.toMatch(/#doc=/); - await click(page, '.action-button.share'); - await page.getByRole('alert').getByText('URL copied to clipboard'); - const fooUrl = page.url(); - expect(fooUrl).toMatch(/#doc=/); - if (browserName !== 'webkit') { - expect(await page.evaluate('navigator.clipboard.readText()')).toEqual( - fooUrl, - ); - } - if (browserName === 'chromium') { - await page.context().clearPermissions(); - } + await withExclusiveClipboardAccess(async () => { + if (browserName === 'chromium') { + await page + .context() + .grantPermissions(['clipboard-read', 'clipboard-write']); + } + expect(page.url()).not.toMatch(/#doc=/); + await click(page, '.action-button.share'); + await page.getByRole('alert').getByText('URL copied to clipboard'); + const fooUrl = page.url(); + expect(fooUrl).toMatch(/#doc=/); + if (browserName !== 'webkit') { + expect(await page.evaluate('navigator.clipboard.readText()')).toEqual( + fooUrl, + ); + } + if (browserName === 'chromium') { + await page.context().clearPermissions(); + } + }); await focusEditor(page); await page.keyboard.type('bar'); await assertHTML( diff --git a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs index fa5d8574235..adbfbc73be2 100644 --- a/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs +++ b/packages/lexical-playground/__tests__/e2e/Tables.spec.mjs @@ -51,6 +51,7 @@ import { toggleColumnHeader, unmergeTableCell, waitForSelector, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; async function fillTablePartiallyWithText(page) { @@ -1246,14 +1247,16 @@ test.describe.parallel('Tables', () => { false, ); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - // For some reason you need to click the paragraph twice for this to pass - // on Collab Firefox. - await click(page, 'div.ContentEditable__root > p:first-of-type'); - await click(page, 'div.ContentEditable__root > p:first-of-type'); + // For some reason you need to click the paragraph twice for this to pass + // on Collab Firefox. + await click(page, 'div.ContentEditable__root > p:first-of-type'); + await click(page, 'div.ContentEditable__root > p:first-of-type'); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); + }); // Check that the character styles are applied. await assertHTML( @@ -3821,27 +3824,29 @@ test.describe.parallel('Tables', () => { await page.keyboard.type('Hello'); await selectCharacters(page, 'left', 'Hello'.length); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - // move caret to the first position of the editor - await click(page, '.PlaygroundEditorTheme__paragraph'); + // move caret to the first position of the editor + await click(page, '.PlaygroundEditorTheme__paragraph'); - // move caret to the table cell (2,2) - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('ArrowDown'); - await page.keyboard.press('ArrowRight'); - await page.keyboard.press('ArrowRight'); + // move caret to the table cell (2,2) + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowDown'); + await page.keyboard.press('ArrowRight'); + await page.keyboard.press('ArrowRight'); - await pasteFromClipboard(page, clipboard); - await pasteFromClipboard(page, clipboard); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); + await page.keyboard.press('Enter'); - await pasteFromClipboard(page, clipboard); + await pasteFromClipboard(page, clipboard); + }); await assertHTML( page, diff --git a/packages/lexical-playground/__tests__/regression/1384-insert-nodes.spec.mjs b/packages/lexical-playground/__tests__/regression/1384-insert-nodes.spec.mjs index 2c37f27c01d..389ae51f9d6 100644 --- a/packages/lexical-playground/__tests__/regression/1384-insert-nodes.spec.mjs +++ b/packages/lexical-playground/__tests__/regression/1384-insert-nodes.spec.mjs @@ -14,6 +14,7 @@ import { initialize, pasteFromClipboard, test, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; test.describe('Regression test #1384', () => { @@ -34,9 +35,11 @@ test.describe('Regression test #1384', () => { await page.keyboard.press('ArrowUp'); await page.keyboard.press('ArrowLeft'); await selectCharacters(page, 'left', 8); - const clipboard = await copyToClipboard(page); - await page.keyboard.press('ArrowLeft'); - await pasteFromClipboard(page, clipboard); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); + await page.keyboard.press('ArrowLeft'); + await pasteFromClipboard(page, clipboard); + }); await assertHTML( page, `alert(1)alert(1);
alert(2);
alert(3);
`, diff --git a/packages/lexical-playground/__tests__/regression/5251-paste-into-inline-element.spec.mjs b/packages/lexical-playground/__tests__/regression/5251-paste-into-inline-element.spec.mjs index eb4df543bf7..730b113b471 100644 --- a/packages/lexical-playground/__tests__/regression/5251-paste-into-inline-element.spec.mjs +++ b/packages/lexical-playground/__tests__/regression/5251-paste-into-inline-element.spec.mjs @@ -23,6 +23,7 @@ import { pasteFromClipboard, pressToggleBold, test, + withExclusiveClipboardAccess, } from '../utils/index.mjs'; test.describe('Regression test #5251', () => { @@ -54,61 +55,63 @@ test.describe('Regression test #5251', () => { // Copy "Hello bold" await moveToLineBeginning(page); await selectCharacters(page, 'right', 'Hello bold'.length); - const clipboard = await copyToClipboard(page); + await withExclusiveClipboardAccess(async () => { + const clipboard = await copyToClipboard(page); - // Drop "bold" - await page.keyboard.press('ArrowLeft'); - await moveToNextWord(page); - await selectCharacters(page, 'right', 'bold '.length); - await page.keyboard.press('Delete'); + // Drop "bold" + await page.keyboard.press('ArrowLeft'); + await moveToNextWord(page); + await selectCharacters(page, 'right', 'bold '.length); + await page.keyboard.press('Delete'); - // Check our current state - await assertHTML( - page, - html` -

- Hello - - World - -

- `, - ); + // Check our current state + await assertHTML( + page, + html` +

+ Hello + + World + +

+ `, + ); - // Replace "Wor" with the contents of the clipboard - if (!IS_WINDOWS) { - await page.keyboard.press('ArrowRight'); - } - await selectCharacters(page, 'right', 'Wor'.length); - await pasteFromClipboard(page, clipboard); + // Replace "Wor" with the contents of the clipboard + if (!IS_WINDOWS) { + await page.keyboard.press('ArrowRight'); + } + await selectCharacters(page, 'right', 'Wor'.length); + await pasteFromClipboard(page, clipboard); - await assertHTML( - page, - html` -

- Hello Hello - - bold - - - ld - -

- `, - ); + await assertHTML( + page, + html` +

+ Hello Hello + + bold + + + ld + +

+ `, + ); + }); }); }); diff --git a/packages/lexical-playground/__tests__/utils/index.mjs b/packages/lexical-playground/__tests__/utils/index.mjs index c2f4d921ff9..2b461f5fb05 100644 --- a/packages/lexical-playground/__tests__/utils/index.mjs +++ b/packages/lexical-playground/__tests__/utils/index.mjs @@ -10,6 +10,7 @@ import {expect, test as base} from '@playwright/test'; import * as glob from 'glob'; import {randomUUID} from 'node:crypto'; import prettier from 'prettier'; +import * as lockfile from 'proper-lockfile'; import {URLSearchParams} from 'url'; import {selectAll} from '../keyboardShortcuts/index.mjs'; @@ -208,6 +209,24 @@ async function assertHTMLOnPageOrFrame( }).toPass({intervals: [100, 250, 500], timeout: 5000}); } +/** + * @function + * @template T + * @param {() => T | Promise} + * @returns {Promise} + */ +export async function withExclusiveClipboardAccess(f) { + const release = await lockfile.lock('.', { + lockfilePath: '.playwright-clipboard.lock', + retries: 5, + }); + try { + return f(); + } finally { + await release(); + } +} + /** * @param {import('@playwright/test').Page} page */