import { test, expect } from '@playwright/test' import { secrets } from './secrets' import { Paths, doExport, getUtils } from './test-utils' import { Models } from '@kittycad/lib' import fsp from 'fs/promises' import { spawn } from 'child_process' import { KCL_DEFAULT_LENGTH } from 'lib/constants' import JSZip from 'jszip' import path from 'path' import { IS_PLAYWRIGHT_KEY, TEST_SETTINGS, TEST_SETTINGS_KEY, } from './storageStates' import * as TOML from '@iarna/toml' test.beforeEach(async ({ page }) => { // reducedMotion kills animations, which speeds up tests and reduces flakiness await page.emulateMedia({ reducedMotion: 'reduce' }) // set the default settings await page.addInitScript( async ({ token, settingsKey, settings, IS_PLAYWRIGHT_KEY }) => { localStorage.setItem('TOKEN_PERSIST_KEY', token) localStorage.setItem('persistCode', ``) localStorage.setItem(settingsKey, settings) localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true') }, { token: secrets.token, settingsKey: TEST_SETTINGS_KEY, settings: TOML.stringify({ settings: TEST_SETTINGS }), IS_PLAYWRIGHT_KEY: IS_PLAYWRIGHT_KEY, } ) // Make the user avatar image always 404 // so we see the fallback menu icon for all snapshot tests await page.route('https://lh3.googleusercontent.com/**', async (route) => { await route.fulfill({ status: 404, contentType: 'text/plain', body: 'Not Found!', }) }) }) test.setTimeout(60_000) test( 'exports of each format should work', { tag: '@snapshot' }, async ({ page, context }) => { // skip on macos and windows. test.skip( // eslint-disable-next-line jest/valid-title process.platform === 'darwin' || process.platform === 'win32', 'Skip on macos and windows' ) // FYI this test doesn't work with only engine running locally // And you will need to have the KittyCAD CLI installed const u = await getUtils(page) await context.addInitScript(async () => { ;(window as any).playwrightSkipFilePicker = true localStorage.setItem( 'persistCode', `const topAng = 25 const bottomAng = 35 const baseLen = 3.5 const baseHeight = 1 const totalHeightHalf = 2 const armThick = 0.5 const totalLen = 9.5 const part001 = startSketchOn('-XZ') |> startProfileAt([0, 0], %) |> yLine(baseHeight, %) |> xLine(baseLen, %) |> angledLineToY({ angle: topAng, to: totalHeightHalf, }, %, $seg04) |> xLineTo(totalLen, %, $seg03) |> yLine(-armThick, %, $seg01) |> angledLineThatIntersects({ angle: HALF_TURN, offset: -armThick, intersectTag: seg04 }, %) |> angledLineToY([segAng(seg04, %) + 180, ZERO], %) |> angledLineToY({ angle: -bottomAng, to: -totalHeightHalf - armThick, }, %, $seg02) |> xLineTo(segEndX(seg03, %) + 0, %) |> yLine(-segLen(seg01, %), %) |> angledLineThatIntersects({ angle: HALF_TURN, offset: -armThick, intersectTag: seg02 }, %) |> angledLineToY([segAng(seg02, %) + 180, -baseHeight], %) |> xLineTo(ZERO, %) |> close(%) |> extrude(4, %)` ) }) await page.setViewportSize({ width: 1200, height: 500 }) await u.waitForAuthSkipAppStart() await u.openDebugPanel() await u.expectCmdLog('[data-message-type="execution-done"]') await u.waitForCmdReceive('extrude') await page.waitForTimeout(1000) await u.clearAndCloseDebugPanel() const axisDirectionPair: Models['AxisDirectionPair_type'] = { axis: 'z', direction: 'positive', } const sysType: Models['System_type'] = { forward: axisDirectionPair, up: axisDirectionPair, } const exportLocations: Paths[] = [] // NOTE it was easiest to leverage existing types and have doExport take Models['OutputFormat_type'] as in input // just note that only `type` and `storage` are used for selecting the drop downs is the app // the rest are only there to make typescript happy exportLocations.push( await doExport( { type: 'step', coords: sysType, }, page ) ) exportLocations.push( await doExport( { type: 'ply', coords: sysType, selection: { type: 'default_scene' }, storage: 'ascii', units: 'in', }, page ) ) exportLocations.push( await doExport( { type: 'ply', storage: 'binary_little_endian', coords: sysType, selection: { type: 'default_scene' }, units: 'in', }, page ) ) exportLocations.push( await doExport( { type: 'ply', storage: 'binary_big_endian', coords: sysType, selection: { type: 'default_scene' }, units: 'in', }, page ) ) exportLocations.push( await doExport( { type: 'stl', storage: 'ascii', coords: sysType, units: 'in', selection: { type: 'default_scene' }, }, page ) ) exportLocations.push( await doExport( { type: 'stl', storage: 'binary', coords: sysType, units: 'in', selection: { type: 'default_scene' }, }, page ) ) exportLocations.push( await doExport( { // obj seems to be a little flaky, times out tests sometimes type: 'obj', coords: sysType, units: 'in', }, page ) ) exportLocations.push( await doExport( { type: 'gltf', storage: 'embedded', presentation: 'pretty', }, page ) ) exportLocations.push( await doExport( { type: 'gltf', storage: 'binary', presentation: 'pretty', }, page ) ) exportLocations.push( await doExport( { type: 'gltf', storage: 'standard', presentation: 'pretty', }, page ) ) // close page to disconnect websocket since we can only have one open atm await page.close() // snapshot exports, good compromise to capture that exports are healthy without getting bogged down in "did the formatting change" changes // context: https://github.com/KittyCAD/modeling-app/issues/1222 for (let { modelPath, imagePath, outputType } of exportLocations) { // May change depending on the file being dealt with let cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}` const fileSize = (await fsp.stat(modelPath)).size console.log(`Size of the file at ${modelPath}: ${fileSize} bytes`) const parentPath = path.dirname(modelPath) // This is actually a zip file. if (modelPath.includes('gltf-standard.gltf')) { console.log('Extracting files from archive') const readZipFile = fsp.readFile(modelPath) const unzip = (archive: any) => Object.values(archive.files).map((file: any) => ({ name: file.name, promise: file.async('nodebuffer'), })) const writeFiles = (files: any) => Promise.all( files.map((file: any) => file.promise.then((data: any) => { console.log(`Writing ${file.name}`) return fsp .writeFile(`${parentPath}/${file.name}`, data) .then(() => file.name) }) ) ) const filenames = await readZipFile .then(JSZip.loadAsync) .then(unzip) .then(writeFiles) const gltfFilename = filenames.filter((t: string) => t.includes('.gltf') )[0] if (!gltfFilename) throw new Error('No output.gltf in this archive') cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}` } console.log(cliCommand) const child = spawn(cliCommand, { shell: true }) const result = await new Promise((resolve, reject) => { child.on('error', (code: any, msg: any) => { console.log('error', code, msg) reject('error') }) child.on('exit', (code, msg) => { console.log('exit', code, msg) if (code !== 0) { reject(`exit code ${code} for model ${modelPath}`) } else { resolve('success') } }) child.stderr.on('data', (data) => console.log(`stderr: ${data}`)) child.stdout.on('data', (data) => console.log(`stdout: ${data}`)) }) expect(result).toBe('success') if (result === 'success') { console.log(`snapshot taken for ${modelPath}`) } else { console.log(`snapshot failed for ${modelPath}`) } } } ) const extrudeDefaultPlane = async (context: any, page: any, plane: string) => { await context.addInitScript(async () => { localStorage.setItem( 'SETTINGS_PERSIST_KEY', JSON.stringify({ baseUnit: 'in', cameraControls: 'KittyCAD', defaultDirectory: '', defaultProjectName: 'project-$nnn', onboardingStatus: 'dismissed', showDebugPanel: true, textWrapping: 'On', theme: 'dark', unitSystem: 'imperial', }) ) }) const code = `const part001 = startSketchOn('${plane}') |> startProfileAt([7.00, 4.40], %) |> line([6.60, -0.20], %) |> line([2.80, 5.00], %) |> line([-5.60, 4.40], %) |> line([-5.40, -3.80], %) |> close(%) |> extrude(10.00, %) ` await page.addInitScript(async (code: string) => { localStorage.setItem('persistCode', code) }) const u = await getUtils(page) await page.setViewportSize({ width: 1200, height: 500 }) await u.waitForAuthSkipAppStart() // wait for execution done await u.openDebugPanel() await u.expectCmdLog('[data-message-type="execution-done"]') await u.clearAndCloseDebugPanel() await page.waitForTimeout(200) // clear code await u.removeCurrentCode() await u.openAndClearDebugPanel() await u.doAndWaitForImageDiff( () => page.locator('.cm-content').fill(code), 200 ) // wait for execution done await u.expectCmdLog('[data-message-type="execution-done"]') await u.clearAndCloseDebugPanel() await u.closeKclCodePanel() await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) await u.openKclCodePanel() } test.describe( 'extrude on default planes should be stable', { tag: '@snapshot' }, () => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') test('XY', async ({ page, context }) => { await extrudeDefaultPlane(context, page, 'XY') }) test('XZ', async ({ page, context }) => { await extrudeDefaultPlane(context, page, 'XZ') }) test('YZ', async ({ page, context }) => { await extrudeDefaultPlane(context, page, 'YZ') }) test('-XY', async ({ page, context }) => { await extrudeDefaultPlane(context, page, '-XY') }) test('-XZ', async ({ page, context }) => { await extrudeDefaultPlane(context, page, '-XZ') }) test('-YZ', async ({ page, context }) => { await extrudeDefaultPlane(context, page, '-YZ') }) } ) test( 'Draft segments should look right', { tag: '@snapshot' }, async ({ page, context }) => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') const u = await getUtils(page) await page.setViewportSize({ width: 1200, height: 500 }) const PUR = 400 / 37.5 //pixeltoUnitRatio await u.waitForAuthSkipAppStart() await u.openDebugPanel() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).not.toBeDisabled() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).toBeVisible() // click on "Start Sketch" button await u.clearCommandLogs() await u.doAndWaitForImageDiff( () => page.getByRole('button', { name: 'Start Sketch' }).click(), 200 ) // select a plane await page.mouse.click(700, 200) let code = `const sketch001 = startSketchOn('XZ')` await expect(page.locator('.cm-content')).toHaveText(code) await page.waitForTimeout(700) // TODO detect animation ending, or disable animation const startXPx = 600 await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) code += ` |> startProfileAt([7.19, -9.7], %)` await expect(page.locator('.cm-content')).toHaveText(code) await page.waitForTimeout(100) await u.closeDebugPanel() await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10) await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.waitForTimeout(100) code += ` |> line([7.25, 0], %)` await expect(page.locator('.cm-content')).toHaveText(code) await page .getByRole('button', { name: 'arc Tangential Arc', exact: true }) .click() await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 }) await page.waitForTimeout(300) await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) } ) test( 'Draft rectangles should look right', { tag: '@snapshot' }, async ({ page, context }) => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') const u = await getUtils(page) await page.setViewportSize({ width: 1200, height: 500 }) const PUR = 400 / 37.5 //pixeltoUnitRatio await u.waitForAuthSkipAppStart() await u.openDebugPanel() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).not.toBeDisabled() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).toBeVisible() // click on "Start Sketch" button await u.clearCommandLogs() await u.doAndWaitForImageDiff( () => page.getByRole('button', { name: 'Start Sketch' }).click(), 200 ) // select a plane await page.mouse.click(700, 200) await expect(page.locator('.cm-content')).toHaveText( `const sketch001 = startSketchOn('XZ')` ) await page.waitForTimeout(500) // TODO detect animation ending, or disable animation await u.closeDebugPanel() const startXPx = 600 // Equip the rectangle tool await page.getByRole('button', { name: 'line Line', exact: true }).click() await page .getByRole('button', { name: 'rectangle Corner rectangle', exact: true }) .click() // Draw the rectangle await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30) await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 }) // Ensure the draft rectangle looks the same as it usually does await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) } ) test.describe( 'Client side scene scale should match engine scale', { tag: '@snapshot' }, () => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') test('Inch scale', async ({ page }) => { const u = await getUtils(page) await page.setViewportSize({ width: 1200, height: 500 }) const PUR = 400 / 37.5 //pixeltoUnitRatio await u.waitForAuthSkipAppStart() await u.openDebugPanel() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).not.toBeDisabled() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).toBeVisible() // click on "Start Sketch" button await u.clearCommandLogs() await u.doAndWaitForImageDiff( () => page.getByRole('button', { name: 'Start Sketch' }).click(), 200 ) // select a plane await page.mouse.click(700, 200) let code = `const sketch001 = startSketchOn('XZ')` await expect(page.locator('.cm-content')).toHaveText(code) await page.waitForTimeout(600) // TODO detect animation ending, or disable animation const startXPx = 600 await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) code += ` |> startProfileAt([7.19, -9.7], %)` await expect(u.codeLocator).toHaveText(code) await page.waitForTimeout(100) await u.closeDebugPanel() await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.waitForTimeout(100) code += ` |> line([7.25, 0], %)` await expect(u.codeLocator).toHaveText(code) await page .getByRole('button', { name: 'arc Tangential Arc', exact: true }) .click() await page.waitForTimeout(100) await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) code += ` |> tangentialArcTo([21.7, -2.44], %)` await expect(u.codeLocator).toHaveText(code) // click tangential arc tool again to unequip it await page .getByRole('button', { name: 'arc Tangential Arc', exact: true }) .click() await page.waitForTimeout(100) // screen shot should show the sketch await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) // exit sketch await u.openAndClearDebugPanel() await u.doAndWaitForImageDiff( () => page.getByRole('button', { name: 'Exit Sketch' }).click(), 200 ) // wait for execution done await u.expectCmdLog('[data-message-type="execution-done"]') await u.clearAndCloseDebugPanel() await page.waitForTimeout(300) // second screen shot should look almost identical, i.e. scale should be the same. await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) }) test('Millimeter scale', async ({ page }) => { await page.addInitScript( async ({ settingsKey, settings }) => { localStorage.setItem(settingsKey, settings) }, { settingsKey: TEST_SETTINGS_KEY, settings: TOML.stringify({ settings: { ...TEST_SETTINGS, modeling: { ...TEST_SETTINGS.modeling, defaultUnit: 'mm', }, }, }), } ) const u = await getUtils(page) await page.setViewportSize({ width: 1200, height: 500 }) const PUR = 400 / 37.5 //pixeltoUnitRatio await u.waitForAuthSkipAppStart() await u.openDebugPanel() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).not.toBeDisabled() await expect( page.getByRole('button', { name: 'Start Sketch' }) ).toBeVisible() // click on "Start Sketch" button await u.clearCommandLogs() await u.doAndWaitForImageDiff( () => page.getByRole('button', { name: 'Start Sketch' }).click(), 200 ) // select a plane await page.mouse.click(700, 200) let code = `const sketch001 = startSketchOn('XZ')` await expect(u.codeLocator).toHaveText(code) await page.waitForTimeout(600) // TODO detect animation ending, or disable animation const startXPx = 600 await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) code += ` |> startProfileAt([182.59, -246.32], %)` await expect(u.codeLocator).toHaveText(code) await page.waitForTimeout(100) await u.closeDebugPanel() await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) await page.waitForTimeout(100) code += ` |> line([184.3, 0], %)` await expect(u.codeLocator).toHaveText(code) await page .getByRole('button', { name: 'arc Tangential Arc', exact: true }) .click() await page.waitForTimeout(100) await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20) code += ` |> tangentialArcTo([551.2, -62.01], %)` await expect(u.codeLocator).toHaveText(code) await page .getByRole('button', { name: 'arc Tangential Arc', exact: true }) .click() await page.waitForTimeout(100) // screen shot should show the sketch await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) // exit sketch await u.openAndClearDebugPanel() await u.doAndWaitForImageDiff( () => page.getByRole('button', { name: 'Exit Sketch' }).click(), 200 ) // wait for execution done await u.expectCmdLog('[data-message-type="execution-done"]') await u.clearAndCloseDebugPanel() await page.waitForTimeout(300) // second screen shot should look almost identical, i.e. scale should be the same. await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) }) } ) test( 'Sketch on face with none z-up', { tag: '@snapshot' }, async ({ page, context }) => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') const u = await getUtils(page) await context.addInitScript(async (KCL_DEFAULT_LENGTH) => { localStorage.setItem( 'persistCode', `const part001 = startSketchOn('-XZ') |> startProfileAt([1.4, 2.47], %) |> line([9.31, 10.55], %, $seg01) |> line([11.91, -10.42], %) |> close(%) |> extrude(${KCL_DEFAULT_LENGTH}, %) const part002 = startSketchOn(part001, seg01) |> startProfileAt([8, 8], %) |> line([4.68, 3.05], %) |> line([0, -7.79], %) |> close(%) |> extrude(${KCL_DEFAULT_LENGTH}, %) ` ) }, KCL_DEFAULT_LENGTH) await page.setViewportSize({ width: 1200, height: 500 }) await u.waitForAuthSkipAppStart() await u.openDebugPanel() // wait for execution done await expect( page.locator('[data-message-type="execution-done"]') ).toHaveCount(1, { timeout: 10_000 }) await u.closeDebugPanel() // Wait for the second extrusion to appear // TODO: Find a way to truly know that the objects have finished // rendering, because an execution-done message is not sufficient. await page.waitForTimeout(1000) await expect( page.getByRole('button', { name: 'Start Sketch' }) ).not.toBeDisabled() await page.getByRole('button', { name: 'Start Sketch' }).click() let previousCodeContent = await page.locator('.cm-content').innerText() // click at 641, 135 await page.mouse.click(641, 135) await expect(page.locator('.cm-content')).not.toHaveText( previousCodeContent ) previousCodeContent = await page.locator('.cm-content').innerText() await page.waitForTimeout(300) await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) } ) test( 'Zoom to fit on load - solid 2d', { tag: '@snapshot' }, async ({ page, context }) => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') const u = await getUtils(page) await context.addInitScript(async () => { localStorage.setItem( 'persistCode', `const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> line([-20, 0], %) |> close(%) ` ) }, KCL_DEFAULT_LENGTH) await page.setViewportSize({ width: 1200, height: 500 }) await u.waitForAuthSkipAppStart() await u.openDebugPanel() // wait for execution done await expect( page.locator('[data-message-type="execution-done"]') ).toHaveCount(1) await u.closeDebugPanel() // Wait for the second extrusion to appear // TODO: Find a way to truly know that the objects have finished // rendering, because an execution-done message is not sufficient. await page.waitForTimeout(1000) await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) } ) test( 'Zoom to fit on load - solid 3d', { tag: '@snapshot' }, async ({ page, context }) => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') const u = await getUtils(page) await context.addInitScript(async () => { localStorage.setItem( 'persistCode', `const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> line([-20, 0], %) |> close(%) |> extrude(10, %) ` ) }, KCL_DEFAULT_LENGTH) await page.setViewportSize({ width: 1200, height: 500 }) await u.waitForAuthSkipAppStart() await u.openDebugPanel() // wait for execution done await expect( page.locator('[data-message-type="execution-done"]') ).toHaveCount(1) await u.closeDebugPanel() // Wait for the second extrusion to appear // TODO: Find a way to truly know that the objects have finished // rendering, because an execution-done message is not sufficient. await page.waitForTimeout(1000) await expect(page).toHaveScreenshot({ maxDiffPixels: 100, }) } ) test.describe('Grid visibility', { tag: '@snapshot' }, () => { // FIXME: Skip on macos its being weird. test.skip(process.platform === 'darwin', 'Skip on macos') test('Grid turned off', async ({ page }) => { const u = await getUtils(page) const stream = page.getByTestId('stream') const mask = [ page.locator('#app-header'), page.locator('#sidebar-top-ribbon'), page.locator('#sidebar-bottom-ribbon'), ] await page.setViewportSize({ width: 1200, height: 500 }) await page.goto('/') await u.waitForAuthSkipAppStart() await u.openDebugPanel() // wait for execution done await expect( page.locator('[data-message-type="execution-done"]') ).toHaveCount(1) await u.closeDebugPanel() await u.closeKclCodePanel() // TODO: Find a way to truly know that the objects have finished // rendering, because an execution-done message is not sufficient. await page.waitForTimeout(1000) await expect(stream).toHaveScreenshot({ maxDiffPixels: 100, mask, }) }) test('Grid turned on', async ({ page }) => { await page.addInitScript( async ({ settingsKey, settings }) => { localStorage.setItem(settingsKey, settings) }, { settingsKey: TEST_SETTINGS_KEY, settings: TOML.stringify({ settings: { ...TEST_SETTINGS, modeling: { ...TEST_SETTINGS.modeling, showScaleGrid: true, }, }, }), } ) const u = await getUtils(page) const stream = page.getByTestId('stream') const mask = [ page.locator('#app-header'), page.locator('#sidebar-top-ribbon'), page.locator('#sidebar-bottom-ribbon'), ] await page.setViewportSize({ width: 1200, height: 500 }) await page.goto('/') await u.waitForAuthSkipAppStart() await u.openDebugPanel() // wait for execution done await expect( page.locator('[data-message-type="execution-done"]') ).toHaveCount(1) await u.closeDebugPanel() await u.closeKclCodePanel() // TODO: Find a way to truly know that the objects have finished // rendering, because an execution-done message is not sufficient. await page.waitForTimeout(1000) await expect(stream).toHaveScreenshot({ maxDiffPixels: 100, mask, }) }) }) test('theme persists', async ({ page, context }) => { const u = await getUtils(page) await context.addInitScript(async () => { localStorage.setItem( 'persistCode', `const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> line([-20, 0], %) |> close(%) |> extrude(10, %) ` ) }, KCL_DEFAULT_LENGTH) await page.setViewportSize({ width: 1200, height: 500 }) await u.waitForAuthSkipAppStart() await page.waitForTimeout(500) // await page.getByRole('link', { name: 'Settings Settings (tooltip)' }).click() await expect(page.getByTestId('settings-link')).toBeVisible() await page.getByTestId('settings-link').click() // open user settingns await page.getByRole('radio', { name: 'person User' }).click() await page.getByTestId('app-theme').selectOption('light') await page.getByTestId('settings-close-button').click() const networkToggle = page.getByTestId('network-toggle') // simulate network down await u.emulateNetworkConditions({ offline: true, // values of 0 remove any active throttling. crbug.com/456324#c9 latency: 0, downloadThroughput: -1, uploadThroughput: -1, }) // Disconnect and reconnect to check the theme persists through a reload // Expect the network to be down await expect(networkToggle).toContainText('Offline') // simulate network up await u.emulateNetworkConditions({ offline: false, // values of 0 remove any active throttling. crbug.com/456324#c9 latency: 0, downloadThroughput: -1, uploadThroughput: -1, }) await expect(networkToggle).toContainText('Connected') await expect(page.getByText('building scene')).not.toBeVisible() await expect(page, 'expect screenshot to have light theme').toHaveScreenshot({ maxDiffPixels: 100, }) })