* Add the unexpected token to the error message This is helpful during development where there's nothing to point at the character index. * Update more tests
		
			
				
	
	
		
			497 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			497 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
import { test, expect, Page } from '@playwright/test'
 | 
						|
import * as fsp from 'fs/promises'
 | 
						|
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
 | 
						|
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
 | 
						|
import { bracket } from 'lib/exampleKcl'
 | 
						|
 | 
						|
test.beforeEach(async ({ context, page }) => {
 | 
						|
  await setup(context, page)
 | 
						|
})
 | 
						|
 | 
						|
test.afterEach(async ({ page }, testInfo) => {
 | 
						|
  await tearDown(page, testInfo)
 | 
						|
})
 | 
						|
 | 
						|
test.describe('Regression tests', () => {
 | 
						|
  // bugs we found that don't fit neatly into other categories
 | 
						|
  test('bad model has inline error #3251', async ({ page }) => {
 | 
						|
    // because the model has `line([0,0]..` it is valid code, but the model is invalid
 | 
						|
    // regression test for https://github.com/KittyCAD/modeling-app/issues/3251
 | 
						|
    // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `const sketch2 = startSketchOn("XY")
 | 
						|
const sketch001 = startSketchAt([-0, -0])
 | 
						|
  |> line([0, 0], %)
 | 
						|
  |> line([-4.84, -5.29], %)
 | 
						|
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
						|
  |> close(%)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await page.setViewportSize({ width: 1000, height: 500 })
 | 
						|
 | 
						|
    await u.waitForAuthSkipAppStart()
 | 
						|
 | 
						|
    // error in guter
 | 
						|
    await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
 | 
						|
 | 
						|
    // error text on hover
 | 
						|
    await page.hover('.cm-lint-marker-error')
 | 
						|
    // this is a cryptic error message, fact that all the lines are co-linear from the `line([0,0])` is the issue why
 | 
						|
    // the close doesn't work
 | 
						|
    // when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
 | 
						|
    // this test will need updating
 | 
						|
    const crypticErrorText = `ApiError`
 | 
						|
    await expect(page.getByText(crypticErrorText).first()).toBeVisible()
 | 
						|
  })
 | 
						|
  test('executes on load', async ({ page }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `const sketch001 = startSketchOn('-XZ')
 | 
						|
    |> startProfileAt([-6.95, 4.98], %)
 | 
						|
    |> line([25.1, 0.41], %)
 | 
						|
    |> line([0.73, -14.93], %)
 | 
						|
    |> line([-23.44, 0.52], %)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
    await page.setViewportSize({ width: 1000, height: 500 })
 | 
						|
 | 
						|
    await u.waitForAuthSkipAppStart()
 | 
						|
 | 
						|
    // expand variables section
 | 
						|
    const variablesTabButton = page.getByTestId('variables-pane-button')
 | 
						|
    await variablesTabButton.click()
 | 
						|
 | 
						|
    // can find sketch001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor)
 | 
						|
    // sketch001 only shows up in the variables summary if it's been executed
 | 
						|
    await page.waitForFunction(() => {
 | 
						|
      const variablesElement = document.querySelector(
 | 
						|
        '.pretty-json-container'
 | 
						|
      ) as HTMLDivElement
 | 
						|
      return variablesElement.innerHTML.includes('sketch001')
 | 
						|
    })
 | 
						|
    await expect(
 | 
						|
      page.locator('.pretty-json-container >> text=sketch001')
 | 
						|
    ).toBeVisible()
 | 
						|
  })
 | 
						|
 | 
						|
  test('re-executes', async ({ page }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem('persistCode', `const myVar = 5`)
 | 
						|
    })
 | 
						|
    await page.setViewportSize({ width: 1000, height: 500 })
 | 
						|
 | 
						|
    await u.waitForAuthSkipAppStart()
 | 
						|
 | 
						|
    const variablesTabButton = page.getByTestId('variables-pane-button')
 | 
						|
    await variablesTabButton.click()
 | 
						|
    // expect to see "myVar:5"
 | 
						|
    await expect(
 | 
						|
      page.locator('.pretty-json-container >> text=myVar:5')
 | 
						|
    ).toBeVisible()
 | 
						|
 | 
						|
    // change 5 to 67
 | 
						|
    await page.getByText('const myVar').click()
 | 
						|
    await page.keyboard.press('End')
 | 
						|
    await page.keyboard.press('Backspace')
 | 
						|
    await page.keyboard.type('67')
 | 
						|
 | 
						|
    await expect(
 | 
						|
      page.locator('.pretty-json-container >> text=myVar:67')
 | 
						|
    ).toBeVisible()
 | 
						|
  })
 | 
						|
  test('ProgramMemory can be serialised', async ({ page }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `const part = startSketchOn('XY')
 | 
						|
    |> startProfileAt([0, 0], %)
 | 
						|
    |> line([0, 1], %)
 | 
						|
    |> line([1, 0], %)
 | 
						|
    |> line([0, -1], %)
 | 
						|
    |> close(%)
 | 
						|
    |> extrude(1, %)
 | 
						|
    |> patternLinear3d({
 | 
						|
          axis: [1, 0, 1],
 | 
						|
          repetitions: 3,
 | 
						|
          distance: 6
 | 
						|
        }, %)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
    await page.setViewportSize({ width: 1000, height: 500 })
 | 
						|
 | 
						|
    const messages: string[] = []
 | 
						|
 | 
						|
    // Listen for all console events and push the message text to an array
 | 
						|
    page.on('console', (message) => messages.push(message.text()))
 | 
						|
    await u.waitForAuthSkipAppStart()
 | 
						|
 | 
						|
    // wait for execution done
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
 | 
						|
    const forbiddenMessages = ['cannot serialize tagged newtype variant']
 | 
						|
    forbiddenMessages.forEach((forbiddenMessage) => {
 | 
						|
      messages.forEach((message) => {
 | 
						|
        expect(message).not.toContain(forbiddenMessage)
 | 
						|
      })
 | 
						|
    })
 | 
						|
  })
 | 
						|
  test('ensure the Zoo logo is not a link in browser app', async ({ page }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.setViewportSize({ width: 1000, height: 500 })
 | 
						|
    await u.waitForAuthSkipAppStart()
 | 
						|
 | 
						|
    const zooLogo = page.locator('[data-testid="app-logo"]')
 | 
						|
    // Make sure it's not a link
 | 
						|
    await expect(zooLogo).not.toHaveAttribute('href')
 | 
						|
  })
 | 
						|
  test('Position _ Is Out Of Range... regression test', async ({ page }) => {
 | 
						|
    // SKip on windows, its being weird.
 | 
						|
    test.skip(
 | 
						|
      process.platform === 'win32',
 | 
						|
      'This test is being weird on windows'
 | 
						|
    )
 | 
						|
 | 
						|
    const u = await getUtils(page)
 | 
						|
    // const PUR = 400 / 37.5 //pixeltoUnitRatio
 | 
						|
    await page.setViewportSize({ width: 1200, height: 500 })
 | 
						|
    await page.addInitScript(async () => {
 | 
						|
      localStorage.setItem(
 | 
						|
        'persistCode',
 | 
						|
        `const exampleSketch = startSketchOn("XZ")
 | 
						|
    |> startProfileAt([0, 0], %)
 | 
						|
    |> angledLine({ angle: 50, length: 45 }, %)
 | 
						|
    |> yLineTo(0, %)
 | 
						|
    |> close(%)
 | 
						|
    |>
 | 
						|
 | 
						|
  const example = extrude(5, exampleSketch)
 | 
						|
  shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
 | 
						|
      )
 | 
						|
    })
 | 
						|
 | 
						|
    await expect(async () => {
 | 
						|
      await page.goto('/')
 | 
						|
      await u.waitForPageLoad()
 | 
						|
      // error in guter
 | 
						|
      await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
 | 
						|
        timeout: 1_000,
 | 
						|
      })
 | 
						|
      await page.waitForTimeout(200)
 | 
						|
      // expect it still to be there (sometimes it just clears for a bit?)
 | 
						|
      await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
 | 
						|
        timeout: 1_000,
 | 
						|
      })
 | 
						|
    }).toPass({ timeout: 40_000, intervals: [1_000] })
 | 
						|
 | 
						|
    // error text on hover
 | 
						|
    await page.hover('.cm-lint-marker-error')
 | 
						|
    await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
 | 
						|
 | 
						|
    // Okay execution finished, let's start editing text below the error.
 | 
						|
    await u.codeLocator.click()
 | 
						|
    // Go to the end of the editor
 | 
						|
    // This bug happens when there is a diagnostic in the editor and you try to
 | 
						|
    // edit text below it.
 | 
						|
    // Or delete a huge chunk of text and then try to edit below it.
 | 
						|
    await page.keyboard.press('End')
 | 
						|
    await page.keyboard.down('Shift')
 | 
						|
    await page.keyboard.press('ArrowUp')
 | 
						|
    await page.keyboard.press('ArrowUp')
 | 
						|
    await page.keyboard.press('ArrowUp')
 | 
						|
    await page.keyboard.press('ArrowUp')
 | 
						|
    await page.keyboard.press('ArrowUp')
 | 
						|
    await page.keyboard.press('End')
 | 
						|
    await page.keyboard.up('Shift')
 | 
						|
    await page.keyboard.press('Backspace')
 | 
						|
    await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
    await page.keyboard.type('thing: "blah"', { delay: 100 })
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
    await page.keyboard.press('ArrowLeft')
 | 
						|
 | 
						|
    await expect(page.locator('.cm-content'))
 | 
						|
      .toContainText(`const exampleSketch = startSketchOn("XZ")
 | 
						|
    |> startProfileAt([0, 0], %)
 | 
						|
    |> angledLine({ angle: 50, length: 45 }, %)
 | 
						|
    |> yLineTo(0, %)
 | 
						|
    |> close(%)
 | 
						|
 | 
						|
    thing: "blah"`)
 | 
						|
 | 
						|
    await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
 | 
						|
  })
 | 
						|
 | 
						|
  test('when engine fails export we handle the failure and alert the user', async ({
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await page.addInitScript(
 | 
						|
      async ({ code }) => {
 | 
						|
        localStorage.setItem('persistCode', code)
 | 
						|
        ;(window as any).playwrightSkipFilePicker = true
 | 
						|
      },
 | 
						|
      { code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
 | 
						|
    )
 | 
						|
 | 
						|
    await page.setViewportSize({ width: 1000, height: 500 })
 | 
						|
 | 
						|
    await u.waitForAuthSkipAppStart()
 | 
						|
 | 
						|
    // wait for execution done
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    // expect zero errors in guter
 | 
						|
    await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
 | 
						|
 | 
						|
    // export the model
 | 
						|
    const exportButton = page.getByTestId('export-pane-button')
 | 
						|
    await expect(exportButton).toBeVisible()
 | 
						|
 | 
						|
    // Click the export button
 | 
						|
    await exportButton.click()
 | 
						|
 | 
						|
    // Click the stl.
 | 
						|
    const stlOption = page.getByText('glTF')
 | 
						|
    await expect(stlOption).toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
 | 
						|
    // Click the checkbox
 | 
						|
    const submitButton = page.getByText('Confirm Export')
 | 
						|
    await expect(submitButton).toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
 | 
						|
    // Find the toast.
 | 
						|
    // Look out for the toast message
 | 
						|
    const exportingToastMessage = page.getByText(`Exporting...`)
 | 
						|
    await expect(exportingToastMessage).toBeVisible()
 | 
						|
 | 
						|
    const errorToastMessage = page.getByText(`Error while exporting`)
 | 
						|
    await expect(errorToastMessage).toBeVisible()
 | 
						|
 | 
						|
    const engineErrorToastMessage = page.getByText(`Nothing to export`)
 | 
						|
    await expect(engineErrorToastMessage).toBeVisible()
 | 
						|
 | 
						|
    // Make sure the exporting toast is gone
 | 
						|
    await expect(exportingToastMessage).not.toBeVisible()
 | 
						|
 | 
						|
    // Click the code editor
 | 
						|
    await page.locator('.cm-content').click()
 | 
						|
 | 
						|
    await page.waitForTimeout(2000)
 | 
						|
 | 
						|
    // Expect the toast to be gone
 | 
						|
    await expect(errorToastMessage).not.toBeVisible()
 | 
						|
    await expect(engineErrorToastMessage).not.toBeVisible()
 | 
						|
 | 
						|
    // Now add in code that works.
 | 
						|
    await page.locator('.cm-content').fill(bracket)
 | 
						|
    await page.keyboard.press('End')
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
 | 
						|
    // wait for execution done
 | 
						|
    await u.openDebugPanel()
 | 
						|
    await u.clearCommandLogs()
 | 
						|
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
    await u.closeDebugPanel()
 | 
						|
 | 
						|
    // Now try exporting
 | 
						|
 | 
						|
    // Click the export button
 | 
						|
    await exportButton.click()
 | 
						|
 | 
						|
    // Click the stl.
 | 
						|
    await expect(stlOption).toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
 | 
						|
    // Click the checkbox
 | 
						|
    await expect(submitButton).toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
 | 
						|
    // Find the toast.
 | 
						|
    // Look out for the toast message
 | 
						|
    await expect(exportingToastMessage).toBeVisible()
 | 
						|
 | 
						|
    // Expect it to succeed.
 | 
						|
    await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
 | 
						|
    await expect(errorToastMessage).not.toBeVisible()
 | 
						|
    await expect(engineErrorToastMessage).not.toBeVisible()
 | 
						|
 | 
						|
    const successToastMessage = page.getByText(`Exported successfully`)
 | 
						|
    await expect(successToastMessage).toBeVisible()
 | 
						|
  })
 | 
						|
  test('ensure you can not export while an export is already going', async ({
 | 
						|
    page,
 | 
						|
  }) => {
 | 
						|
    // This is being weird on ubuntu and windows.
 | 
						|
    test.skip(
 | 
						|
      // eslint-disable-next-line jest/valid-title
 | 
						|
      process.platform === 'linux' || process.platform === 'win32',
 | 
						|
      'This test is being weird on ubuntu'
 | 
						|
    )
 | 
						|
 | 
						|
    const u = await getUtils(page)
 | 
						|
    await test.step('Set up the code and durations', async () => {
 | 
						|
      await page.addInitScript(
 | 
						|
        async ({ code }) => {
 | 
						|
          localStorage.setItem('persistCode', code)
 | 
						|
        },
 | 
						|
        {
 | 
						|
          code: bracket,
 | 
						|
        }
 | 
						|
      )
 | 
						|
 | 
						|
      await page.setViewportSize({ width: 1000, height: 500 })
 | 
						|
 | 
						|
      await u.waitForAuthSkipAppStart()
 | 
						|
 | 
						|
      // wait for execution done
 | 
						|
      await u.openDebugPanel()
 | 
						|
      await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
						|
      await u.closeDebugPanel()
 | 
						|
 | 
						|
      // expect zero errors in guter
 | 
						|
      await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
 | 
						|
    })
 | 
						|
 | 
						|
    const errorToastMessage = page.getByText(`Error while exporting`)
 | 
						|
    const exportingToastMessage = page.getByText(`Exporting...`)
 | 
						|
    const engineErrorToastMessage = page.getByText(`Nothing to export`)
 | 
						|
    const alreadyExportingToastMessage = page.getByText(`Already exporting`)
 | 
						|
    const successToastMessage = page.getByText(`Exported successfully`)
 | 
						|
 | 
						|
    await test.step('Blocked second export', async () => {
 | 
						|
      await clickExportButton(page)
 | 
						|
 | 
						|
      await expect(exportingToastMessage).toBeVisible()
 | 
						|
 | 
						|
      await clickExportButton(page)
 | 
						|
 | 
						|
      await test.step('The second export is blocked', async () => {
 | 
						|
        // Find the toast.
 | 
						|
        // Look out for the toast message
 | 
						|
        await expect(exportingToastMessage).toBeVisible()
 | 
						|
        await expect(alreadyExportingToastMessage).toBeVisible()
 | 
						|
 | 
						|
        await page.waitForTimeout(1000)
 | 
						|
      })
 | 
						|
 | 
						|
      await test.step('The first export still succeeds', async () => {
 | 
						|
        await expect(exportingToastMessage).not.toBeVisible()
 | 
						|
        await expect(errorToastMessage).not.toBeVisible()
 | 
						|
        await expect(engineErrorToastMessage).not.toBeVisible()
 | 
						|
 | 
						|
        await expect(successToastMessage).toBeVisible()
 | 
						|
 | 
						|
        await expect(alreadyExportingToastMessage).not.toBeVisible()
 | 
						|
      })
 | 
						|
    })
 | 
						|
 | 
						|
    await test.step('Successful, unblocked export', async () => {
 | 
						|
      // Try exporting again.
 | 
						|
      await clickExportButton(page)
 | 
						|
 | 
						|
      // Find the toast.
 | 
						|
      // Look out for the toast message
 | 
						|
      await expect(exportingToastMessage).toBeVisible()
 | 
						|
 | 
						|
      // Expect it to succeed.
 | 
						|
      await expect(exportingToastMessage).not.toBeVisible()
 | 
						|
      await expect(errorToastMessage).not.toBeVisible()
 | 
						|
      await expect(engineErrorToastMessage).not.toBeVisible()
 | 
						|
      await expect(alreadyExportingToastMessage).not.toBeVisible()
 | 
						|
 | 
						|
      await expect(successToastMessage).toBeVisible()
 | 
						|
    })
 | 
						|
  })
 | 
						|
 | 
						|
  test(
 | 
						|
    `Network health indicator only appears in modeling view`,
 | 
						|
    { tag: '@electron' },
 | 
						|
    async ({ browserName: _ }, testInfo) => {
 | 
						|
      test.skip(
 | 
						|
        process.platform === 'win32',
 | 
						|
        'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
 | 
						|
      )
 | 
						|
      const { electronApp, page } = await setupElectron({
 | 
						|
        testInfo,
 | 
						|
        folderSetupFn: async (dir) => {
 | 
						|
          await fsp.mkdir(`${dir}/bracket`, { recursive: true })
 | 
						|
          await fsp.copyFile(
 | 
						|
            'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
 | 
						|
            `${dir}/bracket/main.kcl`
 | 
						|
          )
 | 
						|
        },
 | 
						|
      })
 | 
						|
 | 
						|
      await page.setViewportSize({ width: 1200, height: 500 })
 | 
						|
      const u = await getUtils(page)
 | 
						|
 | 
						|
      // Locators
 | 
						|
      const projectsHeading = page.getByRole('heading', {
 | 
						|
        name: 'Your projects',
 | 
						|
      })
 | 
						|
      const projectLink = page.getByRole('link', { name: 'bracket' })
 | 
						|
      const networkHealthIndicator = page.getByTestId('network-toggle')
 | 
						|
 | 
						|
      await test.step('Check the home page', async () => {
 | 
						|
        await expect(projectsHeading).toBeVisible()
 | 
						|
        await expect(projectLink).toBeVisible()
 | 
						|
        await expect(networkHealthIndicator).not.toBeVisible()
 | 
						|
      })
 | 
						|
 | 
						|
      await test.step('Open the project', async () => {
 | 
						|
        await projectLink.click()
 | 
						|
      })
 | 
						|
 | 
						|
      await test.step('Check the modeling view', async () => {
 | 
						|
        await expect(networkHealthIndicator).toBeVisible()
 | 
						|
        await expect(networkHealthIndicator).toContainText('Problem')
 | 
						|
        await u.waitForPageLoad()
 | 
						|
        await expect(networkHealthIndicator).toContainText('Connected')
 | 
						|
      })
 | 
						|
 | 
						|
      await electronApp.close()
 | 
						|
    }
 | 
						|
  )
 | 
						|
})
 | 
						|
 | 
						|
async function clickExportButton(page: Page) {
 | 
						|
  await test.step('Running export flow', async () => {
 | 
						|
    // export the model
 | 
						|
    const exportButton = page.getByTestId('export-pane-button')
 | 
						|
    await expect(exportButton).toBeEnabled()
 | 
						|
 | 
						|
    // Click the export button
 | 
						|
    await exportButton.click()
 | 
						|
 | 
						|
    // Click the gltf.
 | 
						|
    const gltfOption = page.getByRole('option', { name: 'glTF' })
 | 
						|
    await expect(gltfOption).toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
 | 
						|
    // Click the checkbox
 | 
						|
    const submitButton = page.getByText('Confirm Export')
 | 
						|
    await expect(submitButton).toBeVisible()
 | 
						|
 | 
						|
    await page.keyboard.press('Enter')
 | 
						|
  })
 | 
						|
}
 |