Compare commits

...

7 Commits

Author SHA1 Message Date
e1da72a0ae Update common points to reflect empty zoom-to-fit 2024-08-05 17:40:06 +02:00
ec2d1999a7 fmt 2024-08-05 16:31:07 +02:00
95683f1cc1 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-08-05 14:17:29 +00:00
f48f1c21c1 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-08-05 14:16:05 +00:00
5cdf2de89a Reset camera position when artifact graph is cleared 2024-08-05 16:08:28 +02:00
543e809739 Add "report a bug" mention to user menu onboarding step (#3278)
* Mention "report a bug" in user menu onboarding step

* Add to a test so we match QAWolf a bit more

* Re-run CI
2024-08-05 08:24:19 -04:00
61b669cf4e github actions Playwright shard execution (#3199)
* add @snapshot tag to all snapshot-tests

* set workers to 1 on CI

* try sharding on google chrome only

* add retry when navigating to the app (on CI)

* reduce ubuntu-cores to 2

* revert runner back to 8 cores

* re-add retry + enable macos

* Reduce timeouts to 30 minutes

* ensure retry is triggered

* removed sharding when retrying failures

* added --pass-with-no-tests

* ensure failure occurs

* revert failure

* use smaller sized runners

* revert back to supported runner size

* revert to og version

* minimize changes

* yarn fmt

* ensure failure

* undo failure

---------

Co-authored-by: ryanrosello-og <ry@zoo.dev>
2024-08-05 21:30:16 +10:00
12 changed files with 662 additions and 581 deletions

View File

@ -34,8 +34,13 @@ jobs:
- 'src/wasm-lib/**' - 'src/wasm-lib/**'
playwright-ubuntu: playwright-ubuntu:
timeout-minutes: 60 timeout-minutes: 30
runs-on: ubuntu-latest-8-cores runs-on: ubuntu-latest-8-cores
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
needs: check-rust-changes needs: check-rust-changes
steps: steps:
- name: Tune GitHub-hosted runner network - name: Tune GitHub-hosted runner network
@ -107,7 +112,7 @@ jobs:
run: yarn build:local run: yarn build:local
- name: Run ubuntu/chrome snapshots - name: Run ubuntu/chrome snapshots
run: | run: |
yarn playwright test --project="Google Chrome" --retries="3" --update-snapshots e2e/playwright/snapshot-tests.spec.ts yarn playwright test --project="Google Chrome" --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
env: env:
CI: true CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
@ -115,7 +120,7 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: playwright-report-ubuntu-snapshot-${{ github.sha }} name: playwright-report-ubuntu-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
@ -149,7 +154,7 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: steps.git-check.outputs.modified == 'true' if: steps.git-check.outputs.modified == 'true'
with: with:
name: playwright-report-ubuntu-${{ github.sha }} name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
# if have previous run results, use them # if have previous run results, use them
@ -157,7 +162,7 @@ jobs:
if: always() if: always()
continue-on-error: true continue-on-error: true
with: with:
name: test-results-ubuntu-${{ github.sha }} name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
- name: Run ubuntu/chrome flow (with retries) - name: Run ubuntu/chrome flow (with retries)
id: retry id: retry
@ -166,7 +171,7 @@ jobs:
if [[ ! -f "test-results/.last-run.json" ]]; then if [[ ! -f "test-results/.last-run.json" ]]; then
# if no last run artifact, than run plawright normally # if no last run artifact, than run plawright normally
echo "run playwright normally" echo "run playwright normally"
yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts || true yarn playwright test --project="Google Chrome" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true
# # send to axiom # # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi fi
@ -181,7 +186,7 @@ jobs:
if [[ $failed_tests -gt 0 ]]; then if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
yarn playwright test --project="Google Chrome" --last-failed e2e/playwright/flow-tests.spec.ts || true yarn playwright test --project="Google Chrome" --last-failed --grep-invert=@snapshot || true
# send to axiom # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
retry=$((retry + 1)) retry=$((retry + 1))
@ -216,21 +221,26 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: test-results-ubuntu-${{ github.sha }} name: test-results-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: always() if: always()
with: with:
name: playwright-report-ubuntu-${{ github.sha }} name: playwright-report-ubuntu-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
playwright-macos: playwright-macos:
timeout-minutes: 60 timeout-minutes: 30
runs-on: macos-14-large runs-on: macos-14
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
needs: check-rust-changes needs: check-rust-changes
steps: steps:
- name: Tune GitHub-hosted runner network - name: Tune GitHub-hosted runner network
@ -306,7 +316,7 @@ jobs:
if: ${{ always() }} if: ${{ always() }}
continue-on-error: true continue-on-error: true
with: with:
name: test-results-macos-${{ github.sha }} name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
- name: Run macos/safari flow (with retries) - name: Run macos/safari flow (with retries)
id: retry id: retry
@ -315,7 +325,7 @@ jobs:
if [[ ! -f "test-results/.last-run.json" ]]; then if [[ ! -f "test-results/.last-run.json" ]]; then
# if no last run artifact, than run plawright normally # if no last run artifact, than run plawright normally
echo "run playwright normally" echo "run playwright normally"
yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts || true yarn playwright test --project="webkit" --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --grep-invert=@snapshot || true
# # send to axiom # # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
fi fi
@ -330,7 +340,7 @@ jobs:
if [[ $failed_tests -gt 0 ]]; then if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
yarn playwright test --project="webkit" --last-failed e2e/playwright/flow-tests.spec.ts || true yarn playwright test --project="webkit" --last-failed --grep-invert=@snapshot || true
# send to axiom # send to axiom
node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1 node playwrightProcess.mjs | tee /tmp/github-actions.log > /dev/null 2>&1
retry=$((retry + 1)) retry=$((retry + 1))
@ -360,15 +370,14 @@ jobs:
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ always() }} if: ${{ always() }}
with: with:
name: test-results-macos-${{ github.sha }} name: test-results-macos-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/ path: test-results/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
if: ${{ always() }} if: ${{ always() }}
with: with:
name: playwright-report-macos-${{ github.sha }} name: playwright-report-macos-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/ path: playwright-report/
retention-days: 30 retention-days: 30
overwrite: true overwrite: true

