Snapshots, yee shall kneel. (#5627)

* Give snapshotter a better chance of getting a good snapshot

* fmt tsc lint

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Fix tests

* Again

* Make cmdBarFixture Locators getters

* Fix export error test

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
49fl
2025-03-05 14:09:21 -05:00
committed by GitHub
parent 63897bd60e
commit bcac4d3798
28 changed files with 187 additions and 236 deletions

View File

@ -134,7 +134,7 @@ jobs:
# TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest, # TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
# but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes. # but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
run: | run: |
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --trace=on --shard=1/1 yarn test:snapshots
env: env:
CI: true CI: true
NODE_ENV: development NODE_ENV: development

View File

@ -9,7 +9,7 @@ test.describe('integrations tests', () => {
test( test(
'Creating a new file or switching file while in sketchMode should exit sketchMode', 'Creating a new file or switching file while in sketchMode should exit sketchMode',
{ tag: '@electron' }, { tag: '@electron' },
async ({ page, context, homePage, scene, editor, toolbar }) => { async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample') const bracketDir = join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
@ -32,9 +32,10 @@ test.describe('integrations tests', () => {
sortBy: 'last-modified-desc', sortBy: 'last-modified-desc',
}) })
await homePage.openProject('test-sample') await homePage.openProject('test-sample')
await scene.waitForExecutionDone()
}) })
await test.step('enter sketch mode', async () => { await test.step('enter sketch mode', async () => {
await scene.connectionEstablished()
await scene.settled(cmdBar)
await clickObj() await clickObj()
await scene.moveNoWhere() await scene.moveNoWhere()
await editor.expectState({ await editor.expectState({
@ -61,7 +62,9 @@ test.describe('integrations tests', () => {
}) })
await test.step('setup for next assertion', async () => { await test.step('setup for next assertion', async () => {
await toolbar.openFile('main.kcl') await toolbar.openFile('main.kcl')
await scene.waitForExecutionDone()
await scene.settled(cmdBar)
await clickObj() await clickObj()
await scene.moveNoWhere() await scene.moveNoWhere()
await editor.expectState({ await editor.expectState({

View File

@ -27,19 +27,24 @@ type CmdBarSerialised =
export class CmdBarFixture { export class CmdBarFixture {
public page: Page public page: Page
cmdBarOpenBtn!: Locator
cmdBarElement!: Locator get cmdBarOpenBtn() {
return this.page.getByTestId('command-bar-open-button')
}
get cmdBarElement() {
return this.page.getByTestId('command-bar')
}
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
this.cmdBarOpenBtn = page.getByTestId('command-bar-open-button')
this.cmdBarElement = page.getByTestId('command-bar')
} }
get currentArgumentInput() { get currentArgumentInput() {
return this.page.getByTestId('cmd-bar-arg-value') return this.page.getByTestId('cmd-bar-arg-value')
} }
// Put all selectors here because this method is re-run on fixture creation.
reConstruct = (page: Page) => { reConstruct = (page: Page) => {
this.page = page this.page = page
} }
@ -153,11 +158,7 @@ export class CmdBarFixture {
} }
openCmdBar = async (selectCmd?: 'promptToEdit') => { openCmdBar = async (selectCmd?: 'promptToEdit') => {
// TODO why does this button not work in electron tests? await this.cmdBarOpenBtn.click()
// await this.cmdBarOpenBtn.click()
await this.page.keyboard.down('ControlOrMeta')
await this.page.keyboard.press('KeyK')
await this.page.keyboard.up('ControlOrMeta')
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible() await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
if (selectCmd === 'promptToEdit') { if (selectCmd === 'promptToEdit') {
const promptEditCommand = this.page.getByText( const promptEditCommand = this.page.getByText(

View File

@ -1,12 +1,14 @@
import type { Page, Locator } from '@playwright/test' import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test' import { expect } from '../zoo-test'
import { isArray, uuidv4 } from 'lib/utils' import { isArray, uuidv4 } from 'lib/utils'
import { CmdBarFixture } from './cmdBarFixture'
import { import {
closeDebugPanel, closeDebugPanel,
doAndWaitForImageDiff, doAndWaitForImageDiff,
getPixelRGBs, getPixelRGBs,
openAndClearDebugPanel, openAndClearDebugPanel,
sendCustomCmd, sendCustomCmd,
getUtils,
} from '../test-utils' } from '../test-utils'
type MouseParams = { type MouseParams = {
@ -40,9 +42,13 @@ export class SceneFixture {
public page: Page public page: Page
public streamWrapper!: Locator public streamWrapper!: Locator
public loadingIndicator!: Locator public loadingIndicator!: Locator
public networkToggleConnected!: Locator
public startEditSketchBtn!: Locator
get exeIndicator() { get exeIndicator() {
return this.page.getByTestId('model-state-indicator-execution-done') return this.page
.getByTestId('model-state-indicator-execution-done')
.or(this.page.getByTestId('model-state-indicator-receive-reliable'))
} }
constructor(page: Page) { constructor(page: Page) {
@ -70,7 +76,11 @@ export class SceneFixture {
this.page = page this.page = page
this.streamWrapper = page.getByTestId('stream') this.streamWrapper = page.getByTestId('stream')
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.loadingIndicator = this.streamWrapper.getByTestId('loading') this.loadingIndicator = this.streamWrapper.getByTestId('loading')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
} }
makeMouseHelpers = ( makeMouseHelpers = (
@ -229,6 +239,27 @@ export class SceneFixture {
await expect(this.exeIndicator).toBeVisible({ timeout: 30000 }) await expect(this.exeIndicator).toBeVisible({ timeout: 30000 })
} }
connectionEstablished = async () => {
const timeout = 30000
await expect(this.networkToggleConnected).toBeVisible({ timeout })
}
settled = async (cmdBar: CmdBarFixture) => {
const u = await getUtils(this.page)
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('Settings · app · show debug panel')
await cmdBar.selectOption({ name: 'on' }).click()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await this.waitForExecutionDone()
await expect(this.startEditSketchBtn).not.toBeDisabled()
await expect(this.startEditSketchBtn).toBeVisible()
}
expectPixelColor = async ( expectPixelColor = async (
colour: [number, number, number] | [number, number, number][], colour: [number, number, number] | [number, number, number][],
coords: { x: number; y: number }, coords: { x: number; y: number },

View File

@ -76,7 +76,9 @@ export class ToolbarFixture {
} }
get exeIndicator() { get exeIndicator() {
return this.page.getByTestId('model-state-indicator-receive-reliable') return this.page
.getByTestId('model-state-indicator-receive-reliable')
.or(this.page.getByTestId('model-state-indicator-execution-done'))
} }
startSketchPlaneSelection = async () => startSketchPlaneSelection = async () =>

View File

@ -317,7 +317,7 @@ extrude001 = extrude(sketch001, length = 50)
test( test(
'when engine fails export we handle the failure and alert the user', 'when engine fails export we handle the failure and alert the user',
{ tag: '@skipLocalEngine' }, { tag: '@skipLocalEngine' },
async ({ scene, page, homePage }) => { async ({ scene, page, homePage, cmdBar }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript( await page.addInitScript(
async ({ code }) => { async ({ code }) => {
@ -330,15 +330,9 @@ extrude001 = extrude(sketch001, length = 50)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await u.waitForPageLoad()
// wait for execution done await scene.connectionEstablished()
await u.openDebugPanel() await scene.settled(cmdBar)
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// expect zero errors in guter
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// export the model // export the model
const exportButton = page.getByTestId('export-pane-button') const exportButton = page.getByTestId('export-pane-button')
@ -362,7 +356,6 @@ extrude001 = extrude(sketch001, length = 50)
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`) const exportingToastMessage = page.getByText(`Exporting...`)
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`) const engineErrorToastMessage = page.getByText(`Nothing to export`)
await expect(engineErrorToastMessage).toBeVisible() await expect(engineErrorToastMessage).toBeVisible()
@ -376,7 +369,6 @@ extrude001 = extrude(sketch001, length = 50)
await page.waitForTimeout(2000) await page.waitForTimeout(2000)
// Expect the toast to be gone // Expect the toast to be gone
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible() await expect(engineErrorToastMessage).not.toBeVisible()
// Now add in code that works. // Now add in code that works.
@ -384,7 +376,7 @@ extrude001 = extrude(sketch001, length = 50)
await page.keyboard.press('End') await page.keyboard.press('End')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await scene.waitForExecutionDone() await scene.settled(cmdBar)
// Now try exporting // Now try exporting
@ -407,7 +399,6 @@ extrude001 = extrude(sketch001, length = 50)
// Expect it to succeed. // Expect it to succeed.
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }) await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
await expect(errorToastMessage).not.toBeVisible()
await expect(engineErrorToastMessage).not.toBeVisible() await expect(engineErrorToastMessage).not.toBeVisible()
const successToastMessage = page.getByText(`Exported successfully`) const successToastMessage = page.getByText(`Exported successfully`)

View File

@ -1,4 +1,4 @@
import { test, expect } from '@playwright/test' import { test, expect } from './zoo-test'
import { secrets } from './secrets' import { secrets } from './secrets'
import { Paths, doExport, getUtils } from './test-utils' import { Paths, doExport, getUtils } from './test-utils'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
@ -13,27 +13,10 @@ import {
TEST_SETTINGS_KEY, TEST_SETTINGS_KEY,
} from './storageStates' } from './storageStates'
import * as TOML from '@iarna/toml' import * as TOML from '@iarna/toml'
import { SceneFixture } from './fixtures/sceneFixture'
import { CmdBarFixture } from './fixtures/cmdBarFixture'
test.beforeEach(async ({ page }) => { test.beforeEach(async ({ page, context }) => {
// reducedMotion kills animations, which speeds up tests and reduces flakiness
await page.emulateMedia({ reducedMotion: 'reduce' })
// set the default settings
await page.addInitScript(
async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => {
localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings)
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
},
{
token: secrets.token,
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: TEST_SETTINGS }),
IS_PLAYWRIGHT_KEY: IS_PLAYWRIGHT_KEY,
}
)
// Make the user avatar image always 404 // Make the user avatar image always 404
// so we see the fallback menu icon for all snapshot tests // so we see the fallback menu icon for all snapshot tests
await page.route('https://lh3.googleusercontent.com/**', async (route) => { await page.route('https://lh3.googleusercontent.com/**', async (route) => {
@ -45,6 +28,14 @@ test.beforeEach(async ({ page }) => {
}) })
}) })
// Help engine-manager: tear shit down.
test.afterEach(async ({ page }) => {
await page.evaluate(() => {
// @ts-expect-error
window.tearDown()
})
})
test.setTimeout(60_000) test.setTimeout(60_000)
// We test this end to end already - getting this to work on web just to take // We test this end to end already - getting this to work on web just to take
@ -54,7 +45,7 @@ test.setTimeout(60_000)
test.skip( test.skip(
'exports of each format should work', 'exports of each format should work',
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] }, { tag: ['@snapshot', '@skipWin', '@skipMacos'] },
async ({ page, context }) => { async ({ page, context, scene, cmdBar }) => {
// FYI this test doesn't work with only engine running locally // FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed // And you will need to have the KittyCAD CLI installed
const u = await getUtils(page) const u = await getUtils(page)
@ -106,11 +97,8 @@ part001 = startSketchOn('-XZ')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
await u.expectCmdLog('[data-message-type="execution-done"]') await scene.settled(cmdBar)
await u.waitForCmdReceive('extrude')
await page.waitForTimeout(1000)
await u.clearAndCloseDebugPanel()
const axisDirectionPair: Models['AxisDirectionPair_type'] = { const axisDirectionPair: Models['AxisDirectionPair_type'] = {
axis: 'z', axis: 'z',
@ -313,8 +301,25 @@ part001 = startSketchOn('-XZ')
} }
) )
const extrudeDefaultPlane = async (context: any, page: any, plane: string) => { const extrudeDefaultPlane = async (
await context.addInitScript(async () => { context: any,
page: any,
cmdBar: CmdBarFixture,
scene: SceneFixture,
plane: string
) => {
const code = `part001 = startSketchOn('${plane}')
|> startProfileAt([7.00, 4.40], %)
|> line(end = [6.60, -0.20])
|> line(end = [2.80, 5.00])
|> line(end = [-5.60, 4.40])
|> line(end = [-5.40, -3.80])
|> close()
|> extrude(length = 10.00)
`
// This probably does absolutely nothing based on my trip through here.
await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'SETTINGS_PERSIST_KEY', 'SETTINGS_PERSIST_KEY',
JSON.stringify({ JSON.stringify({
@ -331,41 +336,17 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
) )
}) })
const code = `part001 = startSketchOn('${plane}')
|> startProfileAt([7.00, 4.40], %)
|> line(end = [6.60, -0.20])
|> line(end = [2.80, 5.00])
|> line(end = [-5.60, 4.40])
|> line(end = [-5.40, -3.80])
|> close()
|> extrude(length = 10.00)
`
await page.addInitScript(async (code: string) => { await page.addInitScript(async (code: string) => {
localStorage.setItem('persistCode', code) localStorage.setItem('persistCode', code)
}) }, code)
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await scene.connectionEstablished()
await scene.settled(cmdBar)
// wait for execution done
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// clear code
await u.removeCurrentCode()
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff(
() => page.locator('.cm-content').fill(code),
200
)
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await u.closeKclCodePanel()
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')], mask: [page.getByTestId('model-state-indicator')],
@ -380,28 +361,28 @@ test.describe(
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
test('XY', async ({ page, context }) => { test('XY', async ({ page, context, cmdBar, scene }) => {
await extrudeDefaultPlane(context, page, 'XY') await extrudeDefaultPlane(context, page, cmdBar, scene, 'XY')
}) })
test('XZ', async ({ page, context }) => { test('XZ', async ({ page, context, cmdBar, scene }) => {
await extrudeDefaultPlane(context, page, 'XZ') await extrudeDefaultPlane(context, page, cmdBar, scene, 'XZ')
}) })
test('YZ', async ({ page, context }) => { test('YZ', async ({ page, context, cmdBar, scene }) => {
await extrudeDefaultPlane(context, page, 'YZ') await extrudeDefaultPlane(context, page, cmdBar, scene, 'YZ')
}) })
test('-XY', async ({ page, context }) => { test('-XY', async ({ page, context, cmdBar, scene }) => {
await extrudeDefaultPlane(context, page, '-XY') await extrudeDefaultPlane(context, page, cmdBar, scene, '-XY')
}) })
test('-XZ', async ({ page, context }) => { test('-XZ', async ({ page, context, cmdBar, scene }) => {
await extrudeDefaultPlane(context, page, '-XZ') await extrudeDefaultPlane(context, page, cmdBar, scene, '-XZ')
}) })
test('-YZ', async ({ page, context }) => { test('-YZ', async ({ page, context, cmdBar, scene }) => {
await extrudeDefaultPlane(context, page, '-YZ') await extrudeDefaultPlane(context, page, cmdBar, scene, '-YZ')
}) })
} }
) )
@ -409,7 +390,7 @@ test.describe(
test( test(
'Draft segments should look right', 'Draft segments should look right',
{ tag: '@snapshot' }, { tag: '@snapshot' },
async ({ page, context }) => { async ({ page, context, scene, cmdBar }) => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
@ -418,17 +399,9 @@ test(
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button // click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(), () => page.getByRole('button', { name: 'Start Sketch' }).click(),
200 200
@ -448,8 +421,9 @@ test(
await expect(page.locator('.cm-content')).toHaveText(code) await expect(page.locator('.cm-content')).toHaveText(code)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10) await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')], mask: [page.getByTestId('model-state-indicator')],
@ -458,7 +432,7 @@ test(
const lineEndClick = () => const lineEndClick = () =>
page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await lineEndClick() await lineEndClick()
await page.waitForTimeout(100) await page.waitForTimeout(500)
code += ` code += `
|> xLine(7.25, %)` |> xLine(7.25, %)`
@ -469,18 +443,16 @@ test(
.click() .click()
// click on the end of the profile to continue it // click on the end of the profile to continue it
await page.waitForTimeout(300) await page.waitForTimeout(500)
await lineEndClick() await lineEndClick()
await page.waitForTimeout(100) await page.waitForTimeout(500)
// click to continue profile // click to continue profile
await page.mouse.move(813, 392, { steps: 10 }) await page.mouse.move(813, 392, { steps: 10 })
await page.waitForTimeout(100) await page.waitForTimeout(500)
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 }) await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
await page.waitForTimeout(1000)
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')], mask: [page.getByTestId('model-state-indicator')],
@ -491,7 +463,7 @@ test(
test( test(
'Draft rectangles should look right', 'Draft rectangles should look right',
{ tag: '@snapshot' }, { tag: '@snapshot' },
async ({ page, context }) => { async ({ page, context, cmdBar, scene }) => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
@ -500,17 +472,10 @@ test(
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect( await scene.connectionEstablished()
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button // click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(), () => page.getByRole('button', { name: 'Start Sketch' }).click(),
200 200
@ -523,8 +488,8 @@ test(
`sketch001 = startSketchOn('XZ')` `sketch001 = startSketchOn('XZ')`
) )
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation // Wait for camera animation
await u.closeDebugPanel() await page.waitForTimeout(2000)
const startXPx = 600 const startXPx = 600
@ -548,7 +513,7 @@ test(
test( test(
'Draft circle should look right', 'Draft circle should look right',
{ tag: '@snapshot' }, { tag: '@snapshot' },
async ({ page, context }) => { async ({ page, context, cmdBar, scene }) => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
// test.skip(process.platform === 'darwin', 'Skip on macos') // test.skip(process.platform === 'darwin', 'Skip on macos')
@ -557,17 +522,9 @@ test(
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect( await scene.connectionEstablished()
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(), () => page.getByRole('button', { name: 'Start Sketch' }).click(),
200 200
@ -580,8 +537,8 @@ test(
`sketch001 = startSketchOn('XZ')` `sketch001 = startSketchOn('XZ')`
) )
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation // Wait for camera animation
await u.closeDebugPanel() await page.waitForTimeout(2000)
const startXPx = 600 const startXPx = 600
@ -611,23 +568,15 @@ test.describe(
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
test('Inch scale', async ({ page }) => { test('Inch scale', async ({ page, cmdBar, scene }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect( await scene.connectionEstablished()
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(), () => page.getByRole('button', { name: 'Start Sketch' }).click(),
200 200
@ -639,7 +588,8 @@ test.describe(
let code = `sketch001 = startSketchOn('XZ')` let code = `sketch001 = startSketchOn('XZ')`
await expect(page.locator('.cm-content')).toHaveText(code) await expect(page.locator('.cm-content')).toHaveText(code)
await page.waitForTimeout(600) // TODO detect animation ending, or disable animation // Wait for camera animation
await page.waitForTimeout(2000)
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
@ -647,8 +597,6 @@ test.describe(
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -683,17 +631,12 @@ test.describe(
mask: [page.getByTestId('model-state-indicator')], mask: [page.getByTestId('model-state-indicator')],
}) })
// exit sketch
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Exit Sketch' }).click(), () => page.getByRole('button', { name: 'Exit Sketch' }).click(),
200 200
) )
// wait for execution done await scene.settled(cmdBar)
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(300)
// second screen shot should look almost identical, i.e. scale should be the same. // second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
@ -702,8 +645,8 @@ test.describe(
}) })
}) })
test('Millimeter scale', async ({ page }) => { test('Millimeter scale', async ({ page, context, cmdBar, scene }) => {
await page.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings }) => {
localStorage.setItem(settingsKey, settings) localStorage.setItem(settingsKey, settings)
}, },
@ -725,17 +668,10 @@ test.describe(
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect( await scene.connectionEstablished()
page.getByRole('button', { name: 'Start Sketch' }) await scene.settled(cmdBar)
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(), () => page.getByRole('button', { name: 'Start Sketch' }).click(),
200 200
@ -747,7 +683,8 @@ test.describe(
let code = `sketch001 = startSketchOn('XZ')` let code = `sketch001 = startSketchOn('XZ')`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page.waitForTimeout(600) // TODO detect animation ending, or disable animation // Wait for camera animation
await page.waitForTimeout(2000)
const startXPx = 600 const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
@ -755,8 +692,6 @@ test.describe(
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100) await page.waitForTimeout(100)
@ -790,16 +725,12 @@ test.describe(
}) })
// exit sketch // exit sketch
await u.openAndClearDebugPanel()
await u.doAndWaitForImageDiff( await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Exit Sketch' }).click(), () => page.getByRole('button', { name: 'Exit Sketch' }).click(),
200 200
) )
// wait for execution done await scene.settled(cmdBar)
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(300)
// second screen shot should look almost identical, i.e. scale should be the same. // second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
@ -812,7 +743,7 @@ test.describe(
test( test(
'Sketch on face with none z-up', 'Sketch on face with none z-up',
{ tag: '@snapshot' }, { tag: '@snapshot' },
async ({ page, context }) => { async ({ page, context, cmdBar, scene }) => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
@ -840,12 +771,8 @@ part002 = startSketchOn(part001, seg01)
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
// wait for execution done await scene.settled(cmdBar)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(1, { timeout: 10_000 })
await u.closeDebugPanel()
// Wait for the second extrusion to appear // Wait for the second extrusion to appear
// TODO: Find a way to truly know that the objects have finished // TODO: Find a way to truly know that the objects have finished
@ -877,7 +804,7 @@ part002 = startSketchOn(part001, seg01)
test( test(
'Zoom to fit on load - solid 2d', 'Zoom to fit on load - solid 2d',
{ tag: '@snapshot' }, { tag: '@snapshot' },
async ({ page, context }) => { async ({ page, context, cmdBar, scene }) => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
@ -899,12 +826,8 @@ test(
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
// wait for execution done await scene.settled(cmdBar)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(1)
await u.closeDebugPanel()
// Wait for the second extrusion to appear // Wait for the second extrusion to appear
// TODO: Find a way to truly know that the objects have finished // TODO: Find a way to truly know that the objects have finished
@ -920,7 +843,7 @@ test(
test( test(
'Zoom to fit on load - solid 3d', 'Zoom to fit on load - solid 3d',
{ tag: '@snapshot' }, { tag: '@snapshot' },
async ({ page, context }) => { async ({ page, context, cmdBar, scene }) => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
test.skip(process.platform === 'darwin', 'Skip on macos') test.skip(process.platform === 'darwin', 'Skip on macos')
@ -943,12 +866,8 @@ test(
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
// wait for execution done await scene.settled(cmdBar)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(1)
await u.closeDebugPanel()
// Wait for the second extrusion to appear // Wait for the second extrusion to appear
// TODO: Find a way to truly know that the objects have finished // TODO: Find a way to truly know that the objects have finished
@ -965,7 +884,11 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
// FIXME: Skip on macos its being weird. // FIXME: Skip on macos its being weird.
// test.skip(process.platform === 'darwin', 'Skip on macos') // test.skip(process.platform === 'darwin', 'Skip on macos')
test('Grid turned off to on via command bar', async ({ page }) => { test('Grid turned off to on via command bar', async ({
page,
cmdBar,
scene,
}) => {
const u = await getUtils(page) const u = await getUtils(page)
const stream = page.getByTestId('stream') const stream = page.getByTestId('stream')
const mask = [ const mask = [
@ -978,12 +901,9 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
// wait for execution done await scene.settled(cmdBar)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(1)
await u.closeDebugPanel()
await u.closeKclCodePanel() await u.closeKclCodePanel()
// TODO: Find a way to truly know that the objects have finished // TODO: Find a way to truly know that the objects have finished
// rendering, because an execution-done message is not sufficient. // rendering, because an execution-done message is not sufficient.
@ -1033,7 +953,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
}) })
}) })
test('Grid turned off', async ({ page }) => { test('Grid turned off', async ({ page, cmdBar, scene }) => {
const u = await getUtils(page) const u = await getUtils(page)
const stream = page.getByTestId('stream') const stream = page.getByTestId('stream')
const mask = [ const mask = [
@ -1046,12 +966,9 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
// wait for execution done await scene.settled(cmdBar)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(1)
await u.closeDebugPanel()
await u.closeKclCodePanel() await u.closeKclCodePanel()
// TODO: Find a way to truly know that the objects have finished // TODO: Find a way to truly know that the objects have finished
// rendering, because an execution-done message is not sufficient. // rendering, because an execution-done message is not sufficient.
@ -1063,8 +980,8 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
}) })
}) })
test('Grid turned on', async ({ page }) => { test('Grid turned on', async ({ page, context, cmdBar, scene }) => {
await page.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings }) => {
localStorage.setItem(settingsKey, settings) localStorage.setItem(settingsKey, settings)
}, },
@ -1094,12 +1011,9 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await page.goto('/') await page.goto('/')
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
// wait for execution done await scene.settled(cmdBar)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(1)
await u.closeDebugPanel()
await u.closeKclCodePanel() await u.closeKclCodePanel()
// TODO: Find a way to truly know that the objects have finished // TODO: Find a way to truly know that the objects have finished
// rendering, because an execution-done message is not sufficient. // rendering, because an execution-done message is not sufficient.
@ -1179,7 +1093,7 @@ test.fixme('theme persists', async ({ page, context }) => {
}) })
test.describe('code color goober', { tag: '@snapshot' }, () => { test.describe('code color goober', { tag: '@snapshot' }, () => {
test('code color goober', async ({ page, context }) => { test('code color goober', async ({ page, context, scene, cmdBar }) => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -1213,19 +1127,22 @@ sweepSketch = startSketchOn('XY')
}) })
await page.setViewportSize({ width: 1200, height: 1000 }) await page.setViewportSize({ width: 1200, height: 1000 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
await u.expectCmdLog('[data-message-type="execution-done"]') await scene.settled(cmdBar)
await u.clearAndCloseDebugPanel()
await expect(page, 'expect small color widget').toHaveScreenshot({ await expect(page, 'expect small color widget').toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) })
test('code color goober opening window', async ({ page, context }) => { test('code color goober opening window', async ({
page,
context,
scene,
cmdBar,
}) => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -1259,12 +1176,10 @@ sweepSketch = startSketchOn('XY')
}) })
await page.setViewportSize({ width: 1200, height: 1000 }) await page.setViewportSize({ width: 1200, height: 1000 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await scene.connectionEstablished()
await u.expectCmdLog('[data-message-type="execution-done"]') await scene.settled(cmdBar)
await u.clearAndCloseDebugPanel()
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible() await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -118,6 +118,7 @@
"tronb:package:prod": "yarn tronb:vite:prod && electron-builder --config electron-builder.yml --publish always", "tronb:package:prod": "yarn tronb:vite:prod && electron-builder --config electron-builder.yml --publish always",
"test-setup": "yarn install && yarn build:wasm", "test-setup": "yarn install && yarn build:wasm",
"test": "vitest --mode development", "test": "vitest --mode development",
"test:snapshots": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --update-snapshots --grep=@snapshot --trace=on --shard=1/1",
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts", "test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts", "test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", "test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",

View File

@ -19,21 +19,25 @@ export interface ActionIconProps extends React.PropsWithChildren {
bgClassName?: string bgClassName?: string
iconClassName?: string iconClassName?: string
size?: keyof typeof iconSizes size?: keyof typeof iconSizes
'data-testid'?: string
} }
export const ActionIcon = ({ export const ActionIcon = (props: ActionIconProps) => {
icon = faCircleExclamation, const {
className, icon = faCircleExclamation,
bgClassName, className,
iconClassName, bgClassName,
size = 'md', iconClassName,
children, size = 'md',
}: ActionIconProps) => { children,
} = props
const computedIconClassName = `h-auto text-inherit dark:text-current group-disabled:text-chalkboard-60 group-disabled:text-chalkboard-60 ${iconClassName}` const computedIconClassName = `h-auto text-inherit dark:text-current group-disabled:text-chalkboard-60 group-disabled:text-chalkboard-60 ${iconClassName}`
const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 group-disabled:bg-chalkboard-30 dark:group-disabled:bg-chalkboard-80 ${bgClassName}` const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 group-disabled:bg-chalkboard-30 dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
return ( return (
<div <div
data-testid={props['data-testid']}
className={ className={
`w-fit self-stretch inline-grid place-content-center ${className} ` + `w-fit self-stretch inline-grid place-content-center ${className} ` +
computedBgClassName computedBgClassName

View File

@ -99,6 +99,9 @@ export const NetworkHealthIndicator = () => {
> >
<ActionIcon <ActionIcon
icon={overallConnectionStateIcon[overallState]} icon={overallConnectionStateIcon[overallState]}
data-testid={`network-toggle-${
overallState == NetworkHealthState.Ok ? 'ok' : 'other'
}`}
className="p-1" className="p-1"
iconClassName={overallConnectionStateColor[overallState].icon} iconClassName={overallConnectionStateColor[overallState].icon}
bgClassName={ bgClassName={