Nadro/adhoc/e2e improvements (#4013)

* chore: saving off package.json progress unit tests fail in main

* fix: implementing a one liner for unit tests

* fix: renaming test:unit:local

* chore: adding playwright tests

* fix: making package.json not destructive to keep same pipeline commands for now

* fix: reordering

* fix: added tags for OS tests, moved kill-port to dev depen

* fix: OS skipping at tag level

* fix: lint, fmt, tsc, etc...

* Look at this (photo)Graph *in the voice of Nickelback*

* fix: new formatting

* fix: removing the ci copy, do not like it

* Look at this (photo)Graph *in the voice of Nickelback*

* chore: updating readme with explanation on the commands for CI CD simulation locally

* fix: package.json command for unit test, removing cached breaking cache in unit tests

* fix: fixing copy and typos in README.md for CI CD section

* fix: adding a duplicate command for a better name. CI CD will use it in a future PR

* fix: this is wrong... removing it

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)"

This reverts commit f767dd46d4.

* fix: typos in README.md

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
Kevin Nadro
2024-10-02 11:58:17 -04:00
committed by GitHub
parent 74faf0461c
commit 3035ad16fc
11 changed files with 680 additions and 586 deletions

View File

@ -322,6 +322,70 @@ cd src/wasm-lib
cargo test cargo test
``` ```
### Mapping CI CD jobs to local commands
When you see the CI CD fail on jobs you may wonder three things
- Do I have a bug in my code?
- Is the test flaky?
- Is there a bug in `main`?
To answer these questions the following commands will give you confidence to locate the issue.
#### Static Analysis
Part of the CI CD pipeline performs static analysis on the code. Use the following commands to mimic the CI CD jobs.
The following set of commands should get us closer to one and done commands to instantly retest issues.
```
yarn test-setup
```
> Gotcha, are packages up to date and is the wasm built?
```
yarn tsc
yarn fmt-check
yarn lint
yarn xstate:typegen
yarn test:unit:local
```
> Gotcha: Our unit tests have integration tests in them. You need to run a localhost server to run the unit tests.
#### E2E Tests
**Playwright Browser**
These E2E tests run in a browser (without electron).
There are tests that are skipped if they are ran in a windows OS or Linux OS. We can use playwright tags to implement test skipping.
Breaking down the command `yarn test:playwright:browser:chrome:windows`
- The application is `playwright`
- The runtime is a `browser`
- The specific `browser` is `chrome`
- The test should run in a `windows` environment. It will skip tests that are broken or flaky in the windows OS.
```
yarn test:playwright:browser:chrome
yarn test:playwright:browser:chrome:windows
yarn test:playwright:browser:chrome:ubuntu
```
**Playwright Electron**
These E2E tests run in electron. There are tests that are skipped if they are ran in a windows, linux, or macos environment. We can use playwright tags to implement test skipping.
```
yarn test:playwright:electron:local
yarn test:playwright:electron:windows:local
yarn test:playwright:electron:macos:local
yarn test:playwright:electron:ubuntu:local
```
> Why does it say local? The CI CD commands that run in the pipeline cannot be ran locally. A single command will not properly setup the testing environment locally.
#### Some notes on CI #### Some notes on CI
The tests are broken into snapshot tests and non-snapshot tests, and they run in that order, they automatically commit new snap shots, so if you see an image commit check it was an intended change. If we have non-determinism in the snapshots such that they are always committing new images, hopefully this annoyance makes us fix them asap, if you notice this happening let Kurt know. But for the odd occasion `git reset --hard HEAD~ && git push -f` is your friend. The tests are broken into snapshot tests and non-snapshot tests, and they run in that order, they automatically commit new snap shots, so if you see an image commit check it was an intended change. If we have non-determinism in the snapshots such that they are always committing new images, hopefully this annoyance makes us fix them asap, if you notice this happening let Kurt know. But for the odd occasion `git reset --hard HEAD~ && git push -f` is your friend.

View File

