Files
modeling-app/e2e/playwright/zoo-test.ts
49fl fa9e6ccea9 Bring back Playwright-Electron Workbench's on-failure screenshot + new PLAYWRIGHT_RECORD_VIDEO (#5464)
* Fix playwright-electron screenshots on failure and add PLAYWRIGHT_RECORD_VIDEO

* yarn tsc
2025-02-24 13:09:39 -05:00

328 lines
9.7 KiB
TypeScript

import {
test as playwrightTestFn,
TestInfo as TestInfoPlaywright,
BrowserContext as BrowserContextPlaywright,
Page as PagePlaywright,
TestDetails as TestDetailsPlaywright,
PlaywrightTestArgs,
PlaywrightTestOptions,
PlaywrightWorkerArgs,
PlaywrightWorkerOptions,
ElectronApplication,
} from '@playwright/test'
import {
fixtures,
Fixtures,
AuthenticatedTronApp,
AuthenticatedApp,
} from './fixtures/fixtureSetup'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
export { expect } from '@playwright/test'
declare module '@playwright/test' {
interface TestInfo {
tronApp?: AuthenticatedTronApp
}
interface BrowserContext {
folderSetupFn: (
cb: (dir: string) => Promise<void>
) => Promise<{ dir: string }>
}
interface Page {
dir: string
TEST_SETTINGS_FILE_KEY?: string
setBodyDimensions: (dims: {
width: number
height: number
}) => Promise<void>
}
}
export type TestInfo = TestInfoPlaywright
export type BrowserContext = BrowserContextPlaywright
export type Page = PagePlaywright
export type TestDetails = TestDetailsPlaywright & {
cleanProjectDir?: boolean
appSettings?: Partial<SaveSettingsPayload>
}
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and
// switch between web and electron if needed.
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures)
// In JavaScript you cannot replace a function's body only (despite functions
// are themselves objects, which you'd expect a body property or something...)
// 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') {
tronApp = new AuthenticatedApp(context, page, testInfo)
} 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
}
await tronApp.electronApp?.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 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
)
}
await tronApp.page.setBodyDimensions(tronApp.viewPortSize)
// 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