test: add mutex to clipboard copy & paste operation to reduce flaky (#11884)

This commit is contained in:
Brooooooklyn
2025-04-22 11:02:29 +00:00
parent 4fd468c46f
commit bfd3c64615
10 changed files with 32 additions and 28 deletions

View File

@@ -7,7 +7,6 @@ import {
} from '@affine-test/kit/utils/editor'; } from '@affine-test/kit/utils/editor';
import { importImage } from '@affine-test/kit/utils/image'; import { importImage } from '@affine-test/kit/utils/image';
import { import {
pasteByKeyboard,
selectAllByKeyboard, selectAllByKeyboard,
writeTextToClipboard, writeTextToClipboard,
} from '@affine-test/kit/utils/keyboard'; } from '@affine-test/kit/utils/keyboard';
@@ -253,7 +252,6 @@ test('should show toolbar when inline link is preceded by image or surface-ref',
const url = new URL(page.url()); const url = new URL(page.url());
await writeTextToClipboard(page, url.toString()); await writeTextToClipboard(page, url.toString());
await pasteByKeyboard(page);
const toolbar = locateToolbar(page); const toolbar = locateToolbar(page);

View File

@@ -64,7 +64,6 @@ test('not allowed to switch to embed view when linking to the same document', as
const url0 = new URL(page.url()); const url0 = new URL(page.url());
await writeTextToClipboard(page, url0.toString()); await writeTextToClipboard(page, url0.toString());
await pasteByKeyboard(page);
const { toolbar, switchViewBtn, inlineViewBtn, cardViewBtn, embedViewBtn } = const { toolbar, switchViewBtn, inlineViewBtn, cardViewBtn, embedViewBtn } =
toolbarButtons(page); toolbarButtons(page);
@@ -296,7 +295,6 @@ test('allow switching to embed view when linking to the other document with mode
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
await writeTextToClipboard(page, url.toString()); await writeTextToClipboard(page, url.toString());
await pasteByKeyboard(page);
// Inline // Inline
await inlineLink.hover(); await inlineLink.hover();
@@ -516,9 +514,8 @@ test('the viewport should be fit when the linked document is with edgeless mode'
// move viewport // move viewport
const { x, y } = noteBoundingBox; const { x, y } = noteBoundingBox;
await page.mouse.click(x - 10, y - 10); await page.mouse.click(x - 10, y + 100);
await page.keyboard.down('Space'); await page.keyboard.down('Space');
await page.waitForTimeout(50);
await page.mouse.down(); await page.mouse.down();
await page.mouse.move(x + 1000, y); await page.mouse.move(x + 1000, y);
await page.mouse.up(); await page.mouse.up();
@@ -538,7 +535,6 @@ test('the viewport should be fit when the linked document is with edgeless mode'
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
await writeTextToClipboard(page, url.toString()); await writeTextToClipboard(page, url.toString());
await pasteByKeyboard(page);
// Inline // Inline
await page.locator('affine-reference').hover(); await page.locator('affine-reference').hover();
@@ -608,7 +604,7 @@ test('should show edgeless content when switching card view of linked mode doc i
await clickEdgelessModeButton(page); await clickEdgelessModeButton(page);
await page.mouse.move(x, y); await page.mouse.move(x, y);
await writeTextToClipboard(page, url.toString()); await writeTextToClipboard(page, url.toString(), false);
// Inline // Inline
const embed = page.locator('affine-embed-edgeless-linked-doc-block'); const embed = page.locator('affine-embed-edgeless-linked-doc-block');
@@ -1116,7 +1112,6 @@ test('should show full email address', async ({ page }) => {
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
await writeTextToClipboard(page, 'dev@affine.pro'); await writeTextToClipboard(page, 'dev@affine.pro');
await pasteByKeyboard(page);
const inlineLink = page.locator('affine-link'); const inlineLink = page.locator('affine-link');
@@ -1140,7 +1135,6 @@ test('should not show view toggle button when protocol of link is not http(s)',
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
await writeTextToClipboard(page, 'ftp://affine.pro/blocksuite.pdf'); await writeTextToClipboard(page, 'ftp://affine.pro/blocksuite.pdf');
await pasteByKeyboard(page);
const inlineLink = page.locator('affine-link'); const inlineLink = page.locator('affine-link');

View File

@@ -515,8 +515,6 @@ test('can paste a doc link to create link reference', async ({ page }) => {
// paste the url // paste the url
await writeTextToClipboard(page, url); await writeTextToClipboard(page, url);
// check the link reference
await page.waitForTimeout(500);
await expect( await expect(
page.locator('affine-reference:has-text("Getting Started")') page.locator('affine-reference:has-text("Getting Started")')
).toBeVisible(); ).toBeVisible();
@@ -524,8 +522,6 @@ test('can paste a doc link to create link reference', async ({ page }) => {
// can ctrl-z to revert to normal link // can ctrl-z to revert to normal link
await page.keyboard.press('ControlOrMeta+z'); await page.keyboard.press('ControlOrMeta+z');
// check the normal link
await page.waitForTimeout(500);
await expect(page.locator(`affine-link:has-text("${url}")`)).toBeVisible(); await expect(page.locator(`affine-link:has-text("${url}")`)).toBeVisible();
}); });

View File

@@ -15,6 +15,7 @@
"@blocksuite/affine": "workspace:*", "@blocksuite/affine": "workspace:*",
"@node-rs/argon2": "^2.0.2", "@node-rs/argon2": "^2.0.2",
"@playwright/test": "=1.51.1", "@playwright/test": "=1.51.1",
"@toeverything/infra": "workspace:*",
"express": "^5.0.0", "express": "^5.0.0",
"http-proxy-middleware": "^3.0.3" "http-proxy-middleware": "^3.0.3"
}, },

View File

@@ -1,4 +1,5 @@
import type { Page } from '@playwright/test'; import { type Page } from '@playwright/test';
import { AsyncLock } from '@toeverything/infra/utils';
const IS_MAC = process.platform === 'darwin'; const IS_MAC = process.platform === 'darwin';
@@ -102,13 +103,18 @@ export async function undoByKeyboard(page: Page) {
await keyUpCtrlOrMeta(page); await keyUpCtrlOrMeta(page);
} }
export async function writeTextToClipboard(page: Page, text: string) { const clipboardMutex = new AsyncLock();
export async function writeTextToClipboard(
page: Page,
text: string,
paste = true
) {
using _release = await clipboardMutex.acquire();
// paste the url // paste the url
await page.evaluate( await page.evaluate(
async ([text]) => { async ([text]) => {
const clipData = { navigator.clipboard.writeText('');
'text/plain': text,
};
const e = new ClipboardEvent('paste', { const e = new ClipboardEvent('paste', {
clipboardData: new DataTransfer(), clipboardData: new DataTransfer(),
}); });
@@ -116,11 +122,14 @@ export async function writeTextToClipboard(page: Page, text: string) {
writable: false, writable: false,
value: document, value: document,
}); });
Object.entries(clipData).forEach(([key, value]) => { e.clipboardData!.setData('text/plain', text);
e.clipboardData?.setData(key, value);
});
document.dispatchEvent(e); document.dispatchEvent(e);
}, },
[text] [text]
); );
if (paste) {
await keyDownCtrlOrMeta(page);
await page.keyboard.press('v', { delay: 50 });
await keyUpCtrlOrMeta(page);
}
} }

View File

@@ -60,8 +60,7 @@ export async function type(page: Page, content: string, delay = 50) {
} }
export const createLinkedPage = async (page: Page, pageName?: string) => { export const createLinkedPage = async (page: Page, pageName?: string) => {
// fixme: workaround for @ popover not showing up when editor is not ready await waitForEditorLoad(page);
await page.waitForTimeout(500);
await page.keyboard.type('@', { delay: 50 }); await page.keyboard.type('@', { delay: 50 });
const linkedPagePopover = page.locator('.linked-doc-popover'); const linkedPagePopover = page.locator('.linked-doc-popover');
await expect(linkedPagePopover).toBeVisible(); await expect(linkedPagePopover).toBeVisible();

View File

@@ -8,6 +8,7 @@
"include": ["./src"], "include": ["./src"],
"references": [ "references": [
{ "path": "../../tools/utils" }, { "path": "../../tools/utils" },
{ "path": "../../blocksuite/affine/all" } { "path": "../../blocksuite/affine/all" },
{ "path": "../../packages/common/infra" }
] ]
} }

View File

@@ -85,11 +85,12 @@ function getBundleConfigs(pkg: Package) {
const IN_CI = !!process.env.CI; const IN_CI = !!process.env.CI;
const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error'; const httpProxyMiddlewareLogLevel = IN_CI ? 'silent' : 'error';
const defaultDevServerConfig = { const defaultDevServerConfig: DevServerConfiguration = {
host: '0.0.0.0', host: '0.0.0.0',
allowedHosts: 'all', allowedHosts: 'all',
hot: false, hot: false,
liveReload: true, liveReload: true,
compress: !process.env.CI,
client: { client: {
overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined, overlay: process.env.DISABLE_DEV_OVERLAY === 'true' ? false : undefined,
logging: process.env.CI ? 'none' : 'error', logging: process.env.CI ? 'none' : 'error',
@@ -124,7 +125,7 @@ const defaultDevServerConfig = {
logLevel: httpProxyMiddlewareLogLevel, logLevel: httpProxyMiddlewareLogLevel,
}, },
], ],
} as DevServerConfiguration; };
export class BundleCommand extends PackageCommand { export class BundleCommand extends PackageCommand {
static override paths = [['bundle'], ['webpack'], ['pack'], ['bun']]; static override paths = [['bundle'], ['webpack'], ['pack'], ['bun']];

View File

@@ -1209,7 +1209,11 @@ export const PackageList = [
{ {
location: 'tests/kit', location: 'tests/kit',
name: '@affine-test/kit', name: '@affine-test/kit',
workspaceDependencies: ['tools/utils', 'blocksuite/affine/all'], workspaceDependencies: [
'tools/utils',
'blocksuite/affine/all',
'packages/common/infra',
],
}, },
{ {
location: 'tools/@types/build-config', location: 'tools/@types/build-config',

View File

@@ -101,6 +101,7 @@ __metadata:
"@blocksuite/affine": "workspace:*" "@blocksuite/affine": "workspace:*"
"@node-rs/argon2": "npm:^2.0.2" "@node-rs/argon2": "npm:^2.0.2"
"@playwright/test": "npm:=1.51.1" "@playwright/test": "npm:=1.51.1"
"@toeverything/infra": "workspace:*"
express: "npm:^5.0.0" express: "npm:^5.0.0"
http-proxy-middleware: "npm:^3.0.3" http-proxy-middleware: "npm:^3.0.3"
peerDependencies: peerDependencies: