feat: add page.onMouseAction callback + tests

Adds onMouseAction to playwright fork — an async callback on Page
that fires before every mouse action (move/down/up/wheel). Covers
both page.mouse.* and locator-initiated actions.

Test validates:
- Callback fires for page.mouse.click() with correct coordinates
- Callback fires for locator.click() with resolved element center
- Callback stops firing when set to null
This commit is contained in:
Tommy D. Rossi
2026-02-25 12:57:41 +01:00
parent 2d0f4ac456
commit d84cdba528
2 changed files with 140 additions and 1 deletions
+139
View File
@@ -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<void>) | 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,<html><body><button id="btn">Click me</button></body></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)
})