diff --git a/e2e/playwright/electron-setup.spec.ts b/e2e/playwright/electron-setup.spec.ts index 52e2883f1..f703a6c80 100644 --- a/e2e/playwright/electron-setup.spec.ts +++ b/e2e/playwright/electron-setup.spec.ts @@ -38,9 +38,11 @@ test('Electron setup', { tag: '@electron' }, async () => { }, { settingsKey: TEST_SETTINGS_KEY, - settings: TOML.stringify({ settings: { - app: { projectDirectory: fullPath }, - } }), + settings: TOML.stringify({ + settings: { + app: { projectDirectory: fullPath }, + }, + }), } ) }) diff --git a/e2e/playwright/projects.spec.ts b/e2e/playwright/projects.spec.ts index 981055da7..316378075 100644 --- a/e2e/playwright/projects.spec.ts +++ b/e2e/playwright/projects.spec.ts @@ -1,9 +1,5 @@ -import { _electron as electron, test, expect } from '@playwright/test' -import { getUtils, setup, tearDown } from './test-utils' -import fs from 'fs/promises' -import { secrets } from './secrets' -import { join } from 'path' -import { tomlStringify } from 'lang/wasm' +import { test, expect } from '@playwright/test' +import { getUtils, setupElectron, tearDown } from './test-utils' test.afterEach(async ({ page }, testInfo) => { await tearDown(page, testInfo) @@ -12,60 +8,15 @@ test.afterEach(async ({ page }, testInfo) => { test( 'When the project folder is empty, user can create new project and open it.', { tag: '@electron' }, - async ({ page: browserPage, context: browserContext }, testInfo) => { - // create or otherwise clear the folder ./electron-test-projects-dir - const settingsFileName = `./${testInfo.title - .replace(/\s/gi, '-') - .replace(/\W/gi, '')}` - const projectDirName = settingsFileName + '-dir' - try { - await fs.rm(projectDirName, { recursive: true }) - } catch (e) { - console.error(e) - } - - await fs.mkdir(projectDirName) - - // get full path for ./electron-test-projects-dir - const fullProjectPath = await fs.realpath(projectDirName) - - const electronApp = await electron.launch({ - args: ['.'], - }) - const context = electronApp.context() - const page = await electronApp.firstWindow() - - const electronTempDirectory = await page.evaluate(async () => { - return await window.electron.getPath( - 'temp' - ) - }) - const tempSettingsFilePath = join(electronTempDirectory, settingsFileName) - const settingsOverrides = tomlStringify({ - app: { - projectDirectory: fullProjectPath, - }, - }) - - if (settingsOverrides instanceof Error) { - throw settingsOverrides - } - await fs.writeFile(tempSettingsFilePath + '.toml', settingsOverrides) - - console.log('from within test setup', { - settingsFileName, - fullPath: fullProjectPath, - electronApp, - page, - settingsFilePath: tempSettingsFilePath + '.toml', - }) - - await setup(context, page, fullProjectPath) - // Set local storage directly using evaluate - + async ({ browserName }, testInfo) => { + test.skip( + browserName === 'webkit', + 'Skip on Safari because `window.tearDown` does not work' + ) + const { electronApp, page } = await setupElectron({ testInfo }) const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) await page.goto('http://localhost:3000/') + await page.setViewportSize({ width: 1200, height: 500 }) page.on('console', console.log) @@ -111,12 +62,33 @@ const extrude001 = extrude(200, sketch001)`) }) .toBeLessThan(10) - await page.mouse.click(pointOnModel.x, pointOnModel.y) - // check user can interact with model by checking it turns yellow - await expect - .poll(() => u.getGreatestPixDiff(pointOnModel, [176, 180, 132])) - .toBeLessThan(10) + await expect(async () => { + await page.mouse.move(0, 0, { steps: 5 }) + await page.mouse.move(pointOnModel.x, pointOnModel.y, { steps: 5 }) + await page.mouse.click(pointOnModel.x, pointOnModel.y) + // check user can interact with model by checking it turns yellow + await expect + .poll(() => u.getGreatestPixDiff(pointOnModel, [176, 180, 132])) + .toBeLessThan(10) + }).toPass({ timeout: 40_000, intervals: [1_000] }) + await page.getByTestId('app-logo').click() + + await expect( + page.getByRole('button', { name: 'New project' }) + ).toBeVisible() + + const createProject = async (projectNum: number) => { + await page.getByRole('button', { name: 'New project' }).click() + await expect(page.getByText('Successfully created')).toBeVisible() + await expect(page.getByText('Successfully created')).not.toBeVisible() + + const projectNumStr = projectNum.toString().padStart(3, '0') + await expect(page.getByText(`project-${projectNumStr}`)).toBeVisible() + } + for (let i = 1; i <= 10; i++) { + await createProject(i) + } await electronApp.close() } ) diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 6a93f09f0..9f29bef1c 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -4,20 +4,24 @@ import { Download, TestInfo, BrowserContext, + _electron as electron, } from '@playwright/test' import { EngineCommand } from 'lang/std/artifactGraph' import os from 'os' import fsp from 'fs/promises' +import fsSync from 'fs' +import { join } from 'path' import pixelMatch from 'pixelmatch' import { PNG } from 'pngjs' import { Protocol } from 'playwright-core/types/protocol' import type { Models } from '@kittycad/lib' -import { APP_NAME } from 'lib/constants' +import { APP_NAME, TEST_SETTINGS_FILE_KEY } from 'lib/constants' import waitOn from 'wait-on' import { secrets } from './secrets' import { TEST_SETTINGS_KEY, TEST_SETTINGS } from './storageStates' import * as TOML from '@iarna/toml' import { SaveSettingsPayload } from 'lib/settings/settingsTypes' +import { SETTINGS_FILE_NAME } from 'lib/constants' type TestColor = [number, number, number] export const TEST_COLORS = { @@ -626,7 +630,11 @@ export async function tearDown(page: Page, testInfo: TestInfo) { // settingsOverrides may need to be augmented to take more generic items, // but we'll be strict for now -export async function setup(context: BrowserContext, page: Page, overrideDirectory?: string) { +export async function setup( + context: BrowserContext, + page: Page, + overrideDirectory?: string +) { // wait for Vite preview server to be up await waitOn({ resources: ['tcp:3000'], @@ -634,24 +642,81 @@ export async function setup(context: BrowserContext, page: Page, overrideDirecto }) await context.addInitScript( - async ({ token, settingsKey, settings }) => { + async ({ + token, + settingsKey, + settings, + appSettingsFileKey, + appSettingsFileContent, + }) => { localStorage.setItem('TOKEN_PERSIST_KEY', token) localStorage.setItem('persistCode', ``) localStorage.setItem(settingsKey, settings) + localStorage.setItem(appSettingsFileKey, appSettingsFileContent) localStorage.setItem('playwright', 'true') }, { token: secrets.token, + appSettingsFileKey: TEST_SETTINGS_FILE_KEY, + appSettingsFileContent: + overrideDirectory || TEST_SETTINGS.app.projectDirectory, settingsKey: TEST_SETTINGS_KEY, settings: TOML.stringify({ ...TEST_SETTINGS, app: { - ...TEST_SETTINGS.app, - projectDirectory: overrideDirectory || TEST_SETTINGS.app.projectDirectory, + ...TEST_SETTINGS.projects, + projectDirectory: + overrideDirectory || TEST_SETTINGS.app.projectDirectory, }, - }), + } as Partial), } ) // kill animations, speeds up tests and reduced flakiness await page.emulateMedia({ reducedMotion: 'reduce' }) } + +export async function setupElectron({ + testInfo, + folderSetupFn, +}: { + testInfo: TestInfo + folderSetupFn?: (projectDirName: string) => Promise +}) { + // create or otherwise clear the folder + const projectDirName = testInfo.outputPath('electron-test-projects-dir') + try { + if (fsSync.existsSync(projectDirName)) { + await fsp.rm(projectDirName, { recursive: true }) + } + } catch (e) { + console.error(e) + } + + await fsp.mkdir(projectDirName) + + const electronApp = await electron.launch({ + args: ['.'], + }) + const context = electronApp.context() + const page = await electronApp.firstWindow() + context.on('console', console.log) + page.on('console', console.log) + + const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) + const settingsOverrides = TOML.stringify({ + ...TEST_SETTINGS, + settings: { + app: { + ...TEST_SETTINGS.app, + projectDirectory: projectDirName, + }, + }, + }) + await fsp.writeFile(tempSettingsFilePath, settingsOverrides) + + await folderSetupFn?.(tempSettingsFilePath) + + await setup(context, page, projectDirName) + + return { electronApp, page } +} diff --git a/src/lang/wasm.ts b/src/lang/wasm.ts index af3d5a94e..b980ffc0f 100644 --- a/src/lang/wasm.ts +++ b/src/lang/wasm.ts @@ -569,7 +569,9 @@ export function defaultAppSettings(): Partial { } export function parseAppSettings(toml: string): Partial { - return configurationToSettingsPayload(parse_app_settings(toml)) + const parsed = parse_app_settings(toml) + console.log('within wasm.ts, parsed app settings', parsed) + return configurationToSettingsPayload(parsed) } export function defaultProjectSettings(): Partial { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 310af6b72..66d0ee838 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -56,4 +56,8 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = { /** The default KCL length expression */ export const KCL_DEFAULT_LENGTH = `5` /** localStorage key for the playwright test-specific app settings file */ -export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings' \ No newline at end of file +export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings' + +export const DEFAULT_HOST = 'https://api.zoo.dev' +export const SETTINGS_FILE_NAME = 'settings.toml' +export const PROJECT_SETTINGS_FILE_NAME = 'project.toml' diff --git a/src/lib/desktop.ts b/src/lib/desktop.ts index 75bdbb571..e3229921a 100644 --- a/src/lib/desktop.ts +++ b/src/lib/desktop.ts @@ -8,7 +8,6 @@ import { components } from './machine-api' import { isDesktop } from './isDesktop' import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry' import { SaveSettingsPayload } from 'lib/settings/settingsTypes' -import * as TOML from '@iarna/toml' import { defaultAppSettings, @@ -17,15 +16,16 @@ import { parseProjectSettings, } from 'lang/wasm' import { TEST_SETTINGS_KEY } from '../../e2e/playwright/storageStates' -import { TEST_SETTINGS_FILE_KEY } from './constants' +import { + DEFAULT_HOST, + PROJECT_ENTRYPOINT, + PROJECT_FOLDER, + PROJECT_SETTINGS_FILE_NAME, + SETTINGS_FILE_NAME, + TEST_SETTINGS_FILE_KEY, +} from './constants' export { parseProjectRoute } from 'lang/wasm' -const DEFAULT_HOST = 'https://api.zoo.dev' -const SETTINGS_FILE_NAME = 'settings.toml' -const PROJECT_SETTINGS_FILE_NAME = 'project.toml' -const PROJECT_FOLDER = 'zoo-modeling-app-projects' -const DEFAULT_PROJECT_KCL_FILE = 'main.kcl' - export async function renameProjectDirectory( projectPath: string, newName: string @@ -112,10 +112,7 @@ export async function createNewProjectDirectory( } } - const projectFile = window.electron.path.join( - projectDir, - DEFAULT_PROJECT_KCL_FILE - ) + const projectFile = window.electron.path.join(projectDir, PROJECT_ENTRYPOINT) await window.electron.writeFile(projectFile, initialCode ?? '') const metadata = await window.electron.stat(projectFile) @@ -255,7 +252,7 @@ export async function getDefaultKclFileForDir( let defaultFilePath = window.electron.path.join( projectDir, - DEFAULT_PROJECT_KCL_FILE + PROJECT_ENTRYPOINT ) try { await window.electron.stat(defaultFilePath) @@ -377,15 +374,12 @@ export async function writeProjectSettingsFile( const getAppSettingsFilePath = async () => { const isPlaywright = window.localStorage.getItem('playwright') === 'true' - const testDirectoryName = window.localStorage.getItem(TEST_SETTINGS_FILE_KEY) ?? '' - const appConfig = await window.electron.getPath( - isPlaywright ? 'temp' : 'appData' - ) - const fullPath = window.electron.path.join( - appConfig, - isPlaywright ? testDirectoryName : '', - window.electron.packageJson.name - ) + const testSettingsPath = + window.localStorage.getItem(TEST_SETTINGS_FILE_KEY) ?? '' + const appConfig = await window.electron.getPath('appData') + const fullPath = isPlaywright + ? testSettingsPath + : window.electron.path.join(appConfig, window.electron.packageJson.name) try { await window.electron.stat(fullPath) } catch (e) {