Skip to content

Commit c337480

Browse files
authored
fix(browser): scale iframe for non ui case (#6512)
1 parent 6743008 commit c337480

File tree

9 files changed

+133
-11
lines changed

9 files changed

+133
-11
lines changed

packages/browser/src/client/orchestrator.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,25 @@ async function setIframeViewport(
259259
if (ui) {
260260
await ui.setIframeViewport(width, height)
261261
}
262-
else {
262+
else if (getBrowserState().provider === 'webdriverio') {
263263
iframe.style.width = `${width}px`
264264
iframe.style.height = `${height}px`
265+
iframe.parentElement?.setAttribute('data-scale', '1')
266+
}
267+
else {
268+
const scale = Math.min(
269+
1,
270+
iframe.parentElement!.parentElement!.clientWidth / width,
271+
iframe.parentElement!.parentElement!.clientHeight / height,
272+
)
273+
iframe.parentElement!.style.cssText = `
274+
width: ${width}px;
275+
height: ${height}px;
276+
transform: scale(${scale});
277+
transform-origin: left top;
278+
`
279+
iframe.parentElement?.setAttribute('data-scale', String(scale))
280+
await new Promise(r => requestAnimationFrame(r))
265281
}
266282
}
267283

packages/browser/src/client/tester/locators/playwright.ts

+3-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
getByTextSelector,
1010
getByTitleSelector,
1111
} from 'ivya'
12-
import { getBrowserState } from '../../utils'
1312
import { getIframeScale, processTimeoutOptions } from '../utils'
1413
import { Locator, selectorEngine } from './index'
1514

@@ -106,8 +105,7 @@ class PlaywrightLocator extends Locator {
106105
}
107106

108107
function processDragAndDropOptions(options_?: UserEventDragAndDropOptions) {
109-
// only ui scales the iframe, so we need to adjust the position
110-
if (!options_ || !getBrowserState().config.browser.ui) {
108+
if (!options_) {
111109
return options_
112110
}
113111
const options = options_ as NonNullable<
@@ -123,8 +121,7 @@ function processDragAndDropOptions(options_?: UserEventDragAndDropOptions) {
123121
}
124122

125123
function processHoverOptions(options_?: UserEventHoverOptions) {
126-
// only ui scales the iframe, so we need to adjust the position
127-
if (!options_ || !getBrowserState().config.browser.ui) {
124+
if (!options_) {
128125
return options_
129126
}
130127
const options = options_ as NonNullable<
@@ -137,8 +134,7 @@ function processHoverOptions(options_?: UserEventHoverOptions) {
137134
}
138135

139136
function processClickOptions(options_?: UserEventClickOptions) {
140-
// only ui scales the iframe, so we need to adjust the position
141-
if (!options_ || !getBrowserState().config.browser.ui) {
137+
if (!options_) {
142138
return options_
143139
}
144140
const options = options_ as NonNullable<

packages/browser/src/client/tester/utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ export function processTimeoutOptions<T extends { timeout?: number }>(options_?:
167167
}
168168

169169
export function getIframeScale(): number {
170-
const testerUi = window.parent.document.querySelector('#tester-ui') as HTMLElement | null
170+
const testerUi = window.parent.document.querySelector(`iframe[data-vitest]`)?.parentElement
171171
if (!testerUi) {
172172
throw new Error(`Cannot find Tester element. This is a bug in Vitest. Please, open a new issue with reproduction.`)
173173
}

test/browser/fixtures/locators/blog.test.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ test('renders blog posts', async () => {
1818

1919
await expect.element(secondPost.getByRole('heading')).toHaveTextContent('qui est esse')
2020

21+
// TODO: click doesn't work on webdriverio when iframe is scaled
2122
await userEvent.click(secondPost.getByRole('button', { name: 'Delete' }))
2223

2324
expect(screen.getByRole('listitem').all()).toHaveLength(3)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { page, userEvent, server } from "@vitest/browser/context";
2+
import { expect, test } from "vitest";
3+
4+
test("drag and drop over large viewport", async () => {
5+
// put boxes horizontally [1] [2] ... [30]
6+
// then drag-and-drop from [1] to [30]
7+
8+
const wrapper = document.createElement("div");
9+
wrapper.style.cssText = "display: flex; width: 3000px;";
10+
document.body.appendChild(wrapper);
11+
12+
const events: { i: number; type: string }[] = [];
13+
14+
for (let i = 1; i <= 30; i++) {
15+
const el = document.createElement("div");
16+
el.textContent = `[${i}]`;
17+
el.style.cssText = `
18+
flex: none;
19+
width: 100px;
20+
height: 100px;
21+
border: 1px solid black;
22+
box-sizing: border-box;
23+
display: flex;
24+
justify-content: center;
25+
align-items: center;
26+
`;
27+
el.draggable = true;
28+
wrapper.append(el);
29+
30+
el.addEventListener("dragstart", (ev) => {
31+
ev.dataTransfer.effectAllowed = "move";
32+
events.push({ type: "dragstart", i });
33+
});
34+
el.addEventListener("dragover", (ev) => {
35+
ev.preventDefault();
36+
ev.dataTransfer.dropEffect = "move";
37+
events.push({ type: "dragover", i });
38+
});
39+
el.addEventListener("drop", (ev) => {
40+
ev.preventDefault();
41+
events.push({ type: "drop", i });
42+
});
43+
}
44+
45+
// drag and drop only works reliably on playwright
46+
if (server.provider !== 'playwright') {
47+
return
48+
}
49+
50+
await userEvent.dragAndDrop(page.getByText("[1]"), page.getByText("[30]"));
51+
52+
expect(events).toMatchObject(
53+
expect.arrayContaining([
54+
{ type: "dragstart", i: 1 },
55+
{ type: "drop", i: 30 },
56+
]),
57+
);
58+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { fileURLToPath } from 'node:url'
2+
import { defineConfig } from 'vitest/config'
3+
import { instances, provider } from '../../settings'
4+
5+
// pnpm -C test/browser test-fixtures --root fixtures/viewport --browser.ui=false
6+
// pnpm -C test/browser test-fixtures --root fixtures/viewport --browser.headless=true
7+
8+
export default defineConfig({
9+
test: {
10+
browser: {
11+
enabled: true,
12+
provider,
13+
instances: instances.map(instance => ({
14+
...instance,
15+
viewport: { width: 3000, height: 400 }
16+
})),
17+
18+
},
19+
},
20+
cacheDir: fileURLToPath(new URL("./node_modules/.vite", import.meta.url)),
21+
})

test/browser/specs/runner.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@ test('timeout settings', async () => {
222222
}
223223
})
224224

225+
test('viewport', async () => {
226+
const { stdout, stderr } = await runBrowserTests({
227+
root: './fixtures/viewport',
228+
})
229+
expect(stderr).toBe('')
230+
instances.forEach(({ browser }) => {
231+
expect(stdout).toReportPassedTest('basic.test.ts', browser)
232+
})
233+
})
234+
225235
test.runIf(provider === 'playwright')('timeout hooks', async () => {
226236
const { stderr } = await runBrowserTests({
227237
root: './fixtures/timeout-hooks',

test/browser/specs/viewport.test.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from 'vitest'
2+
import { runBrowserTests } from './utils'
3+
4+
test('viewport', async () => {
5+
const { stderr, ctx } = await runBrowserTests({
6+
root: './fixtures/viewport',
7+
})
8+
9+
expect(stderr).toBe('')
10+
expect(
11+
Object.fromEntries(
12+
ctx.state.getFiles().map(f => [f.name, f.result.state]),
13+
),
14+
).toMatchInlineSnapshot(`
15+
{
16+
"basic.test.ts": "pass",
17+
}
18+
`)
19+
})

test/browser/test/userEvent.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,10 @@ describe('userEvent.click', () => {
140140
},
141141
})
142142

143+
// not exact due to scaling and rounding
143144
expect(spy).toHaveBeenCalledWith({
144-
x: 200,
145-
y: 150,
145+
x: expect.closeTo(200, -1),
146+
y: expect.closeTo(150, -1),
146147
})
147148
})
148149
})

0 commit comments

Comments
 (0)