From 3f81b2a48f1ce6320b1271802cc38548af9c364d Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 18 Sep 2026 15:57:22 +0800 Subject: [PATCH 1/3] test(vtable): cover Firefox paste event fallback Protect synchronous paste data when the async Clipboard API is unavailable. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --- .../vtable/__tests__/clipboard-paste.test.ts | 56 ++++++++++++ .../debug/issue-4699-firefox-paste.ts | 85 +++++++++++++++++++ packages/vtable/examples/menu.ts | 4 + 3 files changed, 145 insertions(+) create mode 100644 packages/vtable/__tests__/clipboard-paste.test.ts create mode 100644 packages/vtable/examples/debug/issue-4699-firefox-paste.ts diff --git a/packages/vtable/__tests__/clipboard-paste.test.ts b/packages/vtable/__tests__/clipboard-paste.test.ts new file mode 100644 index 000000000..d599fba70 --- /dev/null +++ b/packages/vtable/__tests__/clipboard-paste.test.ts @@ -0,0 +1,56 @@ +// @ts-nocheck +import { ListTable, TABLE_EVENT_TYPE } from '../src'; +import { createDiv, removeDom } from './dom'; + +global.__VERSION__ = 'none'; + +describe('clipboard paste', () => { + test('uses paste event data before the asynchronous Clipboard API', async () => { + const container = createDiv(); + container.style.width = '600px'; + container.style.height = '400px'; + + const clipboardDescriptor = Object.getOwnPropertyDescriptor(navigator, 'clipboard'); + const read = jest.fn(() => Promise.reject(new Error('clipboard-read is unavailable'))); + Object.defineProperty(navigator, 'clipboard', { + configurable: true, + value: { read } + }); + + const table = new ListTable(container, { + records: [{ name: 'before' }], + columns: [{ field: 'name', title: 'Name', editor: '' }], + keyboardOptions: { + pasteValueToCell: true + } + }); + + try { + table.selectCell(0, table.columnHeaderLevelCount); + const pasted = new Promise(resolve => { + table.on(TABLE_EVENT_TYPE.PASTED_DATA, resolve); + }); + const pasteEvent = new Event('paste', { bubbles: true }); + Object.defineProperty(pasteEvent, 'clipboardData', { + value: { + types: ['text/plain'], + getData: (type: string) => (type === 'text/plain' ? 'pasted in Firefox' : '') + } + }); + + table.getElement().dispatchEvent(pasteEvent); + await pasted; + + expect(read).not.toHaveBeenCalled(); + expect(table.getCellOriginValue(0, table.columnHeaderLevelCount)).toBe('pasted in Firefox'); + } finally { + table.release(); + removeDom(container); + if (clipboardDescriptor) { + Object.defineProperty(navigator, 'clipboard', clipboardDescriptor); + } else { + Reflect.deleteProperty(navigator, 'clipboard'); + } + } + }); +}); diff --git a/packages/vtable/examples/debug/issue-4699-firefox-paste.ts b/packages/vtable/examples/debug/issue-4699-firefox-paste.ts new file mode 100644 index 000000000..875a7d12d --- /dev/null +++ b/packages/vtable/examples/debug/issue-4699-firefox-paste.ts @@ -0,0 +1,85 @@ +import * as VTable from '../../src'; +import { InputEditor } from '@visactor/vtable-editors'; + +const CONTAINER_ID = 'vTable'; +const PASTE_VALUE = 'Firefox paste works'; +const inputEditor = new InputEditor({}); +VTable.register.editor('issue4699-input', inputEditor); + +interface Issue4699Window extends Window { + tableInstance?: VTable.ListTable; + issue4699Run?: () => void; + BUGSERVER_SCREENSHOT?: () => void; +} + +const records = [ + { item: 'Alpha', result: 'Waiting' }, + { item: 'Beta', result: 'Waiting' }, + { item: 'Gamma', result: 'Waiting' } +]; + +const createPasteEvent = (text: string) => { + const event = new Event('paste', { bubbles: true }); + Object.defineProperty(event, 'clipboardData', { + value: { + types: ['text/plain'], + getData: (type: string) => (type === 'text/plain' ? text : '') + } + }); + return event; +}; + +export function createTable() { + const container = document.getElementById(CONTAINER_ID); + if (!container) { + throw new Error(`Missing #${CONTAINER_ID} container`); + } + const status = document.createElement('div'); + status.id = 'issue4699Status'; + status.style.cssText = [ + 'height: 36px', + 'line-height: 36px', + 'margin-bottom: 8px', + 'padding: 0 12px', + 'font: 600 14px/36px sans-serif', + 'background: #f5f5f5', + 'color: #595959' + ].join(';'); + status.textContent = 'READY | Select a Result cell and paste in Firefox'; + container.parentElement?.insertBefore(status, container); + + const tableInstance = new VTable.ListTable(container, { + records, + columns: [ + { field: 'item', title: 'Item', width: 180 }, + { field: 'result', title: 'Result', width: 260, editor: 'issue4699-input' } + ], + keyboardOptions: { + pasteValueToCell: true + }, + widthMode: 'standard', + defaultRowHeight: 44 + }); + + const targetRow = tableInstance.columnHeaderLevelCount; + const runCheck = () => { + tableInstance.selectCell(1, targetRow); + tableInstance.getElement().focus(); + tableInstance.getElement().dispatchEvent(createPasteEvent(PASTE_VALUE)); + }; + + tableInstance.on(VTable.TABLE_EVENT_TYPE.PASTED_DATA, () => { + requestAnimationFrame(() => { + const value = tableInstance.getCellOriginValue(1, targetRow); + const pass = value === PASTE_VALUE; + status.textContent = `${pass ? 'PASS' : 'FAIL'} | pasted value: ${String(value)}`; + status.style.background = pass ? '#f6ffed' : '#fff2f0'; + status.style.color = pass ? '#237804' : '#a8071a'; + (window as Issue4699Window).BUGSERVER_SCREENSHOT?.(); + }); + }); + + (window as Issue4699Window).tableInstance = tableInstance; + (window as Issue4699Window).issue4699Run = runCheck; + requestAnimationFrame(runCheck); +} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index b30a28fde..be58b3652 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -74,6 +74,10 @@ export const menus = [ path: 'debug', name: 'issue-3903-delete-all-bottom-frozen' }, + { + path: 'debug', + name: 'issue-4699-firefox-paste' + }, { path: 'debug', name: 'issue-3752-large-merge-auto-height' From ad3462dc39936fbfeb2c77cd1bfd9b848354710a Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 18 Sep 2026 17:00:09 +0800 Subject: [PATCH 2/3] test(vtable): run Firefox paste regression in Playwright Replace the synthetic Bugserver coverage with a Firefox browser test enforced by CI. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --- .github/workflows/unit-test.yml | 6 ++ .gitignore | 2 + common/config/rush/pnpm-lock.yaml | 43 +++++++++- .../e2e/clipboard-paste.firefox.spec.ts | 72 ++++++++++++++++ packages/vtable/e2e/fixtures/app.ts | 19 +++++ packages/vtable/e2e/fixtures/index.html | 15 ++++ packages/vtable/e2e/vite.config.ts | 15 ++++ .../debug/issue-4699-firefox-paste.ts | 85 ------------------- packages/vtable/examples/menu.ts | 4 - packages/vtable/package.json | 5 +- packages/vtable/playwright.config.ts | 24 ++++++ packages/vtable/tsconfig.eslint.json | 2 +- 12 files changed, 199 insertions(+), 93 deletions(-) create mode 100644 packages/vtable/e2e/clipboard-paste.firefox.spec.ts create mode 100644 packages/vtable/e2e/fixtures/app.ts create mode 100644 packages/vtable/e2e/fixtures/index.html create mode 100644 packages/vtable/e2e/vite.config.ts delete mode 100644 packages/vtable/examples/debug/issue-4699-firefox-paste.ts create mode 100644 packages/vtable/playwright.config.ts diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index e5705096e..874107748 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -36,3 +36,9 @@ jobs: run: node common/scripts/install-run-rush.js build -t @visactor/vtable-search - name: Test run: node common/scripts/install-run-rush.js test -t @visactor/vtable-search + - name: Install Playwright Firefox + working-directory: ./packages/vtable + run: npx playwright install firefox + - name: Test Firefox paste + working-directory: ./packages/vtable + run: node ../../common/scripts/install-run-rushx.js test:firefox diff --git a/.gitignore b/.gitignore index 83c3ca471..dc413e3d5 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,8 @@ stats.html report.html coverage/ +playwright-report/ +test-results/ *.local.ts diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 7ec0174aa..a968e6123 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -492,6 +492,9 @@ importers: '@internal/ts-config': specifier: workspace:* version: link:../../share/ts-config + '@playwright/test': + specifier: 1.55.1 + version: 1.55.1 '@resvg/resvg-js': specifier: ^2.5.0 version: 2.6.2 @@ -2132,7 +2135,7 @@ importers: version: 4.9.5 vitest: specifier: 0.30.1 - version: 0.30.1(jsdom@16.7.0)(less@4.1.3)(sass@1.43.5)(terser@5.17.1) + version: 0.30.1(jsdom@16.7.0)(less@4.1.3)(playwright@1.55.1)(sass@1.43.5)(terser@5.17.1) packages: @@ -2967,6 +2970,11 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@playwright/test@1.55.1': + resolution: {integrity: sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==} + engines: {node: '>=18'} + hasBin: true + '@remix-run/router@1.4.0': resolution: {integrity: sha512-BJ9SxXux8zAg991UmT8slpwpsd31K1dHHbD3Ba4VzD+liLQ4WAMSxQp2d2ZPRPfN0jN2NPRowcSSoM7lCaF08Q==} engines: {node: '>=14'} @@ -5472,6 +5480,11 @@ packages: os: [darwin] deprecated: Upgrade to fsevents v2 to mitigate potential security issues + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -7393,6 +7406,16 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + playwright-core@1.55.1: + resolution: {integrity: sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.55.1: + resolution: {integrity: sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==} + engines: {node: '>=18'} + hasBin: true + plugin-error@0.1.2: resolution: {integrity: sha512-WzZHcm4+GO34sjFMxQMqZbsz3xiNEgonCskQ9v+IroMmYgk/tas8dG+Hr2D6IbRPybZ12oWpzE/w3cGJ6FJzOw==} engines: {node: '>=0.10.0'} @@ -10587,6 +10610,10 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@playwright/test@1.55.1': + dependencies: + playwright: 1.55.1 + '@remix-run/router@1.4.0': {} '@resvg/resvg-js-android-arm-eabi@2.4.1': @@ -13496,6 +13523,9 @@ snapshots: nan: 2.26.2 optional: true + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -15929,6 +15959,14 @@ snapshots: mlly: 1.8.2 pathe: 2.0.3 + playwright-core@1.55.1: {} + + playwright@1.55.1: + dependencies: + playwright-core: 1.55.1 + optionalDependencies: + fsevents: 2.3.2 + plugin-error@0.1.2: dependencies: ansi-cyan: 0.1.1 @@ -17801,7 +17839,7 @@ snapshots: sass: 1.43.5 terser: 5.17.1 - vitest@0.30.1(jsdom@16.7.0)(less@4.1.3)(sass@1.43.5)(terser@5.17.1): + vitest@0.30.1(jsdom@16.7.0)(less@4.1.3)(playwright@1.55.1)(sass@1.43.5)(terser@5.17.1): dependencies: '@types/chai': 4.3.20 '@types/chai-subset': 1.3.6(@types/chai@4.3.20) @@ -17831,6 +17869,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: jsdom: 16.7.0 + playwright: 1.55.1 transitivePeerDependencies: - less - sass diff --git a/packages/vtable/e2e/clipboard-paste.firefox.spec.ts b/packages/vtable/e2e/clipboard-paste.firefox.spec.ts new file mode 100644 index 000000000..9a5be52c8 --- /dev/null +++ b/packages/vtable/e2e/clipboard-paste.firefox.spec.ts @@ -0,0 +1,72 @@ +import { expect, test } from '@playwright/test'; + +interface Issue4699Table { + columnHeaderLevelCount: number; + getCellOriginValue: (col: number, row: number) => unknown; + getElement: () => HTMLElement; + selectCell: (col: number, row: number) => void; +} + +interface Issue4699Window extends Window { + __issue4699ClipboardState: { + readCalls: number; + }; + __issue4699Table: Issue4699Table; +} + +test('pastes event clipboard data into a selected cell without an editor caret', async ({ page, browserName }) => { + expect(browserName).toBe('firefox'); + + await page.addInitScript(() => { + const state = { readCalls: 0 }; + Object.defineProperty(window, '__issue4699ClipboardState', { + configurable: true, + value: state + }); + Object.defineProperty(navigator, 'clipboard', { + configurable: true, + value: { + read: () => { + state.readCalls++; + return Promise.reject(new Error('Firefox clipboard-read is unavailable')); + } + } + }); + }); + + await page.goto('/'); + await page.waitForFunction(() => Boolean((window as unknown as Issue4699Window).__issue4699Table)); + + const activeElement = await page.evaluate(() => { + const testWindow = window as unknown as Issue4699Window; + const table = testWindow.__issue4699Table; + const row = table.columnHeaderLevelCount; + const clipboardData = new DataTransfer(); + clipboardData.setData('text/plain', 'pasted in Firefox'); + const pasteEvent = new ClipboardEvent('paste', { + bubbles: true, + cancelable: true + }); + Object.defineProperty(pasteEvent, 'clipboardData', { + value: clipboardData + }); + + table.selectCell(0, row); + table.getElement().focus(); + table.getElement().dispatchEvent(pasteEvent); + return document.activeElement?.tagName; + }); + + expect(activeElement).not.toMatch(/^(INPUT|TEXTAREA)$/); + await expect + .poll(() => + page.evaluate(() => { + const table = (window as unknown as Issue4699Window).__issue4699Table; + return table.getCellOriginValue(0, table.columnHeaderLevelCount); + }) + ) + .toBe('pasted in Firefox'); + await expect + .poll(() => page.evaluate(() => (window as unknown as Issue4699Window).__issue4699ClipboardState.readCalls)) + .toBe(0); +}); diff --git a/packages/vtable/e2e/fixtures/app.ts b/packages/vtable/e2e/fixtures/app.ts new file mode 100644 index 000000000..c8460e0ad --- /dev/null +++ b/packages/vtable/e2e/fixtures/app.ts @@ -0,0 +1,19 @@ +import { ListTable } from '../../src'; + +const container = document.getElementById('vtable'); +if (!container) { + throw new Error('Missing #vtable container'); +} + +const table = new ListTable(container, { + records: [{ value: 'before' }], + columns: [{ field: 'value', title: 'Value', editor: '' }], + keyboardOptions: { + pasteValueToCell: true + } +}); + +Object.defineProperty(window, '__issue4699Table', { + configurable: true, + value: table +}); diff --git a/packages/vtable/e2e/fixtures/index.html b/packages/vtable/e2e/fixtures/index.html new file mode 100644 index 000000000..8341bf9d1 --- /dev/null +++ b/packages/vtable/e2e/fixtures/index.html @@ -0,0 +1,15 @@ +
+
+
+ + + diff --git a/packages/vtable/e2e/vite.config.ts b/packages/vtable/e2e/vite.config.ts new file mode 100644 index 000000000..5aa218d0d --- /dev/null +++ b/packages/vtable/e2e/vite.config.ts @@ -0,0 +1,15 @@ +import path from 'path'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + define: { + __DEV__: true, + __VERSION__: JSON.stringify('e2e') + }, + resolve: { + alias: { + '@src': path.resolve(__dirname, '../src'), + '@vutils-extension': path.resolve(__dirname, '../src/vutil-extension-temp') + } + } +}); diff --git a/packages/vtable/examples/debug/issue-4699-firefox-paste.ts b/packages/vtable/examples/debug/issue-4699-firefox-paste.ts deleted file mode 100644 index 875a7d12d..000000000 --- a/packages/vtable/examples/debug/issue-4699-firefox-paste.ts +++ /dev/null @@ -1,85 +0,0 @@ -import * as VTable from '../../src'; -import { InputEditor } from '@visactor/vtable-editors'; - -const CONTAINER_ID = 'vTable'; -const PASTE_VALUE = 'Firefox paste works'; -const inputEditor = new InputEditor({}); -VTable.register.editor('issue4699-input', inputEditor); - -interface Issue4699Window extends Window { - tableInstance?: VTable.ListTable; - issue4699Run?: () => void; - BUGSERVER_SCREENSHOT?: () => void; -} - -const records = [ - { item: 'Alpha', result: 'Waiting' }, - { item: 'Beta', result: 'Waiting' }, - { item: 'Gamma', result: 'Waiting' } -]; - -const createPasteEvent = (text: string) => { - const event = new Event('paste', { bubbles: true }); - Object.defineProperty(event, 'clipboardData', { - value: { - types: ['text/plain'], - getData: (type: string) => (type === 'text/plain' ? text : '') - } - }); - return event; -}; - -export function createTable() { - const container = document.getElementById(CONTAINER_ID); - if (!container) { - throw new Error(`Missing #${CONTAINER_ID} container`); - } - const status = document.createElement('div'); - status.id = 'issue4699Status'; - status.style.cssText = [ - 'height: 36px', - 'line-height: 36px', - 'margin-bottom: 8px', - 'padding: 0 12px', - 'font: 600 14px/36px sans-serif', - 'background: #f5f5f5', - 'color: #595959' - ].join(';'); - status.textContent = 'READY | Select a Result cell and paste in Firefox'; - container.parentElement?.insertBefore(status, container); - - const tableInstance = new VTable.ListTable(container, { - records, - columns: [ - { field: 'item', title: 'Item', width: 180 }, - { field: 'result', title: 'Result', width: 260, editor: 'issue4699-input' } - ], - keyboardOptions: { - pasteValueToCell: true - }, - widthMode: 'standard', - defaultRowHeight: 44 - }); - - const targetRow = tableInstance.columnHeaderLevelCount; - const runCheck = () => { - tableInstance.selectCell(1, targetRow); - tableInstance.getElement().focus(); - tableInstance.getElement().dispatchEvent(createPasteEvent(PASTE_VALUE)); - }; - - tableInstance.on(VTable.TABLE_EVENT_TYPE.PASTED_DATA, () => { - requestAnimationFrame(() => { - const value = tableInstance.getCellOriginValue(1, targetRow); - const pass = value === PASTE_VALUE; - status.textContent = `${pass ? 'PASS' : 'FAIL'} | pasted value: ${String(value)}`; - status.style.background = pass ? '#f6ffed' : '#fff2f0'; - status.style.color = pass ? '#237804' : '#a8071a'; - (window as Issue4699Window).BUGSERVER_SCREENSHOT?.(); - }); - }); - - (window as Issue4699Window).tableInstance = tableInstance; - (window as Issue4699Window).issue4699Run = runCheck; - requestAnimationFrame(runCheck); -} diff --git a/packages/vtable/examples/menu.ts b/packages/vtable/examples/menu.ts index be58b3652..b30a28fde 100644 --- a/packages/vtable/examples/menu.ts +++ b/packages/vtable/examples/menu.ts @@ -74,10 +74,6 @@ export const menus = [ path: 'debug', name: 'issue-3903-delete-all-bottom-frozen' }, - { - path: 'debug', - name: 'issue-4699-firefox-paste' - }, { path: 'debug', name: 'issue-3752-large-merge-auto-height' diff --git a/packages/vtable/package.json b/packages/vtable/package.json index aea9cb8e3..1d94e3775 100644 --- a/packages/vtable/package.json +++ b/packages/vtable/package.json @@ -40,11 +40,13 @@ "demo": "vite serve examples", "site-demo": "vite serve site-demo", "compile": "tsc --noEmit", + "e2e:serve": "vite serve e2e/fixtures --config e2e/vite.config.ts --host 127.0.0.1 --port 3010", "eslint": "eslint --debug --fix src/", "build": "npm run fix-memory-limit && bundle --clean", "dev": "bundle --clean -f es -w", "start": "vite serve examples", "test": "jest --silent", + "test:firefox": "playwright test --config playwright.config.ts", "test-cov": "jest --coverage", "ci": "ts-node --transpileOnly --skipProject ./scripts/trigger-test.ts", "build:es": "bundle --clean -f es --ignorePostTasks", @@ -120,7 +122,8 @@ "d3-hexbin": "^0.2.2", "d3-hierarchy": "^3.1.1", "@resvg/resvg-js": "^2.5.0", - "pikaday": "1.8.2" + "pikaday": "1.8.2", + "@playwright/test": "1.55.1" }, "unpkg": "latest", "unpkgFiles": [ diff --git a/packages/vtable/playwright.config.ts b/packages/vtable/playwright.config.ts new file mode 100644 index 000000000..a9c629fb9 --- /dev/null +++ b/packages/vtable/playwright.config.ts @@ -0,0 +1,24 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + outputDir: './test-results', + reporter: 'list', + use: { + baseURL: 'http://127.0.0.1:3010', + trace: 'retain-on-failure' + }, + projects: [ + { + name: 'firefox', + use: { + ...devices['Desktop Firefox'] + } + } + ], + webServer: { + command: 'rushx e2e:serve', + url: 'http://127.0.0.1:3010', + reuseExistingServer: !process.env.CI + } +}); diff --git a/packages/vtable/tsconfig.eslint.json b/packages/vtable/tsconfig.eslint.json index cbfa15726..401bd61b0 100644 --- a/packages/vtable/tsconfig.eslint.json +++ b/packages/vtable/tsconfig.eslint.json @@ -14,6 +14,6 @@ ] } }, - "include": ["src", "__tests__", "examples", "site-demo"], + "include": ["src", "__tests__", "e2e", "examples", "site-demo", "playwright.config.ts"], "exclude": ["bugserver-config"] } From 07723a232a12f34279b782a744193223e33bc1f9 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 18 Sep 2026 17:54:21 +0800 Subject: [PATCH 3/3] test(vtable): harden Firefox paste regression fixture Keep the browser restriction explicit and make the fixture script path relative. Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com --- packages/vtable/e2e/clipboard-paste.firefox.spec.ts | 2 +- packages/vtable/e2e/fixtures/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/vtable/e2e/clipboard-paste.firefox.spec.ts b/packages/vtable/e2e/clipboard-paste.firefox.spec.ts index 9a5be52c8..cac53f266 100644 --- a/packages/vtable/e2e/clipboard-paste.firefox.spec.ts +++ b/packages/vtable/e2e/clipboard-paste.firefox.spec.ts @@ -15,7 +15,7 @@ interface Issue4699Window extends Window { } test('pastes event clipboard data into a selected cell without an editor caret', async ({ page, browserName }) => { - expect(browserName).toBe('firefox'); + test.skip(browserName !== 'firefox', 'Firefox-only clipboard regression'); await page.addInitScript(() => { const state = { readCalls: 0 }; diff --git a/packages/vtable/e2e/fixtures/index.html b/packages/vtable/e2e/fixtures/index.html index 8341bf9d1..fa8f8f2a0 100644 --- a/packages/vtable/e2e/fixtures/index.html +++ b/packages/vtable/e2e/fixtures/index.html @@ -1,7 +1,7 @@
- +