@ -154,7 +154,7 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
} }
test.describe('Basic sketch', () => { test.describe('Basic sketch', () => {
test('code pane open at start', async ({ page }) => { test('code pane open at start', { tag: ['@skipWin'] }, async ({ page }) => {
// 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, ['code'])

View File

@ -5,71 +5,69 @@ import { ToolbarFixture } from './fixtures/toolbarFixture'
// test file is for testing point an click code gen functionality that's not sketch mode related // test file is for testing point an click code gen functionality that's not sketch mode related
test('verify extruding circle works', async ({ test(
app, 'verify extruding circle works',
cmdBar, { tag: ['@skipWin'] },
editor, async ({ app, cmdBar, editor, toolbar, scene }) => {
toolbar, test.skip(
scene, process.platform === 'win32',
}) => { 'Fails on windows in CI, can not be replicated locally on windows.'
test.skip( )
process.platform === 'win32', const file = await app.getInputFile('test-circle-extrude.kcl')
'Fails on windows in CI, can not be replicated locally on windows.' await app.initialise(file)
) const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
const file = await app.getInputFile('test-circle-extrude.kcl')
await app.initialise(file)
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => { await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => {
await scene.clickNoWhere() await scene.clickNoWhere()
await expect(toolbar.extrudeButton).toBeEnabled() await expect(toolbar.extrudeButton).toBeEnabled()
})
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
await moveToCircle()
const circleSnippet =
'circle({ center: [318.33, 168.1], radius: 182.8 }, %)'
await editor.expectState({
activeLines: [],
highlightedCode: circleSnippet,
diagnostics: [],
}) })
await clickCircle() await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
await editor.expectState({ await moveToCircle()
activeLines: [circleSnippet.slice(-5)], const circleSnippet =
highlightedCode: circleSnippet, 'circle({ center: [318.33, 168.1], radius: 182.8 }, %)'
diagnostics: [], await editor.expectState({
activeLines: [],
highlightedCode: circleSnippet,
diagnostics: [],
})
await clickCircle()
await editor.expectState({
activeLines: [circleSnippet.slice(-5)],
highlightedCode: circleSnippet,
diagnostics: [],
})
await expect(toolbar.extrudeButton).toBeEnabled()
}) })
await expect(toolbar.extrudeButton).toBeEnabled()
})
await test.step('do extrude flow and check extrude code is added to editor', async () => { await test.step('do extrude flow and check extrude code is added to editor', async () => {
await toolbar.extrudeButton.click() await toolbar.extrudeButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
currentArgKey: 'distance', currentArgKey: 'distance',
currentArgValue: '5', currentArgValue: '5',
headerArguments: { Selection: '1 face', Distance: '' }, headerArguments: { Selection: '1 face', Distance: '' },
highlightedHeaderArg: 'distance', highlightedHeaderArg: 'distance',
commandName: 'Extrude', commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
const expectString = 'const extrude001 = extrude(5, sketch001)'
await editor.expectEditor.not.toContain(expectString)
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '1 face', Distance: '5' },
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await editor.expectEditor.toContain(expectString)
}) })
await cmdBar.progressCmdBar() }
)
const expectString = 'const extrude001 = extrude(5, sketch001)'
await editor.expectEditor.not.toContain(expectString)
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '1 face', Distance: '5' },
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await editor.expectEditor.toContain(expectString)
})
})
test.describe('verify sketch on chamfer works', () => { test.describe('verify sketch on chamfer works', () => {
const _sketchOnAChamfer = const _sketchOnAChamfer =
@ -152,36 +150,34 @@ test.describe('verify sketch on chamfer works', () => {
}) })
}) })
} }
test('works on all edge selections and can break up multi edges in a chamfer array', async ({ test(
app, 'works on all edge selections and can break up multi edges in a chamfer array',
editor, { tag: ['@skipWin'] },
toolbar, async ({ app, editor, toolbar, scene }) => {
scene, test.skip(
}) => { process.platform === 'win32',
test.skip( 'Fails on windows in CI, can not be replicated locally on windows.'
process.platform === 'win32', )
'Fails on windows in CI, can not be replicated locally on windows.' const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl')
) await app.initialise(file)
const file = await app.getInputFile('e2e-can-sketch-on-chamfer.kcl')
await app.initialise(file)
const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene) const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene)
await sketchOnAChamfer({ await sketchOnAChamfer({
clickCoords: { x: 570, y: 220 }, clickCoords: { x: 570, y: 220 },
cameraPos: { x: 16020, y: -2000, z: 10500 }, cameraPos: { x: 16020, y: -2000, z: 10500 },
cameraTarget: { x: -150, y: -4500, z: -80 }, cameraTarget: { x: -150, y: -4500, z: -80 },
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01) beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
chamfer({length:30,tags:[ chamfer({length:30,tags:[
seg01, seg01,
getNextAdjacentEdge(yo), getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02), getNextAdjacentEdge(seg02),
getOppositeEdge(seg01) getOppositeEdge(seg01)
]}, %)`, ]}, %)`,
afterChamferSelectSnippet: afterChamferSelectSnippet:
'const sketch002 = startSketchOn(extrude001, seg03)', 'const sketch002 = startSketchOn(extrude001, seg03)',
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|> angledLine([ |> angledLine([
segAng(rectangleSegmentA002) - 90, segAng(rectangleSegmentA002) - 90,
105.26 105.26
@ -192,13 +188,13 @@ test.describe('verify sketch on chamfer works', () => {
], %, $rectangleSegmentC001) ], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`, |> close(%)`,
}) })
await sketchOnAChamfer({ await sketchOnAChamfer({
clickCoords: { x: 690, y: 250 }, clickCoords: { x: 690, y: 250 },
cameraPos: { x: 16020, y: -2000, z: 10500 }, cameraPos: { x: 16020, y: -2000, z: 10500 },
cameraTarget: { x: -150, y: -4500, z: -80 }, cameraTarget: { x: -150, y: -4500, z: -80 },
beforeChamferSnippet: `angledLine([ beforeChamferSnippet: `angledLine([
segAng(rectangleSegmentA001) - 90, segAng(rectangleSegmentA001) - 90,
217.26 217.26
], %, $seg01)chamfer({ ], %, $seg01)chamfer({
@ -209,10 +205,10 @@ test.describe('verify sketch on chamfer works', () => {
getNextAdjacentEdge(seg02) getNextAdjacentEdge(seg02)
] ]
}, %)`, }, %)`,
afterChamferSelectSnippet: afterChamferSelectSnippet:
'const sketch003 = startSketchOn(extrude001, seg04)', 'const sketch003 = startSketchOn(extrude001, seg04)',
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|> angledLine([ |> angledLine([
segAng(rectangleSegmentA003) - 90, segAng(rectangleSegmentA003) - 90,
106.84 106.84
@ -223,22 +219,22 @@ test.describe('verify sketch on chamfer works', () => {
], %, $rectangleSegmentC002) ], %, $rectangleSegmentC002)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`, |> close(%)`,
}) })
await sketchOnAChamfer({ await sketchOnAChamfer({
clickCoords: { x: 677, y: 87 }, clickCoords: { x: 677, y: 87 },
cameraPos: { x: -6200, y: 1500, z: 6200 }, cameraPos: { x: -6200, y: 1500, z: 6200 },
cameraTarget: { x: 8300, y: 1100, z: 4800 }, cameraTarget: { x: 8300, y: 1100, z: 4800 },
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({ beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({
length: 30, length: 30,
tags: [ tags: [
getNextAdjacentEdge(yo), getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02) getNextAdjacentEdge(seg02)
] ]
}, %)`, }, %)`,
afterChamferSelectSnippet: afterChamferSelectSnippet:
'const sketch003 = startSketchOn(extrude001, seg04)', 'const sketch003 = startSketchOn(extrude001, seg04)',
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)', afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003) afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|> angledLine([ |> angledLine([
segAng(rectangleSegmentA003) - 90, segAng(rectangleSegmentA003) - 90,
106.84 106.84
@ -249,20 +245,20 @@ test.describe('verify sketch on chamfer works', () => {
], %, $rectangleSegmentC002) ], %, $rectangleSegmentC002)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`, |> close(%)`,
}) })
/// last one /// last one
await sketchOnAChamfer({ await sketchOnAChamfer({
clickCoords: { x: 620, y: 300 }, clickCoords: { x: 620, y: 300 },
cameraPos: { x: -1100, y: -7700, z: 1600 }, cameraPos: { x: -1100, y: -7700, z: 1600 },
cameraTarget: { x: 1450, y: 670, z: 4000 }, cameraTarget: { x: 1450, y: 670, z: 4000 },
beforeChamferSnippet: `chamfer({ beforeChamferSnippet: `chamfer({
length: 30, length: 30,
tags: [getNextAdjacentEdge(yo)] tags: [getNextAdjacentEdge(yo)]
}, %)`, }, %)`,
afterChamferSelectSnippet: afterChamferSelectSnippet:
'const sketch005 = startSketchOn(extrude001, seg06)', 'const sketch005 = startSketchOn(extrude001, seg06)',
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)', afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005) afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|> angledLine([ |> angledLine([
segAng(rectangleSegmentA005) - 90, segAng(rectangleSegmentA005) - 90,
84.07 84.07
@ -273,11 +269,11 @@ test.describe('verify sketch on chamfer works', () => {
], %, $rectangleSegmentC004) ], %, $rectangleSegmentC004)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`, |> close(%)`,
}) })
await test.step('verify at the end of the test that final code is what is expected', async () => { await test.step('verify at the end of the test that final code is what is expected', async () => {
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`const sketch001 = startSketchOn('XZ') `const sketch001 = startSketchOn('XZ')
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag] |> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|> angledLine([0, 268.43], %, $rectangleSegmentA001) |> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([ |> angledLine([
@ -357,44 +353,43 @@ test.describe('verify sketch on chamfer works', () => {
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
`, `,
{ shouldNormalise: true } { shouldNormalise: true }
)
})
}
)
test(
'Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array',
{ tag: ['@skipWin'] },
async ({ app, editor, toolbar, scene }) => {
test.skip(
process.platform === 'win32',
'Fails on windows in CI, can not be replicated locally on windows.'
) )
}) const file = await app.getInputFile(
}) 'e2e-can-sketch-on-chamfer-no-pipeExpr.kcl'
)
await app.initialise(file)
test('Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', async ({ const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene)
app,
editor,
toolbar,
scene,
}) => {
test.skip(
process.platform === 'win32',
'Fails on windows in CI, can not be replicated locally on windows.'
)
const file = await app.getInputFile(
'e2e-can-sketch-on-chamfer-no-pipeExpr.kcl'
)
await app.initialise(file)
const sketchOnAChamfer = _sketchOnAChamfer(app, editor, toolbar, scene) await sketchOnAChamfer({
clickCoords: { x: 570, y: 220 },
await sketchOnAChamfer({ cameraPos: { x: 16020, y: -2000, z: 10500 },
clickCoords: { x: 570, y: 220 }, cameraTarget: { x: -150, y: -4500, z: -80 },
cameraPos: { x: 16020, y: -2000, z: 10500 }, beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
cameraTarget: { x: -150, y: -4500, z: -80 },
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
chamfer({length:30,tags:[ chamfer({length:30,tags:[
seg01, seg01,
getNextAdjacentEdge(yo), getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02), getNextAdjacentEdge(seg02),
getOppositeEdge(seg01) getOppositeEdge(seg01)
]}, extrude001)`, ]}, extrude001)`,
beforeChamferSnippetEnd: '}, extrude001)', beforeChamferSnippetEnd: '}, extrude001)',
afterChamferSelectSnippet: afterChamferSelectSnippet:
'const sketch002 = startSketchOn(extrude001, seg03)', 'const sketch002 = startSketchOn(extrude001, seg03)',
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)', afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002) afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|> angledLine([ |> angledLine([
segAng(rectangleSegmentA002) - 90, segAng(rectangleSegmentA002) - 90,
105.26 105.26
@ -405,9 +400,9 @@ test.describe('verify sketch on chamfer works', () => {
], %, $rectangleSegmentC001) ], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`, |> close(%)`,
}) })
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`const sketch001 = startSketchOn('XZ') `const sketch001 = startSketchOn('XZ')
|> startProfileAt([75.8, 317.2], %) |> startProfileAt([75.8, 317.2], %)
|> angledLine([0, 268.43], %, $rectangleSegmentA001) |> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([ |> angledLine([
@ -447,7 +442,8 @@ const sketch002 = startSketchOn(extrude001, seg03)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
`, `,
{ shouldNormalise: true } { shouldNormalise: true }
) )
}) }
)
}) })

