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:
Taha 2024-05-19 02:02:06 +01:00 committed by GitHub
parent c04b4286ca
commit e559a7cdbb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 172 additions and 11 deletions

View file

@ -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'),

View file

@ -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
})
})