Fix playwright mental model, don't make me psycho, thanks (#5680)
* Fix flakey tests with new toolbar.exitSketch * tsc && lint && fmt * Disable pw electron thing again * Unfrig Playwright-Electron a ton; fix another ton of flakes. * More deflaky * Fix a ton of tests and playwright related hell * Run jess's magic incantation to build rust kcl things * yarn tsc * yarn lint * yarn fmt * Remove double logs * Revert to old settings spreads momentarily * Expect error *in the fixtureSetup*, does not circumvent typechecking for regular usage * Fix unit tests
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import {
|
import {
|
||||||
getUtils,
|
getUtils,
|
||||||
TEST_COLORS,
|
TEST_COLORS,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
|
@ -10,7 +10,11 @@ import fsp from 'fs/promises'
|
|||||||
test(
|
test(
|
||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ page, context, scene }, testInfo) => {
|
async ({ page, context, scene, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||||
@ -86,7 +90,7 @@ test(
|
|||||||
await expect(exportingToastMessage).not.toBeVisible()
|
await expect(exportingToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
const firstFileFullPath = path.resolve(
|
const firstFileFullPath = path.resolve(
|
||||||
getPlaywrightDownloadDir(page),
|
getPlaywrightDownloadDir(tronApp.projectDirName),
|
||||||
exportFileName
|
exportFileName
|
||||||
)
|
)
|
||||||
await test.step('Check the export size', async () => {
|
await test.step('Check the export size', async () => {
|
||||||
@ -165,7 +169,7 @@ test(
|
|||||||
]))
|
]))
|
||||||
|
|
||||||
const secondFileFullPath = path.resolve(
|
const secondFileFullPath = path.resolve(
|
||||||
getPlaywrightDownloadDir(page),
|
getPlaywrightDownloadDir(tronApp.projectDirName),
|
||||||
exportFileName
|
exportFileName
|
||||||
)
|
)
|
||||||
await test.step('Check the export size', async () => {
|
await test.step('Check the export size', async () => {
|
||||||
|
@ -158,11 +158,14 @@ test.describe('when using the file tree to', () => {
|
|||||||
await createNewFile('lee')
|
await createNewFile('lee')
|
||||||
|
|
||||||
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
|
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
|
||||||
await expect(
|
await expect
|
||||||
|
.poll(() =>
|
||||||
page
|
page
|
||||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
.filter({ hasText: /lee[-]?[0-5]?/ })
|
.filter({ hasText: /lee[-]?[0-5]?/ })
|
||||||
).toHaveCount(5)
|
.count()
|
||||||
|
)
|
||||||
|
.toEqual(5)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -27,28 +27,19 @@ type CmdBarSerialised =
|
|||||||
|
|
||||||
export class CmdBarFixture {
|
export class CmdBarFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
public cmdBarOpenBtn!: Locator
|
||||||
get cmdBarOpenBtn() {
|
public cmdBarElement!: Locator
|
||||||
return this.page.getByTestId('command-bar-open-button')
|
|
||||||
}
|
|
||||||
|
|
||||||
get cmdBarElement() {
|
|
||||||
return this.page.getByTestId('command-bar')
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
this.cmdBarOpenBtn = this.page.getByTestId('command-bar-open-button')
|
||||||
|
this.cmdBarElement = this.page.getByTestId('command-bar')
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentArgumentInput() {
|
get currentArgumentInput() {
|
||||||
return this.page.getByTestId('cmd-bar-arg-value')
|
return this.page.getByTestId('cmd-bar-arg-value')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put all selectors here because this method is re-run on fixture creation.
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
}
|
|
||||||
|
|
||||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||||
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
||||||
return { stage: 'commandBarClosed' }
|
return { stage: 'commandBarClosed' }
|
||||||
|
@ -24,11 +24,6 @@ export class EditorFixture {
|
|||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
|
||||||
}
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
|
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
|
||||||
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
||||||
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
||||||
|
@ -1,13 +1,31 @@
|
|||||||
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
ElectronApplication,
|
ElectronApplication,
|
||||||
|
Fixtures as PlaywrightFixtures,
|
||||||
TestInfo,
|
TestInfo,
|
||||||
Page,
|
Page,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
|
|
||||||
import { getUtils, setup, setupElectron } from '../test-utils'
|
import {
|
||||||
|
_electron as electron,
|
||||||
|
PlaywrightTestArgs,
|
||||||
|
PlaywrightWorkerArgs,
|
||||||
|
} from '@playwright/test'
|
||||||
|
|
||||||
|
import * as TOML from '@iarna/toml'
|
||||||
|
import {
|
||||||
|
TEST_SETTINGS_KEY,
|
||||||
|
TEST_SETTINGS_CORRUPTED,
|
||||||
|
TEST_SETTINGS,
|
||||||
|
TEST_SETTINGS_DEFAULT_THEME,
|
||||||
|
} from '../storageStates'
|
||||||
|
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||||
|
import { getUtils, setup } from '../test-utils'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import { join } from 'path'
|
import fs from 'node:fs'
|
||||||
|
import path from 'path'
|
||||||
import { CmdBarFixture } from './cmdBarFixture'
|
import { CmdBarFixture } from './cmdBarFixture'
|
||||||
import { EditorFixture } from './editorFixture'
|
import { EditorFixture } from './editorFixture'
|
||||||
import { ToolbarFixture } from './toolbarFixture'
|
import { ToolbarFixture } from './toolbarFixture'
|
||||||
@ -23,7 +41,7 @@ export class AuthenticatedApp {
|
|||||||
public readonly testInfo: TestInfo
|
public readonly testInfo: TestInfo
|
||||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||||
public electronApp: undefined | ElectronApplication
|
public electronApp: undefined | ElectronApplication
|
||||||
public dir: string = ''
|
public projectDirName: string = ''
|
||||||
|
|
||||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||||
this.context = context
|
this.context = context
|
||||||
@ -46,7 +64,7 @@ export class AuthenticatedApp {
|
|||||||
}
|
}
|
||||||
getInputFile = (fileName: string) => {
|
getInputFile = (fileName: string) => {
|
||||||
return fsp.readFile(
|
return fsp.readFile(
|
||||||
join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
|
path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -59,101 +77,300 @@ export interface Fixtures {
|
|||||||
scene: SceneFixture
|
scene: SceneFixture
|
||||||
homePage: HomePageFixture
|
homePage: HomePageFixture
|
||||||
}
|
}
|
||||||
export class AuthenticatedTronApp {
|
|
||||||
public originalPage: Page
|
|
||||||
public page: Page
|
|
||||||
public browserContext: BrowserContext
|
|
||||||
public context: BrowserContext
|
|
||||||
public readonly testInfo: TestInfo
|
|
||||||
public electronApp: ElectronApplication | undefined
|
|
||||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
|
||||||
public dir: string = ''
|
|
||||||
|
|
||||||
constructor(
|
export class ElectronZoo {
|
||||||
browserContext: BrowserContext,
|
public available: boolean = true
|
||||||
originalPage: Page,
|
public electron!: ElectronApplication
|
||||||
testInfo: TestInfo
|
public firstUrl = ''
|
||||||
) {
|
public viewPortSize = { width: 1200, height: 500 }
|
||||||
this.page = originalPage
|
public projectDirName = ''
|
||||||
this.originalPage = originalPage
|
|
||||||
this.browserContext = browserContext
|
public page!: Page
|
||||||
// Will be overwritten in the initializer
|
public context!: BrowserContext
|
||||||
this.context = browserContext
|
|
||||||
this.testInfo = testInfo
|
constructor() {}
|
||||||
|
|
||||||
|
async makeAvailableAgain() {
|
||||||
|
// Help remote end by signaling we're done with the connection.
|
||||||
|
await this.page.evaluate(async () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!window.engineCommandManager.engineConnection?.state?.type) {
|
||||||
|
return resolve(undefined)
|
||||||
}
|
}
|
||||||
async initialise(
|
|
||||||
arg: {
|
|
||||||
fixtures: Partial<Fixtures>
|
|
||||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
|
||||||
cleanProjectDir?: boolean
|
|
||||||
appSettings?: DeepPartial<Settings>
|
|
||||||
} = { fixtures: {} }
|
|
||||||
) {
|
|
||||||
const { electronApp, page, context, dir } = await setupElectron({
|
|
||||||
testInfo: this.testInfo,
|
|
||||||
folderSetupFn: arg.folderSetupFn,
|
|
||||||
cleanProjectDir: arg.cleanProjectDir,
|
|
||||||
appSettings: arg.appSettings,
|
|
||||||
viewport: this.viewPortSize,
|
|
||||||
})
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
// These assignments "fix" some brokenness in the Playwright Workbench when
|
window.engineCommandManager.tearDown()
|
||||||
// running against electron applications.
|
// Keep polling (per js event tick) until state is Disconnected.
|
||||||
// The timeline is still broken but failure screenshots work again.
|
const checkDisconnected = () => {
|
||||||
this.context = context
|
// It's possible we never even created an engineConnection
|
||||||
// TODO: try to get this to work again for screenshots, but it messed with test ends when enabled
|
// e.g. never left Projects view.
|
||||||
// Object.assign(this.browserContext, this.context)
|
|
||||||
|
|
||||||
this.electronApp = electronApp
|
|
||||||
this.dir = dir
|
|
||||||
|
|
||||||
// Easier to access throughout utils
|
|
||||||
this.page.dir = dir
|
|
||||||
|
|
||||||
// Setup localStorage, addCookies, reload
|
|
||||||
await setup(this.context, this.page, this.testInfo)
|
|
||||||
|
|
||||||
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
|
||||||
const fixture = arg.fixtures[key]
|
|
||||||
if (
|
if (
|
||||||
!fixture ||
|
window.engineCommandManager?.engineConnection?.state.type ===
|
||||||
fixture instanceof AuthenticatedApp ||
|
'disconnected'
|
||||||
fixture instanceof AuthenticatedTronApp
|
) {
|
||||||
)
|
return resolve(undefined)
|
||||||
continue
|
|
||||||
fixture.reConstruct(page)
|
|
||||||
}
|
}
|
||||||
|
setTimeout(checkDisconnected, 0)
|
||||||
|
}
|
||||||
|
checkDisconnected()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.context.tracing.stopChunk({ path: 'trace.zip' })
|
||||||
|
|
||||||
|
// Only after cleanup we're ready.
|
||||||
|
this.available = true
|
||||||
}
|
}
|
||||||
|
|
||||||
close = async () => {
|
async createInstanceIfMissing(testInfo: TestInfo) {
|
||||||
await this.electronApp?.close?.()
|
// Create or otherwise clear the folder.
|
||||||
|
this.projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
||||||
|
|
||||||
|
// We need to expose this in order for some tests that require folder
|
||||||
|
// creation and some code below.
|
||||||
|
const that = this
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
args: ['.', '--no-sandbox'],
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
TEST_SETTINGS_FILE_KEY: this.projectDirName,
|
||||||
|
IS_PLAYWRIGHT: 'true',
|
||||||
|
},
|
||||||
|
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
||||||
|
? {
|
||||||
|
executablePath:
|
||||||
|
process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron',
|
||||||
}
|
}
|
||||||
debugPause = () =>
|
: {}),
|
||||||
new Promise(() => {
|
...(process.env.PLAYWRIGHT_RECORD_VIDEO
|
||||||
console.log('UN-RESOLVING PROMISE')
|
? {
|
||||||
|
recordVideo: {
|
||||||
|
dir: testInfo.snapshotPath(),
|
||||||
|
size: this.viewPortSize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do this once and then reuse window on subsequent calls.
|
||||||
|
if (!this.electron) {
|
||||||
|
this.electron = await electron.launch(options)
|
||||||
|
this.context = this.electron.context()
|
||||||
|
this.page = await this.electron.firstWindow()
|
||||||
|
await this.context.tracing.start({ screenshots: true, snapshots: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.context.tracing.startChunk()
|
||||||
|
|
||||||
|
await setup(this.context, this.page, testInfo)
|
||||||
|
|
||||||
|
await this.cleanProjectDir()
|
||||||
|
|
||||||
|
// Create a consistent way to resize the page across electron and web.
|
||||||
|
// (lee) I had to do everything in the book to make electron change its
|
||||||
|
// damn window size. I succeeded in making it consistently and reliably
|
||||||
|
// do it after a whole afternoon.
|
||||||
|
this.page.setBodyDimensions = async function (dims: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}) {
|
||||||
|
await this.setViewportSize(dims)
|
||||||
|
|
||||||
|
await that.electron?.evaluateHandle(async ({ app }, dims) => {
|
||||||
|
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
|
||||||
|
await app.resizeWindow(dims.width, dims.height)
|
||||||
|
}, dims)
|
||||||
|
|
||||||
|
return this.evaluate(async (dims: { width: number; height: number }) => {
|
||||||
|
await window.electron.resizeWindow(dims.width, dims.height)
|
||||||
|
window.document.body.style.width = dims.width + 'px'
|
||||||
|
window.document.body.style.height = dims.height + 'px'
|
||||||
|
window.document.documentElement.style.width = dims.width + 'px'
|
||||||
|
window.document.documentElement.style.height = dims.height + 'px'
|
||||||
|
}, dims)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.page.setBodyDimensions(this.viewPortSize)
|
||||||
|
|
||||||
|
this.context.folderSetupFn = async function (fn) {
|
||||||
|
return fn(that.projectDirName)
|
||||||
|
.then(() => that.page.reload())
|
||||||
|
.then(() => ({
|
||||||
|
dir: that.projectDirName,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to patch this because addInitScript will bind too late in our
|
||||||
|
// electron tests, never running. We need to call reload() after each call
|
||||||
|
// to guarantee it runs.
|
||||||
|
const oldContextAddInitScript = this.context.addInitScript
|
||||||
|
this.context.addInitScript = async function (a, b) {
|
||||||
|
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
||||||
|
// This code works perfectly fine.
|
||||||
|
await oldContextAddInitScript.apply(this, [a, b])
|
||||||
|
await that.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
// No idea why we mix and match page and context's addInitScript but we do
|
||||||
|
const oldPageAddInitScript = this.page.addInitScript
|
||||||
|
this.page.addInitScript = async function (a: any, b: any) {
|
||||||
|
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
||||||
|
// This code works perfectly fine.
|
||||||
|
await oldPageAddInitScript.apply(this, [a, b])
|
||||||
|
await that.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.firstUrl) {
|
||||||
|
await this.page.getByText('Your Projects').count()
|
||||||
|
this.firstUrl = this.page.url()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to the app controlling its own window context we need to inject new
|
||||||
|
// options and context here.
|
||||||
|
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
|
||||||
|
// await tronApp.electronApp.evaluate(({ app }) => {
|
||||||
|
// return app.reuseWindowForTest();
|
||||||
|
// });
|
||||||
|
|
||||||
|
await this.electron?.evaluate(({ app }, projectDirName) => {
|
||||||
|
// @ts-ignore can't declaration merge see main.ts
|
||||||
|
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
||||||
|
}, this.projectDirName)
|
||||||
|
|
||||||
|
// Always start at the root view
|
||||||
|
await this.page.goto(this.firstUrl)
|
||||||
|
|
||||||
|
// Force a hard reload, destroying the stream and other state
|
||||||
|
await this.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanProjectDir(appSettings?: DeepPartial<Settings>) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(this.projectDirName)) {
|
||||||
|
await fsp.rm(this.projectDirName, { recursive: true })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fsp.mkdir(this.projectDirName)
|
||||||
|
} catch (e) {
|
||||||
|
// Not a problem if it already exists.
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempSettingsFilePath = path.join(
|
||||||
|
this.projectDirName,
|
||||||
|
SETTINGS_FILE_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
let settingsOverridesToml = ''
|
||||||
|
|
||||||
|
if (appSettings) {
|
||||||
|
settingsOverridesToml = TOML.stringify({
|
||||||
|
// @ts-expect-error
|
||||||
|
settings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
...appSettings,
|
||||||
|
app: {
|
||||||
|
...TEST_SETTINGS.app,
|
||||||
|
project_directory: this.projectDirName,
|
||||||
|
...appSettings.app,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
settingsOverridesToml = TOML.stringify({
|
||||||
|
// @ts-expect-error
|
||||||
|
settings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
app: {
|
||||||
|
...TEST_SETTINGS.app,
|
||||||
|
project_directory: this.projectDirName,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
await fsp.writeFile(tempSettingsFilePath, settingsOverridesToml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const fixtures = {
|
// If yee encounter this, please try to type it.
|
||||||
cmdBar: async ({ page }: { page: Page }, use: any) => {
|
type FnUse = any
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
|
const fixturesForElectron = {
|
||||||
|
page: async (
|
||||||
|
{ tronApp }: { tronApp: ElectronZoo },
|
||||||
|
use: FnUse,
|
||||||
|
testInfo: TestInfo
|
||||||
|
) => {
|
||||||
|
await tronApp.createInstanceIfMissing(testInfo)
|
||||||
|
await use(tronApp.page)
|
||||||
|
await tronApp?.makeAvailableAgain()
|
||||||
|
},
|
||||||
|
context: async (
|
||||||
|
{ tronApp }: { tronApp: ElectronZoo },
|
||||||
|
use: FnUse,
|
||||||
|
testInfo: TestInfo
|
||||||
|
) => {
|
||||||
|
await tronApp.createInstanceIfMissing(testInfo)
|
||||||
|
await use(tronApp.context)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixturesForWeb = {
|
||||||
|
page: async (
|
||||||
|
{ page, context }: { page: Page; context: BrowserContext },
|
||||||
|
use: FnUse,
|
||||||
|
testInfo: TestInfo
|
||||||
|
) => {
|
||||||
|
page.setBodyDimensions = page.setViewportSize
|
||||||
|
|
||||||
|
// We do the same thing in ElectronZoo. addInitScript simply doesn't fire
|
||||||
|
// at the correct time, so we reload the page and it fires appropriately.
|
||||||
|
const oldPageAddInitScript = page.addInitScript
|
||||||
|
page.addInitScript = async function (...args) {
|
||||||
|
// @ts-expect-error
|
||||||
|
await oldPageAddInitScript.apply(this, args)
|
||||||
|
await page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldContextAddInitScript = context.addInitScript
|
||||||
|
context.addInitScript = async function (...args) {
|
||||||
|
// @ts-expect-error
|
||||||
|
await oldContextAddInitScript.apply(this, args)
|
||||||
|
await page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const webApp = new AuthenticatedApp(context, page, testInfo)
|
||||||
|
await webApp.initialise()
|
||||||
|
|
||||||
|
await use(page)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixturesBasedOnProcessEnvPlatform = {
|
||||||
|
cmdBar: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
await use(new CmdBarFixture(page))
|
await use(new CmdBarFixture(page))
|
||||||
},
|
},
|
||||||
editor: async ({ page }: { page: Page }, use: any) => {
|
editor: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new EditorFixture(page))
|
await use(new EditorFixture(page))
|
||||||
},
|
},
|
||||||
toolbar: async ({ page }: { page: Page }, use: any) => {
|
toolbar: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new ToolbarFixture(page))
|
await use(new ToolbarFixture(page))
|
||||||
},
|
},
|
||||||
scene: async ({ page }: { page: Page }, use: any) => {
|
scene: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new SceneFixture(page))
|
await use(new SceneFixture(page))
|
||||||
},
|
},
|
||||||
homePage: async ({ page }: { page: Page }, use: any) => {
|
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new HomePageFixture(page))
|
await use(new HomePageFixture(page))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.PLATFORM === 'web') {
|
||||||
|
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
|
||||||
|
} else {
|
||||||
|
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { fixturesBasedOnProcessEnvPlatform }
|
||||||
|
@ -27,10 +27,6 @@ export class HomePageFixture {
|
|||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
|
||||||
}
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
this.projectSection = this.page.getByTestId('home-section')
|
this.projectSection = this.page.getByTestId('home-section')
|
||||||
|
|
||||||
@ -96,8 +92,12 @@ export class HomePageFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
projectsLoaded = async () => {
|
||||||
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
||||||
|
}
|
||||||
|
|
||||||
|
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
||||||
|
await this.projectsLoaded()
|
||||||
await this.projectButtonNew.click()
|
await this.projectButtonNew.click()
|
||||||
await this.projectTextName.click()
|
await this.projectTextName.click()
|
||||||
await this.projectTextName.fill(projectTitle)
|
await this.projectTextName.fill(projectTitle)
|
||||||
|
@ -53,7 +53,12 @@ export class SceneFixture {
|
|||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
this.streamWrapper = page.getByTestId('stream')
|
||||||
|
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
|
||||||
|
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
||||||
|
this.startEditSketchBtn = page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
||||||
}
|
}
|
||||||
private _serialiseScene = async (): Promise<SceneSerialised> => {
|
private _serialiseScene = async (): Promise<SceneSerialised> => {
|
||||||
const camera = await this.getCameraInfo()
|
const camera = await this.getCameraInfo()
|
||||||
@ -72,17 +77,6 @@ export class SceneFixture {
|
|||||||
.toEqual(expected)
|
.toEqual(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
this.streamWrapper = page.getByTestId('stream')
|
|
||||||
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
|
|
||||||
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
|
||||||
this.startEditSketchBtn = page
|
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
|
||||||
}
|
|
||||||
|
|
||||||
makeMouseHelpers = (
|
makeMouseHelpers = (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -253,7 +247,7 @@ export class SceneFixture {
|
|||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await this.waitForExecutionDone()
|
await this.waitForExecutionDone()
|
||||||
await expect(this.startEditSketchBtn).not.toBeDisabled()
|
await expect(this.startEditSketchBtn).not.toBeDisabled()
|
||||||
|
@ -37,13 +37,12 @@ export class ToolbarFixture {
|
|||||||
featureTreeId = 'feature-tree' as const
|
featureTreeId = 'feature-tree' as const
|
||||||
/** The pane element for the Feature Tree */
|
/** The pane element for the Feature Tree */
|
||||||
featureTreePane!: Locator
|
featureTreePane!: Locator
|
||||||
|
gizmo!: Locator
|
||||||
|
gizmoDisabled!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
|
||||||
}
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
this.loftButton = page.getByTestId('loft')
|
||||||
this.sweepButton = page.getByTestId('sweep')
|
this.sweepButton = page.getByTestId('sweep')
|
||||||
@ -67,6 +66,13 @@ export class ToolbarFixture {
|
|||||||
this.filePane = page.locator('#files-pane')
|
this.filePane = page.locator('#files-pane')
|
||||||
this.featureTreePane = page.locator('#feature-tree-pane')
|
this.featureTreePane = page.locator('#feature-tree-pane')
|
||||||
this.fileCreateToast = page.getByText('Successfully created')
|
this.fileCreateToast = page.getByText('Successfully created')
|
||||||
|
|
||||||
|
// Note to test writers: having two locators like this is preferable to one
|
||||||
|
// which changes another el property because it means our test "signal" is
|
||||||
|
// completely decoupled from the elements themselves. It means the same
|
||||||
|
// element or two different elements can represent these states.
|
||||||
|
this.gizmo = page.getByTestId('gizmo')
|
||||||
|
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
||||||
}
|
}
|
||||||
|
|
||||||
get editSketchBtn() {
|
get editSketchBtn() {
|
||||||
@ -86,6 +92,18 @@ export class ToolbarFixture {
|
|||||||
startSketchPlaneSelection = async () =>
|
startSketchPlaneSelection = async () =>
|
||||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||||
|
|
||||||
|
waitUntilSketchingReady = async () => {
|
||||||
|
await expect(this.gizmoDisabled).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
startSketchThenCallbackThenWaitUntilReady = async (
|
||||||
|
cb: () => Promise<void>
|
||||||
|
) => {
|
||||||
|
await this.startSketchBtn.click()
|
||||||
|
await cb()
|
||||||
|
await this.waitUntilSketchingReady()
|
||||||
|
}
|
||||||
|
|
||||||
exitSketch = async () => {
|
exitSketch = async () => {
|
||||||
await this.exitSketchBtn.click()
|
await this.exitSketchBtn.click()
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -21,58 +21,54 @@ import { expectPixelColor } from './fixtures/sceneFixture'
|
|||||||
// we must set it to empty for the tests where we want to see the onboarding immediately.
|
// we must set it to empty for the tests where we want to see the onboarding immediately.
|
||||||
|
|
||||||
test.describe('Onboarding tests', () => {
|
test.describe('Onboarding tests', () => {
|
||||||
test(
|
test('Onboarding code is shown in the editor', async ({
|
||||||
'Onboarding code is shown in the editor',
|
page,
|
||||||
{
|
homePage,
|
||||||
appSettings: {
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
await expect(
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
page.getByText('Welcome to Modeling App! This')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
await expect(
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
page.getByText('Welcome to Modeling App! This')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// *and* that the code is shown in the editor
|
// *and* that the code is shown in the editor
|
||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||||
'// Shelf Bracket'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Make sure the model loaded
|
// Make sure the model loaded
|
||||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||||
const modelColor: [number, number, number] = [45, 45, 45]
|
const modelColor: [number, number, number] = [45, 45, 45]
|
||||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
|
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8)
|
||||||
8
|
})
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Desktop: fresh onboarding executes and loads',
|
'Desktop: fresh onboarding executes and loads',
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
appSettings: {
|
},
|
||||||
|
async ({ page, tronApp }) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ page }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
@ -107,22 +103,30 @@ test.describe('Onboarding tests', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test('Code resets after confirmation', async ({
|
||||||
'Code resets after confirmation',
|
context,
|
||||||
{
|
page,
|
||||||
cleanProjectDir: true,
|
homePage,
|
||||||
},
|
tronApp,
|
||||||
async ({ context, page, homePage }) => {
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir()
|
||||||
|
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||||
|
|
||||||
// Load the page up with some code so we see the confirmation warning
|
// Load the page up with some code so we see the confirmation warning
|
||||||
// when we go to replay onboarding
|
// when we go to replay onboarding
|
||||||
await context.addInitScript((code) => {
|
await page.addInitScript((code) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
}, initialCode)
|
}, initialCode)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Replay the onboarding
|
// Replay the onboarding
|
||||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
@ -142,26 +146,27 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
// Ensure we see the introduction and that the code has been reset
|
// Ensure we see the introduction and that the code has been reset
|
||||||
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||||
'// Shelf Bracket'
|
|
||||||
)
|
|
||||||
|
|
||||||
// There used to be old code here that checked if we stored the reset
|
// There used to be old code here that checked if we stored the reset
|
||||||
// code into localStorage but that isn't the case on desktop. It gets
|
// code into localStorage but that isn't the case on desktop. It gets
|
||||||
// saved to the file system, which we have other tests for.
|
// saved to the file system, which we have other tests for.
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test('Click through each onboarding step and back', async ({
|
||||||
'Click through each onboarding step and back',
|
context,
|
||||||
{
|
page,
|
||||||
appSettings: {
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
@ -181,9 +186,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
await expect(
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
page.getByText('Welcome to Modeling App! This')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
const nextButton = page.getByTestId('onboarding-next')
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
const prevButton = page.getByTestId('onboarding-prev')
|
const prevButton = page.getByTestId('onboarding-prev')
|
||||||
@ -205,20 +208,23 @@ test.describe('Onboarding tests', () => {
|
|||||||
// Test that the onboarding pane is gone
|
// Test that the onboarding pane is gone
|
||||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||||
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test('Onboarding redirects and code updating', async ({
|
||||||
'Onboarding redirects and code updating',
|
context,
|
||||||
{
|
page,
|
||||||
appSettings: {
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '/export',
|
onboarding_status: '/export',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
const originalCode = 'sigmaAllow = 15000'
|
const originalCode = 'sigmaAllow = 15000'
|
||||||
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
@ -260,21 +266,22 @@ test.describe('Onboarding tests', () => {
|
|||||||
await page.locator('[data-testid="onboarding-next"]').hover()
|
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||||
await page.locator('[data-testid="onboarding-next"]').click()
|
await page.locator('[data-testid="onboarding-next"]').click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
|
||||||
'Onboarding code gets reset to demo on Interactive Numbers step',
|
page,
|
||||||
{
|
homePage,
|
||||||
appSettings: {
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '/parametric-modeling',
|
onboarding_status: '/parametric-modeling',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
async ({ page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const badCode = `// This is bad code we shouldn't see`
|
const badCode = `// This is bad code we shouldn't see`
|
||||||
|
|
||||||
@ -307,23 +314,24 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
// Check that the code has been reset
|
// Check that the code has been reset
|
||||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// (lee) The two avatar tests are weird because even on main, we don't have
|
// (lee) The two avatar tests are weird because even on main, we don't have
|
||||||
// anything to do with the avatar inside the onboarding test. Due to the
|
// anything to do with the avatar inside the onboarding test. Due to the
|
||||||
// low impact of an avatar not showing I'm changing this to fixme.
|
// low impact of an avatar not showing I'm changing this to fixme.
|
||||||
test.fixme(
|
test.fixme(
|
||||||
'Avatar text updates depending on image load success',
|
'Avatar text updates depending on image load success',
|
||||||
{
|
async ({ context, page, homePage, tronApp }) => {
|
||||||
appSettings: {
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
@ -388,15 +396,16 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
"Avatar text doesn't mention avatar when no avatar",
|
"Avatar text doesn't mention avatar when no avatar",
|
||||||
{
|
async ({ context, page, homePage, tronApp }) => {
|
||||||
appSettings: {
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
@ -444,15 +453,17 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
'Restarting onboarding on desktop takes one attempt',
|
'Restarting onboarding on desktop takes one attempt',
|
||||||
{
|
async ({ context, page, tronApp }) => {
|
||||||
appSettings: {
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: 'dismissed',
|
onboarding_status: 'dismissed',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page }) => {
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import { EditorFixture } from './fixtures/editorFixture'
|
import { EditorFixture } from './fixtures/editorFixture'
|
||||||
import { SceneFixture } from './fixtures/sceneFixture'
|
import { SceneFixture } from './fixtures/sceneFixture'
|
||||||
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
||||||
|
@ -163,7 +163,7 @@ test(
|
|||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(15)
|
.toBeLessThan(20)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||||
@ -464,7 +464,11 @@ test.describe('Can export from electron app', () => {
|
|||||||
test(
|
test(
|
||||||
`Can export using ${method}`,
|
`Can export using ${method}`,
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -516,6 +520,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
storage: 'embedded',
|
storage: 'embedded',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page,
|
page,
|
||||||
method
|
method
|
||||||
)
|
)
|
||||||
@ -523,7 +528,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const filepath = path.resolve(
|
const filepath = path.resolve(
|
||||||
getPlaywrightDownloadDir(page),
|
getPlaywrightDownloadDir(tronApp.projectDirName),
|
||||||
'main.gltf'
|
'main.gltf'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -781,6 +786,7 @@ test(
|
|||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||||
|
await expect(page.getByText('Loading your Projects...')).not.toBeVisible()
|
||||||
await expect(page.getByText('Your Projects')).toBeVisible()
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
@ -858,7 +864,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
test(
|
test(
|
||||||
`Delete from project page`,
|
`Delete from project page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_delete`
|
const projectName = `my_project_to_delete`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||||
@ -887,6 +893,8 @@ test.describe(`Project management commands`, () => {
|
|||||||
|
|
||||||
await projectHomeLink.click()
|
await projectHomeLink.click()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Run delete command via command palette`, async () => {
|
await test.step(`Run delete command via command palette`, async () => {
|
||||||
@ -909,7 +917,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
test(
|
test(
|
||||||
`Rename from home page`,
|
`Rename from home page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, homePage }, testInfo) => {
|
||||||
const projectName = `my_project_to_rename`
|
const projectName = `my_project_to_rename`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||||
@ -936,6 +944,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
await test.step(`Setup`, async () => {
|
await test.step(`Setup`, async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
await homePage.projectsLoaded()
|
||||||
await expect(projectHomeLink).toBeVisible()
|
await expect(projectHomeLink).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1682,7 +1691,11 @@ test(
|
|||||||
test(
|
test(
|
||||||
'You can change the root projects directory and nothing is lost',
|
'You can change the root projects directory and nothing is lost',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page, electronApp }, testInfo) => {
|
async ({ context, page, tronApp, homePage }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
||||||
@ -1712,6 +1725,8 @@ test(
|
|||||||
await fsp.rm(newProjectDirName, { recursive: true })
|
await fsp.rm(newProjectDirName, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await homePage.projectsLoaded()
|
||||||
|
|
||||||
await test.step('We can change the root project directory', async () => {
|
await test.step('We can change the root project directory', async () => {
|
||||||
// expect to see the project directory settings link
|
// expect to see the project directory settings link
|
||||||
await expect(
|
await expect(
|
||||||
@ -1725,7 +1740,7 @@ test(
|
|||||||
.locator('section#projectDirectory input')
|
.locator('section#projectDirectory input')
|
||||||
.inputValue()
|
.inputValue()
|
||||||
|
|
||||||
const handleFile = electronApp?.evaluate(
|
const handleFile = tronApp.electron.evaluate(
|
||||||
async ({ dialog }, filePaths) => {
|
async ({ dialog }, filePaths) => {
|
||||||
dialog.showOpenDialog = () =>
|
dialog.showOpenDialog = () =>
|
||||||
Promise.resolve({ canceled: false, filePaths })
|
Promise.resolve({ canceled: false, filePaths })
|
||||||
@ -1741,6 +1756,8 @@ test(
|
|||||||
|
|
||||||
await page.getByTestId('settings-close-button').click()
|
await page.getByTestId('settings-close-button').click()
|
||||||
|
|
||||||
|
await homePage.projectsLoaded()
|
||||||
|
|
||||||
await expect(page.getByText('No Projects found')).toBeVisible()
|
await expect(page.getByText('No Projects found')).toBeVisible()
|
||||||
await createProject({ name: 'project-000', page, returnHome: true })
|
await createProject({ name: 'project-000', page, returnHome: true })
|
||||||
await expect(
|
await expect(
|
||||||
@ -1755,7 +1772,7 @@ test(
|
|||||||
|
|
||||||
await page.getByTestId('project-directory-settings-link').click()
|
await page.getByTestId('project-directory-settings-link').click()
|
||||||
|
|
||||||
const handleFile = electronApp?.evaluate(
|
const handleFile = tronApp.electron.evaluate(
|
||||||
async ({ dialog }, filePaths) => {
|
async ({ dialog }, filePaths) => {
|
||||||
dialog.showOpenDialog = () =>
|
dialog.showOpenDialog = () =>
|
||||||
Promise.resolve({ canceled: false, filePaths })
|
Promise.resolve({ canceled: false, filePaths })
|
||||||
@ -1767,6 +1784,7 @@ test(
|
|||||||
await page.getByTestId('project-directory-button').click()
|
await page.getByTestId('project-directory-button').click()
|
||||||
await handleFile
|
await handleFile
|
||||||
|
|
||||||
|
await homePage.projectsLoaded()
|
||||||
await expect(page.locator('section#projectDirectory input')).toHaveValue(
|
await expect(page.locator('section#projectDirectory input')).toHaveValue(
|
||||||
originalProjectDirName
|
originalProjectDirName
|
||||||
)
|
)
|
||||||
@ -2000,8 +2018,8 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Settings persist across restarts',
|
'Settings persist across restarts',
|
||||||
{ tag: '@electron', cleanProjectDir: true },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page, scene, cmdBar }, testInfo) => {
|
||||||
await test.step('We can change a user setting like theme', async () => {
|
await test.step('We can change a user setting like theme', async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -2014,6 +2032,10 @@ test(
|
|||||||
await expect(page.getByTestId('app-theme')).toHaveValue('dark')
|
await expect(page.getByTestId('app-theme')).toHaveValue('dark')
|
||||||
|
|
||||||
await page.getByTestId('app-theme').selectOption('light')
|
await page.getByTestId('app-theme').selectOption('light')
|
||||||
|
await expect(page.getByTestId('app-theme')).toHaveValue('light')
|
||||||
|
|
||||||
|
// Give time to system for writing to a persistent store
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Starting the app again and we can see the same theme', async () => {
|
await test.step('Starting the app again and we can see the same theme', async () => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import { getUtils, executorInputPath } from './test-utils'
|
import { getUtils, executorInputPath } from './test-utils'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||||
@ -2153,6 +2154,8 @@ extrude001 = extrude(profile003, length = 5)
|
|||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000)
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -2165,7 +2168,7 @@ extrude001 = extrude(profile003, length = 5)
|
|||||||
await page.waitForTimeout(600)
|
await page.waitForTimeout(600)
|
||||||
|
|
||||||
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
|
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||||
await toolbar.exitSketchBtn.click()
|
await toolbar.exitSketch()
|
||||||
|
|
||||||
await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`)
|
await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||||
|
|
||||||
@ -2181,6 +2184,8 @@ extrude001 = extrude(profile003, length = 5)
|
|||||||
)`
|
)`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -31,8 +31,7 @@ test.beforeEach(async ({ page, context }) => {
|
|||||||
// Help engine-manager: tear shit down.
|
// Help engine-manager: tear shit down.
|
||||||
test.afterEach(async ({ page }) => {
|
test.afterEach(async ({ page }) => {
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
// @ts-expect-error
|
window.engineCommandManager.tearDown()
|
||||||
window.tearDown()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -45,7 +44,11 @@ test.setTimeout(60_000)
|
|||||||
test.skip(
|
test.skip(
|
||||||
'exports of each format should work',
|
'exports of each format should work',
|
||||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||||
async ({ page, context, scene, cmdBar }) => {
|
async ({ page, context, scene, cmdBar, tronApp }) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
@ -134,6 +137,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'ascii',
|
storage: 'ascii',
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -146,6 +150,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -158,6 +163,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -170,6 +176,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
units: 'in',
|
units: 'in',
|
||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -182,6 +189,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
units: 'in',
|
units: 'in',
|
||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -193,6 +201,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
coords: sysType,
|
coords: sysType,
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -203,6 +212,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'embedded',
|
storage: 'embedded',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -213,6 +223,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'binary',
|
storage: 'binary',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -223,6 +234,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'standard',
|
storage: 'standard',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -84,7 +84,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
'Engine disconnect & reconnect in sketch mode',
|
'Engine disconnect & reconnect in sketch mode',
|
||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
// TODO: Don't skip Mac for these. After `window.engineCommandManager.tearDown` is working in Safari, these should work on webkit
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
@ -5,8 +5,9 @@ import {
|
|||||||
_electron as electron,
|
_electron as electron,
|
||||||
ElectronApplication,
|
ElectronApplication,
|
||||||
Locator,
|
Locator,
|
||||||
|
Page,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
import { test, Page } from './zoo-test'
|
import { test } from './zoo-test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import fsSync from 'fs'
|
import fsSync from 'fs'
|
||||||
@ -337,7 +338,7 @@ export const getMovementUtils = (opts: any) => {
|
|||||||
|
|
||||||
async function waitForAuthAndLsp(page: Page) {
|
async function waitForAuthAndLsp(page: Page) {
|
||||||
const waitForLspPromise = page.waitForEvent('console', {
|
const waitForLspPromise = page.waitForEvent('console', {
|
||||||
predicate: async (message) => {
|
predicate: async (message: any) => {
|
||||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||||
// but that doesn't seem to make it to the console for macos/safari :(
|
// but that doesn't seem to make it to the console for macos/safari :(
|
||||||
if (message.text().includes('start kcl lsp')) {
|
if (message.text().includes('start kcl lsp')) {
|
||||||
@ -420,7 +421,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
const overlay = page.locator(locator)
|
const overlay = page.locator(locator)
|
||||||
const bbox = await overlay
|
const bbox = await overlay
|
||||||
.boundingBox({ timeout: 5_000 })
|
.boundingBox({ timeout: 5_000 })
|
||||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
|
.then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
|
||||||
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
|
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
|
||||||
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
|
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
|
||||||
const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
|
const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
|
||||||
@ -437,7 +438,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
page
|
page
|
||||||
.locator(locator)
|
.locator(locator)
|
||||||
.boundingBox({ timeout: 5_000 })
|
.boundingBox({ timeout: 5_000 })
|
||||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
.then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
||||||
codeLocator: page.locator('.cm-content'),
|
codeLocator: page.locator('.cm-content'),
|
||||||
crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
|
crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
|
||||||
const code = await page.locator('.cm-content').innerText()
|
const code = await page.locator('.cm-content').innerText()
|
||||||
@ -504,7 +505,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
) => {
|
) => {
|
||||||
if (cdpSession === null) {
|
if (cdpSession === null) {
|
||||||
// Use a fail safe if we can't simulate disconnect (on Safari)
|
// Use a fail safe if we can't simulate disconnect (on Safari)
|
||||||
return page.evaluate('window.tearDown()')
|
return page.evaluate('window.engineCommandManager.tearDown()')
|
||||||
}
|
}
|
||||||
|
|
||||||
return cdpSession?.send(
|
return cdpSession?.send(
|
||||||
@ -631,7 +632,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
panesOpen: async (paneIds: PaneId[]) => {
|
panesOpen: async (paneIds: PaneId[]) => {
|
||||||
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ PERSIST_MODELING_CONTEXT, paneIds }) => {
|
({ PERSIST_MODELING_CONTEXT, paneIds }: any) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
PERSIST_MODELING_CONTEXT,
|
PERSIST_MODELING_CONTEXT,
|
||||||
JSON.stringify({ openPanes: paneIds })
|
JSON.stringify({ openPanes: paneIds })
|
||||||
@ -722,14 +723,14 @@ export const makeTemplate: (
|
|||||||
|
|
||||||
const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright'
|
const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright'
|
||||||
|
|
||||||
export const getPlaywrightDownloadDir = (page: Page) => {
|
export const getPlaywrightDownloadDir = (rootDir: string) => {
|
||||||
return path.resolve(page.dir, PLAYWRIGHT_DOWNLOAD_DIR)
|
return path.resolve(rootDir, PLAYWRIGHT_DOWNLOAD_DIR)
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveDownloadedFileTo = async (page: Page, toLocation: string) => {
|
const moveDownloadedFileTo = async (rootDir: string, toLocation: string) => {
|
||||||
await fsp.mkdir(path.dirname(toLocation), { recursive: true })
|
await fsp.mkdir(path.dirname(toLocation), { recursive: true })
|
||||||
|
|
||||||
const downloadDir = getPlaywrightDownloadDir(page)
|
const downloadDir = getPlaywrightDownloadDir(rootDir)
|
||||||
|
|
||||||
// Expect there to be at least one file
|
// Expect there to be at least one file
|
||||||
await expect
|
await expect
|
||||||
@ -756,6 +757,7 @@ export interface Paths {
|
|||||||
|
|
||||||
export const doExport = async (
|
export const doExport = async (
|
||||||
output: Models['OutputFormat_type'],
|
output: Models['OutputFormat_type'],
|
||||||
|
rootDir: string,
|
||||||
page: Page,
|
page: Page,
|
||||||
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
||||||
): Promise<Paths> => {
|
): Promise<Paths> => {
|
||||||
@ -836,7 +838,7 @@ export const doExport = async (
|
|||||||
// (declared in src/lib/exportSave)
|
// (declared in src/lib/exportSave)
|
||||||
// To remain consistent with our old web tests, we want to move some downloads
|
// To remain consistent with our old web tests, we want to move some downloads
|
||||||
// (images) to another directory.
|
// (images) to another directory.
|
||||||
await moveDownloadedFileTo(page, downloadLocation)
|
await moveDownloadedFileTo(rootDir, downloadLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -859,12 +861,6 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
|
|||||||
downloadThroughput: -1,
|
downloadThroughput: -1,
|
||||||
uploadThroughput: -1,
|
uploadThroughput: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// It seems it's best to give the browser about 3s to close things
|
|
||||||
// It's not super reliable but we have no real other choice for now
|
|
||||||
await page.waitForTimeout(3000)
|
|
||||||
|
|
||||||
await testInfo.tronApp?.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// settingsOverrides may need to be augmented to take more generic items,
|
// settingsOverrides may need to be augmented to take more generic items,
|
||||||
@ -936,107 +932,11 @@ let electronApp: ElectronApplication | undefined = undefined
|
|||||||
let context: BrowserContext | undefined = undefined
|
let context: BrowserContext | undefined = undefined
|
||||||
let page: Page | undefined = undefined
|
let page: Page | undefined = undefined
|
||||||
|
|
||||||
export async function setupElectron({
|
|
||||||
testInfo,
|
|
||||||
cleanProjectDir = true,
|
|
||||||
appSettings,
|
|
||||||
viewport,
|
|
||||||
}: {
|
|
||||||
testInfo: TestInfo
|
|
||||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
|
||||||
cleanProjectDir?: boolean
|
|
||||||
appSettings?: DeepPartial<Settings>
|
|
||||||
viewport: {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
}): Promise<{
|
|
||||||
electronApp: ElectronApplication
|
|
||||||
context: BrowserContext
|
|
||||||
page: Page
|
|
||||||
dir: string
|
|
||||||
}> {
|
|
||||||
// create or otherwise clear the folder
|
|
||||||
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
|
||||||
try {
|
|
||||||
if (fsSync.existsSync(projectDirName) && cleanProjectDir) {
|
|
||||||
await fsp.rm(projectDirName, { recursive: true })
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanProjectDir) {
|
|
||||||
await fsp.mkdir(projectDirName)
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
args: ['.', '--no-sandbox'],
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
TEST_SETTINGS_FILE_KEY: projectDirName,
|
|
||||||
IS_PLAYWRIGHT: 'true',
|
|
||||||
},
|
|
||||||
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
|
||||||
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
|
|
||||||
: {}),
|
|
||||||
...(process.env.PLAYWRIGHT_RECORD_VIDEO
|
|
||||||
? {
|
|
||||||
recordVideo: {
|
|
||||||
dir: testInfo.snapshotPath(),
|
|
||||||
size: viewport,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do this once and then reuse window on subsequent calls.
|
|
||||||
if (!electronApp) {
|
|
||||||
electronApp = await electron.launch(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!context || !page) {
|
|
||||||
context = electronApp.context()
|
|
||||||
page = await electronApp.firstWindow()
|
|
||||||
context.on('console', console.log)
|
|
||||||
page.on('console', console.log)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanProjectDir) {
|
|
||||||
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
|
|
||||||
const settingsOverrides = settingsToToml(
|
|
||||||
appSettings
|
|
||||||
? {
|
|
||||||
settings: {
|
|
||||||
...TEST_SETTINGS,
|
|
||||||
...appSettings,
|
|
||||||
app: {
|
|
||||||
...TEST_SETTINGS.app,
|
|
||||||
project_directory: projectDirName,
|
|
||||||
...appSettings.app,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
settings: {
|
|
||||||
...TEST_SETTINGS,
|
|
||||||
app: {
|
|
||||||
...TEST_SETTINGS.app,
|
|
||||||
project_directory: projectDirName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { electronApp, page, context, dir: projectDirName }
|
|
||||||
}
|
|
||||||
|
|
||||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||||
// enabled for chrome for now
|
// enabled for chrome for now
|
||||||
if (page.context().browser()?.browserType().name() === 'chromium') {
|
if (page.context().browser()?.browserType().name() === 'chromium') {
|
||||||
page.on('pageerror', (exception) => {
|
// No idea wtf exception is
|
||||||
|
page.on('pageerror', (exception: any) => {
|
||||||
if (isErrorWhitelisted(exception)) {
|
if (isErrorWhitelisted(exception)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { deg, getUtils, wiggleMove } from './test-utils'
|
import { deg, getUtils, wiggleMove } from './test-utils'
|
||||||
import { LineInputsType } from 'lang/std/sketchcombos'
|
import { LineInputsType } from 'lang/std/sketchcombos'
|
||||||
|
@ -20,14 +20,20 @@ import { DeepPartial } from 'lib/types'
|
|||||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||||
|
|
||||||
test.describe('Testing settings', () => {
|
test.describe('Testing settings', () => {
|
||||||
test(
|
test('Stored settings are validated and fall back to defaults', async ({
|
||||||
'Stored settings are validated and fall back to defaults',
|
page,
|
||||||
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
// with corrupted settings
|
// with corrupted settings
|
||||||
{
|
await tronApp.cleanProjectDir(
|
||||||
appSettings: TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>,
|
TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>
|
||||||
},
|
)
|
||||||
async ({ page, homePage }) => {
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Check the settings were reset
|
// Check the settings were reset
|
||||||
@ -47,8 +53,7 @@ test.describe('Testing settings', () => {
|
|||||||
expect(storedSettings.settings?.project?.default_project_name).toBe(
|
expect(storedSettings.settings?.project?.default_project_name).toBe(
|
||||||
'project-$nnn'
|
'project-$nnn'
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// The behavior is actually broken. Parent always takes precedence
|
// The behavior is actually broken. Parent always takes precedence
|
||||||
test.fixme(
|
test.fixme(
|
||||||
@ -357,8 +362,6 @@ test.describe('Testing settings', () => {
|
|||||||
`Load desktop app with no settings file`,
|
`Load desktop app with no settings file`,
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
// This is what makes no settings file get created
|
|
||||||
cleanProjectDir: false,
|
|
||||||
},
|
},
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -379,13 +382,17 @@ test.describe('Testing settings', () => {
|
|||||||
`Load desktop app with a settings file, but no project directory setting`,
|
`Load desktop app with a settings file, but no project directory setting`,
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
appSettings: {
|
},
|
||||||
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
theme_color: '259',
|
theme_color: '259',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page }, testInfo) => {
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
@ -405,15 +412,20 @@ test.describe('Testing settings', () => {
|
|||||||
'user settings reload on external change, on project and modeling view',
|
'user settings reload on external change, on project and modeling view',
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
appSettings: {
|
},
|
||||||
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
// Doesn't matter what you set it to. It will
|
// Doesn't matter what you set it to. It will
|
||||||
// default to 264.5
|
// default to 264.5
|
||||||
theme_color: '0',
|
theme_color: '0',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page }, testInfo) => {
|
|
||||||
const { dir: projectDirName } = await context.folderSetupFn(
|
const { dir: projectDirName } = await context.folderSetupFn(
|
||||||
async () => {}
|
async () => {}
|
||||||
)
|
)
|
||||||
@ -783,13 +795,20 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(`Changing system theme preferences (via media query) should update UI and stream`, async ({
|
||||||
`Changing system theme preferences (via media query) should update UI and stream`,
|
page,
|
||||||
{
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
// Override the settings so that the theme is set to `system`
|
// Override the settings so that the theme is set to `system`
|
||||||
appSettings: TEST_SETTINGS_DEFAULT_THEME,
|
...TEST_SETTINGS_DEFAULT_THEME,
|
||||||
},
|
})
|
||||||
async ({ page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
@ -829,29 +848,31 @@ test.describe('Testing settings', () => {
|
|||||||
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
|
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
|
||||||
.toBeLessThan(15)
|
.toBeLessThan(15)
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({
|
||||||
`Turning off "Show debug panel" with debug panel open leaves no phantom panel`,
|
context,
|
||||||
{
|
page,
|
||||||
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
// with debug panel open
|
// with debug panel open
|
||||||
// but "show debug panel" set to false
|
// but "show debug panel" set to false
|
||||||
appSettings: {
|
|
||||||
...TEST_SETTINGS,
|
...TEST_SETTINGS,
|
||||||
app: { ...TEST_SETTINGS.app, show_debug_panel: false },
|
app: { ...TEST_SETTINGS.app, show_debug_panel: false },
|
||||||
modeling: { ...TEST_SETTINGS.modeling },
|
modeling: { ...TEST_SETTINGS.modeling },
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem('persistModelingContext', '{"openPanes":["debug"]}')
|
||||||
'persistModelingContext',
|
|
||||||
'{"openPanes":["debug"]}'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
@ -903,8 +924,7 @@ test.describe('Testing settings', () => {
|
|||||||
await expect(debugPaneButton).not.toBeVisible()
|
await expect(debugPaneButton).not.toBeVisible()
|
||||||
await expect(resizeHandle).not.toBeVisible()
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(`Change inline units setting`, async ({
|
test(`Change inline units setting`, async ({
|
||||||
page,
|
page,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import { getUtils, createProject } from './test-utils'
|
import { getUtils, createProject } from './test-utils'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
@ -35,7 +35,7 @@ test.fixme('Units menu', async ({ page, homePage }) => {
|
|||||||
test(
|
test(
|
||||||
'Successful export shows a success toast',
|
'Successful export shows a success toast',
|
||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage, tronApp }) => {
|
||||||
// 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)
|
||||||
@ -92,12 +92,17 @@ part001 = startSketchOn('-XZ')
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
if (!tronApp?.projectDirName) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await doExport(
|
await doExport(
|
||||||
{
|
{
|
||||||
type: 'gltf',
|
type: 'gltf',
|
||||||
storage: 'embedded',
|
storage: 'embedded',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp?.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -465,7 +470,7 @@ test('Delete key does not navigate back', async ({ page, homePage }) => {
|
|||||||
await expect.poll(() => page.url()).not.toContain('/settings')
|
await expect.poll(() => page.url()).not.toContain('/settings')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Sketch on face', async ({ page, homePage, scene, cmdBar }) => {
|
test('Sketch on face', async ({ page, homePage, scene, cmdBar, toolbar }) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -491,17 +496,12 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
|
|
||||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await toolbar.startSketchThenCallbackThenWaitUntilReady(async () => {
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.doAndWaitForCmd(
|
await u.doAndWaitForCmd(
|
||||||
() => page.mouse.click(625, 165),
|
() => page.mouse.click(625, 165),
|
||||||
@ -510,6 +510,8 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
)
|
)
|
||||||
await page.waitForTimeout(150)
|
await page.waitForTimeout(150)
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
const firstClickPosition = [612, 238]
|
const firstClickPosition = [612, 238]
|
||||||
const secondClickPosition = [661, 242]
|
const secondClickPosition = [661, 242]
|
||||||
|
@ -1,21 +1,11 @@
|
|||||||
import {
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
test as playwrightTestFn,
|
|
||||||
TestInfo as TestInfoPlaywright,
|
import { test as playwrightTestFn, ElectronApplication } from '@playwright/test'
|
||||||
BrowserContext as BrowserContextPlaywright,
|
|
||||||
Page as PagePlaywright,
|
|
||||||
TestDetails as TestDetailsPlaywright,
|
|
||||||
PlaywrightTestArgs,
|
|
||||||
PlaywrightTestOptions,
|
|
||||||
PlaywrightWorkerArgs,
|
|
||||||
PlaywrightWorkerOptions,
|
|
||||||
ElectronApplication,
|
|
||||||
} from '@playwright/test'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fixtures,
|
fixturesBasedOnProcessEnvPlatform,
|
||||||
Fixtures,
|
Fixtures,
|
||||||
AuthenticatedTronApp,
|
ElectronZoo,
|
||||||
AuthenticatedApp,
|
|
||||||
} from './fixtures/fixtureSetup'
|
} from './fixtures/fixtureSetup'
|
||||||
|
|
||||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||||
@ -23,9 +13,6 @@ import { DeepPartial } from 'lib/types'
|
|||||||
export { expect } from '@playwright/test'
|
export { expect } from '@playwright/test'
|
||||||
|
|
||||||
declare module '@playwright/test' {
|
declare module '@playwright/test' {
|
||||||
interface TestInfo {
|
|
||||||
tronApp?: AuthenticatedTronApp
|
|
||||||
}
|
|
||||||
interface BrowserContext {
|
interface BrowserContext {
|
||||||
folderSetupFn: (
|
folderSetupFn: (
|
||||||
cb: (dir: string) => Promise<void>
|
cb: (dir: string) => Promise<void>
|
||||||
@ -41,288 +28,29 @@ declare module '@playwright/test' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TestInfo = TestInfoPlaywright
|
// Each worker spawns a new thread, which will spawn its own ElectronZoo.
|
||||||
export type BrowserContext = BrowserContextPlaywright
|
// So in some sense there is an implicit pool.
|
||||||
export type Page = PagePlaywright
|
// For example, the variable just beneath this text is reused many times
|
||||||
export type TestDetails = TestDetailsPlaywright & {
|
// *for one worker*.
|
||||||
cleanProjectDir?: boolean
|
const electronZooInstance = new ElectronZoo()
|
||||||
appSettings?: DeepPartial<Settings>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and
|
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and
|
||||||
// switch between web and electron if needed.
|
// switch between web and electron if needed.
|
||||||
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures)
|
const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
|
||||||
|
tronApp?: ElectronZoo
|
||||||
// In JavaScript you cannot replace a function's body only (despite functions
|
}>({
|
||||||
// are themselves objects, which you'd expect a body property or something...)
|
tronApp: async ({}, use, testInfo) => {
|
||||||
// So we must redefine the function and then re-attach properties.
|
|
||||||
type PWFunction = (
|
|
||||||
args: PlaywrightTestArgs &
|
|
||||||
Fixtures &
|
|
||||||
PlaywrightWorkerArgs &
|
|
||||||
PlaywrightTestOptions &
|
|
||||||
PlaywrightWorkerOptions & {
|
|
||||||
electronApp?: ElectronApplication
|
|
||||||
},
|
|
||||||
testInfo: TestInfo
|
|
||||||
) => void | Promise<void>
|
|
||||||
|
|
||||||
let firstUrl = ''
|
|
||||||
|
|
||||||
export const test = (
|
|
||||||
desc: string,
|
|
||||||
objOrFn: PWFunction | TestDetails,
|
|
||||||
fnMaybe?: PWFunction
|
|
||||||
) => {
|
|
||||||
const hasTestConf = typeof objOrFn === 'object'
|
|
||||||
const fn = hasTestConf ? fnMaybe : objOrFn
|
|
||||||
|
|
||||||
return pwTestFnWithFixtures(
|
|
||||||
desc,
|
|
||||||
hasTestConf ? objOrFn : {},
|
|
||||||
async (
|
|
||||||
{
|
|
||||||
page,
|
|
||||||
context,
|
|
||||||
cmdBar,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
scene,
|
|
||||||
homePage,
|
|
||||||
request,
|
|
||||||
playwright,
|
|
||||||
browser,
|
|
||||||
acceptDownloads,
|
|
||||||
bypassCSP,
|
|
||||||
colorScheme,
|
|
||||||
clientCertificates,
|
|
||||||
deviceScaleFactor,
|
|
||||||
extraHTTPHeaders,
|
|
||||||
geolocation,
|
|
||||||
hasTouch,
|
|
||||||
httpCredentials,
|
|
||||||
ignoreHTTPSErrors,
|
|
||||||
isMobile,
|
|
||||||
javaScriptEnabled,
|
|
||||||
locale,
|
|
||||||
offline,
|
|
||||||
permissions,
|
|
||||||
proxy,
|
|
||||||
storageState,
|
|
||||||
timezoneId,
|
|
||||||
userAgent,
|
|
||||||
viewport,
|
|
||||||
baseURL,
|
|
||||||
contextOptions,
|
|
||||||
actionTimeout,
|
|
||||||
navigationTimeout,
|
|
||||||
serviceWorkers,
|
|
||||||
testIdAttribute,
|
|
||||||
browserName,
|
|
||||||
defaultBrowserType,
|
|
||||||
headless,
|
|
||||||
channel,
|
|
||||||
launchOptions,
|
|
||||||
connectOptions,
|
|
||||||
screenshot,
|
|
||||||
trace,
|
|
||||||
video,
|
|
||||||
},
|
|
||||||
testInfo
|
|
||||||
) => {
|
|
||||||
// To switch to web, use PLATFORM=web environment variable.
|
|
||||||
// Only use this for debugging, since the playwright tracer is busted
|
|
||||||
// for electron.
|
|
||||||
|
|
||||||
let tronApp
|
|
||||||
|
|
||||||
if (process.env.PLATFORM === 'web') {
|
if (process.env.PLATFORM === 'web') {
|
||||||
tronApp = new AuthenticatedApp(context, page, testInfo)
|
await use(undefined)
|
||||||
} else {
|
|
||||||
tronApp = new AuthenticatedTronApp(context, page, testInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fixtures: Fixtures = { cmdBar, editor, toolbar, scene, homePage }
|
|
||||||
if (tronApp instanceof AuthenticatedTronApp) {
|
|
||||||
const options = {
|
|
||||||
fixtures,
|
|
||||||
}
|
|
||||||
if (hasTestConf) {
|
|
||||||
Object.assign(options, {
|
|
||||||
appSettings: objOrFn?.appSettings,
|
|
||||||
cleanProjectDir: objOrFn?.cleanProjectDir,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await tronApp.initialise(options)
|
|
||||||
} else {
|
|
||||||
await tronApp.initialise('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to patch this because addInitScript will bind too late in our
|
|
||||||
// electron tests, never running. We need to call reload() after each call
|
|
||||||
// to guarantee it runs.
|
|
||||||
const oldContextAddInitScript = tronApp.context.addInitScript
|
|
||||||
tronApp.context.addInitScript = async function (a, b) {
|
|
||||||
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
|
||||||
// This code works perfectly fine.
|
|
||||||
await oldContextAddInitScript.apply(this, [a, b])
|
|
||||||
await tronApp.page.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// No idea why we mix and match page and context's addInitScript but we do
|
|
||||||
const oldPageAddInitScript = tronApp.page.addInitScript
|
|
||||||
tronApp.page.addInitScript = async function (a: any, b: any) {
|
|
||||||
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
|
||||||
// This code works perfectly fine.
|
|
||||||
await oldPageAddInitScript.apply(this, [a, b])
|
|
||||||
await tronApp.page.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a consistent way to resize the page across electron and web.
|
|
||||||
// (lee) I had to do everything in the book to make electron change its
|
|
||||||
// damn window size. I succeeded in making it consistently and reliably
|
|
||||||
// do it after a whole afternoon.
|
|
||||||
tronApp.page.setBodyDimensions = async function (dims: {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}) {
|
|
||||||
await tronApp.page.setViewportSize(dims)
|
|
||||||
|
|
||||||
if (!(tronApp instanceof AuthenticatedTronApp)) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await tronApp.electronApp?.evaluateHandle(async ({ app }, dims) => {
|
await use(electronZooInstance)
|
||||||
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
|
|
||||||
await app.resizeWindow(dims.width, dims.height)
|
|
||||||
}, dims)
|
|
||||||
|
|
||||||
return tronApp.page.evaluate(
|
|
||||||
async (dims: { width: number; height: number }) => {
|
|
||||||
await window.electron.resizeWindow(dims.width, dims.height)
|
|
||||||
window.document.body.style.width = dims.width + 'px'
|
|
||||||
window.document.body.style.height = dims.height + 'px'
|
|
||||||
window.document.documentElement.style.width = dims.width + 'px'
|
|
||||||
window.document.documentElement.style.height = dims.height + 'px'
|
|
||||||
},
|
},
|
||||||
dims
|
})
|
||||||
|
|
||||||
|
const test = playwrightTestFnWithFixtures_.extend<Fixtures>(
|
||||||
|
fixturesBasedOnProcessEnvPlatform
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
await tronApp.page.setBodyDimensions(tronApp.viewPortSize)
|
export { test }
|
||||||
|
|
||||||
// We need to expose this in order for some tests that require folder
|
|
||||||
// creation. Before they used to do this by their own electronSetup({...})
|
|
||||||
// calls.
|
|
||||||
if (tronApp instanceof AuthenticatedTronApp) {
|
|
||||||
tronApp.context.folderSetupFn = async function (fn) {
|
|
||||||
return fn(tronApp.dir)
|
|
||||||
.then(() => tronApp.page.reload())
|
|
||||||
.then(() => ({
|
|
||||||
dir: tronApp.dir,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!firstUrl) {
|
|
||||||
await tronApp.page.getByText('Your Projects').count()
|
|
||||||
firstUrl = tronApp.page.url()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to the app controlling its own window context we need to inject new
|
|
||||||
// options and context here.
|
|
||||||
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
|
|
||||||
// await tronApp.electronApp.evaluate(({ app }) => {
|
|
||||||
// return app.reuseWindowForTest();
|
|
||||||
// });
|
|
||||||
|
|
||||||
await tronApp.electronApp?.evaluate(({ app }, projectDirName) => {
|
|
||||||
// @ts-ignore can't declaration merge see main.ts
|
|
||||||
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
|
||||||
}, tronApp.dir)
|
|
||||||
|
|
||||||
// Always start at the root view
|
|
||||||
await tronApp.page.goto(firstUrl)
|
|
||||||
|
|
||||||
// Force a hard reload, destroying the stream and other state
|
|
||||||
await tronApp.page.reload()
|
|
||||||
|
|
||||||
// tsc aint smart enough to know this'll never be undefined
|
|
||||||
// but I dont blame it, the logic to know is complex
|
|
||||||
if (fn) {
|
|
||||||
await fn(
|
|
||||||
{
|
|
||||||
context: tronApp.context,
|
|
||||||
page: tronApp.page,
|
|
||||||
electronApp:
|
|
||||||
tronApp instanceof AuthenticatedTronApp
|
|
||||||
? tronApp.electronApp
|
|
||||||
: undefined,
|
|
||||||
...fixtures,
|
|
||||||
request,
|
|
||||||
playwright,
|
|
||||||
browser,
|
|
||||||
acceptDownloads,
|
|
||||||
bypassCSP,
|
|
||||||
colorScheme,
|
|
||||||
clientCertificates,
|
|
||||||
deviceScaleFactor,
|
|
||||||
extraHTTPHeaders,
|
|
||||||
geolocation,
|
|
||||||
hasTouch,
|
|
||||||
httpCredentials,
|
|
||||||
ignoreHTTPSErrors,
|
|
||||||
isMobile,
|
|
||||||
javaScriptEnabled,
|
|
||||||
locale,
|
|
||||||
offline,
|
|
||||||
permissions,
|
|
||||||
proxy,
|
|
||||||
storageState,
|
|
||||||
timezoneId,
|
|
||||||
userAgent,
|
|
||||||
viewport,
|
|
||||||
baseURL,
|
|
||||||
contextOptions,
|
|
||||||
actionTimeout,
|
|
||||||
navigationTimeout,
|
|
||||||
serviceWorkers,
|
|
||||||
testIdAttribute,
|
|
||||||
browserName,
|
|
||||||
defaultBrowserType,
|
|
||||||
headless,
|
|
||||||
channel,
|
|
||||||
launchOptions,
|
|
||||||
connectOptions,
|
|
||||||
screenshot,
|
|
||||||
trace,
|
|
||||||
video,
|
|
||||||
},
|
|
||||||
testInfo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
testInfo.tronApp =
|
|
||||||
tronApp instanceof AuthenticatedTronApp ? tronApp : undefined
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ZooTest = typeof test
|
|
||||||
|
|
||||||
test.describe = pwTestFnWithFixtures.describe
|
|
||||||
test.beforeEach = pwTestFnWithFixtures.beforeEach
|
|
||||||
test.afterEach = pwTestFnWithFixtures.afterEach
|
|
||||||
test.step = pwTestFnWithFixtures.step
|
|
||||||
test.skip = pwTestFnWithFixtures.skip
|
|
||||||
test.setTimeout = pwTestFnWithFixtures.setTimeout
|
|
||||||
test.fixme = pwTestFnWithFixtures.fixme as unknown as ZooTest
|
|
||||||
test.only = pwTestFnWithFixtures.only
|
|
||||||
test.fail = pwTestFnWithFixtures.fail
|
|
||||||
test.slow = pwTestFnWithFixtures.slow
|
|
||||||
test.beforeAll = pwTestFnWithFixtures.beforeAll
|
|
||||||
test.afterAll = pwTestFnWithFixtures.afterAll
|
|
||||||
test.use = pwTestFnWithFixtures.use
|
|
||||||
test.expect = pwTestFnWithFixtures.expect
|
|
||||||
test.extend = pwTestFnWithFixtures.extend
|
|
||||||
test.info = pwTestFnWithFixtures.info
|
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
|
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
|
||||||
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
||||||
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
||||||
"postinstall": "./node_modules/.bin/electron-rebuild",
|
"postinstall": "yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install && ./node_modules/.bin/electron-rebuild",
|
||||||
"make:dev": "make dev",
|
"make:dev": "make dev",
|
||||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||||
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
|
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
|
||||||
|
@ -136,6 +136,7 @@ export default function Gizmo() {
|
|||||||
<div
|
<div
|
||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
aria-label="View orientation gizmo"
|
aria-label="View orientation gizmo"
|
||||||
|
data-testid={`gizmo${disableOrbitRef.current ? '-disabled' : ''}`}
|
||||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
<canvas ref={canvasRef} />
|
<canvas ref={canvasRef} />
|
||||||
|
@ -1914,7 +1914,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
|
|
||||||
this.engineConnection?.tearDown(opts)
|
this.engineConnection?.tearDown(opts)
|
||||||
|
|
||||||
// Our window.tearDown assignment causes this case to happen which is
|
// Our window.engineCommandManager.tearDown assignment causes this case to happen which is
|
||||||
// only really for tests.
|
// only really for tests.
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
} else if (this.engineCommandManager?.engineConnection) {
|
} else if (this.engineCommandManager?.engineConnection) {
|
||||||
|
@ -405,6 +405,8 @@ export const getAppSettingsFilePath = async () => {
|
|||||||
const testSettingsPath = await window.electron.getAppTestProperty(
|
const testSettingsPath = await window.electron.getAppTestProperty(
|
||||||
'TEST_SETTINGS_FILE_KEY'
|
'TEST_SETTINGS_FILE_KEY'
|
||||||
)
|
)
|
||||||
|
if (isTestEnv && !testSettingsPath) return SETTINGS_FILE_NAME
|
||||||
|
|
||||||
const appConfig = await window.electron.getPath('appData')
|
const appConfig = await window.electron.getPath('appData')
|
||||||
const fullPath = isTestEnv
|
const fullPath = isTestEnv
|
||||||
? testSettingsPath
|
? testSettingsPath
|
||||||
|
@ -10,9 +10,15 @@ export const codeManager = new CodeManager()
|
|||||||
|
|
||||||
export const engineCommandManager = new EngineCommandManager()
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
editorManager: EditorManager
|
||||||
|
engineCommandManager: EngineCommandManager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Accessible for tests mostly
|
// Accessible for tests mostly
|
||||||
// @ts-ignore
|
window.engineCommandManager = engineCommandManager
|
||||||
window.tearDown = engineCommandManager.tearDown
|
|
||||||
|
|
||||||
// This needs to be after codeManager is created.
|
// This needs to be after codeManager is created.
|
||||||
export const kclManager = new KclManager(engineCommandManager)
|
export const kclManager = new KclManager(engineCommandManager)
|
||||||
@ -23,12 +29,6 @@ engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange
|
|||||||
|
|
||||||
export const sceneEntitiesManager = new SceneEntities(engineCommandManager)
|
export const sceneEntitiesManager = new SceneEntities(engineCommandManager)
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
editorManager: EditorManager
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This needs to be after sceneInfra and engineCommandManager are is created.
|
// This needs to be after sceneInfra and engineCommandManager are is created.
|
||||||
export const editorManager = new EditorManager()
|
export const editorManager = new EditorManager()
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user