diff --git a/packages/browser/src/client/orchestrator.ts b/packages/browser/src/client/orchestrator.ts index 37054b21faaf..dbeef5d49e7a 100644 --- a/packages/browser/src/client/orchestrator.ts +++ b/packages/browser/src/client/orchestrator.ts @@ -259,9 +259,25 @@ async function setIframeViewport( if (ui) { await ui.setIframeViewport(width, height) } - else { + else if (getBrowserState().provider === 'webdriverio') { iframe.style.width = `${width}px` iframe.style.height = `${height}px` + iframe.parentElement?.setAttribute('data-scale', '1') + } + else { + const scale = Math.min( + 1, + iframe.parentElement!.parentElement!.clientWidth / width, + iframe.parentElement!.parentElement!.clientHeight / height, + ) + iframe.parentElement!.style.cssText = ` + width: ${width}px; + height: ${height}px; + transform: scale(${scale}); + transform-origin: left top; + ` + iframe.parentElement?.setAttribute('data-scale', String(scale)) + await new Promise(r => requestAnimationFrame(r)) } } diff --git a/packages/browser/src/client/tester/locators/playwright.ts b/packages/browser/src/client/tester/locators/playwright.ts index 0f7a51875652..344a8873ab46 100644 --- a/packages/browser/src/client/tester/locators/playwright.ts +++ b/packages/browser/src/client/tester/locators/playwright.ts @@ -9,7 +9,6 @@ import { getByTextSelector, getByTitleSelector, } from 'ivya' -import { getBrowserState } from '../../utils' import { getIframeScale, processTimeoutOptions } from '../utils' import { Locator, selectorEngine } from './index' @@ -106,8 +105,7 @@ class PlaywrightLocator extends Locator { } function processDragAndDropOptions(options_?: UserEventDragAndDropOptions) { - // only ui scales the iframe, so we need to adjust the position - if (!options_ || !getBrowserState().config.browser.ui) { + if (!options_) { return options_ } const options = options_ as NonNullable< @@ -123,8 +121,7 @@ function processDragAndDropOptions(options_?: UserEventDragAndDropOptions) { } function processHoverOptions(options_?: UserEventHoverOptions) { - // only ui scales the iframe, so we need to adjust the position - if (!options_ || !getBrowserState().config.browser.ui) { + if (!options_) { return options_ } const options = options_ as NonNullable< @@ -137,8 +134,7 @@ function processHoverOptions(options_?: UserEventHoverOptions) { } function processClickOptions(options_?: UserEventClickOptions) { - // only ui scales the iframe, so we need to adjust the position - if (!options_ || !getBrowserState().config.browser.ui) { + if (!options_) { return options_ } const options = options_ as NonNullable< diff --git a/packages/browser/src/client/tester/utils.ts b/packages/browser/src/client/tester/utils.ts index 19afe943726a..a3246b8ff748 100644 --- a/packages/browser/src/client/tester/utils.ts +++ b/packages/browser/src/client/tester/utils.ts @@ -167,7 +167,7 @@ export function processTimeoutOptions(options_?: } export function getIframeScale(): number { - const testerUi = window.parent.document.querySelector('#tester-ui') as HTMLElement | null + const testerUi = window.parent.document.querySelector(`iframe[data-vitest]`)?.parentElement if (!testerUi) { throw new Error(`Cannot find Tester element. This is a bug in Vitest. Please, open a new issue with reproduction.`) } diff --git a/test/browser/fixtures/locators/blog.test.tsx b/test/browser/fixtures/locators/blog.test.tsx index 65faef58c9b7..7a193f2e5083 100644 --- a/test/browser/fixtures/locators/blog.test.tsx +++ b/test/browser/fixtures/locators/blog.test.tsx @@ -18,6 +18,7 @@ test('renders blog posts', async () => { await expect.element(secondPost.getByRole('heading')).toHaveTextContent('qui est esse') + // TODO: click doesn't work on webdriverio when iframe is scaled await userEvent.click(secondPost.getByRole('button', { name: 'Delete' })) expect(screen.getByRole('listitem').all()).toHaveLength(3) diff --git a/test/browser/fixtures/viewport/basic.test.ts b/test/browser/fixtures/viewport/basic.test.ts new file mode 100644 index 000000000000..56746956d933 --- /dev/null +++ b/test/browser/fixtures/viewport/basic.test.ts @@ -0,0 +1,58 @@ +import { page, userEvent, server } from "@vitest/browser/context"; +import { expect, test } from "vitest"; + +test("drag and drop over large viewport", async () => { + // put boxes horizontally [1] [2] ... [30] + // then drag-and-drop from [1] to [30] + + const wrapper = document.createElement("div"); + wrapper.style.cssText = "display: flex; width: 3000px;"; + document.body.appendChild(wrapper); + + const events: { i: number; type: string }[] = []; + + for (let i = 1; i <= 30; i++) { + const el = document.createElement("div"); + el.textContent = `[${i}]`; + el.style.cssText = ` + flex: none; + width: 100px; + height: 100px; + border: 1px solid black; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + `; + el.draggable = true; + wrapper.append(el); + + el.addEventListener("dragstart", (ev) => { + ev.dataTransfer.effectAllowed = "move"; + events.push({ type: "dragstart", i }); + }); + el.addEventListener("dragover", (ev) => { + ev.preventDefault(); + ev.dataTransfer.dropEffect = "move"; + events.push({ type: "dragover", i }); + }); + el.addEventListener("drop", (ev) => { + ev.preventDefault(); + events.push({ type: "drop", i }); + }); + } + + // drag and drop only works reliably on playwright + if (server.provider !== 'playwright') { + return + } + + await userEvent.dragAndDrop(page.getByText("[1]"), page.getByText("[30]")); + + expect(events).toMatchObject( + expect.arrayContaining([ + { type: "dragstart", i: 1 }, + { type: "drop", i: 30 }, + ]), + ); +}); diff --git a/test/browser/fixtures/viewport/vitest.config.ts b/test/browser/fixtures/viewport/vitest.config.ts new file mode 100644 index 000000000000..fa44631c9269 --- /dev/null +++ b/test/browser/fixtures/viewport/vitest.config.ts @@ -0,0 +1,21 @@ +import { fileURLToPath } from 'node:url' +import { defineConfig } from 'vitest/config' +import { instances, provider } from '../../settings' + +// pnpm -C test/browser test-fixtures --root fixtures/viewport --browser.ui=false +// pnpm -C test/browser test-fixtures --root fixtures/viewport --browser.headless=true + +export default defineConfig({ + test: { + browser: { + enabled: true, + provider, + instances: instances.map(instance => ({ + ...instance, + viewport: { width: 3000, height: 400 } + })), + + }, + }, + cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)), +}) diff --git a/test/browser/specs/runner.test.ts b/test/browser/specs/runner.test.ts index 703f76e97fc5..cfcb9e5d7965 100644 --- a/test/browser/specs/runner.test.ts +++ b/test/browser/specs/runner.test.ts @@ -222,6 +222,16 @@ test('timeout settings', async () => { } }) +test('viewport', async () => { + const { stdout, stderr } = await runBrowserTests({ + root: './fixtures/viewport', + }) + expect(stderr).toBe('') + instances.forEach(({ browser }) => { + expect(stdout).toReportPassedTest('basic.test.ts', browser) + }) +}) + test.runIf(provider === 'playwright')('timeout hooks', async () => { const { stderr } = await runBrowserTests({ root: './fixtures/timeout-hooks', diff --git a/test/browser/specs/viewport.test.ts b/test/browser/specs/viewport.test.ts new file mode 100644 index 000000000000..e6daf48badd2 --- /dev/null +++ b/test/browser/specs/viewport.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'vitest' +import { runBrowserTests } from './utils' + +test('viewport', async () => { + const { stderr, ctx } = await runBrowserTests({ + root: './fixtures/viewport', + }) + + expect(stderr).toBe('') + expect( + Object.fromEntries( + ctx.state.getFiles().map(f => [f.name, f.result.state]), + ), + ).toMatchInlineSnapshot(` + { + "basic.test.ts": "pass", + } + `) +}) diff --git a/test/browser/test/userEvent.test.ts b/test/browser/test/userEvent.test.ts index 62653a97af44..9baf75e357b3 100644 --- a/test/browser/test/userEvent.test.ts +++ b/test/browser/test/userEvent.test.ts @@ -140,9 +140,10 @@ describe('userEvent.click', () => { }, }) + // not exact due to scaling and rounding expect(spy).toHaveBeenCalledWith({ - x: 200, - y: 150, + x: expect.closeTo(200, -1), + y: expect.closeTo(150, -1), }) }) })