diff --git a/playwright b/playwright index fc998ca..a023917 160000 --- a/playwright +++ b/playwright @@ -1 +1 @@ -Subproject commit fc998ca8991625496e1d1fbc4ee02389bea6439f +Subproject commit a0239178dabe293a673cea8d8090e6d299c362e5 diff --git a/playwriter/src/on-mouse-action.test.ts b/playwriter/src/on-mouse-action.test.ts new file mode 100644 index 0000000..402058a --- /dev/null +++ b/playwriter/src/on-mouse-action.test.ts @@ -0,0 +1,139 @@ +/** + * Test for the page.onMouseAction callback added to the playwright fork. + * Verifies the callback fires for both explicit page.mouse.* calls + * and locator-initiated actions like page.locator().click(). + */ +import { chromium } from '@xmorse/playwright-core' +import type { MouseActionEvent } from '@xmorse/playwright-core' +import { describe, it, expect, beforeAll, afterAll } from 'vitest' +import { getCdpUrl } from './utils.js' +import { + setupTestContext, + cleanupTestContext, + getExtensionServiceWorker, + type TestContext, + safeCloseCDPBrowser, +} from './test-utils.js' +import './test-declarations.js' + +const TEST_PORT = 19994 + +describe('onMouseAction callback', () => { + let cleanup: (() => Promise) | null = null + let testCtx: TestContext | null = null + + beforeAll(async () => { + testCtx = await setupTestContext({ + port: TEST_PORT, + tempDirPrefix: 'pw-mouse-action-test-', + toggleExtension: true, + }) + }, 600000) + + afterAll(async () => { + await cleanupTestContext(testCtx, cleanup) + cleanup = null + testCtx = null + }) + + it('should fire onMouseAction for page.mouse.click()', async () => { + const browserContext = testCtx!.browserContext + const serviceWorker = await getExtensionServiceWorker(browserContext) + + const page = await browserContext.newPage() + await page.goto('data:text/html,') + await page.bringToFront() + + await serviceWorker.evaluate(async () => { + await (globalThis as any).toggleExtensionForActiveTab() + }) + await new Promise((r) => { setTimeout(r, 200) }) + + const directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })) + const contexts = directBrowser.contexts() + const pages = contexts[0].pages() + const targetPage = pages.find((p) => p.url().startsWith('data:')) + expect(targetPage).toBeDefined() + + const events: MouseActionEvent[] = [] + targetPage!.onMouseAction = async (event) => { + events.push({ ...event }) + } + + await targetPage!.mouse.click(100, 100) + + // click dispatches: move → down → up + const types = events.map((e) => e.type) + expect(types).toContain('move') + expect(types).toContain('down') + expect(types).toContain('up') + + // All events should have coordinates + for (const event of events) { + expect(typeof event.x).toBe('number') + expect(typeof event.y).toBe('number') + expect(typeof event.button).toBe('string') + } + + // The move event should have the target coordinates + const moveEvent = events.find((e) => e.type === 'move')! + expect(moveEvent.x).toBe(100) + expect(moveEvent.y).toBe(100) + + await safeCloseCDPBrowser(directBrowser) + }, 30000) + + it('should fire onMouseAction for locator.click()', async () => { + const browserContext = testCtx!.browserContext + + const directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })) + const contexts = directBrowser.contexts() + const pages = contexts[0].pages() + const targetPage = pages.find((p) => p.url().startsWith('data:')) + expect(targetPage).toBeDefined() + + const events: MouseActionEvent[] = [] + targetPage!.onMouseAction = async (event) => { + events.push({ ...event }) + } + + // locator.click() resolves coordinates server-side, then calls server Mouse + await targetPage!.locator('#btn').click() + + const types = events.map((e) => e.type) + expect(types).toContain('move') + expect(types).toContain('down') + expect(types).toContain('up') + + // The button center should be somewhere reasonable (not 0,0) + const moveEvent = events.find((e) => e.type === 'move')! + expect(moveEvent.x).toBeGreaterThan(0) + expect(moveEvent.y).toBeGreaterThan(0) + + await safeCloseCDPBrowser(directBrowser) + }, 30000) + + it('should not fire when onMouseAction is set to null', async () => { + const browserContext = testCtx!.browserContext + + const directBrowser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT })) + const contexts = directBrowser.contexts() + const pages = contexts[0].pages() + const targetPage = pages.find((p) => p.url().startsWith('data:')) + expect(targetPage).toBeDefined() + + const events: MouseActionEvent[] = [] + targetPage!.onMouseAction = async (event) => { + events.push({ ...event }) + } + + // Disable callback + targetPage!.onMouseAction = null + + await targetPage!.mouse.click(50, 50) + + expect(events).toHaveLength(0) + + await safeCloseCDPBrowser(directBrowser) + }, 30000) +})