diff options
Diffstat (limited to 'remote/test/puppeteer/test/src/mouse.spec.ts')
-rw-r--r-- | remote/test/puppeteer/test/src/mouse.spec.ts | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/remote/test/puppeteer/test/src/mouse.spec.ts b/remote/test/puppeteer/test/src/mouse.spec.ts new file mode 100644 index 0000000000..69229eb147 --- /dev/null +++ b/remote/test/puppeteer/test/src/mouse.spec.ts @@ -0,0 +1,472 @@ +/** + * @license + * Copyright 2018 Google Inc. + * SPDX-License-Identifier: Apache-2.0 + */ +import os from 'os'; + +import expect from 'expect'; +import {MouseButton} from 'puppeteer-core/internal/api/Input.js'; +import type {Page} from 'puppeteer-core/internal/api/Page.js'; +import type {KeyInput} from 'puppeteer-core/internal/common/USKeyboardLayout.js'; + +import {getTestState, setupTestBrowserHooks} from './mocha-utils.js'; + +interface ClickData { + type: string; + detail: number; + clientX: number; + clientY: number; + isTrusted: boolean; + button: number; + buttons: number; +} + +interface Dimensions { + x: number; + y: number; + width: number; + height: number; +} + +function dimensions(): Dimensions { + const rect = document.querySelector('textarea')!.getBoundingClientRect(); + return { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height, + }; +} + +describe('Mouse', function () { + setupTestBrowserHooks(); + + it('should click the document', async () => { + const {page} = await getTestState(); + + await page.evaluate(() => { + (globalThis as any).clickPromise = new Promise(resolve => { + document.addEventListener('click', event => { + resolve({ + type: event.type, + detail: event.detail, + clientX: event.clientX, + clientY: event.clientY, + isTrusted: event.isTrusted, + button: event.button, + }); + }); + }); + }); + await page.mouse.click(50, 60); + const event = await page.evaluate(() => { + return (globalThis as any).clickPromise; + }); + expect(event.type).toBe('click'); + expect(event.detail).toBe(1); + expect(event.clientX).toBe(50); + expect(event.clientY).toBe(60); + expect(event.isTrusted).toBe(true); + expect(event.button).toBe(0); + }); + it('should resize the textarea', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.PREFIX + '/input/textarea.html'); + const {x, y, width, height} = await page.evaluate(dimensions); + const mouse = page.mouse; + await mouse.move(x + width - 4, y + height - 4); + await mouse.down(); + await mouse.move(x + width + 100, y + height + 100); + await mouse.up(); + const newDimensions = await page.evaluate(dimensions); + expect(newDimensions.width).toBe(Math.round(width + 104)); + expect(newDimensions.height).toBe(Math.round(height + 104)); + }); + it('should select the text with mouse', async () => { + const {page, server} = await getTestState(); + + const text = + "This is the text that we are going to try to select. Let's see how it goes."; + + await page.goto(`${server.PREFIX}/input/textarea.html`); + await page.focus('textarea'); + await page.keyboard.type(text); + using handle = await page + .locator('textarea') + .filterHandle(async element => { + return await element.evaluate((element, text) => { + return element.value === text; + }, text); + }) + .waitHandle(); + const {x, y} = await page.evaluate(dimensions); + await page.mouse.move(x + 2, y + 2); + await page.mouse.down(); + await page.mouse.move(100, 100); + await page.mouse.up(); + expect( + await handle.evaluate(element => { + return element.value.substring( + element.selectionStart, + element.selectionEnd + ); + }) + ).toBe(text); + }); + it('should trigger hover state', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.hover('#button-6'); + expect( + await page.evaluate(() => { + return document.querySelector('button:hover')!.id; + }) + ).toBe('button-6'); + await page.hover('#button-2'); + expect( + await page.evaluate(() => { + return document.querySelector('button:hover')!.id; + }) + ).toBe('button-2'); + await page.hover('#button-91'); + expect( + await page.evaluate(() => { + return document.querySelector('button:hover')!.id; + }) + ).toBe('button-91'); + }); + it('should trigger hover state with removed window.Node', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.evaluate(() => { + // @ts-expect-error Expected. + return delete window.Node; + }); + await page.hover('#button-6'); + expect( + await page.evaluate(() => { + return document.querySelector('button:hover')!.id; + }) + ).toBe('button-6'); + }); + it('should set modifier keys on click', async () => { + const {page, server, isFirefox} = await getTestState(); + + await page.goto(server.PREFIX + '/input/scrollable.html'); + await page.evaluate(() => { + return document.querySelector('#button-3')!.addEventListener( + 'mousedown', + e => { + return ((globalThis as any).lastEvent = e); + }, + true + ); + }); + const modifiers = new Map<KeyInput, string>([ + ['Shift', 'shiftKey'], + ['Control', 'ctrlKey'], + ['Alt', 'altKey'], + ['Meta', 'metaKey'], + ]); + // In Firefox, the Meta modifier only exists on Mac + if (isFirefox && os.platform() !== 'darwin') { + modifiers.delete('Meta'); + } + for (const [modifier, key] of modifiers) { + await page.keyboard.down(modifier); + await page.click('#button-3'); + if ( + !(await page.evaluate((mod: string) => { + return (globalThis as any).lastEvent[mod]; + }, key)) + ) { + throw new Error(key + ' should be true'); + } + await page.keyboard.up(modifier); + } + await page.click('#button-3'); + for (const [modifier, key] of modifiers) { + if ( + await page.evaluate((mod: string) => { + return (globalThis as any).lastEvent[mod]; + }, key) + ) { + throw new Error(modifiers.get(modifier) + ' should be false'); + } + } + }); + it('should send mouse wheel events', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.PREFIX + '/input/wheel.html'); + using elem = (await page.$('div'))!; + const boundingBoxBefore = (await elem.boundingBox())!; + expect(boundingBoxBefore).toMatchObject({ + width: 115, + height: 115, + }); + + await page.mouse.move( + boundingBoxBefore.x + boundingBoxBefore.width / 2, + boundingBoxBefore.y + boundingBoxBefore.height / 2 + ); + + await page.mouse.wheel({deltaY: -100}); + const boundingBoxAfter = await elem.boundingBox(); + expect(boundingBoxAfter).toMatchObject({ + width: 230, + height: 230, + }); + }); + it('should tween mouse movement', async () => { + const {page} = await getTestState(); + + await page.mouse.move(100, 100); + await page.evaluate(() => { + (globalThis as any).result = []; + document.addEventListener('mousemove', event => { + (globalThis as any).result.push([event.clientX, event.clientY]); + }); + }); + await page.mouse.move(200, 300, {steps: 5}); + expect(await page.evaluate('result')).toEqual([ + [120, 140], + [140, 180], + [160, 220], + [180, 260], + [200, 300], + ]); + }); + // @see https://crbug.com/929806 + it('should work with mobile viewports and cross process navigations', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + await page.setViewport({width: 360, height: 640, isMobile: true}); + await page.goto(server.CROSS_PROCESS_PREFIX + '/mobile.html'); + await page.evaluate(() => { + document.addEventListener('click', event => { + (globalThis as any).result = {x: event.clientX, y: event.clientY}; + }); + }); + + await page.mouse.click(30, 40); + + expect(await page.evaluate('result')).toEqual({x: 30, y: 40}); + }); + it('should not throw if buttons are pressed twice', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + + await page.mouse.down(); + await page.mouse.down(); + }); + + interface AddMouseDataListenersOptions { + includeMove?: boolean; + } + + const addMouseDataListeners = ( + page: Page, + options: AddMouseDataListenersOptions = {} + ) => { + return page.evaluate(({includeMove}) => { + const clicks: ClickData[] = []; + const mouseEventListener = (event: MouseEvent) => { + clicks.push({ + type: event.type, + detail: event.detail, + clientX: event.clientX, + clientY: event.clientY, + isTrusted: event.isTrusted, + button: event.button, + buttons: event.buttons, + }); + }; + document.addEventListener('mousedown', mouseEventListener); + if (includeMove) { + document.addEventListener('mousemove', mouseEventListener); + } + document.addEventListener('mouseup', mouseEventListener); + document.addEventListener('click', mouseEventListener); + document.addEventListener('auxclick', mouseEventListener); + (window as unknown as {clicks: ClickData[]}).clicks = clicks; + }, options); + }; + + it('should not throw if clicking in parallel', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + await addMouseDataListeners(page); + + await Promise.all([page.mouse.click(0, 5), page.mouse.click(6, 10)]); + + const data = await page.evaluate(() => { + return (window as unknown as {clicks: ClickData[]}).clicks; + }); + const commonAttrs = { + isTrusted: true, + detail: 1, + clientY: 5, + clientX: 0, + button: 0, + }; + expect(data.splice(0, 3)).toMatchObject({ + 0: { + type: 'mousedown', + buttons: 1, + ...commonAttrs, + }, + 1: { + type: 'mouseup', + buttons: 0, + ...commonAttrs, + }, + 2: { + type: 'click', + buttons: 0, + ...commonAttrs, + }, + }); + Object.assign(commonAttrs, { + clientX: 6, + clientY: 10, + }); + expect(data).toMatchObject({ + 0: { + type: 'mousedown', + buttons: 1, + ...commonAttrs, + }, + 1: { + type: 'mouseup', + buttons: 0, + ...commonAttrs, + }, + 2: { + type: 'click', + buttons: 0, + ...commonAttrs, + }, + }); + }); + + it('should reset properly', async () => { + const {page, server, isChrome} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + + await page.mouse.move(5, 5); + await Promise.all([ + page.mouse.down({button: MouseButton.Left}), + page.mouse.down({button: MouseButton.Middle}), + page.mouse.down({button: MouseButton.Right}), + ]); + + await addMouseDataListeners(page, {includeMove: true}); + await page.mouse.reset(); + + const data = await page.evaluate(() => { + return (window as unknown as {clicks: ClickData[]}).clicks; + }); + const commonAttrs = { + isTrusted: true, + clientY: 5, + clientX: 5, + }; + + expect(data.slice(0, 2)).toMatchObject([ + { + ...commonAttrs, + button: 2, + buttons: 5, + detail: 1, + type: 'mouseup', + }, + { + ...commonAttrs, + button: 2, + buttons: 5, + detail: 1, + type: 'auxclick', + }, + ]); + // TODO(crbug/1485040): This should align with the firefox implementation. + if (isChrome) { + expect(data.slice(2)).toMatchObject([ + { + ...commonAttrs, + button: 1, + buttons: 1, + detail: 0, + type: 'mouseup', + }, + { + ...commonAttrs, + button: 0, + buttons: 0, + detail: 0, + type: 'mouseup', + }, + ]); + return; + } + expect(data.slice(2)).toMatchObject([ + { + ...commonAttrs, + button: 1, + buttons: 1, + detail: 1, + type: 'mouseup', + }, + { + ...commonAttrs, + button: 1, + buttons: 1, + detail: 1, + type: 'auxclick', + }, + { + ...commonAttrs, + button: 0, + buttons: 0, + detail: 1, + type: 'mouseup', + }, + { + ...commonAttrs, + button: 0, + buttons: 0, + detail: 1, + type: 'click', + }, + ]); + }); + + it('should evaluate before mouse event', async () => { + const {page, server} = await getTestState(); + + await page.goto(server.EMPTY_PAGE); + await page.goto(server.CROSS_PROCESS_PREFIX + '/input/button.html'); + + using button = await page.waitForSelector('button'); + + const point = await button!.clickablePoint(); + + const result = page.evaluate(() => { + return new Promise(resolve => { + document + .querySelector('button') + ?.addEventListener('click', resolve, {once: true}); + }); + }); + await page.mouse.click(point?.x, point?.y); + await result; + }); +}); |