View File

@ -46,9 +46,9 @@ document.addEventListener('mousemove', (e) =>
const deg = (Math.PI * 2) / 360 const deg = (Math.PI * 2) / 360
const commonPoints = { const commonPoints = {
startAt: '[7.19, -9.7]', startAt: '[0.75, -1.01]',
num1: 7.25, num1: 0.75,
num2: 14.44, num2: 1.5,
} }
test.afterEach(async ({ context, page }, testInfo) => { test.afterEach(async ({ context, page }, testInfo) => {
@ -2535,18 +2535,29 @@ test.describe('Onboarding tests', () => {
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
// Test that the text in this step is correct // Test that the text in this step is correct
const avatarLocator = await page const sidebar = page.getByTestId('user-sidebar-toggle')
.getByTestId('user-sidebar-toggle') const avatar = sidebar.locator('img')
.locator('img') const onboardingOverlayLocator = page
const onboardingOverlayLocator = await page
.getByTestId('onboarding-content') .getByTestId('onboarding-content')
.locator('div') .locator('div')
.nth(1) .nth(1)
// Expect the avatar to be visible and for the text to reference it // Expect the avatar to be visible and for the text to reference it
await expect(avatarLocator).not.toBeVisible() await expect(avatar).not.toBeVisible()
await expect(onboardingOverlayLocator).toBeVisible() await expect(onboardingOverlayLocator).toBeVisible()
await expect(onboardingOverlayLocator).toContainText('the menu button') await expect(onboardingOverlayLocator).toContainText('the menu button')
// Test we mention what else is in this menu for https://github.com/KittyCAD/modeling-app/issues/2939
// which doesn't deserver its own full test spun up
const userMenuFeatures = [
'manage your account',
'report a bug',
'request a feature',
'sign out',
]
for (const feature of userMenuFeatures) {
await expect(onboardingOverlayLocator).toContainText(feature)
}
}) })
}) })

View File

@ -42,7 +42,10 @@ test.beforeEach(async ({ page }) => {
test.setTimeout(60_000) test.setTimeout(60_000)
test('exports of each format should work', async ({ page, context }) => { test(
'exports of each format should work',
{ tag: '@snapshot' },
async ({ page, context }) => {
// 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)
@ -296,7 +299,8 @@ const part001 = startSketchOn('-XZ')
console.log(`snapshot failed for ${modelPath}`) console.log(`snapshot failed for ${modelPath}`)
} }
} }
}) }
)
const extrudeDefaultPlane = async (context: any, page: any, plane: string) => { const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
await context.addInitScript(async () => { await context.addInitScript(async () => {
@ -357,7 +361,10 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
await u.openKclCodePanel() await u.openKclCodePanel()
} }
test.describe('extrude on default planes should be stable', () => { test.describe(
'extrude on default planes should be stable',
{ tag: '@snapshot' },
() => {
test('XY', async ({ page, context }) => { test('XY', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, 'XY') await extrudeDefaultPlane(context, page, 'XY')
}) })
@ -381,9 +388,13 @@ test.describe('extrude on default planes should be stable', () => {
test('-YZ', async ({ page, context }) => { test('-YZ', async ({ page, context }) => {
await extrudeDefaultPlane(context, page, '-YZ') await extrudeDefaultPlane(context, page, '-YZ')
}) })
}) }
)
test('Draft segments should look right', async ({ page, context }) => { test(
'Draft segments should look right',
{ tag: '@snapshot' },
async ({ page, context }) => {
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
@ -394,7 +405,9 @@ test('Draft segments should look right', async ({ page, context }) => {
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible() await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button // click on "Start Sketch" button
await u.clearCommandLogs() await u.clearCommandLogs()
@ -442,9 +455,13 @@ test('Draft segments should look right', async ({ page, context }) => {
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) }
)
test('Draft rectangles should look right', async ({ page, context }) => { test(
'Draft rectangles should look right',
{ tag: '@snapshot' },
async ({ page, context }) => {
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
@ -455,7 +472,9 @@ test('Draft rectangles should look right', async ({ page, context }) => {
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled() ).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible() await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button // click on "Start Sketch" button
await u.clearCommandLogs() await u.clearCommandLogs()
@ -490,9 +509,13 @@ test('Draft rectangles should look right', async ({ page, context }) => {
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) }
)
test.describe('Client side scene scale should match engine scale', () => { test.describe(
'Client side scene scale should match engine scale',
{ tag: '@snapshot' },
() => {
test('Inch scale', async ({ page }) => { test('Inch scale', async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
@ -680,9 +703,13 @@ test.describe('Client side scene scale should match engine scale', () => {
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) })
}) }
)
test('Sketch on face with none z-up', async ({ page, context }) => { test(
'Sketch on face with none z-up',
{ tag: '@snapshot' },
async ({ page, context }) => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => { await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
localStorage.setItem( localStorage.setItem(
@ -728,7 +755,9 @@ const part002 = startSketchOn(part001, seg01)
// click at 641, 135 // click at 641, 135
await page.mouse.click(641, 135) await page.mouse.click(641, 135)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) await expect(page.locator('.cm-content')).not.toHaveText(
previousCodeContent
)
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(300) await page.waitForTimeout(300)
@ -736,9 +765,13 @@ const part002 = startSketchOn(part001, seg01)
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) }
)
test('Zoom to fit on load - solid 2d', async ({ page, context }) => { test(
'Zoom to fit on load - solid 2d',
{ tag: '@snapshot' },
async ({ page, context }) => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -772,9 +805,13 @@ test('Zoom to fit on load - solid 2d', async ({ page, context }) => {
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) }
)
test('Zoom to fit on load - solid 3d', async ({ page, context }) => { test(
'Zoom to fit on load - solid 3d',
{ tag: '@snapshot' },
async ({ page, context }) => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -809,9 +846,10 @@ test('Zoom to fit on load - solid 3d', async ({ page, context }) => {
await expect(page).toHaveScreenshot({ await expect(page).toHaveScreenshot({
maxDiffPixels: 100, maxDiffPixels: 100,
}) })
}) }
)
test.describe('Grid visibility', () => { test.describe('Grid visibility', { tag: '@snapshot' }, () => {
test('Grid turned off', async ({ page }) => { test('Grid turned off', async ({ page }) => {
const u = await getUtils(page) const u = await getUtils(page)
const stream = page.getByTestId('stream') const stream = page.getByTestId('stream')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -15,6 +15,23 @@ export const TEST_COLORS = {
BLUE: [0, 0, 255] as TestColor, BLUE: [0, 0, 255] as TestColor,
} as const } as const
async function waitForPageLoadWithRetry(page: Page) {
await expect(async () => {
await page.goto('/')
const errorMessage = 'App failed to load - 🔃 Retrying ...'
await expect(page.getByTestId('loading'), errorMessage).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' }),
errorMessage
).toBeEnabled({
timeout: 20_000,
})
}).toPass({ timeout: 70_000, intervals: [1_000] })
}
async function waitForPageLoad(page: Page) { async function waitForPageLoad(page: Page) {
// wait for all spinners to be gone // wait for all spinners to be gone
await expect(page.getByTestId('loading')).not.toBeAttached({ await expect(page.getByTestId('loading')).not.toBeAttached({
@ -218,9 +235,12 @@ async function waitForAuthAndLsp(page: Page) {
} }
return false return false
}) })
if (process.env.CI) {
await waitForPageLoadWithRetry(page)
} else {
await page.goto('/') await page.goto('/')
await waitForPageLoad(page) await waitForPageLoad(page)
}
return waitForLspPromise return waitForLspPromise
} }
@ -234,6 +254,7 @@ export async function getUtils(page: Page) {
return { return {
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page), waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
waitForPageLoad: () => waitForPageLoad(page), waitForPageLoad: () => waitForPageLoad(page),
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
removeCurrentCode: () => removeCurrentCode(page), removeCurrentCode: () => removeCurrentCode(page),
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd), sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
updateCamPosition: async (xyz: [number, number, number]) => { updateCamPosition: async (xyz: [number, number, number]) => {

View File

@ -18,7 +18,7 @@ export default defineConfig({
/* Do not retry */ /* Do not retry */
retries: process.env.CI ? 0 : 0, retries: process.env.CI ? 0 : 0,
/* Different amount of parallelism on CI and local. */ /* Different amount of parallelism on CI and local. */
workers: process.env.CI ? 4 : 4, workers: process.env.CI ? 1 : 4,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [ reporter: [
[process.env.CI ? 'dot' : 'list'], [process.env.CI ? 'dot' : 'list'],

View File

@ -366,7 +366,7 @@ export const modelingMachine = createMachine(
'Artifact graph emptied': 'hidePlanes', 'Artifact graph emptied': 'hidePlanes',
}, },
entry: 'show default planes', entry: ['show default planes', 'reset camera position'],
}, },
}, },
@ -1063,6 +1063,8 @@ export const modelingMachine = createMachine(
sketchEnginePathId: '', sketchEnginePathId: '',
sketchPlaneId: '', sketchPlaneId: '',
}), }),
'reset camera position': () =>
sceneInfra.camControls.resetCameraPosition(),
'set new sketch metadata': assign((_, { data }) => ({ 'set new sketch metadata': assign((_, { data }) => ({
sketchDetails: data, sketchDetails: data,
})), })),

View File

@ -43,8 +43,8 @@ export default function UserMenu() {
<h2 className="text-2xl font-bold">User Menu</h2> <h2 className="text-2xl font-bold">User Menu</h2>
<p className="my-4"> <p className="my-4">
Click {buttonDescription} in the upper right to open the user menu. Click {buttonDescription} in the upper right to open the user menu.
You can change your user-level settings, sign out, or request a You can change your user-level settings, sign out, report a bug,
feature. manage your account, request a feature, and more.
</p> </p>
<p className="my-4"> <p className="my-4">
Many settings can be set either a user or per-project level. User Many settings can be set either a user or per-project level. User