diff --git a/e2e/playwright/basic-sketch.spec.ts b/e2e/playwright/basic-sketch.spec.ts index 4e024cc3e..91b3110f4 100644 --- a/e2e/playwright/basic-sketch.spec.ts +++ b/e2e/playwright/basic-sketch.spec.ts @@ -1,29 +1,20 @@ -import { test, expect, Page } from '@playwright/test' +import { test, expect, Page } from './zoo-test' import { getUtils, TEST_COLORS, - setup, - tearDown, commonPoints, PERSIST_MODELING_CONTEXT, } from './test-utils' - -test.beforeEach(async ({ context, page }, testInfo) => { - await setup(context, page, testInfo) -}) - -test.afterEach(async ({ page }, testInfo) => { - await tearDown(page, testInfo) -}) +import { HomePageFixture } from './fixtures/homePageFixture' test.setTimeout(120000) -async function doBasicSketch(page: Page, openPanes: string[]) { +async function doBasicSketch(page: Page, homePage: HomePageFixture, openPanes: string[]) { const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) const PUR = 400 / 37.5 //pixeltoUnitRatio - await u.waitForAuthSkipAppStart() + await homePage.goToModelingScene() await u.openDebugPanel() // If we have the code pane open, we should see the code. @@ -148,20 +139,16 @@ async function doBasicSketch(page: Page, openPanes: string[]) { } test.describe('Basic sketch', () => { - test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => { - // Skip on windows it is being weird. - test.skip(process.platform === 'win32', 'Skip on windows') - await doBasicSketch(page, ['code']) - }) + test('code pane open at start', { tag: ['@skipWin'] }, async ({ page, homePage }) => { // Skip on windows it is being weird. + test.skip(process.platform === 'win32', 'Skip on windows') + await doBasicSketch(page, homePage, ['code']) }) - test('code pane closed at start', async ({ page }) => { - // Load the app with the code panes - await page.addInitScript(async (persistModelingContext) => { - localStorage.setItem( - persistModelingContext, - JSON.stringify({ openPanes: [] }) - ) - }, PERSIST_MODELING_CONTEXT) - await doBasicSketch(page, []) - }) + test('code pane closed at start', async ({ page, homePage }) => { // Load the app with the code panes + await page.addInitScript(async (persistModelingContext) => { + localStorage.setItem( + persistModelingContext, + JSON.stringify({ openPanes: [] }) + ) + }, PERSIST_MODELING_CONTEXT) + await doBasicSketch(page, homePage, []) }) }) diff --git a/e2e/playwright/code-pane-and-errors.spec.ts b/e2e/playwright/code-pane-and-errors.spec.ts index 8bfe6d194..39e136af0 100644 --- a/e2e/playwright/code-pane-and-errors.spec.ts +++ b/e2e/playwright/code-pane-and-errors.spec.ts @@ -1,10 +1,7 @@ -import { test, expect } from '@playwright/test' +import { test, expect } from './zoo-test' import { getUtils, - setup, - setupElectron, - tearDown, executorInputPath, } from './test-utils' import { join } from 'path' @@ -12,261 +9,234 @@ import { bracket } from 'lib/exampleKcl' import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates' import fsp from 'fs/promises' -test.beforeEach(async ({ context, page }, testInfo) => { - await setup(context, page, testInfo) -}) - -test.afterEach(async ({ page }, testInfo) => { - await tearDown(page, testInfo) -}) - test.describe('Code pane and errors', () => { - test('Typing KCL errors induces a badge on the code pane button', async ({ - page, - }) => { - const u = await getUtils(page) + test('Typing KCL errors induces a badge on the code pane button', async ({ page, homePage }) => { const u = await getUtils(page) + + // Load the app with the working starter code + await page.addInitScript(() => { + localStorage.setItem( + 'persistCode', + `// Extruded Triangle + sketch001 = startSketchOn('XZ') + |> startProfileAt([0, 0], %) + |> line([10, 0], %) + |> line([-5, 10], %) + |> lineTo([profileStartX(%), profileStartY(%)], %) + |> close(%) + extrude001 = extrude(5, sketch001)` + ) + }) + + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + + // wait for execution done + await u.openDebugPanel() + await u.expectCmdLog('[data-message-type="execution-done"]') + await u.closeDebugPanel() + + // Ensure no badge is present + const codePaneButtonHolder = page.locator('#code-button-holder') + await expect(codePaneButtonHolder).not.toContainText('notification') + + // Delete a character to break the KCL + await u.openKclCodePanel() + await page.getByText('extrude(').click() + await page.keyboard.press('Backspace') + + // Ensure that a badge appears on the button + await expect(codePaneButtonHolder).toContainText('notification') }) - // Load the app with the working starter code - await page.addInitScript(() => { - localStorage.setItem( - 'persistCode', - `// Extruded Triangle -sketch001 = startSketchOn('XZ') - |> startProfileAt([0, 0], %) - |> line([10, 0], %) - |> line([-5, 10], %) - |> lineTo([profileStartX(%), profileStartY(%)], %) - |> close(%) -extrude001 = extrude(5, sketch001)` + test('Opening and closing the code pane will consistently show error diagnostics', async ({ page, homePage }) => { + + const u = await getUtils(page) + + // Load the app with the working starter code + await page.addInitScript((code) => { + localStorage.setItem('persistCode', code) + }, bracket) + + await page.setBodyDimensions({ width: 1200, height: 900 }) + await homePage.goToModelingScene() + + // wait for execution done + await u.openDebugPanel() + await u.expectCmdLog('[data-message-type="execution-done"]') + await u.closeDebugPanel() + + // Ensure we have no errors in the gutter. + await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() + + // Ensure no badge is present + const codePaneButton = page.getByRole('button', { name: 'KCL Code pane' }) + const codePaneButtonHolder = page.locator('#code-button-holder') + await expect(codePaneButtonHolder).not.toContainText('notification') + + // Delete a character to break the KCL + await u.openKclCodePanel() + await page.getByText('thickness, bracketLeg1Sketch)').click() + await page.keyboard.press('Backspace') + + // Ensure that a badge appears on the button + await expect(codePaneButtonHolder).toContainText('notification') + + // Ensure we have an error diagnostic. + await expect(page.locator('.cm-lint-marker-error')).toBeVisible() + + // error text on hover + await page.hover('.cm-lint-marker-error') + await expect(page.locator('.cm-tooltip').first()).toBeVisible() + + // Close the code pane + await codePaneButton.click() + + await page.waitForTimeout(500) + + // Ensure that a badge appears on the button + await expect(codePaneButtonHolder).toContainText('notification') + // Ensure we have no errors in the gutter. + await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() + + // Open the code pane + await u.openKclCodePanel() + + // Ensure that a badge appears on the button + await expect(codePaneButtonHolder).toContainText('notification') + + // Ensure we have an error diagnostic. + await expect(page.locator('.cm-lint-marker-error')).toBeVisible() + + // error text on hover + await page.hover('.cm-lint-marker-error') + await expect(page.locator('.cm-tooltip').first()).toBeVisible() }) + + test('When error is not in view you can click the badge to scroll to it', async ({ page, homePage, context }) => { const u = await getUtils(page) + + // Load the app with the working starter code + await context.addInitScript((code) => { + localStorage.setItem('persistCode', code) + }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) + + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + + await page.waitForTimeout(1000) + + // Ensure badge is present + const codePaneButtonHolder = page.locator('#code-button-holder') + await expect(codePaneButtonHolder).toContainText('notification') + + // Ensure we have no errors in the gutter, since error out of view. + await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() + + // Click the badge. + const badge = page.locator('#code-badge') + await expect(badge).toBeVisible() + await badge.click() + + // Ensure we have an error diagnostic. + await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() + + // Hover over the error to see the error message + await page.hover('.cm-lint-marker-error') + await expect( + page + .getByText( + 'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]' ) - }) + .first() + ).toBeVisible() }) - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - - // Ensure no badge is present - const codePaneButtonHolder = page.locator('#code-button-holder') - await expect(codePaneButtonHolder).not.toContainText('notification') - - // Delete a character to break the KCL - await u.openKclCodePanel() - await page.getByText('extrude(').click() - await page.keyboard.press('Backspace') - - // Ensure that a badge appears on the button - await expect(codePaneButtonHolder).toContainText('notification') - }) - - test('Opening and closing the code pane will consistently show error diagnostics', async ({ - page, - }) => { - await page.goto('http://localhost:3000') - - const u = await getUtils(page) - - // Load the app with the working starter code - await page.addInitScript((code) => { - localStorage.setItem('persistCode', code) - }, bracket) - - await page.setViewportSize({ width: 1200, height: 900 }) - await u.waitForAuthSkipAppStart() - - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - - // Ensure we have no errors in the gutter. - await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() - - // Ensure no badge is present - const codePaneButton = page.getByRole('button', { name: 'KCL Code pane' }) - const codePaneButtonHolder = page.locator('#code-button-holder') - await expect(codePaneButtonHolder).not.toContainText('notification') - - // Delete a character to break the KCL - await u.openKclCodePanel() - await page.getByText('thickness, bracketLeg1Sketch)').click() - await page.keyboard.press('Backspace') - - // Ensure that a badge appears on the button - await expect(codePaneButtonHolder).toContainText('notification') - - // Ensure we have an error diagnostic. - await expect(page.locator('.cm-lint-marker-error')).toBeVisible() - - // error text on hover - await page.hover('.cm-lint-marker-error') - await expect(page.locator('.cm-tooltip').first()).toBeVisible() - - // Close the code pane - await codePaneButton.click() - - await page.waitForTimeout(500) - - // Ensure that a badge appears on the button - await expect(codePaneButtonHolder).toContainText('notification') - // Ensure we have no errors in the gutter. - await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() - - // Open the code pane - await u.openKclCodePanel() - - // Ensure that a badge appears on the button - await expect(codePaneButtonHolder).toContainText('notification') - - // Ensure we have an error diagnostic. - await expect(page.locator('.cm-lint-marker-error')).toBeVisible() - - // error text on hover - await page.hover('.cm-lint-marker-error') - await expect(page.locator('.cm-tooltip').first()).toBeVisible() - }) - - test('When error is not in view you can click the badge to scroll to it', async ({ - page, - }) => { - const u = await getUtils(page) - - // Load the app with the working starter code - await page.addInitScript((code) => { - localStorage.setItem('persistCode', code) - }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) - - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - - await page.waitForTimeout(1000) - - // Ensure badge is present - const codePaneButtonHolder = page.locator('#code-button-holder') - await expect(codePaneButtonHolder).toContainText('notification') - - // Ensure we have no errors in the gutter, since error out of view. - await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() - - // Click the badge. - const badge = page.locator('#code-badge') - await expect(badge).toBeVisible() - await badge.click() - - // Ensure we have an error diagnostic. - await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() - - // Hover over the error to see the error message - await page.hover('.cm-lint-marker-error') - await expect( - page - .getByText( - 'sketch profile must lie entirely on one side of the revolution axis' - ) - .first() - ).toBeVisible() - }) - - test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ - page, - }) => { - const u = await getUtils(page) - - // Load the app with the working starter code - await page.addInitScript((code) => { - localStorage.setItem('persistCode', code) - }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) - - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - - await page.waitForTimeout(1000) - - // Ensure badge is present - const codePaneButtonHolder = page.locator('#code-button-holder') - await expect(codePaneButtonHolder).toContainText('notification') - - // Ensure we have no errors in the gutter, since error out of view. - await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() - - // click in the editor to focus it - await page.locator('.cm-content').click() - - await page.waitForTimeout(500) - - // go to the start of the editor and enter more text which will trigger - // a lint error. - // GO to the start of the editor. - 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('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('Home') - await page.keyboard.type('foo_bar = 1') - await page.waitForTimeout(500) - await page.keyboard.press('Enter') - - // ensure we have a lint error - await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() - - // Click the badge. - const badge = page.locator('#code-badge') - await expect(badge).toBeVisible() - await badge.click() - - // Ensure we have an error diagnostic. - await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() - - // Hover over the error to see the error message - await page.hover('.cm-lint-marker-error') - await expect( - page - .getByText( - 'sketch profile must lie entirely on one side of the revolution axis' - ) - .first() - ).toBeVisible() - }) + test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ context, page, homePage }) => { const u = await getUtils(page) + + // Load the app with the working starter code + await context.addInitScript((code) => { + localStorage.setItem('persistCode', code) + }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) + + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + + await page.waitForTimeout(1000) + + // Ensure badge is present + const codePaneButtonHolder = page.locator('#code-button-holder') + await expect(codePaneButtonHolder).toContainText('notification') + + // Ensure we have no errors in the gutter, since error out of view. + await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() + + // click in the editor to focus it + await page.locator('.cm-content').click() + + await page.waitForTimeout(500) + + // go to the start of the editor and enter more text which will trigger + // a lint error. + // GO to the start of the editor. + 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('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('Home') + await page.keyboard.type('foo_bar = 1') + await page.waitForTimeout(500) + await page.keyboard.press('Enter') + + // ensure we have a lint error + await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible() + + // Click the badge. + const badge = page.locator('#code-badge') + await expect(badge).toBeVisible() + await badge.click() + + // Ensure we have an error diagnostic. + await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() + + // Hover over the error to see the error message + await page.hover('.cm-lint-marker-error') + await expect( + page + .getByText( + 'sketch profile must lie entirely on one side of the revolution axis' + ) + .first() + ).toBeVisible() }) }) test( 'Opening multiple panes persists when switching projects', { tag: '@electron' }, - async ({ browserName }, testInfo) => { + async ({ context, browserName, page }, testInfo) => { // Setup multiple projects. - const { electronApp, page } = await setupElectron({ - testInfo, - folderSetupFn: async (dir) => { - const routerTemplateDir = join(dir, 'router-template-slate') - const bracketDir = join(dir, 'bracket') - await Promise.all([ - fsp.mkdir(routerTemplateDir, { recursive: true }), - fsp.mkdir(bracketDir, { recursive: true }), - ]) - await Promise.all([ - fsp.copyFile( - executorInputPath('router-template-slate.kcl'), - join(routerTemplateDir, 'main.kcl') - ), - fsp.copyFile( - executorInputPath('focusrite_scarlett_mounting_braket.kcl'), - join(bracketDir, 'main.kcl') - ), - ]) - }, + context.folderSetupFn(async (dir) => { + const routerTemplateDir = join(dir, 'router-template-slate') + const bracketDir = join(dir, 'bracket') + await Promise.all([ + fsp.mkdir(routerTemplateDir, { recursive: true }), + fsp.mkdir(bracketDir, { recursive: true }), + ]) + await Promise.all([ + fsp.copyFile( + executorInputPath('router-template-slate.kcl'), + join(routerTemplateDir, 'main.kcl') + ), + fsp.copyFile( + executorInputPath('focusrite_scarlett_mounting_braket.kcl'), + join(bracketDir, 'main.kcl') + ), + ]) }) const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) await test.step('Opening the bracket project should load', async () => { await expect(page.getByText('bracket')).toBeVisible() @@ -309,30 +279,21 @@ test( await expect(page.locator('#variables-pane')).toBeVisible() await expect(page.locator('#logs-pane')).toBeVisible() }) - - await electronApp.close() } ) test( 'external change of file contents are reflected in editor', { tag: '@electron' }, - async ({ browserName }, testInfo) => { + async ({ context, browserName, page }, testInfo) => { const PROJECT_DIR_NAME = 'lee-was-here' - const { - electronApp, - page, - dir: projectsDir, - } = await setupElectron({ - testInfo, - folderSetupFn: async (dir) => { - const aProjectDir = join(dir, PROJECT_DIR_NAME) - await fsp.mkdir(aProjectDir, { recursive: true }) - }, + const { dir: projectsDir, } = await context.folderSetupFn(async (dir) => { + const aProjectDir = join(dir, PROJECT_DIR_NAME) + await fsp.mkdir(aProjectDir, { recursive: true }) }) const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) await test.step('Open the project', async () => { await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible() @@ -351,7 +312,5 @@ test( ) await u.editorTextMatches(content) }) - - await electronApp.close() } ) diff --git a/e2e/playwright/fixtures/fixtureSetup.ts b/e2e/playwright/fixtures/fixtureSetup.ts index 67066c61c..b43f0de1f 100644 --- a/e2e/playwright/fixtures/fixtureSetup.ts +++ b/e2e/playwright/fixtures/fixtureSetup.ts @@ -65,6 +65,7 @@ export class AuthenticatedTronApp { public readonly testInfo: TestInfo public electronApp?: ElectronApplication public readonly viewPortSize = { width: 1200, height: 500 } + public dir: string = "" constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { this._page = page @@ -80,7 +81,7 @@ export class AuthenticatedTronApp { appSettings?: Partial } = { fixtures: {} } ) { - const { electronApp, page, context } = await setupElectron({ + const { electronApp, page, context, dir } = await setupElectron({ testInfo: this.testInfo, folderSetupFn: arg.folderSetupFn, cleanProjectDir: arg.cleanProjectDir, @@ -89,6 +90,7 @@ export class AuthenticatedTronApp { this.page = page this.context = context this.electronApp = electronApp + this.dir = dir // Setup localStorage, addCookies, reload await setup(this.context, this.page, this.testInfo) diff --git a/e2e/playwright/storageStates.ts b/e2e/playwright/storageStates.ts index ab907d6fa..30f0b5b67 100644 --- a/e2e/playwright/storageStates.ts +++ b/e2e/playwright/storageStates.ts @@ -109,242 +109,21 @@ keychain = startSketchOn("XY") |> close(%) |> extrude(thickness, %) -// generated from /home/paultag/Downloads/zma-logomark.svg -fn svg = (surface, origin, depth) => { - let a0 = surface |> startProfileAt([origin[0] + 45.430427, origin[1] + -14.627736], %) - |> bezierCurve({ - control1: [ 0, 0.764157 ], - control2: [ 0, 1.528314 ], - to: [ 0, 2.292469 ] - }, %) - |> bezierCurve({ - control1: [ -3.03202, 0 ], - control2: [ -6.064039, 0 ], - to: [ -9.09606, 0 ] - }, %) - |> bezierCurve({ - control1: [ 0, -1.077657 ], - control2: [ 0, -2.155312 ], - to: [ 0, -3.232969 ] - }, %) - |> bezierCurve({ - control1: [ 2.741805, 0 ], - control2: [ 5.483613, 0 ], - to: [ 8.225417, 0 ] - }, %) - |> bezierCurve({ - control1: [ -2.740682, -2.961815 ], - control2: [ -5.490342, -5.925794 ], - to: [ -8.225417, -8.886255 ] - }, %) - |> bezierCurve({ - control1: [ 0, -0.723995 ], - control2: [ 0, -1.447988 ], - to: [ 0, -2.171981 ] - }, %) - |> bezierCurve({ - control1: [ 0.712124, 0.05061 ], - control2: [ 1.511636, -0.09877 ], - to: [ 2.172096, 0.07005 ] - }, %) - |> bezierCurve({ - control1: [ 0.68573, 0.740811 ], - control2: [ 1.371459, 1.481622 ], - to: [ 2.057187, 2.222436 ] - }, %) - |> bezierCurve({ - control1: [ 0, -0.76416 ], - control2: [ 0, -1.52832 ], - to: [ 0, -2.29248 ] - }, %) - |> bezierCurve({ - control1: [ 3.032013, 0 ], - control2: [ 6.064026, 0 ], - to: [ 9.096038, 0 ] - }, %) - |> bezierCurve({ - control1: [ 0, 1.077657 ], - control2: [ 0, 2.155314 ], - to: [ 0, 3.232973 ] - }, %) - |> bezierCurve({ - control1: [ -2.741312, 0 ], - control2: [ -5.482623, 0 ], - to: [ -8.223936, 0 ] - }, %) - |> bezierCurve({ - control1: [ 2.741313, 2.961108 ], - control2: [ 5.482624, 5.922216 ], - to: [ 8.223936, 8.883325 ] - }, %) - |> bezierCurve({ - control1: [ 0, 0.724968 ], - control2: [ 0, 1.449938 ], - to: [ 0, 2.174907 ] - }, %) - |> bezierCurve({ - control1: [ -0.712656, -0.05145 ], - control2: [ -1.512554, 0.09643 ], - to: [ -2.173592, -0.07298 ] - }, %) - |> bezierCurve({ - control1: [ -0.685222, -0.739834 ], - control2: [ -1.370445, -1.479669 ], - to: [ -2.055669, -2.219505 ] - }, %) - |> close(%) - |> extrude(depth, %) +keychain1 = startSketchOn("XY") + |> startProfileAt([0, 0], %) + |> lineTo([width, 0], %) + |> lineTo([width, height], %) + |> lineTo([0, height], %) + |> close(%) + |> extrude(thickness, %) - let a1 = surface |> startProfileAt([origin[0] + 57.920488, origin[1] + -15.244943], %) - |> bezierCurve({ - control1: [ -2.78904, 0.106635 ], - control2: [ -5.052548, -2.969529 ], - to: [ -4.055141, -5.598369 ] - }, %) - |> bezierCurve({ - control1: [ 0.841523, -0.918736 ], - control2: [ 0.439412, -1.541892 ], - to: [ -0.368488, -2.214378 ] - }, %) - |> bezierCurve({ - control1: [ -0.418245, -0.448461 ], - control2: [ -0.836489, -0.896922 ], - to: [ -1.254732, -1.345384 ] - }, %) - |> bezierCurve({ - control1: [ -2.76806, 2.995359 ], - control2: [ -2.32667, 8.18409 ], - to: [ 0.897655, 10.678932 ] - }, %) - |> bezierCurve({ - control1: [ 2.562822, 2.186098 ], - control2: [ 6.605111, 2.28043 ], - to: [ 9.271202, 0.226476 ] - }, %) - |> bezierCurve({ - control1: [ -0.743744, -0.797465 ], - control2: [ -1.487487, -1.594932 ], - to: [ -2.231232, -2.392397 ] - }, %) - |> bezierCurve({ - control1: [ -0.672938, 0.421422 ], - control2: [ -1.465362, 0.646946 ], - to: [ -2.259264, 0.64512 ] - }, %) - |> close(%) - |> extrude(depth, %) - - let a2 = surface |> startProfileAt([origin[0] + 62.19406300000001, origin[1] + -19.500698999999997], %) - |> bezierCurve({ - control1: [ 0.302938, 1.281141 ], - control2: [ -1.53575, 2.434288 ], - to: [ -0.10908, 3.279477 ] - }, %) - |> bezierCurve({ - control1: [ 0.504637, 0.54145 ], - control2: [ 1.009273, 1.082899 ], - to: [ 1.513909, 1.624348 ] - }, %) - |> bezierCurve({ - control1: [ 2.767778, -2.995425 ], - control2: [ 2.327135, -8.184384 ], - to: [ -0.897661, -10.679047 ] - }, %) - |> bezierCurve({ - control1: [ -2.562947, -2.186022 ], - control2: [ -6.604089, -2.279606 ], - to: [ -9.271196, -0.227813 ] - }, %) - |> bezierCurve({ - control1: [ 0.744231, 0.797952 ], - control2: [ 1.488461, 1.595904 ], - to: [ 2.232692, 2.393856 ] - }, %) - |> bezierCurve({ - control1: [ 2.302377, -1.564629 ], - control2: [ 5.793126, -0.15358 ], - to: [ 6.396577, 2.547372 ] - }, %) - |> bezierCurve({ - control1: [ 0.08981, 0.346302 ], - control2: [ 0.134865, 0.704078 ], - to: [ 0.13476, 1.061807 ] - }, %) - |> close(%) - |> extrude(depth, %) - - let a3 = surface |> startProfileAt([origin[0] + 74.124866, origin[1] + -15.244943], %) - |> bezierCurve({ - control1: [ -2.78904, 0.106635 ], - control2: [ -5.052549, -2.969529 ], - to: [ -4.055142, -5.598369 ] - }, %) - |> bezierCurve({ - control1: [ 0.841527, -0.918738 ], - control2: [ 0.43941, -1.541892 ], - to: [ -0.368497, -2.214367 ] - }, %) - |> bezierCurve({ - control1: [ -0.418254, -0.448466 ], - control2: [ -0.836507, -0.896931 ], - to: [ -1.254761, -1.345395 ] - }, %) - |> bezierCurve({ - control1: [ -2.768019, 2.995371 ], - control2: [ -2.326624, 8.184088 ], - to: [ 0.897678, 10.678932 ] - }, %) - |> bezierCurve({ - control1: [ 2.56289, 2.186191 ], - control2: [ 6.60516, 2.280307 ], - to: [ 9.271371, 0.226476 ] - }, %) - |> bezierCurve({ - control1: [ -0.743808, -0.797465 ], - control2: [ -1.487616, -1.594932 ], - to: [ -2.231424, -2.392397 ] - }, %) - |> bezierCurve({ - control1: [ -0.672916, 0.421433 ], - control2: [ -1.465344, 0.646926 ], - to: [ -2.259225, 0.64512 ] - }, %) - |> close(%) - |> extrude(depth, %) - - let a4 = surface |> startProfileAt([origin[0] + 77.57333899999998, origin[1] + -16.989262999999998], %) - |> bezierCurve({ - control1: [ 0.743298, 0.797463 ], - control2: [ 1.486592, 1.594926 ], - to: [ 2.229888, 2.392389 ] - }, %) - |> bezierCurve({ - control1: [ 2.767827, -2.995393 ], - control2: [ 2.327103, -8.184396 ], - to: [ -0.897672, -10.679047 ] - }, %) - |> bezierCurve({ - control1: [ -2.562939, -2.186037 ], - control2: [ -6.604077, -2.279589 ], - to: [ -9.271185, -0.227813 ] - }, %) - |> bezierCurve({ - control1: [ 0.744243, 0.797952 ], - control2: [ 1.488486, 1.595904 ], - to: [ 2.232729, 2.393856 ] - }, %) - |> bezierCurve({ - control1: [ 2.302394, -1.564623 ], - control2: [ 5.793201, -0.153598 ], - to: [ 6.396692, 2.547372 ] - }, %) - |> bezierCurve({ - control1: [ 0.32074, 1.215468 ], - control2: [ 0.06159, 2.564765 ], - to: [ -0.690452, 3.573243 ] - }, %) - |> close(%) - |> extrude(depth, %) +keychain2 = startSketchOn("XY") + |> startProfileAt([0, 0], %) + |> lineTo([width, 0], %) + |> lineTo([width, height], %) + |> lineTo([0, height], %) + |> close(%) + |> extrude(thickness, %) box = startSketchOn('XY') |> startProfileAt([0, 0], %) @@ -354,7 +133,7 @@ box = startSketchOn('XY') |> close(%) |> extrude(10, %) - sketch001 = startSketchOn(box, revolveAxis) +sketch001 = startSketchOn(box, revolveAxis) |> startProfileAt([5, 10], %) |> line([0, -10], %) |> line([2, 0], %) @@ -364,18 +143,12 @@ box = startSketchOn('XY') axis: revolveAxis, angle: 90 }, %) - return 0 -} +sketch001 = startSketchOn('XZ') + |> startProfileAt([0.0, 0.0], %) + |> xLine(0.0, %) + |> close(%) - -svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness) - -startSketchOn(keychain, 'end') - |> circle({ center: [ - width / 2, - height - (keychainHoleSize + 1.5) - ], radius: keychainHoleSize }, %) - |> extrude(-thickness, %)` +` export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `thing = 1` diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index e63e7a301..1312284c7 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -11,7 +11,7 @@ import { import { EngineCommand } from 'lang/std/artifactGraph' import fsp from 'fs/promises' import fsSync from 'fs' -import { join } from 'path' +import path from 'path' import pixelMatch from 'pixelmatch' import { PNG } from 'pngjs' import { Protocol } from 'playwright-core/types/protocol' @@ -682,6 +682,29 @@ export const makeTemplate: ( } } +const moveDownloadedFileTo = async (page: Page, toLocation: string) => { + await fsp.mkdir(path.dirname(toLocation), { recursive: true }) + + const downloadDir = path.resolve( + page.TEST_SETTINGS_FILE_KEY, + "downloads-during-playwright" + ) + + // Expect there to be at least one file + expect.poll(async () => { + const files = await fsp.readdir(downloadDir) + return files.length + }).toBe(1) + + // Go through the downloads dir and move files to new location + const files = await fsp.readdir(downloadDir) + + // Assumption: only ever one file here. + for (let file of files) { + await fsp.rename(path.resolve(downloadDir, file), toLocation) + } +} + export interface Paths { modelPath: string imagePath: string @@ -694,7 +717,8 @@ export const doExport = async ( exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' ): Promise => { if (exportFrom === 'dropdown') { - await page.getByRole('button', { name: APP_NAME }).click() + await page.getByTestId('project-sidebar-toggle').click() + const exportMenuButton = page.getByRole('button', { name: 'Export current part', }) @@ -735,25 +759,12 @@ export const doExport = async ( } await expect(page.getByText('Confirm Export')).toBeVisible() - const getPromiseAndResolve = () => { - let resolve: any = () => {} - const promise = new Promise((r) => { - resolve = r - }) - return [promise, resolve] - } - - const [downloadPromise1, downloadResolve1] = getPromiseAndResolve() - let downloadCnt = 0 - - if (exportFrom === 'dropdown') - page.on('download', async (download) => { - if (downloadCnt === 0) { - downloadResolve1(download) - } - downloadCnt++ - }) await page.getByRole('button', { name: 'Submit command' }).click() + + // This usually happens immediately after. If we're too slow we don't + // catch it. + await expect(page.getByText('Exported successfully')).toBeVisible() + if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') { return { modelPath: '', @@ -763,15 +774,12 @@ export const doExport = async ( } // Handle download - const download = await downloadPromise1 const downloadLocationer = (extra = '', isImage = false) => `./e2e/playwright/export-snapshots/${output.type}-${ 'storage' in output ? output.storage : '' }${extra}.${isImage ? 'png' : output.type}` const downloadLocation = downloadLocationer() - await download.saveAs(downloadLocation) - if (output.type === 'step') { // stable timestamps for step files const fileContents = await fsp.readFile(downloadLocation, 'utf-8') @@ -780,6 +788,12 @@ export const doExport = async ( '1970-01-01T00:00:00.0+00:00' ) await fsp.writeFile(downloadLocation, newFileContents) + } else { + // By default all files are downloaded to the same place in playwright + // (declared in src/lib/exportSave) + // To remain consistent with our old web tests, we want to move some downloads + // (images) to another directory. + await moveDownloadedFileTo(page, downloadLocation) } return { @@ -818,12 +832,13 @@ export async function setup( testInfo?: TestInfo ) { await context.addInitScript( - async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => { + async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY, PLAYWRIGHT_TEST_DIR }) => { localStorage.clear() localStorage.setItem('TOKEN_PERSIST_KEY', token) localStorage.setItem('persistCode', ``) localStorage.setItem(settingsKey, settings) localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true') + localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR) }, { token: secrets.token, @@ -840,6 +855,7 @@ export async function setup( } as Partial, }), IS_PLAYWRIGHT_KEY, + PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory, } ) @@ -901,11 +917,13 @@ export async function setupElectron({ const context = electronApp.context() const page = await electronApp.firstWindow() + page.TEST_SETTINGS_FILE_KEY = projectDirName + context.on('console', console.log) page.on('console', console.log) if (cleanProjectDir) { - const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) + const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME) const settingsOverrides = TOML.stringify( appSettings ? { @@ -1022,7 +1040,7 @@ export async function createProject({ } export function executorInputPath(fileName: string): string { - return join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName) + return path.join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName) } export async function doAndWaitForImageDiff( diff --git a/e2e/playwright/various.spec.ts b/e2e/playwright/various.spec.ts index e2ce53138..3b4767cd4 100644 --- a/e2e/playwright/various.spec.ts +++ b/e2e/playwright/various.spec.ts @@ -1,55 +1,45 @@ -import { test, expect } from '@playwright/test' +import { test, expect } from './zoo-test' -import { doExport, getUtils, makeTemplate, setup, tearDown } from './test-utils' +import { doExport, getUtils, makeTemplate } from './test-utils' -test.beforeEach(async ({ context, page }, testInfo) => { - await setup(context, page, testInfo) -}) - -test.afterEach(async ({ page }, testInfo) => { - await tearDown(page, testInfo) -}) - -test('Units menu', async ({ page }) => { +test.fixme('Units menu', async ({ page, homePage }) => { const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() +await page.setBodyDimensions({ width: 1200, height: 500 }) +await homePage.goToModelingScene() - const unitsMenuButton = page.getByRole('button', { - name: 'Current Units', - exact: false, - }) - await expect(unitsMenuButton).toBeVisible() - await expect(unitsMenuButton).toContainText('in') - - await unitsMenuButton.click() - const millimetersButton = page.getByRole('button', { name: 'Millimeters' }) - - await expect(millimetersButton).toBeVisible() - await millimetersButton.click() - - // Look out for the toast message - const toastMessage = page.getByText( - `Set default unit to "mm" for this project` - ) - await expect(toastMessage).toBeVisible() - - // Verify that the popover has closed - await expect(millimetersButton).not.toBeAttached() - - // Verify that the button label has updated - await expect(unitsMenuButton).toContainText('mm') +const unitsMenuButton = page.getByRole('button', { + name: 'Current Units', + exact: false, }) +await expect(unitsMenuButton).toBeVisible() +await expect(unitsMenuButton).toContainText('in') -test('Successful export shows a success toast', async ({ page }) => { - // FYI this test doesn't work with only engine running locally - // And you will need to have the KittyCAD CLI installed - const u = await getUtils(page) - await page.addInitScript(async () => { - ;(window as any).playwrightSkipFilePicker = true - localStorage.setItem( - 'persistCode', - `topAng = 25 +await unitsMenuButton.click() +const millimetersButton = page.getByRole('button', { name: 'Millimeters' }) + +await expect(millimetersButton).toBeVisible() +await millimetersButton.click() + +// Look out for the toast message +const toastMessage = page.getByText( + `Set default unit to "mm" for this project` +) +await expect(toastMessage).toBeVisible() + +// Verify that the popover has closed +await expect(millimetersButton).not.toBeAttached() + +// Verify that the button label has updated +await expect(unitsMenuButton).toContainText('mm') }) + +test('Successful export shows a success toast', async ({ page, homePage }) => { // FYI this test doesn't work with only engine running locally +// And you will need to have the KittyCAD CLI installed +const u = await getUtils(page) +await page.addInitScript(async () => { + ;(window as any).playwrightSkipFilePicker = true + localStorage.setItem( + 'persistCode', + `topAng = 25 bottomAng = 35 baseLen = 3.5 baseHeight = 1 @@ -57,204 +47,188 @@ totalHeightHalf = 2 armThick = 0.5 totalLen = 9.5 part001 = startSketchOn('-XZ') - |> startProfileAt([0, 0], %) - |> yLine(baseHeight, %) - |> xLine(baseLen, %) - |> angledLineToY({ - angle: topAng, - to: totalHeightHalf, - }, %, $seg04) - |> xLineTo(totalLen, %, $seg03) - |> yLine(-armThick, %, $seg01) - |> angledLineThatIntersects({ - angle: HALF_TURN, - offset: -armThick, - intersectTag: seg04 - }, %) - |> angledLineToY([segAng(seg04) + 180, ZERO], %) - |> angledLineToY({ - angle: -bottomAng, - to: -totalHeightHalf - armThick, - }, %, $seg02) - |> xLineTo(segEndX(seg03) + 0, %) - |> yLine(-segLen(seg01), %) - |> angledLineThatIntersects({ - angle: HALF_TURN, - offset: -armThick, - intersectTag: seg02 - }, %) - |> angledLineToY([segAng(seg02) + 180, -baseHeight], %) - |> xLineTo(ZERO, %) - |> close(%) - |> extrude(4, %)` - ) - }) - await page.setViewportSize({ width: 1200, height: 500 }) - - await u.waitForAuthSkipAppStart() - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.waitForCmdReceive('extrude') - await page.waitForTimeout(1000) - await u.clearAndCloseDebugPanel() - - await doExport( - { - type: 'gltf', - storage: 'embedded', - presentation: 'pretty', - }, - page +|> startProfileAt([0, 0], %) +|> yLine(baseHeight, %) +|> xLine(baseLen, %) +|> angledLineToY({ + angle: topAng, + to: totalHeightHalf, + }, %, $seg04) +|> xLineTo(totalLen, %, $seg03) +|> yLine(-armThick, %, $seg01) +|> angledLineThatIntersects({ + angle: HALF_TURN, + offset: -armThick, + intersectTag: seg04 + }, %) +|> angledLineToY([segAng(seg04) + 180, ZERO], %) +|> angledLineToY({ + angle: -bottomAng, + to: -totalHeightHalf - armThick, + }, %, $seg02) +|> xLineTo(segEndX(seg03) + 0, %) +|> yLine(-segLen(seg01), %) +|> angledLineThatIntersects({ + angle: HALF_TURN, + offset: -armThick, + intersectTag: seg02 + }, %) +|> angledLineToY([segAng(seg02) + 180, -baseHeight], %) +|> xLineTo(ZERO, %) +|> close(%) +|> extrude(4, %)` ) +}) +await page.setBodyDimensions({ width: 1200, height: 500 }) + +await homePage.goToModelingScene() +await u.openDebugPanel() +await u.expectCmdLog('[data-message-type="execution-done"]') +await u.waitForCmdReceive('extrude') +await page.waitForTimeout(1000) +await u.clearAndCloseDebugPanel() + +await doExport( + { + type: 'gltf', + storage: 'embedded', + presentation: 'pretty', + }, + page +) - // This is the main thing we're testing, - // We test the export functionality across all - // file types in snapshot-tests.spec.ts - await expect(page.getByText('Exported successfully')).toBeVisible() }) -test('Paste should not work unless an input is focused', async ({ - page, - browserName, -}) => { - // To run this test locally, uncomment Firefox in playwright.config.ts - test.skip( - browserName !== 'firefox', - "This bug is really Firefox-only, which we don't run in CI." +test('Paste should not work unless an input is focused', async ({ page, browserName, homePage }) => { // To run this test locally, uncomment Firefox in playwright.config.ts +test.skip( + browserName !== 'firefox', + "This bug is really Firefox-only, which we don't run in CI." +) +const u = await getUtils(page) +await page.setBodyDimensions({ width: 1200, height: 500 }) +await homePage.goToModelingScene() +await page + .getByRole('button', { name: 'Start Sketch' }) + .waitFor({ state: 'visible' }) + +const codeEditorText = page.locator('.cm-content') +const pasteContent = `// was this pasted?` +const typeContent = `// this should be typed` + +// Load text into the clipboard +await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent) + +// Focus the text editor +await codeEditorText.focus() + +// Show that we can type into it +await page.keyboard.type(typeContent) +await page.keyboard.press('Enter') + +// Paste without the code pane focused +await codeEditorText.blur() +await page.keyboard.press('ControlOrMeta+KeyV') + +// Show that the paste didn't work but typing did +await expect(codeEditorText).not.toContainText(pasteContent) +await expect(codeEditorText).toContainText(typeContent) + +// Paste with the code editor focused +// Following this guidance: https://github.com/microsoft/playwright/issues/8114 +await codeEditorText.focus() +await page.keyboard.press('ControlOrMeta+KeyV') +await expect( + await page.evaluate( + () => document.querySelector('.cm-content')?.textContent ) - const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - await page - .getByRole('button', { name: 'Start Sketch' }) - .waitFor({ state: 'visible' }) +).toContain(pasteContent) }) - const codeEditorText = page.locator('.cm-content') - const pasteContent = `// was this pasted?` - const typeContent = `// this should be typed` +test('Keyboard shortcuts can be viewed through the help menu', async ({ page, homePage }) => { const u = await getUtils(page) +await page.setBodyDimensions({ width: 1200, height: 500 }) +await homePage.goToModelingScene() - // Load text into the clipboard - await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent) +await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' }) +await page + .getByRole('button', { name: 'Start Sketch' }) + .waitFor({ state: 'visible' }) - // Focus the text editor - await codeEditorText.focus() +// Open the help menu +await page.getByRole('button', { name: 'Help and resources' }).click() - // Show that we can type into it - await page.keyboard.type(typeContent) - await page.keyboard.press('Enter') +// Open the keyboard shortcuts +await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click() - // Paste without the code pane focused - await codeEditorText.blur() - await page.keyboard.press('ControlOrMeta+KeyV') - - // Show that the paste didn't work but typing did - await expect(codeEditorText).not.toContainText(pasteContent) - await expect(codeEditorText).toContainText(typeContent) - - // Paste with the code editor focused - // Following this guidance: https://github.com/microsoft/playwright/issues/8114 - await codeEditorText.focus() - await page.keyboard.press('ControlOrMeta+KeyV') - await expect( - await page.evaluate( - () => document.querySelector('.cm-content')?.textContent - ) - ).toContain(pasteContent) +// Verify the URL and that you can see a list of shortcuts +await expect.poll(() => page.url()).toContain('?tab=keybindings') +await expect( + page.getByRole('heading', { name: 'Enter Sketch Mode' }) +).toBeAttached() }) -test('Keyboard shortcuts can be viewed through the help menu', async ({ - page, -}) => { - const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() +test('First escape in tool pops you out of tool, second exits sketch mode', async ({ page, homePage }) => { // Wait for the app to be ready for use +const u = await getUtils(page) +await page.setBodyDimensions({ width: 1200, height: 500 }) - await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) - await page - .getByRole('button', { name: 'Start Sketch' }) - .waitFor({ state: 'visible' }) +await homePage.goToModelingScene() +await u.openDebugPanel() +await u.expectCmdLog('[data-message-type="execution-done"]') +await u.closeDebugPanel() - // Open the help menu - await page.getByRole('button', { name: 'Help and resources' }).click() - - // Open the keyboard shortcuts - await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click() - - // Verify the URL and that you can see a list of shortcuts - await expect(page.url()).toContain('?tab=keybindings') - await expect( - page.getByRole('heading', { name: 'Enter Sketch Mode' }) - ).toBeAttached() +const lineButton = page.getByRole('button', { + name: 'line Line', + exact: true, +}) +const arcButton = page.getByRole('button', { + name: 'arc Tangential Arc', + exact: true, }) -test('First escape in tool pops you out of tool, second exits sketch mode', async ({ - page, -}) => { - // Wait for the app to be ready for use - const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) +// Test these hotkeys perform actions when +// focus is on the canvas +await page.mouse.move(600, 250) +await page.mouse.click(600, 250) - await u.waitForAuthSkipAppStart() - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() +// Start a sketch +await page.keyboard.press('s') +await page.mouse.move(800, 300) +await page.mouse.click(800, 300) +await page.waitForTimeout(1000) +await expect(lineButton).toBeVisible() +await expect(lineButton).toHaveAttribute('aria-pressed', 'true') - const lineButton = page.getByRole('button', { - name: 'line Line', - exact: true, - }) - const arcButton = page.getByRole('button', { - name: 'arc Tangential Arc', - exact: true, - }) +// Draw a line +await page.mouse.move(700, 200, { steps: 5 }) +await page.mouse.click(700, 200) +await page.mouse.move(800, 250, { steps: 5 }) +await page.mouse.click(800, 250) +// Unequip line tool +await page.keyboard.press('Escape') +// Make sure we didn't pop out of sketch mode. +await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible() +await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true') +// Equip arc tool +await page.keyboard.press('a') +await expect(arcButton).toHaveAttribute('aria-pressed', 'true') +await page.mouse.move(1000, 100, { steps: 5 }) +await page.mouse.click(1000, 100) +await page.keyboard.press('Escape') +await page.keyboard.press('l') +await expect(lineButton).toHaveAttribute('aria-pressed', 'true') - // Test these hotkeys perform actions when - // focus is on the canvas - await page.mouse.move(600, 250) - await page.mouse.click(600, 250) +// Do not close the sketch. +// On close it will exit sketch mode. - // Start a sketch - await page.keyboard.press('s') - await page.mouse.move(800, 300) - await page.mouse.click(800, 300) - await page.waitForTimeout(1000) - await expect(lineButton).toBeVisible() - await expect(lineButton).toHaveAttribute('aria-pressed', 'true') - - // Draw a line - await page.mouse.move(700, 200, { steps: 5 }) - await page.mouse.click(700, 200) - await page.mouse.move(800, 250, { steps: 5 }) - await page.mouse.click(800, 250) - // Unequip line tool - await page.keyboard.press('Escape') - // Make sure we didn't pop out of sketch mode. - await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible() - await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true') - // Equip arc tool - await page.keyboard.press('a') - await expect(arcButton).toHaveAttribute('aria-pressed', 'true') - await page.mouse.move(1000, 100, { steps: 5 }) - await page.mouse.click(1000, 100) - await page.keyboard.press('Escape') - await page.keyboard.press('l') - await expect(lineButton).toHaveAttribute('aria-pressed', 'true') - - // Do not close the sketch. - // On close it will exit sketch mode. - - // Unequip line tool - await page.keyboard.press('Escape') - await expect(lineButton).toHaveAttribute('aria-pressed', 'false') - await expect(arcButton).toHaveAttribute('aria-pressed', 'false') - // Make sure we didn't pop out of sketch mode. - await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible() - // Exit sketch - await page.keyboard.press('Escape') - await expect( - page.getByRole('button', { name: 'Exit Sketch' }) - ).not.toBeVisible() -}) +// Unequip line tool +await page.keyboard.press('Escape') +await expect(lineButton).toHaveAttribute('aria-pressed', 'false') +await expect(arcButton).toHaveAttribute('aria-pressed', 'false') +// Make sure we didn't pop out of sketch mode. +await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible() +// Exit sketch +await page.keyboard.press('Escape') +await expect( + page.getByRole('button', { name: 'Exit Sketch' }) +).not.toBeVisible() }) test.fixme( 'Basic default modeling and sketch hotkeys work', @@ -285,8 +259,8 @@ test.fixme( }) ) }) - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() await u.openDebugPanel() await u.expectCmdLog('[data-message-type="execution-done"]') await u.closeDebugPanel() @@ -437,171 +411,171 @@ test.fixme( } ) -test('Delete key does not navigate back', async ({ page }) => { - await page.setViewportSize({ width: 1200, height: 500 }) - await page.goto('/') - await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) +test('Delete key does not navigate back', async ({ page, homePage }) => { - const settingsButton = page.getByRole('link', { - name: 'Settings', - exact: false, - }) - const settingsCloseButton = page.getByTestId('settings-close-button') +await page.setBodyDimensions({ width: 1200, height: 500 }) +await homePage.goToModelingScene(); - await settingsButton.click() - await expect(page.url()).toContain('/settings') +await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' }) - // Make sure that delete doesn't go back from settings - await page.keyboard.press('Delete') - await expect(page.url()).toContain('/settings') +const settingsButton = page.getByRole('link', { + name: 'Settings', + exact: false, +}) +const settingsCloseButton = page.getByTestId('settings-close-button') - // Now close the settings and try delete again, - // make sure it doesn't go back to settings - await settingsCloseButton.click() - await page.keyboard.press('Delete') - await expect(page.url()).not.toContain('/settings') +await settingsButton.click() +await expect.poll(() => page.url()).toContain('/settings') + +// Make sure that delete doesn't go back from settings +await page.keyboard.press('Delete') +await expect.poll(() => page.url()).toContain('/settings') + +// Now close the settings and try delete again, +// make sure it doesn't go back to settings +await settingsCloseButton.click() +await page.keyboard.press('Delete') +await expect.poll(() => page.url()).not.toContain('/settings') }) + +test('Sketch on face', async ({ page, homePage }) => { test.setTimeout(90_000) +const u = await getUtils(page) +await page.addInitScript(async () => { + localStorage.setItem( + 'persistCode', + `sketch001 = startSketchOn('XZ') +|> startProfileAt([3.29, 7.86], %) +|> line([2.48, 2.44], %) +|> line([2.66, 1.17], %) +|> line([3.75, 0.46], %) +|> line([4.99, -0.46], %) +|> line([3.3, -2.12], %) +|> line([2.16, -3.33], %) +|> line([0.85, -3.08], %) +|> line([-0.18, -3.36], %) +|> line([-3.86, -2.73], %) +|> line([-17.67, 0.85], %) +|> close(%) +extrude001 = extrude(5 + 7, sketch001)` + ) }) -test('Sketch on face', async ({ page }) => { - test.setTimeout(90_000) - const u = await getUtils(page) - await page.addInitScript(async () => { - localStorage.setItem( - 'persistCode', - `sketch001 = startSketchOn('XZ') - |> startProfileAt([3.29, 7.86], %) - |> line([2.48, 2.44], %) - |> line([2.66, 1.17], %) - |> line([3.75, 0.46], %) - |> line([4.99, -0.46], %) - |> line([3.3, -2.12], %) - |> line([2.16, -3.33], %) - |> line([0.85, -3.08], %) - |> line([-0.18, -3.36], %) - |> line([-3.86, -2.73], %) - |> line([-17.67, 0.85], %) - |> close(%) - extrude001 = extrude(5 + 7, sketch001)` - ) - }) +await page.setBodyDimensions({ width: 1200, height: 500 }) - await page.setViewportSize({ width: 1200, height: 500 }) +await homePage.goToModelingScene() - await u.waitForAuthSkipAppStart() +// wait for execution done +await u.openDebugPanel() +await u.expectCmdLog('[data-message-type="execution-done"]') +await u.closeDebugPanel() - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() +await expect( + page.getByRole('button', { name: 'Start Sketch' }) +).not.toBeDisabled() - await expect( - page.getByRole('button', { name: 'Start Sketch' }) - ).not.toBeDisabled() +await page.getByRole('button', { name: 'Start Sketch' }).click() +await page.waitForTimeout(300) - await page.getByRole('button', { name: 'Start Sketch' }).click() - await page.waitForTimeout(300) +let previousCodeContent = await page.locator('.cm-content').innerText() - let previousCodeContent = await page.locator('.cm-content').innerText() +await u.openAndClearDebugPanel() +await u.doAndWaitForCmd( + () => page.mouse.click(625, 165), + 'default_camera_get_settings', + true +) +await page.waitForTimeout(150) +await u.closeDebugPanel() - await u.openAndClearDebugPanel() - await u.doAndWaitForCmd( - () => page.mouse.click(625, 165), - 'default_camera_get_settings', - true - ) - await page.waitForTimeout(150) - await u.closeDebugPanel() +const firstClickPosition = [612, 238] +const secondClickPosition = [661, 242] +const thirdClickPosition = [609, 267] - const firstClickPosition = [612, 238] - const secondClickPosition = [661, 242] - const thirdClickPosition = [609, 267] +await page.mouse.click(firstClickPosition[0], firstClickPosition[1]) +await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) +previousCodeContent = await page.locator('.cm-content').innerText() - await page.mouse.click(firstClickPosition[0], firstClickPosition[1]) - await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) - previousCodeContent = await page.locator('.cm-content').innerText() +await page.waitForTimeout(100) +await page.mouse.click(secondClickPosition[0], secondClickPosition[1]) +await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) +previousCodeContent = await page.locator('.cm-content').innerText() - await page.waitForTimeout(100) - await page.mouse.click(secondClickPosition[0], secondClickPosition[1]) - await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) - previousCodeContent = await page.locator('.cm-content').innerText() +await page.waitForTimeout(100) +await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1]) +await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) +previousCodeContent = await page.locator('.cm-content').innerText() - await page.waitForTimeout(100) - await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1]) - await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) - previousCodeContent = await page.locator('.cm-content').innerText() +await page.waitForTimeout(100) +await page.mouse.click(firstClickPosition[0], firstClickPosition[1]) +await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) +previousCodeContent = await page.locator('.cm-content').innerText() - await page.waitForTimeout(100) - await page.mouse.click(firstClickPosition[0], firstClickPosition[1]) - await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) - previousCodeContent = await page.locator('.cm-content').innerText() - - await expect.poll(u.normalisedEditorCode).toContain( - u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01) +await expect.poll(u.normalisedEditorCode).toContain( + u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01) |> startProfileAt([-12.94, 6.6], %) |> line([2.45, -0.2], %) |> line([-2.6, -1.25], %) |> lineTo([profileStartX(%), profileStartY(%)], %) - |> close(%)`) - ) + |> close(%) +`) +) - await u.openAndClearDebugPanel() - await page.getByRole('button', { name: 'Exit Sketch' }).click() - await u.expectCmdLog('[data-message-type="execution-done"]') +await u.openAndClearDebugPanel() +await page.getByRole('button', { name: 'Exit Sketch' }).click() +await u.expectCmdLog('[data-message-type="execution-done"]') - await u.updateCamPosition([1049, 239, 686]) - await u.closeDebugPanel() +await u.updateCamPosition([1049, 239, 686]) +await u.closeDebugPanel() - await page.getByText('startProfileAt([-12').click() - await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible() - await page.getByRole('button', { name: 'Edit Sketch' }).click() - await page.waitForTimeout(400) - await page.waitForTimeout(150) - await page.setViewportSize({ width: 1200, height: 1200 }) - await u.openAndClearDebugPanel() - await u.updateCamPosition([452, -152, 1166]) - await u.closeDebugPanel() - await page.waitForTimeout(200) +await page.getByText('startProfileAt([-12').click() +await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible() +await page.getByRole('button', { name: 'Edit Sketch' }).click() +await page.waitForTimeout(400) +await page.waitForTimeout(150) +await page.setBodyDimensions({ width: 1200, height: 1200 }) +await u.openAndClearDebugPanel() +await u.updateCamPosition([452, -152, 1166]) +await u.closeDebugPanel() +await page.waitForTimeout(200) - const pointToDragFirst = [787, 565] - await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1]) - await page.mouse.down() - await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], { - steps: 5, - }) - await page.mouse.up() - await page.waitForTimeout(100) - await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) - previousCodeContent = await page.locator('.cm-content').innerText() - - const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01) - |> startProfileAt([-12.83, 6.7], %) - |> line([${[2.28, 2.35]}, -${0.07}], %) - |> line([-3.05, -1.47], %) - |> lineTo([profileStartX(%), profileStartY(%)], %) - |> close(%)` - - await expect(page.locator('.cm-content')).toHaveText(result.regExp) - - // exit sketch - await u.openAndClearDebugPanel() - await page.getByRole('button', { name: 'Exit Sketch' }).click() - await u.expectCmdLog('[data-message-type="execution-done"]') - - await page.getByText('startProfileAt([-12').click() - - await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled() - await page.waitForTimeout(100) - await page.getByRole('button', { name: 'Extrude' }).click() - - await expect(page.getByTestId('command-bar')).toBeVisible() - await page.waitForTimeout(100) - - await page.getByRole('button', { name: 'arrow right Continue' }).click() - await page.waitForTimeout(100) - await expect(page.getByText('Confirm Extrude')).toBeVisible() - await page.getByRole('button', { name: 'checkmark Submit command' }).click() - - const result2 = result.genNext` - const sketch002 = extrude(${[5, 5]} + 7, sketch002)` - await expect(page.locator('.cm-content')).toHaveText(result2.regExp) +const pointToDragFirst = [787, 565] +await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1]) +await page.mouse.down() +await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], { + steps: 5, }) +await page.mouse.up() +await page.waitForTimeout(100) +await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) +previousCodeContent = await page.locator('.cm-content').innerText() + +const result = makeTemplate`sketch002 = startSketchOn(extrude001, seg01) +|> startProfileAt([-12.83, 6.7], %) +|> line([${[2.28, 2.35]}, -${0.07}], %) +|> line([-3.05, -1.47], %) +|> lineTo([profileStartX(%), profileStartY(%)], %) +|> close(%)` + +await expect(page.locator('.cm-content')).toHaveText(result.regExp) + +// exit sketch +await u.openAndClearDebugPanel() +await page.getByRole('button', { name: 'Exit Sketch' }).click() +await u.expectCmdLog('[data-message-type="execution-done"]') + +await page.getByText('startProfileAt([-12').click() + +await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled() +await page.waitForTimeout(100) +await page.getByRole('button', { name: 'Extrude' }).click() + +await expect(page.getByTestId('command-bar')).toBeVisible() +await page.waitForTimeout(100) + +await page.getByRole('button', { name: 'arrow right Continue' }).click() +await page.waitForTimeout(100) +await expect(page.getByText('Confirm Extrude')).toBeVisible() +await page.getByRole('button', { name: 'checkmark Submit command' }).click() + +const result2 = result.genNext` +const sketch002 = extrude(${[5, 5]} + 7, sketch002)` +await expect(page.locator('.cm-content')).toHaveText(result2.regExp) }) diff --git a/e2e/playwright/zoo-test.ts b/e2e/playwright/zoo-test.ts index fb8212ba9..57202a05d 100644 --- a/e2e/playwright/zoo-test.ts +++ b/e2e/playwright/zoo-test.ts @@ -10,6 +10,10 @@ export { expect, Page, BrowserContext, TestInfo } from '@playwright/test' // switch between web and electron if needed. const pwTestFnWithFixtures = playwrightTestFn.extend(fixtures) + +// In JavaScript you cannot replace a function's body only (despite functions +// are themselves objects, which you'd expect a body property or something...) +// So we must redefine the function and then re-attach properties. export function test(desc, objOrFn, fnMaybe) { const hasTestConf = typeof objOrFn === 'object' const fn = hasTestConf ? fnMaybe : objOrFn @@ -54,6 +58,13 @@ export function test(desc, objOrFn, fnMaybe) { }, dims) } + // We need to expose this in order for some tests that require folder + // creation. Before they used to do this by their own electronSetup({...}) + // calls. + tronApp.context.folderSetupFn = function(fn) { + return fn(tronApp.dir).then(() => ({ dir: tronApp.dir })) + } + await fn( { context: tronApp.context, @@ -74,3 +85,4 @@ test.afterEach = pwTestFnWithFixtures.afterEach test.step = pwTestFnWithFixtures.step test.skip = pwTestFnWithFixtures.skip test.setTimeout = pwTestFnWithFixtures.setTimeout +test.fixme = pwTestFnWithFixtures.fixme diff --git a/src/lib/exportSave.ts b/src/lib/exportSave.ts index 59d44b1e4..7f5e2e872 100644 --- a/src/lib/exportSave.ts +++ b/src/lib/exportSave.ts @@ -17,9 +17,14 @@ const save_ = async (file: ModelingAppFile, toastId: string) => { } if (window.electron.process.env.IS_PLAYWRIGHT) { - // skip file picker, save to default location + // Skip file picker, save to the test dir downloads directory + const downloadDir = window.electron.join( + window.electron.process.env.TEST_SETTINGS_FILE_KEY, + "downloads-during-playwright", + ) + await window.electron.mkdir(downloadDir, { recursive: true }) await window.electron.writeFile( - file.name, + window.electron.join(downloadDir, file.name), new Uint8Array(file.contents) ) toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })