* Add a Stop command that does a bit of cleanup in the engineStateMachine * Major network code startup refactor; backoff reconnect; many more logs for us * Save camera state after every user interaction in case of disconnection and restoral * Translate basic WebSocket error numbers into useful text * Add a RestartRequest event to the engineCommandManager * Adjust for a rebase * Add E2E test for stream pause behavior * Fix snapshot test * fmt, lint * small issue * Fix tests * Fix up idle test * One last fix * fixes * Remove circ dep * fix test * use a const for time * TEST -> isPlaywright instead * whoops
		
			
				
	
	
		
			974 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			974 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
 | |
| import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
 | |
| import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
 | |
| import {
 | |
|   getUtils,
 | |
|   headerMasks,
 | |
|   lowerRightMasks,
 | |
|   settingsToToml,
 | |
| } from '@e2e/playwright/test-utils'
 | |
| import { expect, test } from '@e2e/playwright/zoo-test'
 | |
| import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
 | |
| 
 | |
| test.beforeEach(async ({ page, context }) => {
 | |
|   // Make the user avatar image always 404
 | |
|   // so we see the fallback menu icon for all snapshot tests
 | |
|   await page.route('https://lh3.googleusercontent.com/**', async (route) => {
 | |
|     await route.fulfill({
 | |
|       status: 404,
 | |
|       contentType: 'text/plain',
 | |
|       body: 'Not Found!',
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| // Help engine-manager: tear shit down.
 | |
| test.afterEach(async ({ page }) => {
 | |
|   await page.evaluate(() => {
 | |
|     window.engineCommandManager.tearDown()
 | |
|   })
 | |
| })
 | |
| 
 | |
| test.setTimeout(60_000)
 | |
| 
 | |
| const extrudeDefaultPlane = async (
 | |
|   context: any,
 | |
|   page: any,
 | |
|   cmdBar: CmdBarFixture,
 | |
|   scene: SceneFixture,
 | |
|   plane: string
 | |
| ) => {
 | |
|   const code = `part001 = startSketchOn(${plane})
 | |
|   |> startProfile(at = [7.00, 4.40])
 | |
|   |> line(end = [6.60, -0.20])
 | |
|   |> line(end = [2.80, 5.00])
 | |
|   |> line(end = [-5.60, 4.40])
 | |
|   |> line(end = [-5.40, -3.80])
 | |
|   |> close()
 | |
|   |> extrude(length = 10.00)
 | |
| `
 | |
| 
 | |
|   // This probably does absolutely nothing based on my trip through here.
 | |
|   await page.addInitScript(async () => {
 | |
|     localStorage.setItem(
 | |
|       'SETTINGS_PERSIST_KEY',
 | |
|       settingsToToml({
 | |
|         settings: {
 | |
|           modeling: {
 | |
|             base_unit: 'in',
 | |
|             mouse_controls: 'zoo',
 | |
|           },
 | |
|           app: {
 | |
|             onboarding_status: 'dismissed',
 | |
|             show_debug_panel: true,
 | |
|             appearance: {
 | |
|               theme: 'dark',
 | |
|             },
 | |
|           },
 | |
|           project: {
 | |
|             default_project_name: 'untitled',
 | |
|           },
 | |
|           text_editor: {
 | |
|             text_wrapping: true,
 | |
|           },
 | |
|         },
 | |
|       })
 | |
|     )
 | |
|   })
 | |
| 
 | |
|   await page.addInitScript(async (code: string) => {
 | |
|     localStorage.setItem('persistCode', code)
 | |
|   }, code)
 | |
| 
 | |
|   const u = await getUtils(page)
 | |
|   await page.setViewportSize({ width: 1200, height: 500 })
 | |
| 
 | |
|   await u.waitForAuthSkipAppStart()
 | |
|   await scene.settled(cmdBar)
 | |
| 
 | |
|   await expect(page).toHaveScreenshot({
 | |
|     maxDiffPixels: 100,
 | |
|     mask: lowerRightMasks(page),
 | |
|   })
 | |
|   await u.openKclCodePanel()
 | |
| }
 | |
| 
 | |
| test.describe(
 | |
|   'extrude on default planes should be stable',
 | |
|   { tag: '@snapshot' },
 | |
|   () => {
 | |
|     test('XY', async ({ page, context, cmdBar, scene }) => {
 | |
|       await extrudeDefaultPlane(context, page, cmdBar, scene, 'XY')
 | |
|     })
 | |
| 
 | |
|     test('XZ', async ({ page, context, cmdBar, scene }) => {
 | |
|       await extrudeDefaultPlane(context, page, cmdBar, scene, 'XZ')
 | |
|     })
 | |
| 
 | |
|     test('YZ', async ({ page, context, cmdBar, scene }) => {
 | |
|       await extrudeDefaultPlane(context, page, cmdBar, scene, 'YZ')
 | |
|     })
 | |
| 
 | |
|     test('-XY', async ({ page, context, cmdBar, scene }) => {
 | |
|       await extrudeDefaultPlane(context, page, cmdBar, scene, '-XY')
 | |
|     })
 | |
| 
 | |
|     test('-XZ', async ({ page, context, cmdBar, scene }) => {
 | |
|       await extrudeDefaultPlane(context, page, cmdBar, scene, '-XZ')
 | |
|     })
 | |
| 
 | |
|     test('-YZ', async ({ page, context, cmdBar, scene }) => {
 | |
|       await extrudeDefaultPlane(context, page, cmdBar, scene, '-YZ')
 | |
|     })
 | |
|   }
 | |
| )
 | |
| 
 | |
| test(
 | |
|   'Draft segments should look right',
 | |
|   { tag: '@snapshot' },
 | |
|   async ({ page, scene, toolbar }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
|     const PUR = 400 / 37.5 //pixeltoUnitRatio
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     const startXPx = 600
 | |
|     const [endOfTangentClk, endOfTangentMv] = scene.makeMouseHelpers(
 | |
|       startXPx + PUR * 30,
 | |
|       500 - PUR * 20,
 | |
|       { steps: 10 }
 | |
|     )
 | |
|     const [threePointArcMidPointClk, threePointArcMidPointMv] =
 | |
|       scene.makeMouseHelpers(800, 250, { steps: 10 })
 | |
|     const [threePointArcEndPointClk, threePointArcEndPointMv] =
 | |
|       scene.makeMouseHelpers(750, 285, { steps: 10 })
 | |
|     const [arcCenterClk, arcCenterMv] = scene.makeMouseHelpers(750, 210, {
 | |
|       steps: 10,
 | |
|     })
 | |
|     const [arcEndClk, arcEndMv] = scene.makeMouseHelpers(750, 150, {
 | |
|       steps: 10,
 | |
|     })
 | |
| 
 | |
|     // click on "Start Sketch" button
 | |
|     await u.doAndWaitForImageDiff(
 | |
|       () => page.getByRole('button', { name: 'Start Sketch' }).click(),
 | |
|       200
 | |
|     )
 | |
| 
 | |
|     // select a plane
 | |
|     await page.mouse.click(700, 200)
 | |
| 
 | |
|     let code = `sketch001 = startSketchOn(XZ)`
 | |
|     await expect(page.locator('.cm-content')).toHaveText(code)
 | |
| 
 | |
|     await page.waitForTimeout(700) // TODO detect animation ending, or disable animation
 | |
| 
 | |
|     await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
 | |
|     code += `profile001 = startProfile(sketch001, at = [182.59, -246.32])`
 | |
|     await expect(page.locator('.cm-content')).toHaveText(code)
 | |
|     await page.waitForTimeout(100)
 | |
| 
 | |
|     await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10)
 | |
| 
 | |
|     await page.waitForTimeout(500)
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
| 
 | |
|     const lineEndClick = () =>
 | |
|       page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
 | |
|     await lineEndClick()
 | |
|     await page.waitForTimeout(500)
 | |
| 
 | |
|     code += `
 | |
|   |> xLine(length = 184.3)`
 | |
|     await expect(page.locator('.cm-content')).toHaveText(code)
 | |
| 
 | |
|     await toolbar.selectTangentialArc()
 | |
| 
 | |
|     // click on the end of the profile to continue it
 | |
|     await page.waitForTimeout(500)
 | |
|     await lineEndClick()
 | |
|     await page.waitForTimeout(500)
 | |
| 
 | |
|     // click to continue profile
 | |
|     await page.mouse.move(813, 392, { steps: 10 })
 | |
|     await page.waitForTimeout(500)
 | |
| 
 | |
|     await endOfTangentMv()
 | |
| 
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|     await endOfTangentClk()
 | |
| 
 | |
|     await toolbar.selectThreePointArc()
 | |
|     await page.waitForTimeout(500)
 | |
|     await endOfTangentClk()
 | |
|     await threePointArcMidPointMv()
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|     await threePointArcMidPointClk()
 | |
|     await page.waitForTimeout(100)
 | |
| 
 | |
|     await threePointArcEndPointMv()
 | |
|     await page.waitForTimeout(500)
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
| 
 | |
|     await threePointArcEndPointClk()
 | |
|     await page.waitForTimeout(100)
 | |
| 
 | |
|     await toolbar.selectArc()
 | |
|     await page.waitForTimeout(100)
 | |
| 
 | |
|     // continue the profile
 | |
|     await threePointArcEndPointClk()
 | |
|     await page.waitForTimeout(100)
 | |
|     await arcCenterMv()
 | |
|     await page.waitForTimeout(500)
 | |
|     await arcCenterClk()
 | |
| 
 | |
|     await arcEndMv()
 | |
|     await page.waitForTimeout(500)
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|     await arcEndClk()
 | |
|   }
 | |
| )
 | |
| 
 | |
| test(
 | |
|   'Draft rectangles should look right',
 | |
|   { tag: '@snapshot' },
 | |
|   async ({ page, context, cmdBar, scene }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
|     const PUR = 400 / 37.5 //pixeltoUnitRatio
 | |
| 
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     // click on "Start Sketch" button
 | |
|     await u.doAndWaitForImageDiff(
 | |
|       () => page.getByRole('button', { name: 'Start Sketch' }).click(),
 | |
|       200
 | |
|     )
 | |
| 
 | |
|     // select a plane
 | |
|     await page.mouse.click(700, 200)
 | |
| 
 | |
|     await expect(page.locator('.cm-content')).toHaveText(
 | |
|       `sketch001 = startSketchOn(XZ)`
 | |
|     )
 | |
| 
 | |
|     // Wait for camera animation
 | |
|     await page.waitForTimeout(2000)
 | |
| 
 | |
|     const startXPx = 600
 | |
| 
 | |
|     // Equip the rectangle tool
 | |
|     await page
 | |
|       .getByRole('button', { name: 'rectangle Corner rectangle', exact: true })
 | |
|       .click()
 | |
| 
 | |
|     // Draw the rectangle
 | |
|     await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
 | |
|     await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
 | |
|     await page.waitForTimeout(800)
 | |
| 
 | |
|     // Ensure the draft rectangle looks the same as it usually does
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|   }
 | |
| )
 | |
| test(
 | |
|   'Draft circle should look right',
 | |
|   { tag: '@snapshot' },
 | |
|   async ({ page, context, cmdBar, scene }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
|     const PUR = 400 / 37.5 //pixeltoUnitRatio
 | |
| 
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await u.doAndWaitForImageDiff(
 | |
|       () => page.getByRole('button', { name: 'Start Sketch' }).click(),
 | |
|       200
 | |
|     )
 | |
| 
 | |
|     // select a plane
 | |
|     await page.mouse.click(700, 200)
 | |
| 
 | |
|     await expect(page.locator('.cm-content')).toHaveText(
 | |
|       `sketch001 = startSketchOn(XZ)`
 | |
|     )
 | |
| 
 | |
|     // Wait for camera animation
 | |
|     await page.waitForTimeout(2000)
 | |
| 
 | |
|     const startXPx = 600
 | |
| 
 | |
|     // Equip the rectangle tool
 | |
|     // await page.getByRole('button', { name: 'line Line', exact: true }).click()
 | |
|     await page.getByTestId('circle-center').click()
 | |
| 
 | |
|     // Draw the rectangle
 | |
|     await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
 | |
|     await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
 | |
| 
 | |
|     // Ensure the draft rectangle looks the same as it usually does
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|     await expect(page.locator('.cm-content')).toHaveText(
 | |
|       `sketch001 = startSketchOn(XZ)profile001 = circle(sketch001, center = [366.89, -62.01], radius = 1)`
 | |
|     )
 | |
|   }
 | |
| )
 | |
| 
 | |
| test.describe(
 | |
|   'Client side scene scale should match engine scale',
 | |
|   { tag: '@snapshot' },
 | |
|   () => {
 | |
|     test('Inch scale', async ({ page, cmdBar, scene, toolbar }) => {
 | |
|       const u = await getUtils(page)
 | |
|       await page.setViewportSize({ width: 1200, height: 500 })
 | |
|       const PUR = 400 / 37.5 //pixeltoUnitRatio
 | |
| 
 | |
|       await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|       await u.doAndWaitForImageDiff(
 | |
|         () => page.getByRole('button', { name: 'Start Sketch' }).click(),
 | |
|         200
 | |
|       )
 | |
| 
 | |
|       // select a plane
 | |
|       await page.mouse.click(700, 200)
 | |
| 
 | |
|       let code = `sketch001 = startSketchOn(XZ)`
 | |
|       await expect(page.locator('.cm-content')).toHaveText(code)
 | |
| 
 | |
|       // Wait for camera animation
 | |
|       await page.waitForTimeout(2000)
 | |
| 
 | |
|       const startXPx = 600
 | |
|       await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
 | |
|       code += `profile001 = startProfile(sketch001, at = [182.59, -246.32])`
 | |
|       await expect(u.codeLocator).toHaveText(code)
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       code += `
 | |
|   |> xLine(length = 184.3)`
 | |
|       await expect(u.codeLocator).toHaveText(code)
 | |
| 
 | |
|       await toolbar.selectTangentialArc()
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       // click to continue profile
 | |
|       await page.mouse.click(813, 392)
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
 | |
| 
 | |
|       code += `
 | |
|   |> tangentialArc(endAbsolute = [551.2, -62.01])`
 | |
|       await expect(u.codeLocator).toHaveText(code)
 | |
| 
 | |
|       // click tangential arc tool again to unequip it
 | |
|       // it will be available directly in the toolbar since it was last equipped
 | |
|       await toolbar.tangentialArcBtn.click()
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       // screen shot should show the sketch
 | |
|       await expect(page).toHaveScreenshot({
 | |
|         maxDiffPixels: 100,
 | |
|         mask: lowerRightMasks(page),
 | |
|       })
 | |
| 
 | |
|       await u.doAndWaitForImageDiff(
 | |
|         () => page.getByRole('button', { name: 'Exit Sketch' }).click(),
 | |
|         200
 | |
|       )
 | |
| 
 | |
|       await scene.settled(cmdBar)
 | |
| 
 | |
|       // second screen shot should look almost identical, i.e. scale should be the same.
 | |
|       await expect(page).toHaveScreenshot({
 | |
|         maxDiffPixels: 100,
 | |
|         mask: lowerRightMasks(page),
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     test('Millimeter scale', async ({
 | |
|       page,
 | |
|       context,
 | |
|       cmdBar,
 | |
|       scene,
 | |
|       toolbar,
 | |
|     }) => {
 | |
|       await context.addInitScript(
 | |
|         async ({ settingsKey, settings }) => {
 | |
|           localStorage.setItem(settingsKey, settings)
 | |
|         },
 | |
|         {
 | |
|           settingsKey: TEST_SETTINGS_KEY,
 | |
|           settings: settingsToToml({
 | |
|             settings: {
 | |
|               ...TEST_SETTINGS,
 | |
|               modeling: {
 | |
|                 ...TEST_SETTINGS.modeling,
 | |
|                 base_unit: 'mm',
 | |
|               },
 | |
|             },
 | |
|           }),
 | |
|         }
 | |
|       )
 | |
|       const u = await getUtils(page)
 | |
|       await page.setViewportSize({ width: 1200, height: 500 })
 | |
|       const PUR = 400 / 37.5 //pixeltoUnitRatio
 | |
| 
 | |
|       await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|       await scene.settled(cmdBar)
 | |
| 
 | |
|       await u.doAndWaitForImageDiff(
 | |
|         () => page.getByRole('button', { name: 'Start Sketch' }).click(),
 | |
|         200
 | |
|       )
 | |
| 
 | |
|       // select a plane
 | |
|       await page.mouse.click(700, 200)
 | |
| 
 | |
|       let code = `sketch001 = startSketchOn(XZ)`
 | |
|       await expect(u.codeLocator).toHaveText(code)
 | |
| 
 | |
|       // Wait for camera animation
 | |
|       await page.waitForTimeout(2000)
 | |
| 
 | |
|       const startXPx = 600
 | |
|       await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
 | |
|       code += `profile001 = startProfile(sketch001, at = [182.59, -246.32])`
 | |
|       await expect(u.codeLocator).toHaveText(code)
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       code += `
 | |
|   |> xLine(length = 184.3)`
 | |
|       await expect(u.codeLocator).toHaveText(code)
 | |
| 
 | |
|       await toolbar.selectTangentialArc()
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       // click to continue profile
 | |
|       await page.mouse.click(813, 392)
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
 | |
| 
 | |
|       code += `
 | |
|   |> tangentialArc(endAbsolute = [551.2, -62.01])`
 | |
|       await expect(u.codeLocator).toHaveText(code)
 | |
| 
 | |
|       await toolbar.tangentialArcBtn.click()
 | |
|       await page.waitForTimeout(100)
 | |
| 
 | |
|       // screen shot should show the sketch
 | |
|       await expect(page).toHaveScreenshot({
 | |
|         maxDiffPixels: 100,
 | |
|         mask: lowerRightMasks(page),
 | |
|       })
 | |
| 
 | |
|       // exit sketch
 | |
|       await u.doAndWaitForImageDiff(
 | |
|         () => page.getByRole('button', { name: 'Exit Sketch' }).click(),
 | |
|         200
 | |
|       )
 | |
| 
 | |
|       await scene.settled(cmdBar)
 | |
| 
 | |
|       // second screen shot should look almost identical, i.e. scale should be the same.
 | |
|       await expect(page).toHaveScreenshot({
 | |
|         maxDiffPixels: 100,
 | |
|         mask: lowerRightMasks(page),
 | |
|       })
 | |
|     })
 | |
|   }
 | |
| )
 | |
| 
 | |
| test(
 | |
|   'Sketch on face with none z-up',
 | |
|   { tag: '@snapshot' },
 | |
|   async ({ page, context, cmdBar, scene }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
 | |
|       localStorage.setItem(
 | |
|         'persistCode',
 | |
|         `part001 = startSketchOn(-XZ)
 | |
|   |> startProfile(at = [1.4, 2.47])
 | |
|   |> line(end = [9.31, 10.55], tag = $seg01)
 | |
|   |> line(end = [11.91, -10.42])
 | |
|   |> close()
 | |
|   |> extrude(length = ${KCL_DEFAULT_LENGTH})
 | |
| part002 = startSketchOn(part001, face = seg01)
 | |
|   |> startProfile(at = [8, 8])
 | |
|   |> line(end = [4.68, 3.05])
 | |
|   |> line(end = [0, -7.79])
 | |
|   |> close()
 | |
|   |> extrude(length = ${KCL_DEFAULT_LENGTH})
 | |
| `
 | |
|       )
 | |
|     }, KCL_DEFAULT_LENGTH)
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
| 
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     // Wait for the second extrusion to appear
 | |
|     // TODO: Find a way to truly know that the objects have finished
 | |
|     // rendering, because an execution-done message is not sufficient.
 | |
|     await page.waitForTimeout(1000)
 | |
| 
 | |
|     await expect(
 | |
|       page.getByRole('button', { name: 'Start Sketch' })
 | |
|     ).not.toBeDisabled()
 | |
| 
 | |
|     await page.getByRole('button', { name: 'Start Sketch' }).click()
 | |
|     let previousCodeContent = await page.locator('.cm-content').innerText()
 | |
| 
 | |
|     // click at 641, 135
 | |
|     await page.mouse.click(641, 135)
 | |
|     await expect(page.locator('.cm-content')).not.toHaveText(
 | |
|       previousCodeContent
 | |
|     )
 | |
|     previousCodeContent = await page.locator('.cm-content').innerText()
 | |
| 
 | |
|     await page.waitForTimeout(300)
 | |
| 
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|   }
 | |
| )
 | |
| 
 | |
| test(
 | |
|   'Zoom to fit on load - solid 2d',
 | |
|   { tag: '@snapshot' },
 | |
|   async ({ page, context, cmdBar, scene }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await context.addInitScript(async () => {
 | |
|       localStorage.setItem(
 | |
|         'persistCode',
 | |
|         `part001 = startSketchOn(XY)
 | |
|   |> startProfile(at = [-10, -10])
 | |
|   |> line(end = [20, 0])
 | |
|   |> line(end = [0, 20])
 | |
|   |> line(end = [-20, 0])
 | |
|   |> close()
 | |
| `
 | |
|       )
 | |
|     }, KCL_DEFAULT_LENGTH)
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
| 
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     // Wait for the second extrusion to appear
 | |
|     // TODO: Find a way to truly know that the objects have finished
 | |
|     // rendering, because an execution-done message is not sufficient.
 | |
|     await page.waitForTimeout(2000)
 | |
| 
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|   }
 | |
| )
 | |
| 
 | |
| test(
 | |
|   'Zoom to fit on load - solid 3d',
 | |
|   { tag: '@snapshot' },
 | |
|   async ({ page, context, cmdBar, scene }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await context.addInitScript(async () => {
 | |
|       localStorage.setItem(
 | |
|         'persistCode',
 | |
|         `part001 = startSketchOn(XY)
 | |
|   |> startProfile(at = [-10, -10])
 | |
|   |> line(end = [20, 0])
 | |
|   |> line(end = [0, 20])
 | |
|   |> line(end = [-20, 0])
 | |
|   |> close()
 | |
|   |> extrude(length = 10)
 | |
| `
 | |
|       )
 | |
|     }, KCL_DEFAULT_LENGTH)
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
| 
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     // Wait for the second extrusion to appear
 | |
|     // TODO: Find a way to truly know that the objects have finished
 | |
|     // rendering, because an execution-done message is not sufficient.
 | |
|     await page.waitForTimeout(2000)
 | |
| 
 | |
|     await expect(page).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|   }
 | |
| )
 | |
| 
 | |
| test.describe('Grid visibility', { tag: '@snapshot' }, () => {
 | |
|   test('Grid turned off to on via command bar', async ({
 | |
|     page,
 | |
|     cmdBar,
 | |
|     scene,
 | |
|   }) => {
 | |
|     const u = await getUtils(page)
 | |
|     const stream = page.getByTestId('stream')
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
|     await page.goto('/')
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     await u.closeKclCodePanel()
 | |
|     // TODO: Find a way to truly know that the objects have finished
 | |
|     // rendering, because an execution-done message is not sufficient.
 | |
|     await page.waitForTimeout(1000)
 | |
| 
 | |
|     // Open the command bar.
 | |
|     await page
 | |
|       .getByRole('button', { name: 'Commands', exact: false })
 | |
|       .or(page.getByRole('button', { name: '⌘K' }))
 | |
|       .click()
 | |
|     const commandName = 'show scale grid'
 | |
|     const commandOption = page.getByRole('option', {
 | |
|       name: commandName,
 | |
|       exact: false,
 | |
|     })
 | |
|     const cmdSearchBar = page.getByPlaceholder('Search commands')
 | |
|     // This selector changes after we set the setting
 | |
|     await cmdSearchBar.fill(commandName)
 | |
|     await expect(commandOption).toBeVisible()
 | |
|     await commandOption.click()
 | |
| 
 | |
|     const toggleInput = page.getByPlaceholder('Off')
 | |
|     await expect(toggleInput).toBeVisible()
 | |
|     await expect(toggleInput).toBeFocused()
 | |
| 
 | |
|     // Select On
 | |
|     await page.keyboard.press('ArrowDown')
 | |
|     await expect(page.getByRole('option', { name: 'Off' })).toHaveAttribute(
 | |
|       'data-headlessui-state',
 | |
|       'active selected'
 | |
|     )
 | |
|     await page.keyboard.press('ArrowUp')
 | |
|     await expect(page.getByRole('option', { name: 'On' })).toHaveAttribute(
 | |
|       'data-headlessui-state',
 | |
|       'active'
 | |
|     )
 | |
|     await page.keyboard.press('Enter')
 | |
| 
 | |
|     // Check the toast appeared
 | |
|     await expect(
 | |
|       page.getByText(`Set show scale grid to "true" as a user default`)
 | |
|     ).toBeVisible()
 | |
| 
 | |
|     await expect(stream).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: [...headerMasks(page), ...lowerRightMasks(page)],
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   test('Grid turned off', async ({ page, cmdBar, scene }) => {
 | |
|     const u = await getUtils(page)
 | |
|     const stream = page.getByTestId('stream')
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
|     await page.goto('/')
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     await u.closeKclCodePanel()
 | |
|     // TODO: Find a way to truly know that the objects have finished
 | |
|     // rendering, because an execution-done message is not sufficient.
 | |
|     await page.waitForTimeout(1000)
 | |
| 
 | |
|     await expect(stream).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: [...headerMasks(page), ...lowerRightMasks(page)],
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   test('Grid turned on', async ({ page, context, cmdBar, scene }) => {
 | |
|     await context.addInitScript(
 | |
|       async ({ settingsKey, settings }) => {
 | |
|         localStorage.setItem(settingsKey, settings)
 | |
|       },
 | |
|       {
 | |
|         settingsKey: TEST_SETTINGS_KEY,
 | |
|         settings: settingsToToml({
 | |
|           settings: {
 | |
|             ...TEST_SETTINGS,
 | |
|             modeling: {
 | |
|               ...TEST_SETTINGS.modeling,
 | |
|               show_scale_grid: true,
 | |
|             },
 | |
|           },
 | |
|         }),
 | |
|       }
 | |
|     )
 | |
| 
 | |
|     const u = await getUtils(page)
 | |
|     const stream = page.getByTestId('stream')
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 500 })
 | |
|     await page.goto('/')
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     await u.closeKclCodePanel()
 | |
|     // TODO: Find a way to truly know that the objects have finished
 | |
|     // rendering, because an execution-done message is not sufficient.
 | |
|     await page.waitForTimeout(1000)
 | |
| 
 | |
|     await expect(stream).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: [...headerMasks(page), ...lowerRightMasks(page)],
 | |
|     })
 | |
|   })
 | |
| })
 | |
| 
 | |
| test('theme persists', async ({ page, context, homePage }) => {
 | |
|   const u = await getUtils(page)
 | |
|   await context.addInitScript(async () => {
 | |
|     localStorage.setItem(
 | |
|       'persistCode',
 | |
|       `part001 = startSketchOn(XY)
 | |
|   |> startProfile(at = [-10, -10])
 | |
|   |> line(end = [20, 0])
 | |
|   |> line(end = [0, 20])
 | |
|   |> line(end = [-20, 0])
 | |
|   |> close()
 | |
|   |> extrude(length = 10)
 | |
| `
 | |
|     )
 | |
|   }, KCL_DEFAULT_LENGTH)
 | |
| 
 | |
|   await page.setViewportSize({ width: 1200, height: 500 })
 | |
| 
 | |
|   await homePage.goToModelingScene()
 | |
|   await page.waitForTimeout(500)
 | |
| 
 | |
|   // await page.getByRole('link', { name: 'Settings Settings (tooltip)' }).click()
 | |
|   await expect(page.getByTestId('settings-link')).toBeVisible()
 | |
|   await page.getByTestId('settings-link').click()
 | |
| 
 | |
|   // open user settingns
 | |
|   await page.getByRole('radio', { name: 'person User' }).click()
 | |
| 
 | |
|   await page.getByTestId('app-theme').selectOption('light')
 | |
| 
 | |
|   await page.getByTestId('settings-close-button').click()
 | |
| 
 | |
|   const networkToggle = page.getByTestId('network-toggle')
 | |
| 
 | |
|   // simulate network down
 | |
|   await u.emulateNetworkConditions({
 | |
|     offline: true,
 | |
|     // values of 0 remove any active throttling. crbug.com/456324#c9
 | |
|     latency: 0,
 | |
|     downloadThroughput: -1,
 | |
|     uploadThroughput: -1,
 | |
|   })
 | |
| 
 | |
|   // Disconnect and reconnect to check the theme persists through a reload
 | |
| 
 | |
|   // Expect the network to be down
 | |
|   await expect(networkToggle).toContainText('Problem')
 | |
| 
 | |
|   // simulate network up
 | |
|   await u.emulateNetworkConditions({
 | |
|     offline: false,
 | |
|     // values of 0 remove any active throttling. crbug.com/456324#c9
 | |
|     latency: 0,
 | |
|     downloadThroughput: -1,
 | |
|     uploadThroughput: -1,
 | |
|   })
 | |
| 
 | |
|   await expect(networkToggle).toContainText('Connected')
 | |
| 
 | |
|   await expect(page.getByText('building scene')).not.toBeVisible()
 | |
| 
 | |
|   await expect(page, 'expect screenshot to have light theme').toHaveScreenshot({
 | |
|     maxDiffPixels: 100,
 | |
|     mask: lowerRightMasks(page),
 | |
|   })
 | |
| })
 | |
| 
 | |
| test.describe('code color goober', { tag: '@snapshot' }, () => {
 | |
|   test('code color goober', async ({ page, context, scene, cmdBar }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await context.addInitScript(async () => {
 | |
|       localStorage.setItem(
 | |
|         'persistCode',
 | |
|         `// Create a pipe using a sweep.
 | |
| 
 | |
| // Create a path for the sweep.
 | |
| sweepPath = startSketchOn(XZ)
 | |
|   |> startProfile(at = [0.05, 0.05])
 | |
|   |> line(end = [0, 7])
 | |
|   |> tangentialArc(angle = 90, radius = 5)
 | |
|   |> line(end = [-3, 0])
 | |
|   |> tangentialArc(angle = -90, radius = 5)
 | |
|   |> line(end = [0, 7])
 | |
| 
 | |
| sweepSketch = startSketchOn(XY)
 | |
|   |> startProfile(at = [2, 0])
 | |
|   |> arc(angleStart = 0, angleEnd = 360, radius = 2)
 | |
|   |> sweep(path = sweepPath)
 | |
|   |> appearance(
 | |
|        color = "#bb00ff",
 | |
|        metalness = 90,
 | |
|        roughness = 90
 | |
|      )
 | |
| `
 | |
|       )
 | |
|     })
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 1000 })
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     await expect(page, 'expect small color widget').toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|   })
 | |
|   test('code color goober works with single quotes', async ({
 | |
|     page,
 | |
|     context,
 | |
|     scene,
 | |
|     cmdBar,
 | |
|   }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await context.addInitScript(async () => {
 | |
|       localStorage.setItem(
 | |
|         'persistCode',
 | |
|         `// Create a pipe using a sweep.
 | |
| 
 | |
| // Create a path for the sweep.
 | |
| sweepPath = startSketchOn(XZ)
 | |
|   |> startProfile(at = [0.05, 0.05])
 | |
|   |> line(end = [0, 7])
 | |
|   |> tangentialArc(angle = 90, radius = 5)
 | |
|   |> line(end = [-3, 0])
 | |
|   |> tangentialArc(angle = -90, radius = 5)
 | |
|   |> line(end = [0, 7])
 | |
| 
 | |
| sweepSketch = startSketchOn(XY)
 | |
|   |> startProfile(at = [2, 0])
 | |
|   |> arc(angleStart = 0, angleEnd = 360, radius = 2)
 | |
|   |> sweep(path = sweepPath)
 | |
|   |> appearance(
 | |
|        color = '#bb00ff',
 | |
|        metalness = 90,
 | |
|        roughness = 90
 | |
|      )
 | |
| `
 | |
|       )
 | |
|     })
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 1000 })
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     await expect(page, 'expect small color widget').toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   test('code color goober opening window', async ({
 | |
|     page,
 | |
|     context,
 | |
|     scene,
 | |
|     cmdBar,
 | |
|   }) => {
 | |
|     const u = await getUtils(page)
 | |
|     await context.addInitScript(async () => {
 | |
|       localStorage.setItem(
 | |
|         'persistCode',
 | |
|         `// Create a pipe using a sweep.
 | |
| 
 | |
| // Create a path for the sweep.
 | |
| sweepPath = startSketchOn(XZ)
 | |
|   |> startProfile(at = [0.05, 0.05])
 | |
|   |> line(end = [0, 7])
 | |
|   |> tangentialArc(angle = 90, radius = 5)
 | |
|   |> line(end = [-3, 0])
 | |
|   |> tangentialArc(angle = -90, radius = 5)
 | |
|   |> line(end = [0, 7])
 | |
| 
 | |
| sweepSketch = startSketchOn(XY)
 | |
|   |> startProfile(at = [2, 0])
 | |
|   |> arc(angleStart = 0, angleEnd = 360, radius = 2)
 | |
|   |> sweep(path = sweepPath)
 | |
|   |> appearance(
 | |
|        color = "#bb00ff",
 | |
|        metalness = 90,
 | |
|        roughness = 90
 | |
|      )
 | |
| `
 | |
|       )
 | |
|     })
 | |
| 
 | |
|     await page.setViewportSize({ width: 1200, height: 1000 })
 | |
|     await u.waitForAuthSkipAppStart()
 | |
| 
 | |
|     await scene.settled(cmdBar)
 | |
| 
 | |
|     await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
 | |
| 
 | |
|     // Click the color widget
 | |
|     await page.locator('.cm-css-color-picker-wrapper input').click()
 | |
| 
 | |
|     await expect(
 | |
|       page,
 | |
|       'expect small color widget to have window open'
 | |
|     ).toHaveScreenshot({
 | |
|       maxDiffPixels: 100,
 | |
|       mask: lowerRightMasks(page),
 | |
|     })
 | |
|   })
 | |
| })
 |