Pass various.spec.ts, by far the hardest one

This commit is contained in:
49lf
2024-11-22 14:34:32 -05:00
parent b1616f9c6b
commit d01832e86c
8 changed files with 664 additions and 934 deletions

View File

@ -1,29 +1,20 @@
import { test, expect, Page } from '@playwright/test' import { test, expect, Page } from './zoo-test'
import { import {
getUtils, getUtils,
TEST_COLORS, TEST_COLORS,
setup,
tearDown,
commonPoints, commonPoints,
PERSIST_MODELING_CONTEXT, PERSIST_MODELING_CONTEXT,
} from './test-utils' } from './test-utils'
import { HomePageFixture } from './fixtures/homePageFixture'
test.beforeEach(async ({ context, page }, testInfo) => {
await setup(context, page, testInfo)
})
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test.setTimeout(120000) test.setTimeout(120000)
async function doBasicSketch(page: Page, openPanes: string[]) { async function doBasicSketch(page: Page, homePage: HomePageFixture, openPanes: string[]) {
const u = await getUtils(page) 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 const PUR = 400 / 37.5 //pixeltoUnitRatio
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await u.openDebugPanel() await u.openDebugPanel()
// If we have the code pane open, we should see the code. // 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.describe('Basic sketch', () => {
test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => { test('code pane open at start', { tag: ['@skipWin'] }, async ({ page, homePage }) => { // Skip on windows it is being weird.
// Skip on windows it is being weird.
test.skip(process.platform === 'win32', 'Skip on windows') test.skip(process.platform === 'win32', 'Skip on windows')
await doBasicSketch(page, ['code']) await doBasicSketch(page, homePage, ['code']) })
})
test('code pane closed at start', async ({ page }) => { test('code pane closed at start', async ({ page, homePage }) => { // Load the app with the code panes
// Load the app with the code panes
await page.addInitScript(async (persistModelingContext) => { await page.addInitScript(async (persistModelingContext) => {
localStorage.setItem( localStorage.setItem(
persistModelingContext, persistModelingContext,
JSON.stringify({ openPanes: [] }) JSON.stringify({ openPanes: [] })
) )
}, PERSIST_MODELING_CONTEXT) }, PERSIST_MODELING_CONTEXT)
await doBasicSketch(page, []) await doBasicSketch(page, homePage, []) })
})
}) })

View File