View File

@ -221,20 +221,23 @@ const sketch001 = startSketchAt([-0, -0])
// Make sure it's not a link // Make sure it's not a link
await expect(zooLogo).not.toHaveAttribute('href') await expect(zooLogo).not.toHaveAttribute('href')
}) })
test('Position _ Is Out Of Range... regression test', async ({ page }) => { test(
// SKip on windows, its being weird. 'Position _ Is Out Of Range... regression test',
test.skip( { tag: ['@skipWin'] },
process.platform === 'win32', async ({ page }) => {
'This test is being weird on windows' // SKip on windows, its being weird.
) test.skip(
process.platform === 'win32',
'This test is being weird on windows'
)
const u = await getUtils(page) const u = await getUtils(page)
// const PUR = 400 / 37.5 //pixeltoUnitRatio // const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
`const exampleSketch = startSketchOn("XZ") `const exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ angle: 50, length: 45 }, %) |> angledLine({ angle: 50, length: 45 }, %)
|> yLineTo(0, %) |> yLineTo(0, %)
@ -243,53 +246,53 @@ const sketch001 = startSketchAt([-0, -0])
const example = extrude(5, exampleSketch) const example = extrude(5, exampleSketch)
shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)` shell({ faces: ['end'], thickness: 0.25 }, exampleSketch)`
) )
})
await expect(async () => {
await page.goto('/')
await u.waitForPageLoad()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
timeout: 1_000,
}) })
await page.waitForTimeout(200)
// expect it still to be there (sometimes it just clears for a bit?)
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
timeout: 1_000,
})
}).toPass({ timeout: 40_000, intervals: [1_000] })
// error text on hover await expect(async () => {
await page.hover('.cm-lint-marker-error') await page.goto('/')
await expect(page.getByText('Unexpected token: |').first()).toBeVisible() await u.waitForPageLoad()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
timeout: 1_000,
})
await page.waitForTimeout(200)
// expect it still to be there (sometimes it just clears for a bit?)
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
timeout: 1_000,
})
}).toPass({ timeout: 40_000, intervals: [1_000] })
// Okay execution finished, let's start editing text below the error. // error text on hover
await u.codeLocator.click() await page.hover('.cm-lint-marker-error')
// Go to the end of the editor await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
// This bug happens when there is a diagnostic in the editor and you try to
// edit text below it.
// Or delete a huge chunk of text and then try to edit below it.
await page.keyboard.press('End')
await page.keyboard.down('Shift')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('End')
await page.keyboard.up('Shift')
await page.keyboard.press('Backspace')
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await page.keyboard.press('Enter') // Okay execution finished, let's start editing text below the error.
await page.keyboard.press('Enter') await u.codeLocator.click()
await page.keyboard.type('thing: "blah"', { delay: 100 }) // Go to the end of the editor
await page.keyboard.press('Enter') // This bug happens when there is a diagnostic in the editor and you try to
await page.keyboard.press('ArrowLeft') // edit text below it.
// Or delete a huge chunk of text and then try to edit below it.
await page.keyboard.press('End')
await page.keyboard.down('Shift')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('End')
await page.keyboard.up('Shift')
await page.keyboard.press('Backspace')
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await expect(page.locator('.cm-content')) await page.keyboard.press('Enter')
.toContainText(`const exampleSketch = startSketchOn("XZ") await page.keyboard.press('Enter')
await page.keyboard.type('thing: "blah"', { delay: 100 })
await page.keyboard.press('Enter')
await page.keyboard.press('ArrowLeft')
await expect(page.locator('.cm-content'))
.toContainText(`const exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine({ angle: 50, length: 45 }, %) |> angledLine({ angle: 50, length: 45 }, %)
|> yLineTo(0, %) |> yLineTo(0, %)
@ -297,8 +300,9 @@ const sketch001 = startSketchAt([-0, -0])
thing: "blah"`) thing: "blah"`)
await expect(page.locator('.cm-lint-marker-error')).toBeVisible() await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
}) }
)
test('when engine fails export we handle the failure and alert the user', async ({ test('when engine fails export we handle the failure and alert the user', async ({
page, page,
@ -401,95 +405,97 @@ const sketch001 = startSketchAt([-0, -0])
const successToastMessage = page.getByText(`Exported successfully`) const successToastMessage = page.getByText(`Exported successfully`)
await expect(successToastMessage).toBeVisible() await expect(successToastMessage).toBeVisible()
}) })
test('ensure you can not export while an export is already going', async ({ test(
page, 'ensure you can not export while an export is already going',
}) => { { tag: ['@skipLinux', '@skipWin'] },
// This is being weird on ubuntu and windows. async ({ page }) => {
test.skip( // This is being weird on ubuntu and windows.
// eslint-disable-next-line jest/valid-title test.skip(
process.platform === 'linux' || process.platform === 'win32', // eslint-disable-next-line jest/valid-title
'This test is being weird on ubuntu' process.platform === 'linux' || process.platform === 'win32',
) 'This test is being weird on ubuntu'
const u = await getUtils(page)
await test.step('Set up the code and durations', async () => {
await page.addInitScript(
async ({ code }) => {
localStorage.setItem('persistCode', code)
;(window as any).playwrightSkipFilePicker = true
},
{
code: bracket,
}
) )
await page.setViewportSize({ width: 1000, height: 500 }) const u = await getUtils(page)
await test.step('Set up the code and durations', async () => {
await page.addInitScript(
async ({ code }) => {
localStorage.setItem('persistCode', code)
;(window as any).playwrightSkipFilePicker = true
},
{
code: bracket,
}
)
await u.waitForAuthSkipAppStart() await page.setViewportSize({ width: 1000, height: 500 })
// wait for execution done await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// expect zero errors in guter // wait for execution done
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await u.openDebugPanel()
}) await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const errorToastMessage = page.getByText(`Error while exporting`) // expect zero errors in guter
const exportingToastMessage = page.getByText(`Exporting...`) await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
const engineErrorToastMessage = page.getByText(`Nothing to export`) })
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
const successToastMessage = page.getByText(`Exported successfully`)
await test.step('Blocked second export', async () => { const errorToastMessage = page.getByText(`Error while exporting`)
await clickExportButton(page) const exportingToastMessage = page.getByText(`Exporting...`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
const successToastMessage = page.getByText(`Exported successfully`)
await expect(exportingToastMessage).toBeVisible() await test.step('Blocked second export', async () => {
await clickExportButton(page)
await clickExportButton(page) await expect(exportingToastMessage).toBeVisible()
await clickExportButton(page)
await test.step('The second export is blocked', async () => {
// Find the toast.
// Look out for the toast message
await Promise.all([
expect(exportingToastMessage.first()).toBeVisible(),
expect(alreadyExportingToastMessage).toBeVisible(),
])
})
await test.step('The first export still succeeds', async () => {
await Promise.all([
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
expect(errorToastMessage).not.toBeVisible(),
expect(engineErrorToastMessage).not.toBeVisible(),
expect(successToastMessage).toBeVisible({ timeout: 15_000 }),
expect(alreadyExportingToastMessage).not.toBeVisible({
timeout: 15_000,
}),
])
})
})
await test.step('Successful, unblocked export', async () => {
// Try exporting again.
await clickExportButton(page)
await test.step('The second export is blocked', async () => {
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
await Promise.all([ await expect(exportingToastMessage).toBeVisible()
expect(exportingToastMessage.first()).toBeVisible(),
expect(alreadyExportingToastMessage).toBeVisible(),
])
})
await test.step('The first export still succeeds', async () => { // Expect it to succeed.
await Promise.all([ await Promise.all([
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }), expect(exportingToastMessage).not.toBeVisible(),
expect(errorToastMessage).not.toBeVisible(), expect(errorToastMessage).not.toBeVisible(),
expect(engineErrorToastMessage).not.toBeVisible(), expect(engineErrorToastMessage).not.toBeVisible(),
expect(successToastMessage).toBeVisible({ timeout: 15_000 }), expect(alreadyExportingToastMessage).not.toBeVisible(),
expect(alreadyExportingToastMessage).not.toBeVisible({
timeout: 15_000,
}),
]) ])
await expect(successToastMessage).toBeVisible()
}) })
}) }
)
await test.step('Successful, unblocked export', async () => {
// Try exporting again.
await clickExportButton(page)
// Find the toast.
// Look out for the toast message
await expect(exportingToastMessage).toBeVisible()
// Expect it to succeed.
await Promise.all([
expect(exportingToastMessage).not.toBeVisible(),
expect(errorToastMessage).not.toBeVisible(),
expect(engineErrorToastMessage).not.toBeVisible(),
expect(alreadyExportingToastMessage).not.toBeVisible(),
])
await expect(successToastMessage).toBeVisible()
})
})
test( test(
`Network health indicator only appears in modeling view`, `Network health indicator only appears in modeling view`,

View File

@ -49,7 +49,7 @@ test.setTimeout(60_000)
test( test(
'exports of each format should work', 'exports of each format should work',
{ tag: '@snapshot' }, { tag: ['@snapshot', '@skipWin', '@skipMacos'] },
async ({ page, context }) => { async ({ page, context }) => {
// skip on macos and windows. // skip on macos and windows.
test.skip( test.skip(

View File

@ -15,234 +15,240 @@ test.afterEach(async ({ page }, testInfo) => {
test.describe('Testing selections', () => { test.describe('Testing selections', () => {
test.setTimeout(90_000) test.setTimeout(90_000)
test('Selections work on fresh and edited sketch', async ({ page }) => { test(
// Skip on windows its being weird. 'Selections work on fresh and edited sketch',
test.skip(process.platform === 'win32', 'Skip on windows') { tag: ['@skipWin'] },
async ({ page }) => {
// Skip on windows its being weird.
test.skip(process.platform === 'win32', 'Skip on windows')
// tests mapping works on fresh sketch and edited sketch // tests mapping works on fresh sketch and edited sketch
// tests using hovers which is the same as selections, because if // tests using hovers which is the same as selections, because if
// source ranges are wrong, hovers won't work // source ranges are wrong, hovers won't work
const u = await getUtils(page) const u = await getUtils(page)
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await u.openDebugPanel() await u.openDebugPanel()
const xAxisClick = () => const xAxisClick = () =>
page.mouse.click(700, 253).then(() => page.waitForTimeout(100)) page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
const xAxisClickAfterExitingSketch = () => const xAxisClickAfterExitingSketch = () =>
page.mouse.click(639, 278).then(() => page.waitForTimeout(100)) page.mouse.click(639, 278).then(() => page.waitForTimeout(100))
const emptySpaceHover = () => const emptySpaceHover = () =>
test.step('Hover over empty space', async () => { test.step('Hover over empty space', async () => {
await page.mouse.move(700, 143, { steps: 5 }) await page.mouse.move(700, 143, { steps: 5 })
await expect(page.locator('.hover-highlight')).not.toBeVisible() await expect(page.locator('.hover-highlight')).not.toBeVisible()
}) })
const emptySpaceClick = () => const emptySpaceClick = () =>
test.step(`Click in empty space`, async () => { test.step(`Click in empty space`, async () => {
await page.mouse.click(700, 143) await page.mouse.click(700, 143)
await expect(page.locator('.cm-line').last()).toHaveClass( await expect(page.locator('.cm-line').last()).toHaveClass(
/cm-activeLine/ /cm-activeLine/
) )
}) })
const topHorzSegmentClick = () => const topHorzSegmentClick = () =>
page.mouse page.mouse
.click(startXPx, 500 - PUR * 20) .click(startXPx, 500 - PUR * 20)
.then(() => page.waitForTimeout(100)) .then(() => page.waitForTimeout(100))
const bottomHorzSegmentClick = () => const bottomHorzSegmentClick = () =>
page.mouse page.mouse
.click(startXPx + PUR * 10, 500 - PUR * 10) .click(startXPx + PUR * 10, 500 - PUR * 10)
.then(() => page.waitForTimeout(100)) .then(() => page.waitForTimeout(100))
await u.clearCommandLogs() await u.clearCommandLogs()
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()
// select a plane // select a plane
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await page.waitForTimeout(700) // wait for animation await page.waitForTimeout(700) // wait for animation
const startXPx = 600 const startXPx = 600
await u.closeDebugPanel() await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`) |> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`) |> line([${commonPoints.num1}, 0], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)`) |> line([0, ${commonPoints.num1 + 0.01}], %)`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') .toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %) |> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %) |> line([0, ${commonPoints.num1 + 0.01}], %)
|> line([-${commonPoints.num2}, 0], %)`) |> line([-${commonPoints.num2}, 0], %)`)
// deselect line tool // deselect line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click() await page.getByRole('button', { name: 'line Line', exact: true }).click()
await u.closeDebugPanel() await u.closeDebugPanel()
const selectionSequence = async () => { const selectionSequence = async () => {
await expect(page.getByTestId('hover-highlight')).not.toBeVisible() await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10) await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
await expect(page.getByTestId('hover-highlight').first()).toBeVisible() await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
// bg-yellow-300/70 is more brittle than hover-highlight, but is closer to the user experience // bg-yellow-300/70 is more brittle than hover-highlight, but is closer to the user experience
// and will be an easy fix if it breaks because we change the colour // and will be an easy fix if it breaks because we change the colour
await expect(page.locator('.bg-yellow-300\\/70')).toBeVisible() await expect(page.locator('.bg-yellow-300\\/70')).toBeVisible()
// check mousing off, than mousing onto another line // check mousing off, than mousing onto another line
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
await expect(page.getByTestId('hover-highlight')).not.toBeVisible() await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line
await expect(page.getByTestId('hover-highlight').first()).toBeVisible() await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
// now check clicking works including axis // now check clicking works including axis
// click a segment hold shift and click an axis, see that a relevant constraint is enabled // click a segment hold shift and click an axis, see that a relevant constraint is enabled
await topHorzSegmentClick() await topHorzSegmentClick()
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
const constrainButton = page.getByRole('button', { const constrainButton = page.getByRole('button', {
name: 'Length: open menu', name: 'Length: open menu',
})
const absYButton = page.getByRole('button', { name: 'Absolute Y' })
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await absYButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await emptySpaceClick()
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await emptySpaceClick()
// select segment in editor than another segment in scene and check there are two cursors
// TODO change this back to shift click in the scene, not cmd click in the editor
await bottomHorzSegmentClick()
await expect(page.locator('.cm-cursor')).toHaveCount(1)
await page.keyboard.down(
process.platform === 'linux' ? 'Control' : 'Meta'
)
await page.waitForTimeout(100)
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(500)
await page.keyboard.up(
process.platform === 'linux' ? 'Control' : 'Meta'
)
// clear selection by clicking on nothing
await emptySpaceClick()
}
await test.step(`Test hovering and selecting on fresh sketch`, async () => {
await selectionSequence()
}) })
const absYButton = page.getByRole('button', { name: 'Absolute Y' })
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await absYButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing // hovering in fresh sketch worked, lets try exiting and re-entering
await emptySpaceClick() await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(200)
// wait for execution done
await page.waitForTimeout(100) await u.expectCmdLog('[data-message-type="execution-done"]')
// same selection but click the axis first await u.closeDebugPanel()
await xAxisClick()
await constrainButton.click() // select a line, this verifies that sketches in the scene can be selected outside of sketch mode
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick() await topHorzSegmentClick()
await xAxisClickAfterExitingSketch()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await emptySpaceHover()
await page.keyboard.up('Shift') // enter sketch again
await constrainButton.click() await u.doAndWaitForCmd(
await expect(absYButton).not.toBeDisabled() () => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings'
// clear selection by clicking on nothing
await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
// clear selection by clicking on nothing
await emptySpaceClick()
// select segment in editor than another segment in scene and check there are two cursors
// TODO change this back to shift click in the scene, not cmd click in the editor
await bottomHorzSegmentClick()
await expect(page.locator('.cm-cursor')).toHaveCount(1)
await page.keyboard.down(
process.platform === 'linux' ? 'Control' : 'Meta'
) )
await page.waitForTimeout(450) // wait for animation
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 0, y: 0, z: 0 },
vantage: { x: 0, y: -1378.01, z: 0 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(500)
await page.keyboard.up(process.platform === 'linux' ? 'Control' : 'Meta')
// clear selection by clicking on nothing
await emptySpaceClick() await emptySpaceClick()
await u.closeDebugPanel()
await test.step(`Test hovering and selecting on edited sketch`, async () => {
await selectionSequence()
})
} }
)
await test.step(`Test hovering and selecting on fresh sketch`, async () => {
await selectionSequence()
})
// hovering in fresh sketch worked, lets try exiting and re-entering
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(200)
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// select a line, this verifies that sketches in the scene can be selected outside of sketch mode
await topHorzSegmentClick()
await xAxisClickAfterExitingSketch()
await page.waitForTimeout(100)
await emptySpaceHover()
// enter sketch again
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings'
)
await page.waitForTimeout(450) // wait for animation
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 0, y: 0, z: 0 },
vantage: { x: 0, y: -1378.01, z: 0 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100)
await emptySpaceClick()
await u.closeDebugPanel()
await test.step(`Test hovering and selecting on edited sketch`, async () => {
await selectionSequence()
})
})
test('Solids should be select and deletable', async ({ page }) => { test('Solids should be select and deletable', async ({ page }) => {
test.setTimeout(90_000) test.setTimeout(90_000)

View File

@ -258,7 +258,7 @@ test.describe('Testing settings', () => {
test( test(
`Project settings override user settings on desktop`, `Project settings override user settings on desktop`,
{ tag: '@electron' }, { tag: ['@electron', '@skipWin'] },
async ({ browser: _ }, testInfo) => { async ({ browser: _ }, testInfo) => {
test.skip( test.skip(
process.platform === 'win32', process.platform === 'win32',

View File

@ -447,125 +447,127 @@ test.describe('Text-to-CAD tests', () => {
await expect(page.getByText(promptWithNewline)).toBeVisible() await expect(page.getByText(promptWithNewline)).toBeVisible()
}) })
test('can do many at once and get many prompts back, and interact with many', async ({ test(
page, 'can do many at once and get many prompts back, and interact with many',
}) => { { tag: ['@skipWin'] },
// Let this test run longer since we've seen it timeout. async ({ page }) => {
test.setTimeout(180_000) // Let this test run longer since we've seen it timeout.
// skip on windows test.setTimeout(180_000)
test.skip( // skip on windows
process.platform === 'win32', test.skip(
'This test is flaky, skipping for now' process.platform === 'win32',
) 'This test is flaky, skipping for now'
)
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1000, height: 500 }) await page.setViewportSize({ width: 1000, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
await sendPromptFromCommandBar(page, 'a 2x4 lego') await sendPromptFromCommandBar(page, 'a 2x4 lego')
await sendPromptFromCommandBar(page, 'a 2x8 lego') await sendPromptFromCommandBar(page, 'a 2x8 lego')
await sendPromptFromCommandBar(page, 'a 2x10 lego') await sendPromptFromCommandBar(page, 'a 2x10 lego')
// Find the toast. // Find the toast.
// Look out for the toast message // Look out for the toast message
const submittingToastMessage = page.getByText( const submittingToastMessage = page.getByText(
`Submitting to Text-to-CAD API...` `Submitting to Text-to-CAD API...`
) )
await expect(submittingToastMessage.first()).toBeVisible() await expect(submittingToastMessage.first()).toBeVisible()
const generatingToastMessage = page.getByText( const generatingToastMessage = page.getByText(
`Generating parametric model...` `Generating parametric model...`
) )
await expect(generatingToastMessage.first()).toBeVisible({ await expect(generatingToastMessage.first()).toBeVisible({
timeout: 10_000, timeout: 10_000,
}) })
const successToastMessage = page.getByText(`Text-to-CAD successful`) const successToastMessage = page.getByText(`Text-to-CAD successful`)
// We should have three success toasts. // We should have three success toasts.
await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 }) await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 })
await expect(page.getByText('Copied')).not.toBeVisible() await expect(page.getByText('Copied')).not.toBeVisible()
await expect(page.getByText(`a 2x4 lego`)).toBeVisible() await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
await expect(page.getByText(`a 2x8 lego`)).toBeVisible() await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
await expect(page.getByText(`a 2x10 lego`)).toBeVisible() await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
// Ensure if you reject one, the others stay. // Ensure if you reject one, the others stay.
const rejectButton = page.getByRole('button', { name: 'Reject' }) const rejectButton = page.getByRole('button', { name: 'Reject' })
await expect(rejectButton.first()).toBeVisible() await expect(rejectButton.first()).toBeVisible()
// Click the reject button on the first toast. // Click the reject button on the first toast.
await rejectButton.first().click() await rejectButton.first().click()
// The first toast should disappear, but not the others. // The first toast should disappear, but not the others.
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible() await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
await expect(page.getByText(`a 2x8 lego`)).toBeVisible() await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
await expect(page.getByText(`a 2x4 lego`)).toBeVisible() await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
// Ensure you can copy the code for one of the models remaining. // Ensure you can copy the code for one of the models remaining.
const copyToClipboardButton = page.getByRole('button', { const copyToClipboardButton = page.getByRole('button', {
name: 'Copy to clipboard', name: 'Copy to clipboard',
}) })
await expect(copyToClipboardButton.first()).toBeVisible() await expect(copyToClipboardButton.first()).toBeVisible()
// Click the button. // Click the button.
await copyToClipboardButton.first().click() await copyToClipboardButton.first().click()
// Expect the code to be copied. // Expect the code to be copied.
await expect(page.getByText('Copied')).toBeVisible() await expect(page.getByText('Copied')).toBeVisible()
// Click in the code editor. // Click in the code editor.
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
// Paste the code. // Paste the code.
await page.keyboard.down('ControlOrMeta') await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyV') await page.keyboard.press('KeyV')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up('ControlOrMeta')
// Expect the code to be pasted. // Expect the code to be pasted.
await expect(page.locator('.cm-content')).toContainText(`2x8`) await expect(page.locator('.cm-content')).toContainText(`2x8`)
// Find the toast close button. // Find the toast close button.
const closeButton = page.locator('[data-negative-button="close"]').first() const closeButton = page.locator('[data-negative-button="close"]').first()
await expect(closeButton).toBeVisible() await expect(closeButton).toBeVisible()
await closeButton.click() await closeButton.click()
// Ensure the final toast remains. // Ensure the final toast remains.
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible() await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible() await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
await expect(page.getByText(`a 2x4 lego`)).toBeVisible() await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
// Ensure you can copy the code for the final model. // Ensure you can copy the code for the final model.
await expect(copyToClipboardButton).toBeVisible() await expect(copyToClipboardButton).toBeVisible()
// Click the button. // Click the button.
await copyToClipboardButton.click() await copyToClipboardButton.click()
// Expect the code to be copied. // Expect the code to be copied.
await expect(page.getByText('Copied')).toBeVisible() await expect(page.getByText('Copied')).toBeVisible()
// Click in the code editor. // Click in the code editor.
await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) await page.locator('.cm-content').click({ position: { x: 10, y: 10 } })
// Paste the code. // Paste the code.
await page.keyboard.down('ControlOrMeta') await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyA') await page.keyboard.press('KeyA')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up('ControlOrMeta')
await page.keyboard.press('Backspace') await page.keyboard.press('Backspace')
await page.keyboard.down('ControlOrMeta') await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyV') await page.keyboard.press('KeyV')
await page.keyboard.up('ControlOrMeta') await page.keyboard.up('ControlOrMeta')
// Expect the code to be pasted. // Expect the code to be pasted.
await expect(page.locator('.cm-content')).toContainText(`2x4`) await expect(page.locator('.cm-content')).toContainText(`2x4`)
// Expect the toast to disappear. // Expect the toast to disappear.
// Find the toast close button. // Find the toast close button.
await expect(closeButton).toBeVisible() await expect(closeButton).toBeVisible()
await closeButton.click() await closeButton.click()
await expect(successToastMessage).not.toBeVisible() await expect(successToastMessage).not.toBeVisible()
}) }
)
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({ test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
page, page,

View File

@ -75,11 +75,10 @@
"build:both": "vite build", "build:both": "vite build",
"build:both:local": "yarn build:wasm && vite build", "build:both:local": "yarn build:wasm && vite build",
"pretest": "yarn remove-importmeta", "pretest": "yarn remove-importmeta",
"test": "vitest --mode development",
"test:nowatch": "vitest run --mode development",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)", "test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000", "simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &", "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages", "fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages", "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh", "fetch:wasm": "./get-latest-wasm-bundle.sh",
@ -89,7 +88,8 @@
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt", "build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"", "remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings", "wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src e2e packages/codemirror-lsp-client", "lint-fix": "eslint --fix src e2e packages/codemirror-lsp-client",
"lint": "eslint --max-warnings 0 src e2e packages/codemirror-lsp-client",
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", "bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild", "postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"", "xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
@ -101,7 +101,23 @@
"tron:publish": "electron-forge publish", "tron:publish": "electron-forge publish",
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron", "tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts", "tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
"tronb:package": "electron-builder --config electron-builder.yml" "tronb:package": "electron-builder --config electron-builder.yml",
"test-setup": "yarn install && yarn build:wasm",
"test": "vitest --mode development",
"test:nowatch": "vitest run --mode development",
"test:unit": "vitest run --mode development",
"test:playwright:browser:chrome": "playwright test '--project=Google Chrome' --config=playwright.ci.config.ts '--grep-invert=@snapshot|@electron'",
"test:playwright:browser:chrome:windows": "playwright test '--project=Google Chrome' --config=playwright.ci.config.ts '--grep-invert=@snapshot|@electron|@skipWin'",
"test:playwright:browser:chrome:ubuntu": "playwright test '--project=Google Chrome' --config=playwright.ci.config.ts '--grep-invert=@snapshot|@electron|@skipLinux'",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep=@electron",
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin",
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos",
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux",
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron",
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipWin",
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipMacos",
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep=@electron --grep-invert=@skipLinux",
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000"
}, },
"prettier": { "prettier": {
"trailingComma": "es5", "trailingComma": "es5",
@ -174,6 +190,7 @@
"happy-dom": "^14.3.10", "happy-dom": "^14.3.10",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"husky": "^9.1.5", "husky": "^9.1.5",
"kill-port": "^2.0.1",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"pixelmatch": "^5.3.0", "pixelmatch": "^5.3.0",
"pngjs": "^7.0.0", "pngjs": "^7.0.0",

View File

@ -14,7 +14,7 @@ import {
} from './artifactGraph' } from './artifactGraph'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { CI, VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_DEV_TOKEN } from 'env'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import fs from 'fs' import fs from 'fs'
import { chromium } from 'playwright' import { chromium } from 'playwright'
@ -97,21 +97,6 @@ type CacheShape = {
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
let parsed
try {
const file = await fsp.readFile(fullPath, 'utf-8')
parsed = JSON.parse(file)
} catch (e) {
parsed = false
}
if (!CI && parsed) {
// caching the results of the websocket commands makes testing this locally much faster
// real calls to the engine are needed to test the artifact map
// bust the cache with: `rm -rf src/lang/std/artifactGraphCache`
return
}
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local // THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({

View File

@ -5695,6 +5695,11 @@ get-symbol-description@^1.0.2:
es-errors "^1.3.0" es-errors "^1.3.0"
get-intrinsic "^1.2.4" get-intrinsic "^1.2.4"
get-them-args@1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/get-them-args/-/get-them-args-1.3.2.tgz#74a20ba8a4abece5ae199ad03f2bcc68fdfc9ba5"
integrity sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==
glob-parent@^5.1.2, glob-parent@~5.1.2: glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@ -6645,6 +6650,14 @@ keyv@^4.0.0, keyv@^4.5.3:
dependencies: dependencies:
json-buffer "3.0.1" json-buffer "3.0.1"
kill-port@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/kill-port/-/kill-port-2.0.1.tgz#e5e18e2706b13d54320938be42cb7d40609b15cf"
integrity sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ==
dependencies:
get-them-args "1.3.2"
shell-exec "1.0.2"
klaw@^4.1.0: klaw@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-4.1.0.tgz#5df608067d8cb62bbfb24374f8e5d956323338f3" resolved "https://registry.yarnpkg.com/klaw/-/klaw-4.1.0.tgz#5df608067d8cb62bbfb24374f8e5d956323338f3"
@ -8579,6 +8592,11 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shell-exec@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/shell-exec/-/shell-exec-1.0.2.tgz#2e9361b0fde1d73f476c4b6671fa17785f696756"
integrity sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==
side-channel@^1.0.4, side-channel@^1.0.6: side-channel@^1.0.4, side-channel@^1.0.6:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"