import { test, expect } from './zoo-test' import { EngineCommand } from 'lang/std/artifactGraph' import { uuidv4 } from 'lib/utils' import { getUtils } from './test-utils' test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => { test('Can move camera reliably', async ({ page, context, homePage }) => { const u = await getUtils(page) await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() await u.waitForPageLoad() await u.openAndClearDebugPanel() await u.closeKclCodePanel() const camPos: [number, number, number] = [0, 85, 85] const bakeInRetries = async ( mouseActions: any, xyz: [number, number, number], cnt = 0 ) => { // hack that we're implemented our own retry instead of using retries built into playwright. // however each of these camera drags can be flaky, because of udp // and so putting them together means only one needs to fail to make this test extra flaky. // this way we can retry within the test // We could break them out into separate tests, but the longest past of the test is waiting // for the stream to start, so it can be good to bundle related things together. const camCommand: EngineCommand = { type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { type: 'default_camera_look_at', center: { x: 0, y: 0, z: 0 }, vantage: { x: camPos[0], y: camPos[1], z: camPos[2] }, up: { x: 0, y: 0, z: 1 }, }, } const updateCamCommand: EngineCommand = { type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { type: 'default_camera_get_settings', }, } await u.sendCustomCmd(camCommand) await page.waitForTimeout(100) await u.sendCustomCmd(updateCamCommand) await page.waitForTimeout(100) // rotate await u.closeDebugPanel() await page.getByRole('button', { name: 'Start Sketch' }).click() await page.waitForTimeout(100) // const yo = page.getByTestId('cam-x-position').inputValue() await u.doAndWaitForImageDiff(async () => { await mouseActions() await u.openAndClearDebugPanel() await u.closeDebugPanel() await page.waitForTimeout(100) }, 300) await u.openAndClearDebugPanel() await page.getByTestId('cam-x-position').isVisible() const vals = await Promise.all([ page.getByTestId('cam-x-position').inputValue(), page.getByTestId('cam-y-position').inputValue(), page.getByTestId('cam-z-position').inputValue(), ]) const xError = Math.abs(Number(vals[0]) + xyz[0]) const yError = Math.abs(Number(vals[1]) + xyz[1]) const zError = Math.abs(Number(vals[2]) + xyz[2]) let shouldRetry = false if (xError > 5 || yError > 5 || zError > 5) { if (cnt > 2) { console.log('xVal', vals[0], 'xError', xError) console.log('yVal', vals[1], 'yError', yError) console.log('zVal', vals[2], 'zError', zError) throw new Error('Camera position not as expected') } shouldRetry = true } await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.waitForTimeout(100) if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1) } await bakeInRetries(async () => { await page.mouse.move(700, 200) await page.mouse.down({ button: 'right' }) const appLogoBBox = await page.getByTestId('app-logo').boundingBox() expect(appLogoBBox).not.toBeNull() if (!appLogoBBox) throw new Error('app logo not found') await page.mouse.move( appLogoBBox.x + appLogoBBox.width / 2, appLogoBBox.y + appLogoBBox.height / 2 ) await page.mouse.move(600, 303) await page.mouse.up({ button: 'right' }) }, [4, -10.5, -120]) await bakeInRetries(async () => { await page.keyboard.down('Shift') await page.mouse.move(600, 200) await page.mouse.down({ button: 'right' }) // Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine. await page.mouse.move(700, 200) await page.mouse.up({ button: 'right' }) await page.keyboard.up('Shift') }, [-19, -85, -85]) const camCommand: EngineCommand = { type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { type: 'default_camera_look_at', center: { x: 0, y: 0, z: 0 }, vantage: { x: camPos[0], y: camPos[1], z: camPos[2] }, up: { x: 0, y: 0, z: 1 }, }, } const updateCamCommand: EngineCommand = { type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { type: 'default_camera_get_settings', }, } await u.sendCustomCmd(camCommand) await page.waitForTimeout(100) await u.sendCustomCmd(updateCamCommand) await page.waitForTimeout(100) await u.clearCommandLogs() await u.closeDebugPanel() await page.getByRole('button', { name: 'Start Sketch' }).click() await page.waitForTimeout(200) // zoom await u.doAndWaitForImageDiff(async () => { await page.keyboard.down('Control') await page.mouse.move(700, 400) await page.mouse.down({ button: 'right' }) await page.mouse.move(700, 300) await page.mouse.up({ button: 'right' }) await page.keyboard.up('Control') await u.openDebugPanel() await page.waitForTimeout(300) await u.clearCommandLogs() await u.closeDebugPanel() }, 300) // zoom with scroll await u.openAndClearDebugPanel() // TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom // await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5) // await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5) // await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5) await page.getByRole('button', { name: 'Exit Sketch' }).click() await bakeInRetries(async () => { await page.mouse.move(700, 400) await page.mouse.wheel(0, -100) }, [0, -85, -85]) }) // TODO: fix after electron migration is merged test.fixme( 'Zoom should be consistent when exiting or entering sketches', async ({ page, homePage }) => { // start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place // than zoom and pan outside of sketch mode and enter again and it should not change from where it is // than again for sketching const u = await getUtils(page) await page.setBodyDimensions({ width: 1200, height: 500 }) await homePage.goToModelingScene() await u.waitForPageLoad() await u.openDebugPanel() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).not.toBeDisabled() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).toBeVisible() // click on "Start Sketch" button await u.clearCommandLogs() await page.getByRole('button', { name: 'Start Sketch' }).click() await page.waitForTimeout(100) // select a plane await page.mouse.click(700, 325) let code = `sketch001 = startSketchOn('XY')` await expect(u.codeLocator).toHaveText(code) await u.closeDebugPanel() await page.waitForTimeout(500) // TODO detect animation ending, or disable animation // move the camera slightly await page.keyboard.down('Shift') await page.mouse.move(700, 300) await page.mouse.down({ button: 'right' }) await page.mouse.move(800, 200) await page.mouse.up({ button: 'right' }) await page.keyboard.up('Shift') let y = 350, x = 948 await u.canvasLocator.click({ position: { x: 783, y } }) code += `\n |> startProfileAt([8.12, -12.98], %)` // await expect(u.codeLocator).toHaveText(code) await u.canvasLocator.click({ position: { x, y } }) code += `\n |> line(end = [11.18, 0])` // await expect(u.codeLocator).toHaveText(code) await u.canvasLocator.click({ position: { x, y: 275 } }) code += `\n |> line(end = [0, 6.99])` // await expect(u.codeLocator).toHaveText(code) // click the line button await page.getByRole('button', { name: 'line Line', exact: true }).click() const hoverOverNothing = async () => { // await u.canvasLocator.hover({position: {x: 700, y: 325}}) await page.mouse.move(700, 325) await page.waitForTimeout(100) await expect(page.getByTestId('hover-highlight')).not.toBeVisible({ timeout: 10_000, }) } await expect(page.getByTestId('hover-highlight')).not.toBeVisible() await page.waitForTimeout(200) // hover over horizontal line await u.canvasLocator.hover({ position: { x: 800, y } }) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) await page.waitForTimeout(200) await hoverOverNothing() await page.waitForTimeout(200) // hover over vertical line await u.canvasLocator.hover({ position: { x, y: 325 } }) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) await hoverOverNothing() // click exit sketch await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.waitForTimeout(400) await hoverOverNothing() await page.waitForTimeout(200) // hover over horizontal line await page.mouse.move(858, y, { steps: 5 }) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) await hoverOverNothing() // hover over vertical line await page.mouse.move(x, 325) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) await hoverOverNothing() // hover over vertical line await page.mouse.move(857, y) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) // now click it await page.mouse.click(857, y) await expect( page.getByRole('button', { name: 'Edit Sketch' }) ).toBeVisible() await hoverOverNothing() await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.waitForTimeout(400) x = 975 y = 468 await page.waitForTimeout(100) await page.mouse.move(x, 419, { steps: 5 }) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) await hoverOverNothing() await page.mouse.move(855, y) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) await hoverOverNothing() await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.waitForTimeout(200) await hoverOverNothing() await page.waitForTimeout(200) await page.mouse.move(x, 419) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) await hoverOverNothing() await page.mouse.move(855, y) await expect(page.getByTestId('hover-highlight').first()).toBeVisible({ timeout: 10_000, }) } ) test(`Zoom by scroll should not fire while orbiting`, async ({ homePage, page, }) => { /** * Currently we only allow zooming by scroll when no other camera movement is happening, * set within cameraMouseDragGuards in cameraControls.ts, * until the engine supports unifying multiple camera movements. * This verifies that scrollCallback's guard is working as expected. */ const u = await getUtils(page) // Constants and locators const settingsLink = page.getByTestId('settings-link') const settingsDialogHeading = page.getByRole('heading', { name: 'Settings', exact: true, }) const userSettingsTab = page.getByRole('radio', { name: 'User' }) const mouseControlsSetting = page .locator('#mouseControls') .getByRole('combobox') const mouseControlSuccesToast = page.getByText( 'Set mouse controls to "Solidworks"' ) const settingsCloseButton = page.getByTestId('settings-close-button') const gizmo = page.locator('[aria-label*=gizmo]') const resetCameraButton = page.getByRole('button', { name: 'Reset view' }) const orbitMouseStart = { x: 800, y: 130 } const orbitMouseEnd = { x: 0, y: 130 } const mid = (v1: number, v2: number) => v1 + (v2 - v1) / 2 type Point = { x: number; y: number } const midPoint = (p1: Point, p2: Point) => ({ x: mid(p1.x, p2.x), y: mid(p1.y, p2.y), }) const orbitMouseStepOne = midPoint(orbitMouseStart, orbitMouseEnd) const expectedStartCamZPosition = 64.0 const expectedZoomCamZPosition = 32.0 const expectedOrbitCamZPosition = 64.0 await test.step(`Test setup`, async () => { await homePage.goToModelingScene() await u.waitForPageLoad() await u.closeKclCodePanel() // This test requires the mouse controls to be set to Solidworks await u.openDebugPanel() await test.step(`Set mouse controls setting to Solidworks`, async () => { await settingsLink.click() await expect(settingsDialogHeading).toBeVisible() await userSettingsTab.click() await mouseControlsSetting.selectOption({ label: 'Solidworks' }) await expect(mouseControlSuccesToast).toBeVisible() await settingsCloseButton.click() }) }) await test.step(`Test scrolling zoom works`, async () => { await resetCamera() await page.mouse.move(orbitMouseStart.x, orbitMouseStart.y) await page.mouse.wheel(0, -100) await test.step(`Force a refresh of the camera position`, async () => { await u.openAndClearDebugPanel() await u.sendCustomCmd({ type: 'modeling_cmd_req', cmd_id: uuidv4(), cmd: { type: 'default_camera_get_settings', }, }) await u.waitForCmdReceive('default_camera_get_settings') }) await expect .poll(getCameraZValue, { message: 'Camera should be at expected position after zooming', }) .toEqual(expectedZoomCamZPosition) }) await test.step(`Test orbiting works`, async () => { await doOrbitWith() }) await test.step(`Test scrolling while orbiting doesn't zoom`, async () => { await doOrbitWith(async () => { await page.mouse.wheel(0, -100) }) }) // Helper functions async function resetCamera() { await test.step(`Reset camera`, async () => { await u.openDebugPanel() await u.clearCommandLogs() await u.doAndWaitForCmd(async () => { await gizmo.click({ button: 'right' }) await resetCameraButton.click() }, 'zoom_to_fit') await expect .poll(getCameraZValue, { message: 'Camera Z should be at expected position after reset', }) .toEqual(expectedStartCamZPosition) }) } async function getCameraZValue() { return page .getByTestId('cam-z-position') .inputValue() .then((value) => parseFloat(value)) } async function doOrbitWith(callback = async () => {}) { await resetCamera() await test.step(`Perform orbit`, async () => { await page.mouse.move(orbitMouseStart.x, orbitMouseStart.y) await page.mouse.down({ button: 'middle' }) await page.mouse.move(orbitMouseStepOne.x, orbitMouseStepOne.y, { steps: 3, }) await callback() await page.mouse.move(orbitMouseEnd.x, orbitMouseEnd.y, { steps: 3, }) }) await test.step(`Verify orbit`, async () => { await expect .poll(getCameraZValue, { message: 'Camera should be at expected position after orbiting', }) .toEqual(expectedOrbitCamZPosition) await page.mouse.up({ button: 'middle' }) }) } }) test('Right-click opens context menu when not dragged', async ({ homePage, page, }) => { const u = await getUtils(page) await homePage.goToModelingScene() await u.waitForPageLoad() await test.step(`The menu should not show if we drag the mouse`, async () => { await page.mouse.move(900, 200) await page.mouse.down({ button: 'right' }) await page.mouse.move(900, 300) await page.mouse.up({ button: 'right' }) await expect(page.getByTestId('view-controls-menu')).not.toBeVisible() }) await test.step(`The menu should show if we don't drag the mouse`, async () => { await page.mouse.move(900, 200) await page.mouse.down({ button: 'right' }) await page.mouse.up({ button: 'right' }) await expect(page.getByTestId('view-controls-menu')).toBeVisible() }) }) })