Merge Frank test setup work (#3418)

* Working window.electron.getPath

* Loading project-specific settings in electron tests

* Simplify test until we can get snapshots/traces working in electron tests

* test tweaks

---------

Co-authored-by: Frank Noirot <frank@kittycad.io>
This commit is contained in:
Kurt Hutten
2024-08-14 13:01:01 +10:00
committed by GitHub
parent 6ba050727a
commit c17f0ab04f
6 changed files with 135 additions and 96 deletions

View File

@ -38,9 +38,11 @@ test('Electron setup', { tag: '@electron' }, async () => {
},
{
settingsKey: TEST_SETTINGS_KEY,
settings: TOML.stringify({ settings: {
settings: TOML.stringify({
settings: {
app: { projectDirectory: fullPath },
} }),
},
}),
}
)
})

View File

@ -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'
async ({ browserName }, testInfo) => {
test.skip(
browserName === 'webkit',
'Skip on Safari because `window.tearDown` does not work'
)
})
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
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 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()
}
)

View File

@ -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<SaveSettingsPayload>),
}
)
// 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<void>
}) {
// 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 }
}

View File

@ -569,7 +569,9 @@ export function defaultAppSettings(): Partial<SaveSettingsPayload> {
}
export function parseAppSettings(toml: string): Partial<SaveSettingsPayload> {
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<SaveSettingsPayload> {

View File

@ -57,3 +57,7 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
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'
export const DEFAULT_HOST = 'https://api.zoo.dev'
export const SETTINGS_FILE_NAME = 'settings.toml'
export const PROJECT_SETTINGS_FILE_NAME = 'project.toml'

View File

@ -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) {