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