@ -1,10 +1,7 @@
import { test, expect } from '@playwright/test' import { test, expect } from './zoo-test'
import { import {
getUtils, getUtils,
setup,
setupElectron,
tearDown,
executorInputPath, executorInputPath,
} from './test-utils' } from './test-utils'
import { join } from 'path' import { join } from 'path'
@ -12,37 +9,26 @@ import { bracket } from 'lib/exampleKcl'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates' import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from './storageStates'
import fsp from 'fs/promises' 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.describe('Code pane and errors', () => {
test('Typing KCL errors induces a badge on the code pane button', async ({ test('Typing KCL errors induces a badge on the code pane button', async ({ page, homePage }) => { const u = await getUtils(page)
page,
}) => {
const u = await getUtils(page)
// Load the app with the working starter code // Load the app with the working starter code
await page.addInitScript(() => { await page.addInitScript(() => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`// Extruded Triangle `// Extruded Triangle
sketch001 = startSketchOn('XZ') sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([10, 0], %) |> line([10, 0], %)
|> line([-5, 10], %) |> line([-5, 10], %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(5, sketch001)` extrude001 = extrude(5, sketch001)`
) )
}) })
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
// wait for execution done // wait for execution done
await u.openDebugPanel() await u.openDebugPanel()
@ -59,13 +45,9 @@ extrude001 = extrude(5, sketch001)`
await page.keyboard.press('Backspace') await page.keyboard.press('Backspace')
// Ensure that a badge appears on the button // Ensure that a badge appears on the button
await expect(codePaneButtonHolder).toContainText('notification') await expect(codePaneButtonHolder).toContainText('notification') })
})
test('Opening and closing the code pane will consistently show error diagnostics', async ({ test('Opening and closing the code pane will consistently show error diagnostics', async ({ page, homePage }) => {
page,
}) => {
await page.goto('http://localhost:3000')
const u = await getUtils(page) const u = await getUtils(page)
@ -74,8 +56,8 @@ extrude001 = extrude(5, sketch001)`
localStorage.setItem('persistCode', code) localStorage.setItem('persistCode', code)
}, bracket) }, bracket)
await page.setViewportSize({ width: 1200, height: 900 }) await page.setBodyDimensions({ width: 1200, height: 900 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
// wait for execution done // wait for execution done
await u.openDebugPanel() await u.openDebugPanel()
@ -126,21 +108,17 @@ extrude001 = extrude(5, sketch001)`
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
await expect(page.locator('.cm-tooltip').first()).toBeVisible() 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 ({ 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)
page,
}) => {
const u = await getUtils(page)
// Load the app with the working starter code // Load the app with the working starter code
await page.addInitScript((code) => { await context.addInitScript((code) => {
localStorage.setItem('persistCode', code) localStorage.setItem('persistCode', code)
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
@ -164,24 +142,20 @@ extrude001 = extrude(5, sketch001)`
await expect( await expect(
page page
.getByText( .getByText(
'sketch profile must lie entirely on one side of the revolution axis' 'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
) )
.first() .first()
).toBeVisible() ).toBeVisible() })
})
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ 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)
page,
}) => {
const u = await getUtils(page)
// Load the app with the working starter code // Load the app with the working starter code
await page.addInitScript((code) => { await context.addInitScript((code) => {
localStorage.setItem('persistCode', code) localStorage.setItem('persistCode', code)
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW) }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
@ -234,18 +208,15 @@ extrude001 = extrude(5, sketch001)`
'sketch profile must lie entirely on one side of the revolution axis' 'sketch profile must lie entirely on one side of the revolution axis'
) )
.first() .first()
).toBeVisible() ).toBeVisible() })
})
}) })
test( test(
'Opening multiple panes persists when switching projects', 'Opening multiple panes persists when switching projects',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ context, browserName, page }, testInfo) => {
// Setup multiple projects. // Setup multiple projects.
const { electronApp, page } = await setupElectron({ context.folderSetupFn(async (dir) => {
testInfo,
folderSetupFn: async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate') const routerTemplateDir = join(dir, 'router-template-slate')
const bracketDir = join(dir, 'bracket') const bracketDir = join(dir, 'bracket')
await Promise.all([ await Promise.all([
@ -262,11 +233,10 @@ test(
join(bracketDir, 'main.kcl') join(bracketDir, 'main.kcl')
), ),
]) ])
},
}) })
const u = await getUtils(page) 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 test.step('Opening the bracket project should load', async () => {
await expect(page.getByText('bracket')).toBeVisible() await expect(page.getByText('bracket')).toBeVisible()
@ -309,30 +279,21 @@ test(
await expect(page.locator('#variables-pane')).toBeVisible() await expect(page.locator('#variables-pane')).toBeVisible()
await expect(page.locator('#logs-pane')).toBeVisible() await expect(page.locator('#logs-pane')).toBeVisible()
}) })
await electronApp.close()
} }
) )
test( test(
'external change of file contents are reflected in editor', 'external change of file contents are reflected in editor',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ context, browserName, page }, testInfo) => {
const PROJECT_DIR_NAME = 'lee-was-here' const PROJECT_DIR_NAME = 'lee-was-here'
const { const { dir: projectsDir, } = await context.folderSetupFn(async (dir) => {
electronApp,
page,
dir: projectsDir,
} = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const aProjectDir = join(dir, PROJECT_DIR_NAME) const aProjectDir = join(dir, PROJECT_DIR_NAME)
await fsp.mkdir(aProjectDir, { recursive: true }) await fsp.mkdir(aProjectDir, { recursive: true })
},
}) })
const u = await getUtils(page) 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 test.step('Open the project', async () => {
await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible() await expect(page.getByText(PROJECT_DIR_NAME)).toBeVisible()
@ -351,7 +312,5 @@ test(
) )
await u.editorTextMatches(content) await u.editorTextMatches(content)
}) })
await electronApp.close()
} }
) )

View File

@ -65,6 +65,7 @@ export class AuthenticatedTronApp {
public readonly testInfo: TestInfo public readonly testInfo: TestInfo
public electronApp?: ElectronApplication public electronApp?: ElectronApplication
public readonly viewPortSize = { width: 1200, height: 500 } public readonly viewPortSize = { width: 1200, height: 500 }
public dir: string = ""
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this._page = page this._page = page
@ -80,7 +81,7 @@ export class AuthenticatedTronApp {
appSettings?: Partial<SaveSettingsPayload> appSettings?: Partial<SaveSettingsPayload>
} = { fixtures: {} } } = { fixtures: {} }
) { ) {
const { electronApp, page, context } = await setupElectron({ const { electronApp, page, context, dir } = await setupElectron({
testInfo: this.testInfo, testInfo: this.testInfo,
folderSetupFn: arg.folderSetupFn, folderSetupFn: arg.folderSetupFn,
cleanProjectDir: arg.cleanProjectDir, cleanProjectDir: arg.cleanProjectDir,
@ -89,6 +90,7 @@ export class AuthenticatedTronApp {
this.page = page this.page = page
this.context = context this.context = context
this.electronApp = electronApp this.electronApp = electronApp
this.dir = dir
// Setup localStorage, addCookies, reload // Setup localStorage, addCookies, reload
await setup(this.context, this.page, this.testInfo) await setup(this.context, this.page, this.testInfo)

View File

@ -109,242 +109,21 @@ keychain = startSketchOn("XY")
|> close(%) |> close(%)
|> extrude(thickness, %) |> extrude(thickness, %)
// generated from /home/paultag/Downloads/zma-logomark.svg keychain1 = startSketchOn("XY")
fn svg = (surface, origin, depth) => { |> startProfileAt([0, 0], %)
let a0 = surface |> startProfileAt([origin[0] + 45.430427, origin[1] + -14.627736], %) |> lineTo([width, 0], %)
|> bezierCurve({ |> lineTo([width, height], %)
control1: [ 0, 0.764157 ], |> lineTo([0, height], %)
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(%) |> close(%)
|> extrude(depth, %) |> extrude(thickness, %)
let a1 = surface |> startProfileAt([origin[0] + 57.920488, origin[1] + -15.244943], %) keychain2 = startSketchOn("XY")
|> bezierCurve({ |> startProfileAt([0, 0], %)
control1: [ -2.78904, 0.106635 ], |> lineTo([width, 0], %)
control2: [ -5.052548, -2.969529 ], |> lineTo([width, height], %)
to: [ -4.055141, -5.598369 ] |> lineTo([0, height], %)
}, %)
|> 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(%) |> close(%)
|> extrude(depth, %) |> extrude(thickness, %)
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, %)
box = startSketchOn('XY') box = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
@ -354,7 +133,7 @@ box = startSketchOn('XY')
|> close(%) |> close(%)
|> extrude(10, %) |> extrude(10, %)
sketch001 = startSketchOn(box, revolveAxis) sketch001 = startSketchOn(box, revolveAxis)
|> startProfileAt([5, 10], %) |> startProfileAt([5, 10], %)
|> line([0, -10], %) |> line([0, -10], %)
|> line([2, 0], %) |> line([2, 0], %)
@ -364,18 +143,12 @@ box = startSketchOn('XY')
axis: revolveAxis, axis: revolveAxis,
angle: 90 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` export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `thing = 1`

View File

@ -11,7 +11,7 @@ import {
import { EngineCommand } from 'lang/std/artifactGraph' import { EngineCommand } from 'lang/std/artifactGraph'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import fsSync from 'fs' import fsSync from 'fs'
import { join } from 'path' import path from 'path'
import pixelMatch from 'pixelmatch' import pixelMatch from 'pixelmatch'
import { PNG } from 'pngjs' import { PNG } from 'pngjs'
import { Protocol } from 'playwright-core/types/protocol' 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 { export interface Paths {
modelPath: string modelPath: string
imagePath: string imagePath: string
@ -694,7 +717,8 @@ export const doExport = async (
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
): Promise<Paths> => { ): Promise<Paths> => {
if (exportFrom === 'dropdown') { if (exportFrom === 'dropdown') {
await page.getByRole('button', { name: APP_NAME }).click() await page.getByTestId('project-sidebar-toggle').click()
const exportMenuButton = page.getByRole('button', { const exportMenuButton = page.getByRole('button', {
name: 'Export current part', name: 'Export current part',
}) })
@ -735,25 +759,12 @@ export const doExport = async (
} }
await expect(page.getByText('Confirm Export')).toBeVisible() await expect(page.getByText('Confirm Export')).toBeVisible()
const getPromiseAndResolve = () => {
let resolve: any = () => {}
const promise = new Promise<Download>((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() 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') { if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
return { return {
modelPath: '', modelPath: '',
@ -763,15 +774,12 @@ export const doExport = async (
} }
// Handle download // Handle download
const download = await downloadPromise1
const downloadLocationer = (extra = '', isImage = false) => const downloadLocationer = (extra = '', isImage = false) =>
`./e2e/playwright/export-snapshots/${output.type}-${ `./e2e/playwright/export-snapshots/${output.type}-${
'storage' in output ? output.storage : '' 'storage' in output ? output.storage : ''
}${extra}.${isImage ? 'png' : output.type}` }${extra}.${isImage ? 'png' : output.type}`
const downloadLocation = downloadLocationer() const downloadLocation = downloadLocationer()
await download.saveAs(downloadLocation)
if (output.type === 'step') { if (output.type === 'step') {
// stable timestamps for step files // stable timestamps for step files
const fileContents = await fsp.readFile(downloadLocation, 'utf-8') const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
@ -780,6 +788,12 @@ export const doExport = async (
'1970-01-01T00:00:00.0+00:00' '1970-01-01T00:00:00.0+00:00'
) )
await fsp.writeFile(downloadLocation, newFileContents) 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 { return {
@ -818,12 +832,13 @@ export async function setup(
testInfo?: TestInfo testInfo?: TestInfo
) { ) {
await context.addInitScript( await context.addInitScript(
async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => { async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY, PLAYWRIGHT_TEST_DIR }) => {
localStorage.clear() localStorage.clear()
localStorage.setItem('TOKEN_PERSIST_KEY', token) localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``) localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings) localStorage.setItem(settingsKey, settings)
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true') localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR)
}, },
{ {
token: secrets.token, token: secrets.token,
@ -840,6 +855,7 @@ export async function setup(
} as Partial<SaveSettingsPayload>, } as Partial<SaveSettingsPayload>,
}), }),
IS_PLAYWRIGHT_KEY, IS_PLAYWRIGHT_KEY,
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory,
} }
) )
@ -901,11 +917,13 @@ export async function setupElectron({
const context = electronApp.context() const context = electronApp.context()
const page = await electronApp.firstWindow() const page = await electronApp.firstWindow()
page.TEST_SETTINGS_FILE_KEY = projectDirName
context.on('console', console.log) context.on('console', console.log)
page.on('console', console.log) page.on('console', console.log)
if (cleanProjectDir) { if (cleanProjectDir) {
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = TOML.stringify( const settingsOverrides = TOML.stringify(
appSettings appSettings
? { ? {
@ -1022,7 +1040,7 @@ export async function createProject({
} }
export function executorInputPath(fileName: string): string { 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( export async function doAndWaitForImageDiff(

View File

@ -1,51 +1,41 @@
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) => { test.fixme('Units menu', async ({ page, homePage }) => {
await setup(context, page, testInfo)
})
test.afterEach(async ({ page }, testInfo) => {
await tearDown(page, testInfo)
})
test('Units menu', async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
const unitsMenuButton = page.getByRole('button', { const unitsMenuButton = page.getByRole('button', {
name: 'Current Units', name: 'Current Units',
exact: false, 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')
}) })
await expect(unitsMenuButton).toBeVisible()
await expect(unitsMenuButton).toContainText('in')
test('Successful export shows a success toast', async ({ page }) => { await unitsMenuButton.click()
// FYI this test doesn't work with only engine running locally const millimetersButton = page.getByRole('button', { name: 'Millimeters' })
// And you will need to have the KittyCAD CLI installed
const u = await getUtils(page) await expect(millimetersButton).toBeVisible()
await page.addInitScript(async () => { 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 ;(window as any).playwrightSkipFilePicker = true
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -57,205 +47,189 @@ totalHeightHalf = 2
armThick = 0.5 armThick = 0.5
totalLen = 9.5 totalLen = 9.5
part001 = startSketchOn('-XZ') part001 = startSketchOn('-XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> yLine(baseHeight, %) |> yLine(baseHeight, %)
|> xLine(baseLen, %) |> xLine(baseLen, %)
|> angledLineToY({ |> angledLineToY({
angle: topAng, angle: topAng,
to: totalHeightHalf, to: totalHeightHalf,
}, %, $seg04) }, %, $seg04)
|> xLineTo(totalLen, %, $seg03) |> xLineTo(totalLen, %, $seg03)
|> yLine(-armThick, %, $seg01) |> yLine(-armThick, %, $seg01)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle: HALF_TURN, angle: HALF_TURN,
offset: -armThick, offset: -armThick,
intersectTag: seg04 intersectTag: seg04
}, %) }, %)
|> angledLineToY([segAng(seg04) + 180, ZERO], %) |> angledLineToY([segAng(seg04) + 180, ZERO], %)
|> angledLineToY({ |> angledLineToY({
angle: -bottomAng, angle: -bottomAng,
to: -totalHeightHalf - armThick, to: -totalHeightHalf - armThick,
}, %, $seg02) }, %, $seg02)
|> xLineTo(segEndX(seg03) + 0, %) |> xLineTo(segEndX(seg03) + 0, %)
|> yLine(-segLen(seg01), %) |> yLine(-segLen(seg01), %)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle: HALF_TURN, angle: HALF_TURN,
offset: -armThick, offset: -armThick,
intersectTag: seg02 intersectTag: seg02
}, %) }, %)
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %) |> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|> xLineTo(ZERO, %) |> xLineTo(ZERO, %)
|> close(%) |> close(%)
|> extrude(4, %)` |> extrude(4, %)`
) )
}) })
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.waitForCmdReceive('extrude') await u.waitForCmdReceive('extrude')
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
await doExport( await doExport(
{ {
type: 'gltf', type: 'gltf',
storage: 'embedded', storage: 'embedded',
presentation: 'pretty', presentation: 'pretty',
}, },
page 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 ({ 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
page, test.skip(
browserName,
}) => {
// To run this test locally, uncomment Firefox in playwright.config.ts
test.skip(
browserName !== 'firefox', browserName !== 'firefox',
"This bug is really Firefox-only, which we don't run in CI." "This bug is really Firefox-only, which we don't run in CI."
) )
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await page await page
.getByRole('button', { name: 'Start Sketch' }) .getByRole('button', { name: 'Start Sketch' })
.waitFor({ state: 'visible' }) .waitFor({ state: 'visible' })
const codeEditorText = page.locator('.cm-content') const codeEditorText = page.locator('.cm-content')
const pasteContent = `// was this pasted?` const pasteContent = `// was this pasted?`
const typeContent = `// this should be typed` const typeContent = `// this should be typed`
// Load text into the clipboard // Load text into the clipboard
await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent) await page.evaluate((t) => navigator.clipboard.writeText(t), pasteContent)
// Focus the text editor // Focus the text editor
await codeEditorText.focus() await codeEditorText.focus()
// Show that we can type into it // Show that we can type into it
await page.keyboard.type(typeContent) await page.keyboard.type(typeContent)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Paste without the code pane focused // Paste without the code pane focused
await codeEditorText.blur() await codeEditorText.blur()
await page.keyboard.press('ControlOrMeta+KeyV') await page.keyboard.press('ControlOrMeta+KeyV')
// Show that the paste didn't work but typing did // Show that the paste didn't work but typing did
await expect(codeEditorText).not.toContainText(pasteContent) await expect(codeEditorText).not.toContainText(pasteContent)
await expect(codeEditorText).toContainText(typeContent) await expect(codeEditorText).toContainText(typeContent)
// Paste with the code editor focused // Paste with the code editor focused
// Following this guidance: https://github.com/microsoft/playwright/issues/8114 // Following this guidance: https://github.com/microsoft/playwright/issues/8114
await codeEditorText.focus() await codeEditorText.focus()
await page.keyboard.press('ControlOrMeta+KeyV') await page.keyboard.press('ControlOrMeta+KeyV')
await expect( await expect(
await page.evaluate( await page.evaluate(
() => document.querySelector('.cm-content')?.textContent () => document.querySelector('.cm-content')?.textContent
) )
).toContain(pasteContent) ).toContain(pasteContent) })
})
test('Keyboard shortcuts can be viewed through the help menu', async ({ test('Keyboard shortcuts can be viewed through the help menu', async ({ page, homePage }) => { const u = await getUtils(page)
page, await page.setBodyDimensions({ width: 1200, height: 500 })
}) => { await homePage.goToModelingScene()
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
await page await page
.getByRole('button', { name: 'Start Sketch' }) .getByRole('button', { name: 'Start Sketch' })
.waitFor({ state: 'visible' }) .waitFor({ state: 'visible' })
// Open the help menu // Open the help menu
await page.getByRole('button', { name: 'Help and resources' }).click() await page.getByRole('button', { name: 'Help and resources' }).click()
// Open the keyboard shortcuts // Open the keyboard shortcuts
await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click() await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
// Verify the URL and that you can see a list of shortcuts // Verify the URL and that you can see a list of shortcuts
await expect(page.url()).toContain('?tab=keybindings') await expect.poll(() => page.url()).toContain('?tab=keybindings')
await expect( await expect(
page.getByRole('heading', { name: 'Enter Sketch Mode' }) page.getByRole('heading', { name: 'Enter Sketch Mode' })
).toBeAttached() ).toBeAttached()
}) })
test('First escape in tool pops you out of tool, second exits sketch mode', async ({ 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
page, const u = await getUtils(page)
}) => { await page.setBodyDimensions({ width: 1200, height: 500 })
// Wait for the app to be ready for use
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.closeDebugPanel()
const lineButton = page.getByRole('button', { const lineButton = page.getByRole('button', {
name: 'line Line', name: 'line Line',
exact: true, exact: true,
}) })
const arcButton = page.getByRole('button', { const arcButton = page.getByRole('button', {
name: 'arc Tangential Arc', name: 'arc Tangential Arc',
exact: true, exact: true,
})
// Test these hotkeys perform actions when
// focus is on the canvas
await page.mouse.move(600, 250)
await page.mouse.click(600, 250)
// 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()
}) })
// Test these hotkeys perform actions when
// focus is on the canvas
await page.mouse.move(600, 250)
await page.mouse.click(600, 250)
// 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() })
test.fixme( test.fixme(
'Basic default modeling and sketch hotkeys work', 'Basic default modeling and sketch hotkeys work',
async ({ page }) => { async ({ page }) => {
@ -285,8 +259,8 @@ test.fixme(
}) })
) )
}) })
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.closeDebugPanel()
@ -437,171 +411,171 @@ test.fixme(
} }
) )
test('Delete key does not navigate back', async ({ page }) => { test('Delete key does not navigate back', async ({ page, homePage }) => {
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
const settingsButton = page.getByRole('link', { await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene();
await page.waitForURL('file:///**', { waitUntil: 'domcontentloaded' })
const settingsButton = page.getByRole('link', {
name: 'Settings', name: 'Settings',
exact: false, exact: false,
})
const settingsCloseButton = page.getByTestId('settings-close-button')
await settingsButton.click()
await expect(page.url()).toContain('/settings')
// Make sure that delete doesn't go back from settings
await page.keyboard.press('Delete')
await expect(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(page.url()).not.toContain('/settings')
}) })
const settingsCloseButton = page.getByTestId('settings-close-button')
test('Sketch on face', async ({ page }) => { await settingsButton.click()
test.setTimeout(90_000) await expect.poll(() => page.url()).toContain('/settings')
const u = await getUtils(page)
await page.addInitScript(async () => { // 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( localStorage.setItem(
'persistCode', 'persistCode',
`sketch001 = startSketchOn('XZ') `sketch001 = startSketchOn('XZ')
|> startProfileAt([3.29, 7.86], %) |> startProfileAt([3.29, 7.86], %)
|> line([2.48, 2.44], %) |> line([2.48, 2.44], %)
|> line([2.66, 1.17], %) |> line([2.66, 1.17], %)
|> line([3.75, 0.46], %) |> line([3.75, 0.46], %)
|> line([4.99, -0.46], %) |> line([4.99, -0.46], %)
|> line([3.3, -2.12], %) |> line([3.3, -2.12], %)
|> line([2.16, -3.33], %) |> line([2.16, -3.33], %)
|> line([0.85, -3.08], %) |> line([0.85, -3.08], %)
|> line([-0.18, -3.36], %) |> line([-0.18, -3.36], %)
|> line([-3.86, -2.73], %) |> line([-3.86, -2.73], %)
|> line([-17.67, 0.85], %) |> line([-17.67, 0.85], %)
|> close(%) |> close(%)
extrude001 = extrude(5 + 7, sketch001)` extrude001 = extrude(5 + 7, sketch001)`
) )
}) })
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
// wait for execution done // wait for execution done
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.closeDebugPanel()
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click() await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(300) 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.openAndClearDebugPanel()
await u.doAndWaitForCmd( await u.doAndWaitForCmd(
() => page.mouse.click(625, 165), () => page.mouse.click(625, 165),
'default_camera_get_settings', 'default_camera_get_settings',
true true
) )
await page.waitForTimeout(150) await page.waitForTimeout(150)
await u.closeDebugPanel() await u.closeDebugPanel()
const firstClickPosition = [612, 238] const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242] const secondClickPosition = [661, 242]
const thirdClickPosition = [609, 267] const thirdClickPosition = [609, 267]
await page.mouse.click(firstClickPosition[0], firstClickPosition[1]) await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(secondClickPosition[0], secondClickPosition[1]) await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1]) await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1]) await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
await expect.poll(u.normalisedEditorCode).toContain( await expect.poll(u.normalisedEditorCode).toContain(
u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01) u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
|> startProfileAt([-12.94, 6.6], %) |> startProfileAt([-12.94, 6.6], %)
|> line([2.45, -0.2], %) |> line([2.45, -0.2], %)
|> line([-2.6, -1.25], %) |> line([-2.6, -1.25], %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`) |> close(%)
) `)
)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.updateCamPosition([1049, 239, 686]) await u.updateCamPosition([1049, 239, 686])
await u.closeDebugPanel() await u.closeDebugPanel()
await page.getByText('startProfileAt([-12').click() await page.getByText('startProfileAt([-12').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible() await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400) await page.waitForTimeout(400)
await page.waitForTimeout(150) await page.waitForTimeout(150)
await page.setViewportSize({ width: 1200, height: 1200 }) await page.setBodyDimensions({ width: 1200, height: 1200 })
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await u.updateCamPosition([452, -152, 1166]) await u.updateCamPosition([452, -152, 1166])
await u.closeDebugPanel() await u.closeDebugPanel()
await page.waitForTimeout(200) await page.waitForTimeout(200)
const pointToDragFirst = [787, 565] const pointToDragFirst = [787, 565]
await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1]) await page.mouse.move(pointToDragFirst[0], pointToDragFirst[1])
await page.mouse.down() await page.mouse.down()
await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], { await page.mouse.move(pointToDragFirst[0] - 20, pointToDragFirst[1], {
steps: 5, 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)
}) })
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) })

