E2E camera tests (#3747)
This PR adds E2E tests for panning and zooming using touch gestures and zooming using the scrollwheel input. ### Change Type <!-- ❗ Please select a 'Scope' label ❗️ --> - [ ] `sdk` — Changes the tldraw SDK - [ ] `dotcom` — Changes the tldraw.com web app - [ ] `docs` — Changes to the documentation, examples, or templates. - [ ] `vs code` — Changes to the vscode plugin - [x] `internal` — Does not affect user-facing stuff <!-- ❗ Please select a 'Type' label ❗️ --> - [ ] `bugfix` — Bug fix - [ ] `feature` — New feature - [ ] `improvement` — Improving existing features - [ ] `chore` — Updating dependencies, other boring stuff - [ ] `galaxy brain` — Architectural changes - [x] `tests` — Changes to any test code - [ ] `tools` — Changes to infrastructure, CI, internal scripts, debugging tools, etc. - [ ] `dunno` — I don't know ### Test Plan 1. Add a step-by-step description of how to test your PR here. 2. - [ ] Unit Tests - [ ] End to end tests ### Release Notes - Adds E2E tests for the camera
This commit is contained in:
parent
c04b4286ca
commit
e559a7cdbb
2 changed files with 172 additions and 11 deletions
|
@ -21,6 +21,7 @@ export class Toolbar {
|
|||
cloud: this.page.getByTestId('tools.cloud'),
|
||||
eraser: this.page.getByTestId('tools.eraser'),
|
||||
rectangle: this.page.getByTestId('tools.rectangle'),
|
||||
hand: this.page.getByTestId('tools.hand'),
|
||||
}
|
||||
this.popOverTools = {
|
||||
popoverCloud: this.page.getByTestId('tools.more.cloud'),
|
||||
|
|
|
@ -1,5 +1,83 @@
|
|||
import test from '@playwright/test'
|
||||
import { CDPSession, Page, expect } from '@playwright/test'
|
||||
import { Editor } from 'tldraw'
|
||||
import { setup } from '../shared-e2e'
|
||||
import test from './fixtures/fixtures'
|
||||
|
||||
declare const editor: Editor
|
||||
|
||||
let client: CDPSession
|
||||
|
||||
const dispatchTouch = async (
|
||||
client: CDPSession,
|
||||
type: 'touchStart' | 'touchMove' | 'touchEnd' | 'touchCancel',
|
||||
touchPoints: { x: number; y: number }[]
|
||||
) => {
|
||||
await client.send('Input.dispatchTouchEvent', { type, touchPoints })
|
||||
}
|
||||
|
||||
const multiTouchGesture = async ({
|
||||
start,
|
||||
end,
|
||||
steps,
|
||||
}: {
|
||||
start: { x: number; y: number }[]
|
||||
end: { x: number; y: number }[]
|
||||
steps: number
|
||||
}) => {
|
||||
const finger1 = { x: start[0].x, y: start[0].y }
|
||||
const finger2 = { x: start[1].x, y: start[1].x }
|
||||
const finalTouch = [finger1, finger2]
|
||||
|
||||
await dispatchTouch(client, 'touchStart', [finger1, finger2])
|
||||
await sleep(10)
|
||||
|
||||
// how far should the fingers move each step to reach the end point?
|
||||
const finger1StepDistance = {
|
||||
x: (end[0].x - start[0].x) / steps,
|
||||
y: (end[0].y - start[0].y) / steps,
|
||||
}
|
||||
const finger2StepDistance = {
|
||||
x: (end[1].x - start[1].x) / steps,
|
||||
y: (end[1].y - start[1].y) / steps,
|
||||
}
|
||||
|
||||
for (let i = 1; i < steps; i++) {
|
||||
await dispatchTouch(client, 'touchMove', [
|
||||
{ x: finger1.x + i * finger1StepDistance.x, y: finger1.y + i * finger1StepDistance.y },
|
||||
{ x: finger2.x + i * finger2StepDistance.x, y: finger2.y + i * finger2StepDistance.y },
|
||||
])
|
||||
|
||||
await sleep(10)
|
||||
}
|
||||
|
||||
await dispatchTouch(client, 'touchEnd', finalTouch)
|
||||
await sleep(10)
|
||||
}
|
||||
|
||||
const scrollZoom = async ({
|
||||
page,
|
||||
currentZoomLevel,
|
||||
zoomDirection,
|
||||
steps,
|
||||
}: {
|
||||
page: Page
|
||||
currentZoomLevel: number
|
||||
zoomDirection: 'in' | 'out'
|
||||
steps: number
|
||||
}) => {
|
||||
page.keyboard.down('Control')
|
||||
// zooming with the scroll wheel is capped within the editor to make sure it's consistent between browsers,
|
||||
//scrolling further than 15px per event doesn't have any effect
|
||||
for (let i = 0; i < steps; i++) {
|
||||
await page.mouse.wheel(0, zoomDirection === 'out' ? -15 : 15)
|
||||
}
|
||||
page.keyboard.up('Control')
|
||||
// scrolling 15px on the mouse wheel changes the zoom level by 10%
|
||||
const expectedZoomLevel =
|
||||
zoomDirection === 'out' ? currentZoomLevel * 1.1 ** steps : currentZoomLevel / 1.1 ** steps
|
||||
|
||||
return expectedZoomLevel
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
@ -8,19 +86,101 @@ export function sleep(ms: number) {
|
|||
test.describe('camera', () => {
|
||||
test.beforeEach(setup)
|
||||
|
||||
test.fixme('panning', () => {
|
||||
test('panning', async ({ isMobile, page }) => {
|
||||
test.skip(!isMobile)
|
||||
client = await page.context().newCDPSession(page)
|
||||
expect(await page.evaluate(() => editor.inputs.currentPagePoint)).toEqual({
|
||||
x: 50,
|
||||
y: 50,
|
||||
z: 0,
|
||||
})
|
||||
|
||||
// the fewer steps you use, the faster the pan. So the ratio of pixels moved is not one-to-one.
|
||||
await multiTouchGesture({
|
||||
start: [
|
||||
{ x: 50, y: 50 },
|
||||
{ x: 100, y: 100 },
|
||||
],
|
||||
end: [
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 150, y: 150 },
|
||||
],
|
||||
steps: 50,
|
||||
})
|
||||
|
||||
// With current panning speed, the above gesture moves the camera by 36px on each axis
|
||||
expect(
|
||||
await page.evaluate(() => [
|
||||
editor.inputs.currentPagePoint.x,
|
||||
editor.inputs.currentPagePoint.y,
|
||||
])
|
||||
).toStrictEqual([86, 86])
|
||||
})
|
||||
|
||||
test('pinching on trackpad', async ({ page, isMobile }) => {
|
||||
// pinching on trackpad is the same event as ctrl+scrollwheel
|
||||
test.skip(isMobile)
|
||||
expect(await page.evaluate(() => editor.getZoomLevel())).toBe(1)
|
||||
|
||||
let expectedZoomLevel = await scrollZoom({
|
||||
page,
|
||||
currentZoomLevel: 1,
|
||||
zoomDirection: 'in',
|
||||
steps: 3,
|
||||
})
|
||||
// toBeCloseTo is used to avoid floating point errors
|
||||
expect(await page.evaluate(() => editor.getZoomLevel())).toBeCloseTo(expectedZoomLevel, 1)
|
||||
|
||||
expectedZoomLevel = await scrollZoom({
|
||||
page,
|
||||
currentZoomLevel: expectedZoomLevel,
|
||||
zoomDirection: 'out',
|
||||
steps: 3,
|
||||
})
|
||||
expect(await page.evaluate(() => editor.getZoomLevel())).toBeCloseTo(expectedZoomLevel, 1)
|
||||
})
|
||||
|
||||
test('pinching on touchscreen', async ({ page, isMobile }) => {
|
||||
test.skip(!isMobile)
|
||||
|
||||
client = await page.context().newCDPSession(page)
|
||||
|
||||
expect(await page.evaluate(() => editor.getZoomLevel())).toBe(1)
|
||||
|
||||
// zoom out
|
||||
await multiTouchGesture({
|
||||
start: [
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 200, y: 200 },
|
||||
],
|
||||
end: [
|
||||
{ x: 149, y: 149 },
|
||||
{ x: 151, y: 151 },
|
||||
],
|
||||
steps: 50,
|
||||
})
|
||||
expect(await page.evaluate(() => editor.getZoomLevel())).toBe(0.1)
|
||||
|
||||
// now zoom in
|
||||
await multiTouchGesture({
|
||||
start: [
|
||||
{ x: 149, y: 149 },
|
||||
{ x: 151, y: 151 },
|
||||
],
|
||||
end: [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 300, y: 300 },
|
||||
],
|
||||
steps: 50,
|
||||
})
|
||||
expect(await page.evaluate(() => editor.getZoomLevel())).toBe(8)
|
||||
})
|
||||
|
||||
test.fixme('minimap', async () => {
|
||||
// todo
|
||||
})
|
||||
|
||||
test.fixme('pinching', () => {
|
||||
// todo
|
||||
})
|
||||
|
||||
test.fixme('minimap', () => {
|
||||
// todo
|
||||
})
|
||||
|
||||
test.fixme('hand tool', () => {
|
||||
test.fixme('hand tool', async () => {
|
||||
// todo
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue