diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 9a0e99ac0..124141ae5 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -34,7 +34,7 @@ jobs: - 'src/wasm-lib/**' playwright-chrome: - timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 30 }} + timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 40 }} strategy: fail-fast: false matrix: diff --git a/e2e/playwright/projects.spec.ts b/e2e/playwright/projects.spec.ts index fd850d51b..d4c359713 100644 --- a/e2e/playwright/projects.spec.ts +++ b/e2e/playwright/projects.spec.ts @@ -1,5 +1,11 @@ import { test, expect } from '@playwright/test' -import { getUtils, setupElectron, tearDown } from './test-utils' +import { + doExport, + getUtils, + Paths, + setupElectron, + tearDown, +} from './test-utils' import fsp from 'fs/promises' import fs from 'fs' import { join } from 'path' @@ -8,6 +14,94 @@ test.afterEach(async ({ page }, testInfo) => { await tearDown(page, testInfo) }) +test( + 'Can export from electron app', + { tag: '@electron' }, + async ({ browserName }, testInfo) => { + const { electronApp, page } = await setupElectron({ + testInfo, + folderSetupFn: async (dir) => { + await fsp.mkdir(`${dir}/bracket`, { recursive: true }) + await fsp.copyFile( + 'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl', + `${dir}/bracket/main.kcl` + ) + }, + }) + + await page.setViewportSize({ width: 1200, height: 500 }) + const u = await getUtils(page) + + page.on('console', console.log) + await electronApp.context().addInitScript(async () => { + ;(window as any).playwrightSkipFilePicker = true + }) + + const pointOnModel = { x: 630, y: 280 } + + await test.step('Opening the bracket project should load the stream', async () => { + // expect to see the text bracket + await expect(page.getByText('bracket')).toBeVisible() + + await page.getByText('bracket').click() + + await expect(page.getByTestId('loading')).toBeAttached() + await expect(page.getByTestId('loading')).not.toBeAttached({ + timeout: 20_000, + }) + + await expect( + page.getByRole('button', { name: 'Start Sketch' }) + ).toBeEnabled({ + timeout: 20_000, + }) + + // gray at this pixel means the stream has loaded in the most + // user way we can verify it (pixel color) + await expect + .poll(() => u.getGreatestPixDiff(pointOnModel, [75, 75, 75]), { + timeout: 10_000, + }) + .toBeLessThan(10) + }) + + const exportLocations: Array = [] + await test.step('export the model as a glTF', async () => { + exportLocations.push( + await doExport( + { + type: 'gltf', + storage: 'embedded', + presentation: 'pretty', + }, + page, + true + ) + ) + }) + + await test.step('Check the export size', async () => { + await expect + .poll( + async () => { + try { + const outputGltf = await fsp.readFile('output.gltf') + return outputGltf.byteLength + } catch (e) { + return 0 + } + }, + { timeout: 15_000 } + ) + .toBe(477327) + + // clean up output.gltf + await fsp.rm('output.gltf') + }) + + await electronApp.close() + } +) test( 'Rename and delete projects, also spam arrow keys when renaming', { tag: '@electron' }, @@ -66,10 +160,10 @@ test( await page.waitForTimeout(100) - // type "updated project name" await page.keyboard.press('Backspace') await page.keyboard.type('updated project name') + // spam arrow keys to make sure it doesn't impact the text for (let i = 0; i < 10; i++) { await page.keyboard.press('ArrowRight') } diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index a4a52deaf..635b76e86 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -538,14 +538,19 @@ export interface Paths { export const doExport = async ( output: Models['OutputFormat_type'], - page: Page + page: Page, + isElectron = false ): Promise => { - await page.getByRole('button', { name: APP_NAME }).click() - const exportMenuButton = page.getByRole('button', { - name: 'Export current part', - }) - await expect(exportMenuButton).toBeVisible() - await exportMenuButton.click() + if (!isElectron) { + await page.getByRole('button', { name: APP_NAME }).click() + const exportMenuButton = page.getByRole('button', { + name: 'Export current part', + }) + await expect(exportMenuButton).toBeVisible() + await exportMenuButton.click() + } else { + await page.getByTestId('export-pane-button').click() + } await expect(page.getByTestId('command-bar')).toBeVisible() // Go through export via command bar @@ -572,13 +577,21 @@ export const doExport = async ( const [downloadPromise1, downloadResolve1] = getPromiseAndResolve() let downloadCnt = 0 - page.on('download', async (download) => { - if (downloadCnt === 0) { - downloadResolve1(download) - } - downloadCnt++ - }) + if (!isElectron) + page.on('download', async (download) => { + if (downloadCnt === 0) { + downloadResolve1(download) + } + downloadCnt++ + }) await page.getByRole('button', { name: 'Submit command' }).click() + if (isElectron) { + return { + modelPath: '', + imagePath: '', + outputType: output.type, + } + } // Handle download const download = await downloadPromise1 diff --git a/src/lib/exportSave.ts b/src/lib/exportSave.ts index dc22df178..f21d94140 100644 --- a/src/lib/exportSave.ts +++ b/src/lib/exportSave.ts @@ -14,6 +14,15 @@ const save_ = async (file: ModelingAppFile) => { extensions.push(extension) } + if (!(window as any).playwrightSkipFilePicker) { + // skip file picker, save to default location + await window.electron.writeFile( + file.name, + new Uint8Array(file.contents) + ) + return + } + // Open a dialog to save the file. const filePathMeta = await window.electron.save({ defaultPath: file.name,