View File

@ -10,6 +10,10 @@ export { expect, Page, BrowserContext, TestInfo } from '@playwright/test'
// switch between web and electron if needed. // switch between web and electron if needed.
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures) const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(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) { export function test(desc, objOrFn, fnMaybe) {
const hasTestConf = typeof objOrFn === 'object' const hasTestConf = typeof objOrFn === 'object'
const fn = hasTestConf ? fnMaybe : objOrFn const fn = hasTestConf ? fnMaybe : objOrFn
@ -54,6 +58,13 @@ export function test(desc, objOrFn, fnMaybe) {
}, dims) }, 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( await fn(
{ {
context: tronApp.context, context: tronApp.context,
@ -74,3 +85,4 @@ test.afterEach = pwTestFnWithFixtures.afterEach
test.step = pwTestFnWithFixtures.step test.step = pwTestFnWithFixtures.step
test.skip = pwTestFnWithFixtures.skip test.skip = pwTestFnWithFixtures.skip
test.setTimeout = pwTestFnWithFixtures.setTimeout test.setTimeout = pwTestFnWithFixtures.setTimeout
test.fixme = pwTestFnWithFixtures.fixme

View File

@ -17,9 +17,14 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
} }
if (window.electron.process.env.IS_PLAYWRIGHT) { 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( await window.electron.writeFile(
file.name, window.electron.join(downloadDir, file.name),
new Uint8Array(file.contents) new Uint8Array(file.contents)
) )
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId }) toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })