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:
@ -38,9 +38,11 @@ test('Electron setup', { tag: '@electron' }, async () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
settings: TOML.stringify({ settings: {
|
settings: TOML.stringify({
|
||||||
app: { projectDirectory: fullPath },
|
settings: {
|
||||||
} }),
|
app: { projectDirectory: fullPath },
|
||||||
|
},
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import { _electron as electron, test, expect } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { getUtils, setup, tearDown } from './test-utils'
|
import { getUtils, setupElectron, tearDown } from './test-utils'
|
||||||
import fs from 'fs/promises'
|
|
||||||
import { secrets } from './secrets'
|
|
||||||
import { join } from 'path'
|
|
||||||
import { tomlStringify } from 'lang/wasm'
|
|
||||||
|
|
||||||
test.afterEach(async ({ page }, testInfo) => {
|
test.afterEach(async ({ page }, testInfo) => {
|
||||||
await tearDown(page, testInfo)
|
await tearDown(page, testInfo)
|
||||||
@ -12,60 +8,15 @@ test.afterEach(async ({ page }, testInfo) => {
|
|||||||
test(
|
test(
|
||||||
'When the project folder is empty, user can create new project and open it.',
|
'When the project folder is empty, user can create new project and open it.',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page: browserPage, context: browserContext }, testInfo) => {
|
async ({ browserName }, testInfo) => {
|
||||||
// create or otherwise clear the folder ./electron-test-projects-dir
|
test.skip(
|
||||||
const settingsFileName = `./${testInfo.title
|
browserName === 'webkit',
|
||||||
.replace(/\s/gi, '-')
|
'Skip on Safari because `window.tearDown` does not work'
|
||||||
.replace(/\W/gi, '')}`
|
)
|
||||||
const projectDirName = settingsFileName + '-dir'
|
const { electronApp, page } = await setupElectron({ testInfo })
|
||||||
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
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await page.goto('http://localhost:3000/')
|
await page.goto('http://localhost:3000/')
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
@ -111,12 +62,33 @@ const extrude001 = extrude(200, sketch001)`)
|
|||||||
})
|
})
|
||||||
.toBeLessThan(10)
|
.toBeLessThan(10)
|
||||||
|
|
||||||
await page.mouse.click(pointOnModel.x, pointOnModel.y)
|
await expect(async () => {
|
||||||
// check user can interact with model by checking it turns yellow
|
await page.mouse.move(0, 0, { steps: 5 })
|
||||||
await expect
|
await page.mouse.move(pointOnModel.x, pointOnModel.y, { steps: 5 })
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [176, 180, 132]))
|
await page.mouse.click(pointOnModel.x, pointOnModel.y)
|
||||||
.toBeLessThan(10)
|
// 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()
|
await electronApp.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -4,20 +4,24 @@ import {
|
|||||||
Download,
|
Download,
|
||||||
TestInfo,
|
TestInfo,
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
|
_electron as electron,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
|
import fsSync from 'fs'
|
||||||
|
import { join } from 'path'
|
||||||
import pixelMatch from 'pixelmatch'
|
import pixelMatch from 'pixelmatch'
|
||||||
import { PNG } from 'pngjs'
|
import { PNG } from 'pngjs'
|
||||||
import { Protocol } from 'playwright-core/types/protocol'
|
import { Protocol } from 'playwright-core/types/protocol'
|
||||||
import type { Models } from '@kittycad/lib'
|
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 waitOn from 'wait-on'
|
||||||
import { secrets } from './secrets'
|
import { secrets } from './secrets'
|
||||||
import { TEST_SETTINGS_KEY, TEST_SETTINGS } from './storageStates'
|
import { TEST_SETTINGS_KEY, TEST_SETTINGS } from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
|
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
||||||
|
|
||||||
type TestColor = [number, number, number]
|
type TestColor = [number, number, number]
|
||||||
export const TEST_COLORS = {
|
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,
|
// settingsOverrides may need to be augmented to take more generic items,
|
||||||
// but we'll be strict for now
|
// 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
|
// wait for Vite preview server to be up
|
||||||
await waitOn({
|
await waitOn({
|
||||||
resources: ['tcp:3000'],
|
resources: ['tcp:3000'],
|
||||||
@ -634,24 +642,81 @@ export async function setup(context: BrowserContext, page: Page, overrideDirecto
|
|||||||
})
|
})
|
||||||
|
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ token, settingsKey, settings }) => {
|
async ({
|
||||||
|
token,
|
||||||
|
settingsKey,
|
||||||
|
settings,
|
||||||
|
appSettingsFileKey,
|
||||||
|
appSettingsFileContent,
|
||||||
|
}) => {
|
||||||
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
localStorage.setItem('TOKEN_PERSIST_KEY', token)
|
||||||
localStorage.setItem('persistCode', ``)
|
localStorage.setItem('persistCode', ``)
|
||||||
localStorage.setItem(settingsKey, settings)
|
localStorage.setItem(settingsKey, settings)
|
||||||
|
localStorage.setItem(appSettingsFileKey, appSettingsFileContent)
|
||||||
localStorage.setItem('playwright', 'true')
|
localStorage.setItem('playwright', 'true')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
token: secrets.token,
|
token: secrets.token,
|
||||||
|
appSettingsFileKey: TEST_SETTINGS_FILE_KEY,
|
||||||
|
appSettingsFileContent:
|
||||||
|
overrideDirectory || TEST_SETTINGS.app.projectDirectory,
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
settingsKey: TEST_SETTINGS_KEY,
|
||||||
settings: TOML.stringify({
|
settings: TOML.stringify({
|
||||||
...TEST_SETTINGS,
|
...TEST_SETTINGS,
|
||||||
app: {
|
app: {
|
||||||
...TEST_SETTINGS.app,
|
...TEST_SETTINGS.projects,
|
||||||
projectDirectory: overrideDirectory || TEST_SETTINGS.app.projectDirectory,
|
projectDirectory:
|
||||||
|
overrideDirectory || TEST_SETTINGS.app.projectDirectory,
|
||||||
},
|
},
|
||||||
}),
|
} as Partial<SaveSettingsPayload>),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
// kill animations, speeds up tests and reduced flakiness
|
// kill animations, speeds up tests and reduced flakiness
|
||||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
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 }
|
||||||
|
}
|
||||||
|
@ -569,7 +569,9 @@ export function defaultAppSettings(): Partial<SaveSettingsPayload> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseAppSettings(toml: string): 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> {
|
export function defaultProjectSettings(): Partial<SaveSettingsPayload> {
|
||||||
|
@ -56,4 +56,8 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
|||||||
/** The default KCL length expression */
|
/** The default KCL length expression */
|
||||||
export const KCL_DEFAULT_LENGTH = `5`
|
export const KCL_DEFAULT_LENGTH = `5`
|
||||||
/** localStorage key for the playwright test-specific app settings file */
|
/** localStorage key for the playwright test-specific app settings file */
|
||||||
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
|
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'
|
||||||
|
@ -8,7 +8,6 @@ import { components } from './machine-api'
|
|||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
||||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||||
import * as TOML from '@iarna/toml'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defaultAppSettings,
|
defaultAppSettings,
|
||||||
@ -17,15 +16,16 @@ import {
|
|||||||
parseProjectSettings,
|
parseProjectSettings,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { TEST_SETTINGS_KEY } from '../../e2e/playwright/storageStates'
|
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'
|
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(
|
export async function renameProjectDirectory(
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
newName: string
|
newName: string
|
||||||
@ -112,10 +112,7 @@ export async function createNewProjectDirectory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const projectFile = window.electron.path.join(
|
const projectFile = window.electron.path.join(projectDir, PROJECT_ENTRYPOINT)
|
||||||
projectDir,
|
|
||||||
DEFAULT_PROJECT_KCL_FILE
|
|
||||||
)
|
|
||||||
await window.electron.writeFile(projectFile, initialCode ?? '')
|
await window.electron.writeFile(projectFile, initialCode ?? '')
|
||||||
const metadata = await window.electron.stat(projectFile)
|
const metadata = await window.electron.stat(projectFile)
|
||||||
|
|
||||||
@ -255,7 +252,7 @@ export async function getDefaultKclFileForDir(
|
|||||||
|
|
||||||
let defaultFilePath = window.electron.path.join(
|
let defaultFilePath = window.electron.path.join(
|
||||||
projectDir,
|
projectDir,
|
||||||
DEFAULT_PROJECT_KCL_FILE
|
PROJECT_ENTRYPOINT
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
await window.electron.stat(defaultFilePath)
|
await window.electron.stat(defaultFilePath)
|
||||||
@ -377,15 +374,12 @@ export async function writeProjectSettingsFile(
|
|||||||
|
|
||||||
const getAppSettingsFilePath = async () => {
|
const getAppSettingsFilePath = async () => {
|
||||||
const isPlaywright = window.localStorage.getItem('playwright') === 'true'
|
const isPlaywright = window.localStorage.getItem('playwright') === 'true'
|
||||||
const testDirectoryName = window.localStorage.getItem(TEST_SETTINGS_FILE_KEY) ?? ''
|
const testSettingsPath =
|
||||||
const appConfig = await window.electron.getPath(
|
window.localStorage.getItem(TEST_SETTINGS_FILE_KEY) ?? ''
|
||||||
isPlaywright ? 'temp' : 'appData'
|
const appConfig = await window.electron.getPath('appData')
|
||||||
)
|
const fullPath = isPlaywright
|
||||||
const fullPath = window.electron.path.join(
|
? testSettingsPath
|
||||||
appConfig,
|
: window.electron.path.join(appConfig, window.electron.packageJson.name)
|
||||||
isPlaywright ? testDirectoryName : '',
|
|
||||||
window.electron.packageJson.name
|
|
||||||
)
|
|
||||||
try {
|
try {
|
||||||
await window.electron.stat(fullPath)
|
await window.electron.stat(fullPath)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Reference in New Issue
Block a user