Reuse electron window; difficult task

This commit is contained in:
49lf
2024-12-11 12:25:03 -05:00
parent f9bb026580
commit 764a0e8f88
16 changed files with 387 additions and 238 deletions

View File

@ -120,46 +120,45 @@ test.describe('Code pane and errors', () => {
await expect(page.locator('.cm-tooltip').first()).toBeVisible() await expect(page.locator('.cm-tooltip').first()).toBeVisible()
}) })
test.fixme('When error is not in view you can click the badge to scroll to it', async ({ test.fixme(
page, 'When error is not in view you can click the badge to scroll to it',
homePage, async ({ page, homePage, context }) => {
context, // Load the app with the working starter code
}) => { await context.addInitScript((code) => {
// Load the app with the working starter code localStorage.setItem('persistCode', code)
await context.addInitScript((code) => { }, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
localStorage.setItem('persistCode', code)
}, TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
// Ensure badge is present // Ensure badge is present
const codePaneButtonHolder = page.locator('#code-button-holder') const codePaneButtonHolder = page.locator('#code-button-holder')
await expect(codePaneButtonHolder).toContainText('notification') await expect(codePaneButtonHolder).toContainText('notification')
// Ensure we have no errors in the gutter, since error out of view. // Ensure we have no errors in the gutter, since error out of view.
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Click the badge. // Click the badge.
const badge = page.locator('#code-badge') const badge = page.locator('#code-badge')
await expect(badge).toBeVisible() await expect(badge).toBeVisible()
await badge.click() await badge.click()
// Ensure we have an error diagnostic. // Ensure we have an error diagnostic.
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
// Hover over the error to see the error message // Hover over the error to see the error message
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
await expect( await expect(
page page
.getByText( .getByText(
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]' 'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
) )
.first() .first()
).toBeVisible() ).toBeVisible()
}) }
)
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({ test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({
context, context,

View File

@ -568,15 +568,14 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
}) })
test.fixme('error with 2 source ranges gets 2 diagnostics', async ({ test.fixme(
page, 'error with 2 source ranges gets 2 diagnostics',
homePage, async ({ page, homePage }) => {
}) => { const u = await getUtils(page)
const u = await getUtils(page) await page.addInitScript(async () => {
await page.addInitScript(async () => { localStorage.setItem(
localStorage.setItem( 'persistCode',
'persistCode', `length = .750
`length = .750
width = 0.500 width = 0.500
height = 0.500 height = 0.500
dia = 4 dia = 4
@ -591,52 +590,53 @@ test.describe('Editor tests', () => {
return squareHoleSketch return squareHoleSketch
} }
` `
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await u.waitForPageLoad() await u.waitForPageLoad()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await u.openDebugPanel() await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.closeDebugPanel()
// check no error to begin with // check no error to begin with
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Click on the bottom of the code editor to add a new line // Click on the bottom of the code editor to add a new line
await u.codeLocator.click() await u.codeLocator.click()
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await page.keyboard.type(`extrusion = startSketchOn('XY') await page.keyboard.type(`extrusion = startSketchOn('XY')
|> circle({ center: [0, 0], radius: dia/2 }, %) |> circle({ center: [0, 0], radius: dia/2 }, %)
|> hole(squareHole(length, width, height), %) |> hole(squareHole(length, width, height), %)
|> extrude(height, %)`) |> extrude(height, %)`)
// error in gutter // error in gutter
await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible() await expect(page.locator('.cm-lint-marker-error').first()).toBeVisible()
await page.hover('.cm-lint-marker-error:first-child') await page.hover('.cm-lint-marker-error:first-child')
await expect( await expect(
page.getByText('Expected 2 arguments, got 3').first() page.getByText('Expected 2 arguments, got 3').first()
).toBeVisible() ).toBeVisible()
// Make sure there are two diagnostics // Make sure there are two diagnostics
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2) await expect(page.locator('.cm-lint-marker-error')).toHaveCount(2)
}) }
)
test('if your kcl gets an error from the engine it is inlined', async ({ test('if your kcl gets an error from the engine it is inlined', async ({
context, context,
page, page,

View File

@ -29,7 +29,7 @@ export class EditorFixture {
reConstruct = (page: Page) => { reConstruct = (page: Page) => {
this.page = page this.page = page
this.codeContent = page.locator('.cm-content') 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')
this.activeLine = this.page.locator('.cm-activeLine') this.activeLine = this.page.locator('.cm-activeLine')

View File

@ -79,7 +79,7 @@ export class AuthenticatedTronApp {
appSettings?: Partial<SaveSettingsPayload> appSettings?: Partial<SaveSettingsPayload>
} = { fixtures: {} } } = { fixtures: {} }
) { ) {
const { electronApp, page, context, dir } = await setupElectron({ const { electronApp, page, context, dir, options } = await setupElectron({
testInfo: this.testInfo, testInfo: this.testInfo,
folderSetupFn: arg.folderSetupFn, folderSetupFn: arg.folderSetupFn,
cleanProjectDir: arg.cleanProjectDir, cleanProjectDir: arg.cleanProjectDir,
@ -96,8 +96,6 @@ export class AuthenticatedTronApp {
// Setup localStorage, addCookies, reload // Setup localStorage, addCookies, reload
await setup(this.context, this.page, this.testInfo) await setup(this.context, this.page, this.testInfo)
await page.setViewportSize(this.viewPortSize)
for (const key of unsafeTypedKeys(arg.fixtures)) { for (const key of unsafeTypedKeys(arg.fixtures)) {
const fixture = arg.fixtures[key] const fixture = arg.fixtures[key]
if ( if (

View File

@ -851,7 +851,9 @@ const shellPointAndClickCapCases = [
] ]
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => { shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({ test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
app, context,
page,
homePage,
scene, scene,
editor, editor,
toolbar, toolbar,
@ -861,7 +863,11 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|> circle({ center = [0, 0], radius = 30 }, %) |> circle({ center = [0, 0], radius = 30 }, %)
extrude001 = extrude(30, sketch001) extrude001 = extrude(30, sketch001)
` `
await app.initialise(initialCode) await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 } const testPoint = { x: 575, y: 200 }
@ -888,7 +894,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
commandName: 'Shell', commandName: 'Shell',
}) })
await clickOnCap() await clickOnCap()
await app.page.waitForTimeout(500) await page.waitForTimeout(500)
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
@ -904,7 +910,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
} else { } else {
await test.step(`Preselect the cap`, async () => { await test.step(`Preselect the cap`, async () => {
await clickOnCap() await clickOnCap()
await app.page.waitForTimeout(500) await page.waitForTimeout(500)
}) })
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => { await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
@ -936,8 +942,9 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
}) })
test('Shell point-and-click wall', async ({ test('Shell point-and-click wall', async ({
app, context,
page, page,
homePage,
scene, scene,
editor, editor,
toolbar, toolbar,
@ -952,7 +959,11 @@ test('Shell point-and-click wall', async ({
|> close(%) |> close(%)
extrude001 = extrude(40, sketch001) extrude001 = extrude(40, sketch001)
` `
await app.initialise(initialCode) await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 580, y: 180 } const testPoint = { x: 580, y: 180 }
@ -983,7 +994,7 @@ extrude001 = extrude(40, sketch001)
await clickOnCap() await clickOnCap()
await page.keyboard.down('Shift') await page.keyboard.down('Shift')
await clickOnWall() await clickOnWall()
await app.page.waitForTimeout(500) await page.waitForTimeout(500)
await page.keyboard.up('Shift') await page.keyboard.up('Shift')
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()

View File

@ -115,21 +115,21 @@ test(
) )
test( test(
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene', 'yyyyyyyyy open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
const errorDir = join(dir, 'broken-code') const errorDir = path.join(dir, 'broken-code')
await fsp.mkdir(errorDir, { recursive: true }) await fsp.mkdir(errorDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('broken-code-test.kcl'), executorInputPath('broken-code-test.kcl'),
join(errorDir, 'main.kcl') path.join(errorDir, 'main.kcl')
) )
}) })
@ -199,19 +199,19 @@ test(
) )
test( test(
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene', 'aaayyyyyyyy open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
const emptyDir = join(dir, 'empty') const emptyDir = path.join(dir, 'empty')
await fsp.mkdir(emptyDir, { recursive: true }) await fsp.mkdir(emptyDir, { recursive: true })
await fsp.writeFile(join(emptyDir, 'main.kcl'), '') await fsp.writeFile(path.join(emptyDir, 'main.kcl'), '')
}) })
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -276,18 +276,18 @@ test(
) )
test( test(
'open a file in a project works and renders, open empty file, it should clear the scene', 'nooooooooooooo open a file in a project works and renders, open empty file, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
await fsp.writeFile(join(bracketDir, 'empty.kcl'), '') await fsp.writeFile(path.join(bracketDir, 'empty.kcl'), '')
}) })
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -297,17 +297,13 @@ test(
const pointOnModel = { x: 630, y: 280 } const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => { await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket // expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible() await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click() await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({ ).toBeEnabled({
@ -347,27 +343,25 @@ test(
) )
test( test(
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene', 'xxxxx open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ browserName }, testInfo) => { async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => { const { dir } = await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'bracket') const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true }) await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'), executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl') path.join(bracketDir, 'main.kcl')
) )
await fsp.copyFile( await fsp.copyFile(
executorInputPath('broken-code-test.kcl'), executorInputPath('broken-code-test.kcl'),
join(bracketDir, 'broken-code-test.kcl') path.join(bracketDir, 'broken-code-test.kcl')
) )
}) })
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page) const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 } const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => { await test.step('Opening the bracket project should load the stream', async () => {
@ -399,7 +393,7 @@ test(
// open the file pane. // open the file pane.
await page.getByTestId('files-pane-button').click() await page.getByTestId('files-pane-button').click()
// OPen the other file. // Open the other file.
const file = page.getByRole('button', { name: 'broken-code-test.kcl' }) const file = page.getByRole('button', { name: 'broken-code-test.kcl' })
await expect(file).toBeVisible() await expect(file).toBeVisible()
@ -997,7 +991,7 @@ test.describe(`Project management commands`, () => {
test( test(
'File in the file pane should open with a single click', 'File in the file pane should open with a single click',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, homePage, page }, testInfo) => {
const projectName = 'router-template-slate' const projectName = 'router-template-slate'
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
@ -1010,16 +1004,14 @@ test(
`${dir}/${projectName}/otherThingToClickOn.kcl` `${dir}/${projectName}/otherThingToClickOn.kcl`
) )
}) })
const u = await getUtils(page) const u = await getUtils(page)
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 page.getByText(projectName).click() await page.getByText(projectName).click()
await expect(page.getByTestId('loading')).toBeAttached() await u.waitForPageLoad()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(u.codeLocator).toContainText('routerDiameter') await expect(u.codeLocator).toContainText('routerDiameter')
await expect(u.codeLocator).toContainText('templateGap') await expect(u.codeLocator).toContainText('templateGap')

View File

@ -47,7 +47,6 @@ test.beforeEach(async ({ page }) => {
test.setTimeout(60_000) test.setTimeout(60_000)
// We test this end to end already - getting this to work on web just to take // We test this end to end already - getting this to work on web just to take
// a snapshot of it feels weird. I'd rather our regular tests fail. // a snapshot of it feels weird. I'd rather our regular tests fail.
// The primary failure is doExport now relies on the filesystem. We can follow // The primary failure is doExport now relies on the filesystem. We can follow

View File

@ -28,6 +28,107 @@ import { isErrorWhitelisted } from './lib/console-error-whitelist'
import { isArray } from 'lib/utils' import { isArray } from 'lib/utils'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
// The below is copied from playwright-core because it exports none of them :(
import { Env, BrowserContextOptions } from 'playwright-core'
import type * as channels from '@protocol/channels';
// Copied from playwright-core
function envObjectToArray(env: Env): { name: string, value: string }[] {
const result: { name: string, value: string }[] = [];
for (const name in env) {
if (!Object.is(env[name], undefined))
result.push({ name, value: String(env[name]) });
}
return result;
}
// Copied from playwright-core
export async function toClientCertificatesProtocol(certs?: BrowserContextOptions['clientCertificates']): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
if (!certs)
return undefined;
const bufferizeContent = async (value?: Buffer, path?: string): Promise<Buffer | undefined> => {
if (value)
return value;
if (path)
return await fs.promises.readFile(path);
};
return await Promise.all(certs.map(async cert => ({
origin: cert.origin,
cert: await bufferizeContent(cert.cert, cert.certPath),
key: await bufferizeContent(cert.key, cert.keyPath),
pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
passphrase: cert.passphrase,
})));
}
// Copied from playwright-core
function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
if (acceptDownloads === undefined)
return undefined;
if (acceptDownloads)
return 'accept';
return 'deny';
}
// Copied from playwright-core
function prepareRecordHarOptions(options: BrowserContextOptions['recordHar']): channels.RecordHarOptions | undefined {
if (!options)
return;
return {
path: options.path,
content: options.content || (options.omitContent ? 'omit' : undefined),
urlGlob: isString(options.urlFilter) ? options.urlFilter : undefined,
urlRegexSource: isRegExp(options.urlFilter) ? options.urlFilter.source : undefined,
urlRegexFlags: isRegExp(options.urlFilter) ? options.urlFilter.flags : undefined,
mode: options.mode
};
}
// Copied from playwright-core
async function prepareStorageState(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams['storageState']> {
if (typeof options.storageState !== 'string')
return options.storageState;
try {
return JSON.parse(await fs.promises.readFile(options.storageState, 'utf8'));
} catch (e) {
rewriteErrorMessage(e, `Error reading storage state from ${options.storageState}:\n` + e.message);
throw e;
}
}
// Copied from playwright-core
async function prepareBrowserContextParams(options: BrowserContextOptions): Promise<channels.BrowserNewContextParams> {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders)
network.validateHeaders(options.extraHTTPHeaders);
const contextParams: channels.BrowserNewContextParams = {
...options,
viewport: options.viewport === null ? undefined : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? headersObjectToArray(options.extraHTTPHeaders) : undefined,
storageState: await prepareStorageState(options),
serviceWorkers: options.serviceWorkers,
recordHar: prepareRecordHarOptions(options.recordHar),
colorScheme: options.colorScheme === null ? 'no-override' : options.colorScheme,
reducedMotion: options.reducedMotion === null ? 'no-override' : options.reducedMotion,
forcedColors: options.forcedColors === null ? 'no-override' : options.forcedColors,
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
clientCertificates: await toClientCertificatesProtocol(options.clientCertificates),
};
if (!contextParams.recordVideo && options.videosPath) {
contextParams.recordVideo = {
dir: options.videosPath,
size: options.videoSize
};
}
if (contextParams.recordVideo && contextParams.recordVideo.dir)
contextParams.recordVideo.dir = path.resolve(process.cwd(), contextParams.recordVideo.dir);
return contextParams;
}
const toNormalizedCode = (text: string) => { const toNormalizedCode = (text: string) => {
return text.replace(/\s+/g, '') return text.replace(/\s+/g, '')
} }
@ -723,7 +824,7 @@ const moveDownloadedFileTo = async (page: Page, toLocation: string) => {
const files = await fsp.readdir(downloadDir) const files = await fsp.readdir(downloadDir)
return files.length return files.length
}) })
.toBe(1) .toBeGreaterThan(0)
// Go through the downloads dir and move files to new location // Go through the downloads dir and move files to new location
const files = await fsp.readdir(downloadDir) const files = await fsp.readdir(downloadDir)
@ -867,10 +968,12 @@ export async function setup(
settings, settings,
IS_PLAYWRIGHT_KEY, IS_PLAYWRIGHT_KEY,
PLAYWRIGHT_TEST_DIR, PLAYWRIGHT_TEST_DIR,
PERSIST_MODELING_CONTEXT,
}) => { }) => {
localStorage.clear() localStorage.clear()
localStorage.setItem('TOKEN_PERSIST_KEY', token) localStorage.setItem('TOKEN_PERSIST_KEY', token)
localStorage.setItem('persistCode', ``) localStorage.setItem('persistCode', ``)
localStorage.setItem(PERSIST_MODELING_CONTEXT, JSON.stringify({openPanes: ['code']}))
localStorage.setItem(settingsKey, settings) localStorage.setItem(settingsKey, settings)
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true') localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR) localStorage.setItem('PLAYWRIGHT_TEST_DIR', PLAYWRIGHT_TEST_DIR)
@ -891,6 +994,7 @@ export async function setup(
}), }),
IS_PLAYWRIGHT_KEY, IS_PLAYWRIGHT_KEY,
PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory, PLAYWRIGHT_TEST_DIR: TEST_SETTINGS.app.projectDirectory,
PERSIST_MODELING_CONTEXT,
} }
) )
@ -909,12 +1013,15 @@ export async function setup(
await page.emulateMedia({ reducedMotion: 'reduce' }) await page.emulateMedia({ reducedMotion: 'reduce' })
// Trigger a navigation, since loading file:// doesn't. // Trigger a navigation, since loading file:// doesn't.
await page.reload() // await page.reload()
} }
let electronApp = undefined
let context = undefined
let page = undefined
export async function setupElectron({ export async function setupElectron({
testInfo, testInfo,
folderSetupFn,
cleanProjectDir = true, cleanProjectDir = true,
appSettings, appSettings,
}: { }: {
@ -937,7 +1044,7 @@ export async function setupElectron({
await fsp.mkdir(projectDirName) await fsp.mkdir(projectDirName)
} }
const electronApp = await electron.launch({ const options = {
args: ['.', '--no-sandbox'], args: ['.', '--no-sandbox'],
env: { env: {
...process.env, ...process.env,
@ -947,15 +1054,19 @@ export async function setupElectron({
...(process.env.ELECTRON_OVERRIDE_DIST_PATH ...(process.env.ELECTRON_OVERRIDE_DIST_PATH
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' } ? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
: {}), : {}),
}) }
const context = electronApp.context() // Do this once and then reuse window on subsequent calls.
const page = await electronApp.firstWindow() if (!electronApp) {
electronApp = await electron.launch(options)
}
page.TEST_SETTINGS_FILE_KEY = projectDirName if (!context || !page) {
context = electronApp.context()
context.on('console', console.log) page = await electronApp.firstWindow()
page.on('console', console.log) context.on('console', console.log)
page.on('console', console.log)
}
if (cleanProjectDir) { if (cleanProjectDir) {
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME) const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
@ -985,11 +1096,7 @@ export async function setupElectron({
await fsp.writeFile(tempSettingsFilePath, settingsOverrides) await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
} }
await folderSetupFn?.(projectDirName) return { electronApp, page, context, dir: projectDirName, options }
await setup(context, page)
return { electronApp, page, context, dir: projectDirName }
} }
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) { function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {

View File

@ -62,7 +62,7 @@ test.describe('Testing constraints', () => {
page.getByRole('button', { name: 'Exit Sketch' }) page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible() ).toBeVisible()
await page.waitForTimeout(500) // wait for animation await page.waitForTimeout(2500) // wait for animation
// Exit sketch // Exit sketch
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10) await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
@ -668,7 +668,7 @@ test.describe('Testing constraints', () => {
}, },
] as const ] as const
for (const { testName, addVariable, value, constraint } of cases) { for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ page }) => { test(`${testName}`, async ({ context, homePage, page }) => {
// constants and locators // constants and locators
const cmdBarKclInput = page const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value') .getByTestId('cmd-bar-arg-value')
@ -698,9 +698,9 @@ part002 = startSketchOn('XZ')
) )
}) })
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await homePage.goToModelingScene()
await page.getByText('line([74.36, 130.4], %)').click() await page.getByText('line([74.36, 130.4], %)').click()
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()

View File

@ -162,91 +162,94 @@ test.describe('Testing settings', () => {
await expect(hotkey).toHaveText(text) await expect(hotkey).toHaveText(text)
}) })
test.fixme('Project and user settings can be reset', async ({ page, homePage }) => { test.fixme(
const u = await getUtils(page) 'Project and user settings can be reset',
await test.step(`Setup`, async () => { async ({ page, homePage }) => {
await page.setBodyDimensions({ width: 1200, height: 500 }) const u = await getUtils(page)
await homePage.goToModelingScene() await test.step(`Setup`, async () => {
await u.waitForPageLoad() await page.setBodyDimensions({ width: 1200, height: 500 })
await page.waitForTimeout(1000) await homePage.goToModelingScene()
}) await u.waitForPageLoad()
await page.waitForTimeout(1000)
// Selectors and constants
const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
const userSettingsTab = page.getByRole('radio', { name: 'User' })
const resetButton = (level: SettingsLevel) =>
page.getByRole('button', {
name: `Reset ${level}-level settings`,
})
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
const settingValues = {
default: '259',
user: '120',
project: '50',
}
const resetToast = (level: SettingsLevel) =>
page.getByText(`${level}-level settings were reset`)
await test.step(`Open the settings modal`, async () => {
await page.getByRole('link', { name: 'Settings' }).last().click()
await expect(
page.getByRole('heading', { name: 'Settings', exact: true })
).toBeVisible()
})
await test.step('Set up theme color', async () => {
// Verify we're looking at the project-level settings,
// and it's set to default value
await expect(projectSettingsTab).toBeChecked()
await expect(themeColorSetting).toHaveValue(settingValues.default)
// Set project-level value to 50
await themeColorSetting.fill(settingValues.project)
// Set user-level value to 120
await userSettingsTab.click()
await themeColorSetting.fill(settingValues.user)
await projectSettingsTab.click()
})
await test.step('Reset project settings', async () => {
// Click the reset settings button.
await resetButton('project').click()
await expect(resetToast('project')).toBeVisible()
await expect(resetToast('project')).not.toBeVisible()
// Verify it is now set to the inherited user value
await expect(themeColorSetting).toHaveValue(settingValues.user)
await test.step(`Check that the user settings did not change`, async () => {
await userSettingsTab.click()
await expect(themeColorSetting).toHaveValue(settingValues.user)
}) })
await test.step(`Set project-level again to test the user-level reset`, async () => { // Selectors and constants
await projectSettingsTab.click() const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
const userSettingsTab = page.getByRole('radio', { name: 'User' })
const resetButton = (level: SettingsLevel) =>
page.getByRole('button', {
name: `Reset ${level}-level settings`,
})
const themeColorSetting = page.locator('#themeColor').getByRole('slider')
const settingValues = {
default: '259',
user: '120',
project: '50',
}
const resetToast = (level: SettingsLevel) =>
page.getByText(`${level}-level settings were reset`)
await test.step(`Open the settings modal`, async () => {
await page.getByRole('link', { name: 'Settings' }).last().click()
await expect(
page.getByRole('heading', { name: 'Settings', exact: true })
).toBeVisible()
})
await test.step('Set up theme color', async () => {
// Verify we're looking at the project-level settings,
// and it's set to default value
await expect(projectSettingsTab).toBeChecked()
await expect(themeColorSetting).toHaveValue(settingValues.default)
// Set project-level value to 50
await themeColorSetting.fill(settingValues.project) await themeColorSetting.fill(settingValues.project)
// Set user-level value to 120
await userSettingsTab.click() await userSettingsTab.click()
}) await themeColorSetting.fill(settingValues.user)
})
await test.step('Reset user settings', async () => {
// Click the reset settings button.
await resetButton('user').click()
await expect(resetToast('user')).toBeVisible()
await expect(resetToast('user')).not.toBeVisible()
// Verify it is now set to the default value
await expect(themeColorSetting).toHaveValue(settingValues.default)
await test.step(`Check that the project settings did not change`, async () => {
await projectSettingsTab.click() await projectSettingsTab.click()
await expect(themeColorSetting).toHaveValue(settingValues.project)
}) })
})
}) await test.step('Reset project settings', async () => {
// Click the reset settings button.
await resetButton('project').click()
await expect(resetToast('project')).toBeVisible()
await expect(resetToast('project')).not.toBeVisible()
// Verify it is now set to the inherited user value
await expect(themeColorSetting).toHaveValue(settingValues.user)
await test.step(`Check that the user settings did not change`, async () => {
await userSettingsTab.click()
await expect(themeColorSetting).toHaveValue(settingValues.user)
})
await test.step(`Set project-level again to test the user-level reset`, async () => {
await projectSettingsTab.click()
await themeColorSetting.fill(settingValues.project)
await userSettingsTab.click()
})
})
await test.step('Reset user settings', async () => {
// Click the reset settings button.
await resetButton('user').click()
await expect(resetToast('user')).toBeVisible()
await expect(resetToast('user')).not.toBeVisible()
// Verify it is now set to the default value
await expect(themeColorSetting).toHaveValue(settingValues.default)
await test.step(`Check that the project settings did not change`, async () => {
await projectSettingsTab.click()
await expect(themeColorSetting).toHaveValue(settingValues.project)
})
})
}
)
test.fixme( test.fixme(
`Project settings override user settings on desktop`, `Project settings override user settings on desktop`,

View File

@ -66,6 +66,8 @@ type PWFunction = (
testInfo: TestInfo testInfo: TestInfo
) => void | Promise<void> ) => void | Promise<void>
let firstUrl = ''
// The below error is due to the extreme type spaghetti going on. playwright/ // The below error is due to the extreme type spaghetti going on. playwright/
// types/test.d.ts does not export 2 functions (below is one of them) but tsc // types/test.d.ts does not export 2 functions (below is one of them) but tsc
// is trying to use a interface name it can't see. // is trying to use a interface name it can't see.
@ -213,17 +215,44 @@ export const test = (
) )
} }
await tronApp.page.setBodyDimensions(tronApp.viewPortSize)
// We need to expose this in order for some tests that require folder // We need to expose this in order for some tests that require folder
// creation. Before they used to do this by their own electronSetup({...}) // creation. Before they used to do this by their own electronSetup({...})
// calls. // calls.
if (tronApp instanceof AuthenticatedTronApp) { if (tronApp instanceof AuthenticatedTronApp) {
tronApp.context.folderSetupFn = function (fn) { tronApp.context.folderSetupFn = async function (fn) {
return fn(tronApp.dir).then(() => ({ return fn(tronApp.dir)
.then(() => tronApp.page.reload())
.then(() => ({
dir: tronApp.dir, 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) => {
console.log("ABCDEFGHI", app.testProperty['TEST_SETTINGS_FILE_KEY'])
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 // tsc aint smart enough to know this'll never be undefined
// but I dont blame it, the logic to know is complex // but I dont blame it, the logic to know is complex
if (fn) { if (fn) {

View File

@ -13,7 +13,7 @@ export default defineConfig({
/* Do not retry */ /* Do not retry */
retries: 0, retries: 0,
/* Different amount of parallelism on CI and local. */ /* Different amount of parallelism on CI and local. */
workers: 1, workers: 30,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: [ reporter: [
['dot'], ['dot'],

View File

@ -391,7 +391,7 @@ const getAppFolderName = () => {
export const getAppSettingsFilePath = async () => { export const getAppSettingsFilePath = async () => {
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true' const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
const testSettingsPath = window.electron.process.env.TEST_SETTINGS_FILE_KEY const testSettingsPath = await window.electron.getAppTestProperty('TEST_SETTINGS_FILE_KEY')
const appConfig = await window.electron.getPath('appData') const appConfig = await window.electron.getPath('appData')
const fullPath = isTestEnv const fullPath = isTestEnv
? testSettingsPath ? testSettingsPath
@ -408,7 +408,7 @@ export const getAppSettingsFilePath = async () => {
} }
const getTokenFilePath = async () => { const getTokenFilePath = async () => {
const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true' const isTestEnv = window.electron.process.env.IS_PLAYWRIGHT === 'true'
const testSettingsPath = window.electron.process.env.TEST_SETTINGS_FILE_KEY const testSettingsPath = await window.electron.getAppTestProperty('TEST_SETTINGS_FILE_KEY')
const appConfig = await window.electron.getPath('appData') const appConfig = await window.electron.getPath('appData')
const fullPath = isTestEnv const fullPath = isTestEnv
? testSettingsPath ? testSettingsPath

View File

@ -18,8 +18,9 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
if (window.electron.process.env.IS_PLAYWRIGHT) { if (window.electron.process.env.IS_PLAYWRIGHT) {
// Skip file picker, save to the test dir downloads directory // Skip file picker, save to the test dir downloads directory
const testSettingsPath = await window.electron.getAppTestProperty('TEST_SETTINGS_FILE_KEY')
const downloadDir = window.electron.join( const downloadDir = window.electron.join(
window.electron.process.env.TEST_SETTINGS_FILE_KEY, testSettingsPath,
'downloads-during-playwright' 'downloads-during-playwright'
) )
await window.electron.mkdir(downloadDir, { recursive: true }) await window.electron.mkdir(downloadDir, { recursive: true })

View File

@ -61,8 +61,8 @@ if (process.defaultApp) {
// Must be done before ready event. // Must be done before ready event.
registerStartupListeners() registerStartupListeners()
const createWindow = (filePath?: string): BrowserWindow => { const createWindow = (filePath?: string, reuse?: boolean): BrowserWindow => {
const newWindow = new BrowserWindow({ const newWindow = reuse ? mainWindow : new BrowserWindow({
autoHideMenuBar: true, autoHideMenuBar: true,
show: false, show: false,
width: 1800, width: 1800,
@ -110,7 +110,9 @@ const createWindow = (filePath?: string): BrowserWindow => {
// Open the DevTools. // Open the DevTools.
// mainWindow.webContents.openDevTools() // mainWindow.webContents.openDevTools()
newWindow.show() if (!reuse) {
newWindow.show()
}
return newWindow return newWindow
} }
@ -141,6 +143,12 @@ app.resizeWindow = async (width: number, height: number) => {
return mainWindow?.setSize(width, height) return mainWindow?.setSize(width, height)
} }
app.testProperty = {}
ipcMain.handle('app.testProperty', (event, propertyName) => {
return app.testProperty[propertyName]
})
ipcMain.handle('app.resizeWindow', (event, data) => { ipcMain.handle('app.resizeWindow', (event, data) => {
return mainWindow?.setSize(data[0], data[1]) return mainWindow?.setSize(data[0], data[1])
}) })

View File

@ -31,6 +31,7 @@ const onUpdateDownloadStart = (
const onUpdateError = (callback: (value: Error) => void) => const onUpdateError = (callback: (value: Error) => void) =>
ipcRenderer.on('update-error', (_event: any, value) => callback(value)) ipcRenderer.on('update-error', (_event: any, value) => callback(value))
const appRestart = () => ipcRenderer.invoke('app.restart') const appRestart = () => ipcRenderer.invoke('app.restart')
const getAppTestProperty = (propertyName: string) => ipcRenderer.invoke('app.testProperty', propertyName)
const isMac = os.platform() === 'darwin' const isMac = os.platform() === 'darwin'
const isWindows = os.platform() === 'win32' const isWindows = os.platform() === 'win32'
@ -163,14 +164,15 @@ contextBridge.exposeInMainWorld('electron', {
isWindows, isWindows,
isLinux, isLinux,
}, },
// Use this to access dynamic properties from the node side.
// INTENDED ONLY TO BE USED FOR TESTS.
getAppTestProperty,
process: { process: {
// Setter/getter has to be created because // These are read-only over the boundary.
// these are read-only over the boundary.
env: Object.assign( env: Object.assign(
{}, {},
exposeProcessEnvs([ exposeProcessEnvs([
'NODE_ENV', 'NODE_ENV',
'TEST_SETTINGS_FILE_KEY',
'VITE_KC_API_WS_MODELING_URL', 'VITE_KC_API_WS_MODELING_URL',
'VITE_KC_API_BASE_URL', 'VITE_KC_API_BASE_URL',
'VITE_KC_SITE_BASE_URL', 'VITE_KC_SITE_BASE_URL',