diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 45b27b43c..f6b8f6d3e 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -30,6 +30,10 @@ import { isErrorWhitelisted } from './lib/console-error-whitelist' import { isArray } from 'lib/utils' import { reportRejection } from 'lib/trap' +const toNormalizedCode = (text: string) => { + return text.replace(/\s+/g, '') +} + type TestColor = [number, number, number] export const TEST_COLORS = { WHITE: [249, 249, 249] as TestColor, @@ -505,13 +509,16 @@ export async function getUtils(page: Page, test_?: typeof test) { ) }, - toNormalizedCode: (text: string) => { - return text.replace(/\s+/g, '') + toNormalizedCode(text: string) { + return toNormalizedCode(text) }, - editorTextMatches: async (code: string) => { + async editorTextMatches(code: string) { const editor = page.locator(editorSelector) - return expect.poll(() => editor.textContent()).toContain(code) + return expect.poll(async () => { + const text = await editor.textContent() + return toNormalizedCode(text) + }).toContain(toNormalizedCode(code)) }, pasteCodeInEditor: async (code: string) => { diff --git a/e2e/playwright/testing-settings.spec.ts b/e2e/playwright/testing-settings.spec.ts index a3ab72944..3c5783693 100644 --- a/e2e/playwright/testing-settings.spec.ts +++ b/e2e/playwright/testing-settings.spec.ts @@ -1,11 +1,8 @@ -import { test, expect } from '@playwright/test' +import { test, expect } from './zoo-test' import * as fsp from 'fs/promises' import { join } from 'path' import { getUtils, - setup, - setupElectron, - tearDown, executorInputPath, createProject, } from './test-utils' @@ -19,271 +16,245 @@ import { } from './storageStates' import * as TOML from '@iarna/toml' -test.beforeEach(async ({ context, page }, testInfo) => { - await setup(context, page, testInfo) -}) - -test.afterEach(async ({ page }, testInfo) => { - await tearDown(page, testInfo) -}) - test.describe('Testing settings', () => { - test('Stored settings are validated and fall back to defaults', async ({ - page, - }) => { - const u = await getUtils(page) - - // Override beforeEach test setup - // with corrupted settings - await page.addInitScript( - async ({ settingsKey, settings }) => { - localStorage.setItem(settingsKey, settings) - }, - { - settingsKey: TEST_SETTINGS_KEY, - settings: TOML.stringify({ settings: TEST_SETTINGS_CORRUPTED }), - } + test('Stored settings are validated and fall back to defaults', + // Override beforeEach test setup + // with corrupted settings + { + appSettings: TEST_SETTINGS_CORRUPTED + }, + async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1200, height: 500 }) + + // Check the settings were reset + const storedSettings = TOML.parse( + await page.evaluate( + ({ settingsKey }) => localStorage.getItem(settingsKey) || '', + { settingsKey: TEST_SETTINGS_KEY } ) + ) as { settings: SaveSettingsPayload } + + expect(storedSettings.settings?.app?.theme).toBe('dark') + + // Check that the invalid settings were changed to good defaults + expect(storedSettings.settings?.modeling?.defaultUnit).toBe("in") + expect(storedSettings.settings?.modeling?.mouseControls).toBe("KittyCAD") + expect(storedSettings.settings?.app?.projectDirectory).toBe("") + expect(storedSettings.settings?.projects?.defaultProjectName).toBe( + "project-$nnn" + ) }) - await page.setViewportSize({ width: 1200, height: 500 }) - - await u.waitForAuthSkipAppStart() - - // Check the settings were reset - const storedSettings = TOML.parse( - await page.evaluate( - ({ settingsKey }) => localStorage.getItem(settingsKey) || '', - { settingsKey: TEST_SETTINGS_KEY } - ) - ) as { settings: SaveSettingsPayload } - - expect(storedSettings.settings?.app?.theme).toBe(undefined) - - // Check that the invalid settings were removed - expect(storedSettings.settings?.modeling?.defaultUnit).toBe(undefined) - expect(storedSettings.settings?.modeling?.mouseControls).toBe(undefined) - expect(storedSettings.settings?.app?.projectDirectory).toBe(undefined) - expect(storedSettings.settings?.projects?.defaultProjectName).toBe( - undefined - ) - }) - - test('Project settings can be set and override user settings', async ({ - page, - }) => { - const u = await getUtils(page) - await test.step(`Setup`, async () => { - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - await page - .getByRole('button', { name: 'Start Sketch' }) - .waitFor({ state: 'visible' }) - }) - - // Selectors and constants - const paneButtonLocator = page.getByTestId('debug-pane-button') - const headingLocator = page.getByRole('heading', { - name: 'Settings', - exact: true, - }) - const inputLocator = page.locator('input[name="modeling-showDebugPanel"]') - - await test.step('Open settings dialog and set "Show debug panel" to on', async () => { - await page.keyboard.press('ControlOrMeta+Shift+,') - await expect(headingLocator).toBeVisible() - - /** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */ - await test.step(`Confirm that this dialog has a solid background`, async () => { - await expect - .poll(() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), { - timeout: 1000, - message: - 'Checking for solid background, should not see default plane colors', - }) - .toBeLessThan(15) - }) - - await page.locator('#showDebugPanel').getByText('OffOn').click() - }) - - // Close it and open again with keyboard shortcut, while KCL editor is focused - // Put the cursor in the editor - await test.step('Open settings with keyboard shortcut', async () => { - await page.getByTestId('settings-close-button').click() - await page.locator('.cm-content').click() - await page.keyboard.press('ControlOrMeta+Shift+,') - await expect(headingLocator).toBeVisible() - }) - - // Verify the toast appeared - await expect( - page.getByText(`Set show debug panel to "false" for this project`) - ).toBeVisible() - // Check that the theme changed - await expect(paneButtonLocator).not.toBeVisible() - - // Check that the user setting was not changed - await page.getByRole('radio', { name: 'User' }).click() - await expect(inputLocator).toBeChecked() - - // Roll back to default of "off" - await await page - .getByText('show debug panelRoll back show debug panelRoll back to match') - .hover() + // The behavior is actually broken. Parent always takes precedence + test.fixme('Project settings can be set and override user settings', async ({ page, homePage }) => { const u = await getUtils(page) + await test.step(`Setup`, async () => { + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() await page - .getByRole('button', { - name: 'Roll back show debug panel', - }) - .click() - await expect(inputLocator).not.toBeChecked() + .getByRole('button', { name: 'Start Sketch' }) + .waitFor({ state: 'visible' }) + }) + + // Selectors and constants + const paneButtonLocator = page.getByTestId('debug-pane-button') + const headingLocator = page.getByRole('heading', { + name: 'Settings', + exact: true, + }) + const inputLocator = page.locator('input[name="modeling-showDebugPanel"]') + + await test.step('Open settings dialog and set "Show debug panel" to on', async () => { + await page.keyboard.press('ControlOrMeta+,') + await expect(headingLocator).toBeVisible() + + /** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */ + await test.step(`Confirm that this dialog has a solid background`, async () => { + await expect + .poll(() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), { + timeout: 1000, + message: + 'Checking for solid background, should not see default plane colors', + }) + .toBeLessThan(15) + }) + + await page.locator('#showDebugPanel').getByText('OffOn').click() + }) + + // Close it and open again with keyboard shortcut, while KCL editor is focused + // Put the cursor in the editor + await test.step('Open settings with keyboard shortcut', async () => { + await page.getByTestId('settings-close-button').click() + await page.locator('.cm-content').click() + await page.keyboard.press('ControlOrMeta+,') + await expect(headingLocator).toBeVisible() + }) + + // Verify the toast appeared + await expect( + page.getByText(`Set show debug panel to "false" for this project`) + ).toBeVisible() + await expect( + page.getByText(`Set show debug panel to "false" for this project`) + ).not.toBeVisible() - // Check that the project setting did not change - await page.getByRole('radio', { name: 'Project' }).click() + // Check that the debug panel button is gone + await expect(paneButtonLocator).not.toBeVisible() + + // Check that the user setting was not changed + await page.getByRole('radio', { name: 'User' }).click() + await expect(inputLocator).toBeChecked() + + // Roll back to default of "off" + await await page + .getByText('show debug panelRoll back show debug panelRoll back to match') + .hover() + await page + .getByRole('button', { + name: 'Roll back show debug panel', + }) + .click() + await expect(inputLocator).not.toBeChecked() + + // Check that the project setting did not change + await page.getByRole('radio', { name: 'Project' }).click() + await expect( + page.locator('input[name="modeling-showDebugPanel"]') + ).not.toBeChecked() }) + + test('Keybindings display the correct hotkey for Command Palette', async ({ page, homePage }) => { + const u = await getUtils(page) + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + await u.waitForPageLoad() + + await test.step('Open keybindings settings', async () => { + // Open the settings modal with the keyboard shortcut + await page.keyboard.press('ControlOrMeta+,') + + // Go to Keybindings tab. + const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' }) + await keybindingsTab.click() + }) + + // Go to the hotkey for Command Palette. + const commandPalette = page.getByText('Toggle Command Palette') + await commandPalette.scrollIntoViewIfNeeded() + + // The heading is above it and should be in view now. + const commandPaletteHeading = page.getByRole('heading', { + name: 'Command Palette', + }) + // The hotkey is in a kbd element next to the heading. + const hotkey = commandPaletteHeading.locator('+ div kbd') + const text = process.platform === 'darwin' ? 'Command+K' : 'Control+K' + await expect(hotkey).toHaveText(text) }) + + test('Project and user settings can be reset', async ({ page, homePage }) => { const u = await getUtils(page) + await test.step(`Setup`, async () => { + await page.setBodyDimensions({ width: 1200, height: 500 }) + 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.locator('input[name="modeling-showDebugPanel"]') - ).not.toBeChecked() + page.getByRole('heading', { name: 'Settings', exact: true }) + ).toBeVisible() }) - - test('Keybindings display the correct hotkey for Command Palette', async ({ - page, - }) => { - const u = await getUtils(page) - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - - await test.step('Open keybindings settings', async () => { - // Open the settings modal with the browser keyboard shortcut - await page.keyboard.press('ControlOrMeta+Shift+,') - - // Go to Keybindings tab. - const keybindingsTab = page.getByRole('radio', { name: 'Keybindings' }) - await keybindingsTab.click() - }) - - // Go to the hotkey for Command Palette. - const commandPalette = page.getByText('Toggle Command Palette') - await commandPalette.scrollIntoViewIfNeeded() - - // The heading is above it and should be in view now. - const commandPaletteHeading = page.getByRole('heading', { - name: 'Command Palette', - }) - // The hotkey is in a kbd element next to the heading. - const hotkey = commandPaletteHeading.locator('+ div kbd') - const text = process.platform === 'darwin' ? 'Command+K' : 'Control+K' - await expect(hotkey).toHaveText(text) + + 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() }) - - test('Project and user settings can be reset', async ({ page }) => { - const u = await getUtils(page) - await test.step(`Setup`, async () => { - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - }) - - // 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 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 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 () => { - 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) - }) + + 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( `Project settings override user settings on desktop`, { tag: ['@electron', '@skipWin'] }, - async ({ browser: _ }, testInfo) => { + async ({ context, page, browser: _ }, testInfo) => { test.skip( process.platform === 'win32', 'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557' ) const projectName = 'bracket' const { - electronApp, - page, dir: projectDirName, - } = await setupElectron({ - testInfo, - folderSetupFn: async (dir) => { - const bracketDir = join(dir, projectName) - await fsp.mkdir(bracketDir, { recursive: true }) - await fsp.copyFile( - executorInputPath('focusrite_scarlett_mounting_braket.kcl'), - join(bracketDir, 'main.kcl') - ) - }, + } = await context.folderSetupFn(async (dir) => { + const bracketDir = join(dir, projectName) + await fsp.mkdir(bracketDir, { recursive: true }) + await fsp.copyFile( + executorInputPath('focusrite_scarlett_mounting_braket.kcl'), + join(bracketDir, 'main.kcl') + ) }) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) // Selectors and constants const tempProjectSettingsFilePath = join( @@ -354,21 +325,18 @@ test.describe('Testing settings', () => { await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) }) - await electronApp.close() } ) test( `Load desktop app with no settings file`, - { tag: '@electron' }, - async ({ browser: _ }, testInfo) => { - const { electronApp, page } = await setupElectron({ - // This is what makes no settings file get created - cleanProjectDir: false, - testInfo, - }) - - await page.setViewportSize({ width: 1200, height: 500 }) + { + tag: '@electron', + // This is what makes no settings file get created + cleanProjectDir: false, + }, + async ({ page, browser: _ }, testInfo) => { + await page.setBodyDimensions({ width: 1200, height: 500 }) // Selectors and constants const errorHeading = page.getByRole('heading', { @@ -380,24 +348,21 @@ test.describe('Testing settings', () => { await expect(errorHeading).not.toBeVisible() await expect(projectDirLink).toBeVisible() - await electronApp.close() } ) test( `Load desktop app with a settings file, but no project directory setting`, - { tag: '@electron' }, - async ({ browser: _ }, testInfo) => { - const { electronApp, page } = await setupElectron({ - testInfo, - appSettings: { - app: { - themeColor: '259', - }, + { + tag: '@electron', + appSettings: { + app: { + themeColor: '259', }, - }) - - await page.setViewportSize({ width: 1200, height: 500 }) + }, + }, + async ({ context, page, browser: _ }, testInfo) => { + await page.setBodyDimensions({ width: 1200, height: 500 }) // Selectors and constants const errorHeading = page.getByRole('heading', { @@ -408,32 +373,26 @@ test.describe('Testing settings', () => { // If the app loads without exploding we're in the clear await expect(errorHeading).not.toBeVisible() await expect(projectDirLink).toBeVisible() - - await electronApp.close() } ) // It was much easier to test the logo color than the background stream color. test.fixme( 'user settings reload on external change, on project and modeling view', - { tag: '@electron' }, - async ({ browserName }, testInfo) => { - const { - electronApp, - page, - dir: projectDirName, - } = await setupElectron({ - testInfo, - appSettings: { - app: { - // Doesn't matter what you set it to. It will - // default to 264.5 - themeColor: '0', - }, + { + tag: '@electron', + appSettings: { + app: { + // Doesn't matter what you set it to. It will + // default to 264.5 + themeColor: '0', }, - }) + } + }, + async ({ context, page, browserName }, testInfo) => { + const { dir: projectDirName } = await context.folderSetupFn(async () => {}) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) const logoLink = page.getByTestId('app-logo') const projectDirLink = page.getByText('Loaded from') @@ -467,23 +426,16 @@ test.describe('Testing settings', () => { await changeColor('21') await expect(logoLink).toHaveCSS('--primary-hue', '21') }) - await electronApp.close() } ) test( 'project settings reload on external change', { tag: '@electron' }, - async ({ browserName: _ }, testInfo) => { - const { - electronApp, - page, - dir: projectDirName, - } = await setupElectron({ - testInfo, - }) + async ({ context, page, browserName: _ }, testInfo) => { + const { dir: projectDirName } = await context.folderSetupFn(async () => {}) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) const logoLink = page.getByTestId('app-logo') const projectDirLink = page.getByText('Loaded from') @@ -515,28 +467,24 @@ test.describe('Testing settings', () => { await expect(logoLink).toHaveCSS('--primary-hue', '99') }) - await electronApp.close() } ) test( `Closing settings modal should go back to the original file being viewed`, { tag: '@electron' }, - async ({ browser: _ }, testInfo) => { - const { electronApp, page } = await setupElectron({ - testInfo, - folderSetupFn: async (dir) => { - const bracketDir = join(dir, 'project-000') - await fsp.mkdir(bracketDir, { recursive: true }) - await fsp.copyFile( - executorInputPath('cube.kcl'), - join(bracketDir, 'main.kcl') - ) - await fsp.copyFile( - executorInputPath('cylinder.kcl'), - join(bracketDir, '2.kcl') - ) - }, + async ({ context, page, browser: _ }, testInfo) => { + await context.folderSetupFn(async (dir) => { + const bracketDir = join(dir, 'project-000') + await fsp.mkdir(bracketDir, { recursive: true }) + await fsp.copyFile( + executorInputPath('cube.kcl'), + join(bracketDir, 'main.kcl') + ) + await fsp.copyFile( + executorInputPath('cylinder.kcl'), + join(bracketDir, '2.kcl') + ) }) const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8') const kclCylinder = await fsp.readFile( @@ -552,7 +500,7 @@ test.describe('Testing settings', () => { editorTextMatches, } = await getUtils(page, test) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) page.on('console', console.log) await test.step('Precondition: Open to second project file', async () => { @@ -583,344 +531,336 @@ test.describe('Testing settings', () => { await test.step('Postcondition: Same file content is in editor as before settings opened', async () => { await editorTextMatches(kclCylinder) }) - - await electronApp.close() } ) - test('Changing modeling default unit', async ({ page }) => { - const u = await getUtils(page) - await test.step(`Test setup`, async () => { - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - await page - .getByRole('button', { name: 'Start Sketch' }) - .waitFor({ state: 'visible' }) - }) - - // Selectors and constants - const userSettingsTab = page.getByRole('radio', { name: 'User' }) - const projectSettingsTab = page.getByRole('radio', { name: 'Project' }) - const defaultUnitSection = page.getByText( - 'default unitRoll back default unitRoll back to match' + test('Changing modeling default unit', async ({ page, homePage }) => { const u = await getUtils(page) + await test.step(`Test setup`, async () => { + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + const toastMessage = page.getByText( + `Successfully created "testDefault"` ) - const defaultUnitRollbackButton = page.getByRole('button', { - name: 'Roll back default unit', - }) - - 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(`Reset unit setting`, async () => { - await userSettingsTab.click() - await defaultUnitSection.hover() - await defaultUnitRollbackButton.click() - await projectSettingsTab.click() - }) - - await test.step('Change modeling default unit within project tab', async () => { - const changeUnitOfMeasureInProjectTab = async (unitOfMeasure: string) => { - await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => { - await page - .getByTestId('modeling-defaultUnit') - .selectOption(`${unitOfMeasure}`) - const toastMessage = page.getByText( - `Set default unit to "${unitOfMeasure}" for this project` - ) - await expect(toastMessage).toBeVisible() - }) - } - await changeUnitOfMeasureInProjectTab('in') - await changeUnitOfMeasureInProjectTab('ft') - await changeUnitOfMeasureInProjectTab('yd') - await changeUnitOfMeasureInProjectTab('mm') - await changeUnitOfMeasureInProjectTab('cm') - await changeUnitOfMeasureInProjectTab('m') - }) - - // Go to the user tab + await expect(toastMessage).not.toBeVisible() + await page + .getByRole('button', { name: 'Start Sketch' }) + .waitFor({ state: 'visible' }) + }) + + // Selectors and constants + const userSettingsTab = page.getByRole('radio', { name: 'User' }) + const projectSettingsTab = page.getByRole('radio', { name: 'Project' }) + const defaultUnitSection = page.getByText( + 'default unitRoll back default unitRoll back to match' + ) + const defaultUnitRollbackButton = page.getByRole('button', { + name: 'Roll back default unit', + }) + + 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(`Reset unit setting`, async () => { await userSettingsTab.click() - await test.step('Change modeling default unit within user tab', async () => { - const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => { - await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => { - await page - .getByTestId('modeling-defaultUnit') - .selectOption(`${unitOfMeasure}`) - const toastMessage = page.getByText( - `Set default unit to "${unitOfMeasure}" as a user default` - ) - await expect(toastMessage).toBeVisible() - }) - } - await changeUnitOfMeasureInUserTab('in') - await changeUnitOfMeasureInUserTab('ft') - await changeUnitOfMeasureInUserTab('yd') - await changeUnitOfMeasureInUserTab('mm') - await changeUnitOfMeasureInUserTab('cm') - await changeUnitOfMeasureInUserTab('m') - }) - - // Close settings - const settingsCloseButton = page.getByTestId('settings-close-button') - await settingsCloseButton.click() - - await test.step('Change modeling default unit within command bar', async () => { - const commands = page.getByRole('button', { name: 'Commands' }) - const changeUnitOfMeasureInCommandBar = async (unitOfMeasure: string) => { - // Open command bar - await commands.click() - const settingsModelingDefaultUnitCommand = page.getByText( - 'Settings · modeling · default unit' - ) - await settingsModelingDefaultUnitCommand.click() - - const commandOption = page.getByRole('option', { - name: unitOfMeasure, - exact: true, - }) - await commandOption.click() - + await defaultUnitSection.hover() + await defaultUnitRollbackButton.click() + await projectSettingsTab.hover() + await projectSettingsTab.click() + await page.waitForTimeout(1000) + }) + + await test.step('Change modeling default unit within project tab', async () => { + const changeUnitOfMeasureInProjectTab = async (unitOfMeasure: string) => { + await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => { + await page + .getByTestId('modeling-defaultUnit') + .selectOption(`${unitOfMeasure}`) const toastMessage = page.getByText( `Set default unit to "${unitOfMeasure}" for this project` ) + + // Assert visibility and disapperance await expect(toastMessage).toBeVisible() - } - await changeUnitOfMeasureInCommandBar('in') - await changeUnitOfMeasureInCommandBar('ft') - await changeUnitOfMeasureInCommandBar('yd') - await changeUnitOfMeasureInCommandBar('mm') - await changeUnitOfMeasureInCommandBar('cm') - await changeUnitOfMeasureInCommandBar('m') - }) - - await test.step('Change modeling default unit within gizmo', async () => { - const changeUnitOfMeasureInGizmo = async ( - unitOfMeasure: string, - copy: string - ) => { - const gizmo = page.getByRole('button', { - name: 'Current units are: ', - }) - await gizmo.click() - const button = page.getByRole('button', { - name: copy, - exact: true, - }) - await button.click() - const toastMessage = page.getByText( - `Set default unit to "${unitOfMeasure}" for this project` - ) - await expect(toastMessage).toBeVisible() - } - - await changeUnitOfMeasureInGizmo('in', 'Inches') - await changeUnitOfMeasureInGizmo('ft', 'Feet') - await changeUnitOfMeasureInGizmo('yd', 'Yards') - await changeUnitOfMeasureInGizmo('mm', 'Millimeters') - await changeUnitOfMeasureInGizmo('cm', 'Centimeters') - await changeUnitOfMeasureInGizmo('m', 'Meters') - }) - }) - - test('Changing theme in sketch mode', async ({ page }) => { - const u = await getUtils(page) - await page.addInitScript(() => { - localStorage.setItem( - 'persistCode', - `sketch001 = startSketchOn('XZ') - |> startProfileAt([0, 0], %) - |> line([5, 0], %) - |> line([0, 5], %) - |> line([-5, 0], %) - |> lineTo([profileStartX(%), profileStartY(%)], %) - |> close(%) -extrude001 = extrude(5, sketch001) -` - ) - }) - await page.setViewportSize({ width: 1200, height: 500 }) - - // Selectors and constants - const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' }) - const lineToolButton = page.getByTestId('line') - const segmentOverlays = page.getByTestId('segment-overlay') - const sketchOriginLocation = { x: 600, y: 250 } - const darkThemeSegmentColor: [number, number, number] = [215, 215, 215] - const lightThemeSegmentColor: [number, number, number] = [90, 90, 90] - - await test.step(`Get into sketch mode`, async () => { - await u.waitForAuthSkipAppStart() - await page.mouse.click(700, 200) - await expect(editSketchButton).toBeVisible() - await editSketchButton.click() - - // We use the line tool as a proxy for sketch mode - await expect(lineToolButton).toBeVisible() - await expect(segmentOverlays).toHaveCount(4) - // but we allow more time to pass for animating to the sketch - await page.waitForTimeout(1000) - }) - - await test.step(`Check the sketch line color before`, async () => { - await expect - .poll(() => - u.getGreatestPixDiff(sketchOriginLocation, darkThemeSegmentColor) - ) - .toBeLessThan(15) - }) - - await test.step(`Change theme to light using command palette`, async () => { - await page.keyboard.press('ControlOrMeta+K') - await page.getByRole('option', { name: 'theme' }).click() - await page.getByRole('option', { name: 'light' }).click() - await expect(page.getByText('theme to "light"')).toBeVisible() - - // Make sure we haven't left sketch mode - await expect(lineToolButton).toBeVisible() - }) - - await test.step(`Check the sketch line color after`, async () => { - await expect - .poll(() => - u.getGreatestPixDiff(sketchOriginLocation, lightThemeSegmentColor) - ) - .toBeLessThan(15) - }) - }) - - test(`Changing system theme preferences (via media query) should update UI and stream`, async ({ - page, - }) => { - // Override the settings so that the theme is set to `system` - await page.addInitScript( - ({ settingsKey, settings }) => { - localStorage.setItem(settingsKey, settings) - }, - { - settingsKey: TEST_SETTINGS_KEY, - settings: TOML.stringify({ - settings: TEST_SETTINGS_DEFAULT_THEME, - }), - } - ) - const u = await getUtils(page) - - // Selectors and constants - const darkBackgroundCss = 'oklch(0.3012 0 264.5)' - const lightBackgroundCss = 'oklch(0.9911 0 264.5)' - const darkBackgroundColor: [number, number, number] = [27, 27, 27] - const lightBackgroundColor: [number, number, number] = [245, 245, 245] - const streamBackgroundPixelIsColor = async ( - color: [number, number, number] - ) => { - return u.getGreatestPixDiff({ x: 1000, y: 200 }, color) + await expect(toastMessage).not.toBeVisible() + }) } - const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' }) - - await test.step(`Test setup`, async () => { - await page.setViewportSize({ width: 1200, height: 500 }) - await u.waitForAuthSkipAppStart() - await expect(toolbar).toBeVisible() - }) - - await test.step(`Check the background color is light before`, async () => { - await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss) - await expect - .poll(() => streamBackgroundPixelIsColor(lightBackgroundColor)) - .toBeLessThan(15) - }) - - await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => { - await page.emulateMedia({ colorScheme: 'dark' }) - }) - - await test.step(`Check the background color is dark after`, async () => { - await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss) - await expect - .poll(() => streamBackgroundPixelIsColor(darkBackgroundColor)) - .toBeLessThan(15) - }) + await changeUnitOfMeasureInProjectTab('in') + await changeUnitOfMeasureInProjectTab('ft') + await changeUnitOfMeasureInProjectTab('yd') + await changeUnitOfMeasureInProjectTab('mm') + await changeUnitOfMeasureInProjectTab('cm') + await changeUnitOfMeasureInProjectTab('m') }) + + // Go to the user tab + await userSettingsTab.hover() + await userSettingsTab.click() + await page.waitForTimeout(1000) - test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({ - page, - }) => { - const u = await getUtils(page) + await test.step('Change modeling default unit within user tab', async () => { + const changeUnitOfMeasureInUserTab = async (unitOfMeasure: string) => { + await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => { + await page + .getByTestId('modeling-defaultUnit') + .selectOption(`${unitOfMeasure}`) + const toastMessage = page.getByText( + `Set default unit to "${unitOfMeasure}" as a user default` + ) + await expect(toastMessage).toBeVisible() + }) + } + await changeUnitOfMeasureInUserTab('in') + await changeUnitOfMeasureInUserTab('ft') + await changeUnitOfMeasureInUserTab('yd') + await changeUnitOfMeasureInUserTab('mm') + await changeUnitOfMeasureInUserTab('cm') + await changeUnitOfMeasureInUserTab('m') + }) + + // Close settings + const settingsCloseButton = page.getByTestId('settings-close-button') + await settingsCloseButton.click() + + await test.step('Change modeling default unit within command bar', async () => { + const commands = page.getByRole('button', { name: 'Commands' }) + const changeUnitOfMeasureInCommandBar = async (unitOfMeasure: string) => { + // Open command bar + await commands.click() + const settingsModelingDefaultUnitCommand = page.getByText( + 'Settings · modeling · default unit' + ) + await settingsModelingDefaultUnitCommand.click() + + const commandOption = page.getByRole('option', { + name: unitOfMeasure, + exact: true, + }) + await commandOption.click() + + const toastMessage = page.getByText( + `Set default unit to "${unitOfMeasure}" for this project` + ) + await expect(toastMessage).toBeVisible() + } + await changeUnitOfMeasureInCommandBar('in') + await changeUnitOfMeasureInCommandBar('ft') + await changeUnitOfMeasureInCommandBar('yd') + await changeUnitOfMeasureInCommandBar('mm') + await changeUnitOfMeasureInCommandBar('cm') + await changeUnitOfMeasureInCommandBar('m') + }) + + await test.step('Change modeling default unit within gizmo', async () => { + const changeUnitOfMeasureInGizmo = async ( + unitOfMeasure: string, + copy: string + ) => { + const gizmo = page.getByRole('button', { + name: 'Current units are: ', + }) + await gizmo.click() + const button = page.getByRole('button', { + name: copy, + exact: true, + }) + await button.click() + const toastMessage = page.getByText( + `Set default unit to "${unitOfMeasure}" for this project` + ) + await expect(toastMessage).toBeVisible() + } + + await changeUnitOfMeasureInGizmo('in', 'Inches') + await changeUnitOfMeasureInGizmo('ft', 'Feet') + await changeUnitOfMeasureInGizmo('yd', 'Yards') + await changeUnitOfMeasureInGizmo('mm', 'Millimeters') + await changeUnitOfMeasureInGizmo('cm', 'Centimeters') + await changeUnitOfMeasureInGizmo('m', 'Meters') + }) }) + test('Changing theme in sketch mode', async ({ context, page, homePage }) => { const u = await getUtils(page) + await context.addInitScript(() => { + localStorage.setItem( + 'persistCode', + `sketch001 = startSketchOn('XZ') + |> startProfileAt([0, 0], %) + |> line([5, 0], %) + |> line([0, 5], %) + |> line([-5, 0], %) + |> lineTo([profileStartX(%), profileStartY(%)], %) + |> close(%) + extrude001 = extrude(5, sketch001) + ` + ) + }) + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + await u.waitForPageLoad() + await page.waitForTimeout(1000) + + // Selectors and constants + const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' }) + const lineToolButton = page.getByTestId('line') + const segmentOverlays = page.getByTestId('segment-overlay') + const sketchOriginLocation = { x: 600, y: 250 } + const darkThemeSegmentColor: [number, number, number] = [215, 215, 215] + const lightThemeSegmentColor: [number, number, number] = [90, 90, 90] + + await test.step(`Get into sketch mode`, async () => { + await page.mouse.click(700, 200) + await expect(editSketchButton).toBeVisible() + await editSketchButton.click() + + // We use the line tool as a proxy for sketch mode + await expect(lineToolButton).toBeVisible() + await expect(segmentOverlays).toHaveCount(4) + // but we allow more time to pass for animating to the sketch + await page.waitForTimeout(1000) + }) + + await test.step(`Check the sketch line color before`, async () => { + await expect + .poll(() => + u.getGreatestPixDiff(sketchOriginLocation, darkThemeSegmentColor) + ) + .toBeLessThan(15) + }) + + await test.step(`Change theme to light using command palette`, async () => { + await page.keyboard.press('ControlOrMeta+K') + await page.getByRole('option', { name: 'theme' }).click() + await page.getByRole('option', { name: 'light' }).click() + await expect(page.getByText('theme to "light"')).toBeVisible() + + // Make sure we haven't left sketch mode + await expect(lineToolButton).toBeVisible() + }) + + await test.step(`Check the sketch line color after`, async () => { + await expect + .poll(() => + u.getGreatestPixDiff(sketchOriginLocation, lightThemeSegmentColor) + ) + .toBeLessThan(15) + }) }) + + test(`Changing system theme preferences (via media query) should update UI and stream`, { + // Override the settings so that the theme is set to `system` + appSettings: TEST_SETTINGS_DEFAULT_THEME, + }, async ({ page, homePage }) => { + + const u = await getUtils(page) + + // Selectors and constants + const darkBackgroundCss = 'oklch(0.3012 0 264.5)' + const lightBackgroundCss = 'oklch(0.9911 0 264.5)' + const darkBackgroundColor: [number, number, number] = [27, 27, 27] + const lightBackgroundColor: [number, number, number] = [245, 245, 245] + const streamBackgroundPixelIsColor = async ( + color: [number, number, number] + ) => { + return u.getGreatestPixDiff({ x: 1000, y: 200 }, color) + } + const toolbar = page.locator('menu').filter({ hasText: 'Start Sketch' }) + + await test.step(`Test setup`, async () => { + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + await u.waitForPageLoad() + await page.waitForTimeout(1000) + await expect(toolbar).toBeVisible() + }) + + await test.step(`Check the background color is light before`, async () => { + await expect(toolbar).toHaveCSS('background-color', lightBackgroundCss) + await expect + .poll(() => streamBackgroundPixelIsColor(lightBackgroundColor)) + .toBeLessThan(15) + }) + + await test.step(`Change media query preference to dark, emulating dusk with system theme`, async () => { + await page.emulateMedia({ colorScheme: 'dark' }) + }) + + await test.step(`Check the background color is dark after`, async () => { + await expect(toolbar).toHaveCSS('background-color', darkBackgroundCss) + await expect + .poll(() => streamBackgroundPixelIsColor(darkBackgroundColor)) + .toBeLessThan(15) + }) }) + + test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, { // Override beforeEach test setup // with debug panel open // but "show debug panel" set to false - await page.addInitScript( - async ({ settingsKey, settings }) => { - localStorage.setItem(settingsKey, settings) - localStorage.setItem( - 'persistModelingContext', - '{"openPanes":["debug"]}' - ) - }, - { - settingsKey: TEST_SETTINGS_KEY, - settings: TOML.stringify({ - settings: { - ...TEST_SETTINGS, - modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false }, - }, - }), - } - ) - await page.setViewportSize({ width: 1200, height: 500 }) - - // Constants and locators - const resizeHandle = page.locator('.sidebar-resize-handles > div.block') - const debugPaneButton = page.getByTestId('debug-pane-button') - const commandsButton = page.getByRole('button', { name: 'Commands' }) - const debugPaneOption = page.getByRole('option', { - name: 'Settings · modeling · show debug panel', - }) - - async function setShowDebugPanelTo(value: 'On' | 'Off') { - await commandsButton.click() - await debugPaneOption.click() - await page.getByRole('option', { name: value }).click() - await expect( - page.getByText( - `Set show debug panel to "${value === 'On'}" for this project` - ) - ).toBeVisible() + appSettings: { + ...TEST_SETTINGS, + modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false }, } + }, async ({ context, page, homePage }) => { - await test.step(`Initial load with corrupted settings`, async () => { - await u.waitForAuthSkipAppStart() - // Check that the debug panel is not visible - await expect(debugPaneButton).not.toBeVisible() - // Check the pane resize handle wrapper is not visible - await expect(resizeHandle).not.toBeVisible() - }) - - await test.step(`Open code pane to verify we see the resize handles`, async () => { - await u.openKclCodePanel() - await expect(resizeHandle).toBeVisible() - await u.closeKclCodePanel() - }) - - await test.step(`Turn on debug panel, open it`, async () => { - await setShowDebugPanelTo('On') - await expect(debugPaneButton).toBeVisible() - // We want the logic to clear the phantom panel, so we shouldn't see - // the real panel (and therefore the resize handle) yet - await expect(resizeHandle).not.toBeVisible() - await u.openDebugPanel() - await expect(resizeHandle).toBeVisible() - }) - - await test.step(`Turn off debug panel setting with it open`, async () => { - await setShowDebugPanelTo('Off') - await expect(debugPaneButton).not.toBeVisible() - await expect(resizeHandle).not.toBeVisible() - }) + const u = await getUtils(page) + + await context.addInitScript( + async ({ }) => { + localStorage.setItem( + 'persistModelingContext', + '{"openPanes":["debug"]}' + ) + } + ) + await page.setBodyDimensions({ width: 1200, height: 500 }) + await homePage.goToModelingScene() + + // Constants and locators + const resizeHandle = page.locator('.sidebar-resize-handles > div.block') + const debugPaneButton = page.getByTestId('debug-pane-button') + const commandsButton = page.getByRole('button', { name: 'Commands' }) + const debugPaneOption = page.getByRole('option', { + name: 'Settings · modeling · show debug panel', }) + + async function setShowDebugPanelTo(value: 'On' | 'Off') { + await commandsButton.click() + await debugPaneOption.click() + await page.getByRole('option', { name: value }).click() + await expect( + page.getByText( + `Set show debug panel to "${value === 'On'}" for this project` + ) + ).toBeVisible() + } + + await test.step(`Initial load with corrupted settings`, async () => { + // Check that the debug panel is not visible + await expect(debugPaneButton).not.toBeVisible() + // Check the pane resize handle wrapper is not visible + await expect(resizeHandle).not.toBeVisible() + }) + + await test.step(`Open code pane to verify we see the resize handles`, async () => { + await u.openKclCodePanel() + await expect(resizeHandle).toBeVisible() + await u.closeKclCodePanel() + }) + + await test.step(`Turn on debug panel, open it`, async () => { + await setShowDebugPanelTo('On') + await expect(debugPaneButton).toBeVisible() + // We want the logic to clear the phantom panel, so we shouldn't see + // the real panel (and therefore the resize handle) yet + await expect(resizeHandle).not.toBeVisible() + await u.openDebugPanel() + await expect(resizeHandle).toBeVisible() + }) + + await test.step(`Turn off debug panel setting with it open`, async () => { + await setShowDebugPanelTo('Off') + await expect(debugPaneButton).not.toBeVisible() + await expect(resizeHandle).not.toBeVisible() + }) }) }) diff --git a/e2e/playwright/text-to-cad-tests.spec.ts b/e2e/playwright/text-to-cad-tests.spec.ts index edeb9107b..cf8f00661 100644 --- a/e2e/playwright/text-to-cad-tests.spec.ts +++ b/e2e/playwright/text-to-cad-tests.spec.ts @@ -1,670 +1,624 @@ -import { test, expect, Page } from '@playwright/test' -import { - getUtils, - setup, - tearDown, - setupElectron, - createProject, -} from './test-utils' +import { test, expect, Page } from './zoo-test' +import { getUtils, createProject, } from './test-utils' import { join } from 'path' import fs from 'fs' -test.beforeEach(async ({ context, page }) => { - await setup(context, page) -}) - -test.afterEach(async ({ page }, testInfo) => { - await tearDown(page, testInfo) -}) - test.describe('Text-to-CAD tests', () => { - test('basic lego happy case', async ({ page }) => { - const u = await getUtils(page) - - await test.step('Set up', async () => { - await page.setViewportSize({ width: 1000, height: 500 }) - await u.waitForAuthSkipAppStart() - }) - - await sendPromptFromCommandBar(page, 'a 2x4 lego') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - - const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible({ timeout: 15000 }) - - await expect(page.getByText('Copied')).not.toBeVisible() - - // Hit copy to clipboard. - const copyToClipboardButton = page.getByRole('button', { - name: 'Copy to clipboard', - }) - await expect(copyToClipboardButton).toBeVisible() - - await copyToClipboardButton.click() - - // Expect the code to be copied. - await expect(page.getByText('Copied')).toBeVisible() - - // Click in the code editor. - await page.locator('.cm-content').click() - - // Paste the code. - await page.keyboard.press('ControlOrMeta+KeyV') - - // Expect the code to be pasted. - await expect(page.locator('.cm-content')).toContainText(`const`) - - // make sure a model renders. - // wait for execution done - await u.openDebugPanel() - await u.expectCmdLog('[data-message-type="execution-done"]') - await u.closeDebugPanel() - - // Find the toast close button. - const closeButton = page - .getByRole('status') - .locator('div') - .filter({ hasText: 'Text-to-CAD successfulPrompt' }) - .first() - .getByRole('button', { name: 'Close' }) - await expect(closeButton).toBeVisible() - await closeButton.click() - - // The toast should disappear. - await expect(successToastMessage).not.toBeVisible() + test('basic lego happy case', async ({ page, homePage }) => { const u = await getUtils(page) + + await test.step('Set up', async () => { + await page.setBodyDimensions({ width: 1000, height: 500 }) + await homePage.goToModelingScene() }) - - test('success model, then ignore success toast, user can create new prompt from command bar', async ({ - page, - }) => { - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - await sendPromptFromCommandBar(page, 'a 2x6 lego') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - - const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible({ timeout: 15000 }) - - await expect(page.getByText('Copied')).not.toBeVisible() - - await expect(successToastMessage).toBeVisible() - - // Can send a new prompt from the command bar. - await sendPromptFromCommandBar(page, 'a 2x4 lego') - - // Find the toast. - // Look out for the toast message - await expect(submittingToastMessage).toBeVisible() - await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - - // Expect 2 success toasts. - await expect(successToastMessage).toHaveCount(2, { - timeout: 15000, - }) - await expect(page.getByText('a 2x4 lego')).toBeVisible() - await expect(page.getByText('a 2x6 lego')).toBeVisible() - }) - - test('you can reject text-to-cad output and it does nothing', async ({ - page, - }) => { - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - await sendPromptFromCommandBar(page, 'a 2x4 lego') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - - const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible({ timeout: 15000 }) - - // Hit copy to clipboard. - const rejectButton = page.getByRole('button', { name: 'Reject' }) - await expect(rejectButton).toBeVisible() - - await rejectButton.click() - - // The toast should disappear. - await expect(successToastMessage).not.toBeVisible() - - // Expect no code. - await expect(page.locator('.cm-content')).toContainText(``) - }) - - test('sending a bad prompt fails, can dismiss', async ({ page }) => { - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - const commandBarButton = page.getByRole('button', { name: 'Commands' }) - await expect(commandBarButton).toBeVisible() - // Click the command bar button - await commandBarButton.click() - - // Wait for the command bar to appear - const cmdSearchBar = page.getByPlaceholder('Search commands') - await expect(cmdSearchBar).toBeVisible() - - const textToCadCommand = page.getByText('Text-to-CAD') - await expect(textToCadCommand.first()).toBeVisible() - // Click the Text-to-CAD command - await textToCadCommand.first().click() - - // Enter the prompt. - const prompt = page.getByText('Prompt') - await expect(prompt.first()).toBeVisible() - - // Type the prompt. - const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF` - await page.keyboard.type(randomPrompt) - await page.waitForTimeout(1000) - await page.keyboard.press('Enter') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage).toBeVisible() - - const failureToastMessage = page.getByText( - `The prompt must clearly describe a CAD model` - ) - await expect(failureToastMessage).toBeVisible() - - await page.waitForTimeout(1000) - - // Make sure the toast did not say it was successful. - const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).not.toBeVisible() - await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() - - // Find the toast dismiss button. - const dismissButton = page.getByRole('button', { name: 'Dismiss' }) - await expect(dismissButton).toBeVisible() - await dismissButton.click() - - // The toast should disappear. - await expect(failureToastMessage).not.toBeVisible() - }) - - test('sending a bad prompt fails, can start over from toast', async ({ - page, - }) => { - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - const commandBarButton = page.getByRole('button', { name: 'Commands' }) - await expect(commandBarButton).toBeVisible() - // Click the command bar button - await commandBarButton.click() - - // Wait for the command bar to appear - const cmdSearchBar = page.getByPlaceholder('Search commands') - await expect(cmdSearchBar).toBeVisible() - - const textToCadCommand = page.getByText('Text-to-CAD') - await expect(textToCadCommand.first()).toBeVisible() - // Click the Text-to-CAD command - await textToCadCommand.first().click() - - // Enter the prompt. - const prompt = page.getByText('Prompt') - await expect(prompt.first()).toBeVisible() - - const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss' - - // Type the prompt. - await page.keyboard.type(badPrompt) - await page.waitForTimeout(1000) - await page.keyboard.press('Enter') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage).toBeVisible() - - const failureToastMessage = page.getByText( - `The prompt must clearly describe a CAD model` - ) - await expect(failureToastMessage).toBeVisible() - - await page.waitForTimeout(1000) - - // Make sure the toast did not say it was successful. - const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).not.toBeVisible() - await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() - - // Click the edit prompt button to try again. - const editPromptButton = page.getByRole('button', { name: 'Edit prompt' }) - await expect(editPromptButton).toBeVisible() - await editPromptButton.click() - - // The toast should disappear. - await expect(failureToastMessage).not.toBeVisible() - - // Make sure the old prompt is still there and can be edited. - await expect(page.locator('textarea')).toContainText(badPrompt) - - // Select all and start a new prompt. - await page.keyboard.down('ControlOrMeta') - await page.keyboard.press('KeyA') - await page.keyboard.up('ControlOrMeta') - await page.keyboard.type('a 2x4 lego') - - // Submit the new prompt. - await page.keyboard.press('Enter') - - // Make sure the new prompt works. - // Find the toast. - // Look out for the toast message - await expect(submittingToastMessage).toBeVisible() - - await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - - await expect(successToastMessage).toBeVisible({ timeout: 15000 }) - }) - - test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({ - page, - }) => { - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - const commandBarButton = page.getByRole('button', { name: 'Commands' }) - await expect(commandBarButton).toBeVisible() - // Click the command bar button - await commandBarButton.click() - - // Wait for the command bar to appear - const cmdSearchBar = page.getByPlaceholder('Search commands') - await expect(cmdSearchBar).toBeVisible() - - const textToCadCommand = page.getByText('Text-to-CAD') - await expect(textToCadCommand.first()).toBeVisible() - // Click the Text-to-CAD command - await textToCadCommand.first().click() - - // Enter the prompt. - const prompt = page.getByText('Prompt') - await expect(prompt.first()).toBeVisible() - - const badPrompt = 'akjsndladflajbhflauweyf15;' - - // Type the prompt. - await page.keyboard.type(badPrompt) - await page.waitForTimeout(1000) - await page.keyboard.press('Enter') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage).toBeVisible() - - const failureToastMessage = page.getByText( - `The prompt must clearly describe a CAD model` - ) - await expect(failureToastMessage).toBeVisible() - - await page.waitForTimeout(1000) - - // Make sure the toast did not say it was successful. - const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).not.toBeVisible() - await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() - - // They should be able to try again from the command bar. - await sendPromptFromCommandBar(page, 'a 2x4 lego') - - // Find the toast. - // Look out for the toast message - await expect(submittingToastMessage).toBeVisible() - - await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - - await expect(successToastMessage).toBeVisible({ timeout: 15000 }) - - await expect(page.getByText('Copied')).not.toBeVisible() - - // old failure toast should stick around. - await expect(failureToastMessage).toBeVisible() - await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() - }) - - test('ensure you can shift+enter in the prompt box', async ({ page }) => { - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - const promptWithNewline = `a 2x4\nlego` - - const commandBarButton = page.getByRole('button', { name: 'Commands' }) - await expect(commandBarButton).toBeVisible() - // Click the command bar button - await commandBarButton.click() - - // Wait for the command bar to appear - const cmdSearchBar = page.getByPlaceholder('Search commands') - await expect(cmdSearchBar).toBeVisible() - - const textToCadCommand = page.getByText('Text-to-CAD') - await expect(textToCadCommand.first()).toBeVisible() - // Click the Text-to-CAD command - await textToCadCommand.first().click() - - // Enter the prompt. - const prompt = page.getByText('Prompt') - await expect(prompt.first()).toBeVisible() - - // Type the prompt. - await page.keyboard.type('a 2x4') - await page.waitForTimeout(1000) - await page.keyboard.down('Shift') - await page.keyboard.press('Enter') - await page.keyboard.up('Shift') - await page.keyboard.type('lego') - await page.waitForTimeout(1000) - await page.keyboard.press('Enter') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) - - const successToastMessage = page.getByText(`Text-to-CAD successful`) - await expect(successToastMessage).toBeVisible({ timeout: 15000 }) - - await expect(page.getByText(promptWithNewline)).toBeVisible() - }) - - test( - 'can do many at once and get many prompts back, and interact with many', - { tag: ['@skipWin'] }, - async ({ page }) => { - // Let this test run longer since we've seen it timeout. - test.setTimeout(180_000) - // skip on windows - test.skip( - process.platform === 'win32', - 'This test is flaky, skipping for now' - ) - - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - await sendPromptFromCommandBar(page, 'a 2x4 lego') - - await sendPromptFromCommandBar(page, 'a 2x8 lego') - - await sendPromptFromCommandBar(page, 'a 2x10 lego') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage.first()).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage.first()).toBeVisible({ - timeout: 10_000, - }) - - const successToastMessage = page.getByText(`Text-to-CAD successful`) - // We should have three success toasts. - await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 }) - - await expect(page.getByText('Copied')).not.toBeVisible() - - await expect(page.getByText(`a 2x4 lego`)).toBeVisible() - await expect(page.getByText(`a 2x8 lego`)).toBeVisible() - await expect(page.getByText(`a 2x10 lego`)).toBeVisible() - - // Ensure if you reject one, the others stay. - const rejectButton = page.getByRole('button', { name: 'Reject' }) - await expect(rejectButton.first()).toBeVisible() - // Click the reject button on the first toast. - await rejectButton.first().click() - - // The first toast should disappear, but not the others. - await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible() - await expect(page.getByText(`a 2x8 lego`)).toBeVisible() - await expect(page.getByText(`a 2x4 lego`)).toBeVisible() - - // Ensure you can copy the code for one of the models remaining. - const copyToClipboardButton = page.getByRole('button', { - name: 'Copy to clipboard', - }) - await expect(copyToClipboardButton.first()).toBeVisible() - // Click the button. - await copyToClipboardButton.first().click() - - // Expect the code to be copied. - await expect(page.getByText('Copied')).toBeVisible() - - // Click in the code editor. - await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) - - // Paste the code. - await page.keyboard.down('ControlOrMeta') - await page.keyboard.press('KeyV') - await page.keyboard.up('ControlOrMeta') - - // Expect the code to be pasted. - await expect(page.locator('.cm-content')).toContainText(`2x8`) - - // Find the toast close button. - const closeButton = page.locator('[data-negative-button="close"]').first() - await expect(closeButton).toBeVisible() - await closeButton.click() - - // Ensure the final toast remains. - await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible() - await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible() - await expect(page.getByText(`a 2x4 lego`)).toBeVisible() - - // Ensure you can copy the code for the final model. - await expect(copyToClipboardButton).toBeVisible() - // Click the button. - await copyToClipboardButton.click() - - // Expect the code to be copied. - await expect(page.getByText('Copied')).toBeVisible() - - // Click in the code editor. - await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) - - // Paste the code. - await page.keyboard.down('ControlOrMeta') - await page.keyboard.press('KeyA') - await page.keyboard.up('ControlOrMeta') - await page.keyboard.press('Backspace') - await page.keyboard.down('ControlOrMeta') - await page.keyboard.press('KeyV') - await page.keyboard.up('ControlOrMeta') - - // Expect the code to be pasted. - await expect(page.locator('.cm-content')).toContainText(`2x4`) - - // Expect the toast to disappear. - // Find the toast close button. - await expect(closeButton).toBeVisible() - await closeButton.click() - await expect(successToastMessage).not.toBeVisible() - } + + await sendPromptFromCommandBar(page, 'a 2x4 lego') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` ) - - test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({ - page, - }) => { - const u = await getUtils(page) - - await page.setViewportSize({ width: 1000, height: 500 }) - - await u.waitForAuthSkipAppStart() - - await sendPromptFromCommandBar(page, 'a 2x4 lego') - - await sendPromptFromCommandBar(page, 'alkjsdnlajshdbfjlhsbdf a;askjdnf') - - // Find the toast. - // Look out for the toast message - const submittingToastMessage = page.getByText( - `Submitting to Text-to-CAD API...` - ) - await expect(submittingToastMessage.first()).toBeVisible() - - const generatingToastMessage = page.getByText( - `Generating parametric model...` - ) - await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 }) - - const successToastMessage = page.getByText(`Text-to-CAD successful`) - // We should have three success toasts. - await expect(successToastMessage).toHaveCount(1, { timeout: 15000 }) - - await expect(page.getByText('Copied')).not.toBeVisible() - - const failureToastMessage = page.getByText( - `The prompt must clearly describe a CAD model` - ) - await expect(failureToastMessage).toBeVisible() - - // Make sure the toast did not say it was successful. - await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() - - await expect(page.getByText(`a 2x4 lego`)).toBeVisible() - - // Ensure if you dismiss the error the others stay. - const dismissButton = page.getByRole('button', { name: 'Dismiss' }) - await expect(dismissButton).toBeVisible() - // Click the dismiss button on the first toast. - await dismissButton.first().click() - - // Make sure the failure toast disappears. - await expect(failureToastMessage).not.toBeVisible() - await expect(page.getByText(`Text-to-CAD failed`)).not.toBeVisible() - - // The first toast should disappear, but not the others. - await expect(page.getByText(`a 2x4 lego`)).toBeVisible() - - // Ensure you can copy the code for one of the models remaining. - const copyToClipboardButton = page.getByRole('button', { - name: 'Copy to clipboard', - }) - await expect(copyToClipboardButton.first()).toBeVisible() - // Click the button. - await copyToClipboardButton.first().click() - - // Expect the code to be copied. - await expect(page.getByText('Copied')).toBeVisible() - - // Click in the code editor. - await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) - - // Paste the code. - await page.keyboard.down('ControlOrMeta') - await page.keyboard.press('KeyV') - await page.keyboard.up('ControlOrMeta') - - // Expect the code to be pasted. - await expect(page.locator('.cm-content')).toContainText(`2x4`) - - // Find the toast close button. - const closeButton = page - .getByRole('status') - .locator('div') - .filter({ hasText: 'Text-to-CAD successfulPrompt' }) - .first() - .getByRole('button', { name: 'Close' }) - await expect(closeButton).toBeVisible() - await closeButton.click() - - // Expect the toast to disappear. - await expect(page.getByText('Copied')).not.toBeVisible() - await expect(successToastMessage).not.toBeVisible() + await expect(submittingToastMessage).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) + + const successToastMessage = page.getByText(`Text-to-CAD successful`) + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) + + await expect(page.getByText('Copied')).not.toBeVisible() + + // Hit copy to clipboard. + const copyToClipboardButton = page.getByRole('button', { + name: 'Copy to clipboard', }) + await expect(copyToClipboardButton).toBeVisible() + + await copyToClipboardButton.click() + + // Expect the code to be copied. + await expect(page.getByText('Copied')).toBeVisible() + + // Click in the code editor. + await page.locator('.cm-content').click() + + // Paste the code. + await page.keyboard.press('ControlOrMeta+KeyV') + + // Expect the code to be pasted. + await expect(page.locator('.cm-content')).toContainText(`const`) + + // make sure a model renders. + // wait for execution done + await u.openDebugPanel() + await u.expectCmdLog('[data-message-type="execution-done"]') + await u.closeDebugPanel() + + // Find the toast close button. + const closeButton = page + .getByRole('status') + .locator('div') + .filter({ hasText: 'Text-to-CAD successfulPrompt' }) + .first() + .getByRole('button', { name: 'Close' }) + await expect(closeButton).toBeVisible() + await closeButton.click() + + // The toast should disappear. + await expect(successToastMessage).not.toBeVisible() }) + + test('success model, then ignore success toast, user can create new prompt from command bar', async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + await sendPromptFromCommandBar(page, 'a 2x6 lego') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) + + const successToastMessage = page.getByText(`Text-to-CAD successful`) + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) + + await expect(page.getByText('Copied')).not.toBeVisible() + + await expect(successToastMessage).toBeVisible() + + // Can send a new prompt from the command bar. + await sendPromptFromCommandBar(page, 'a 2x4 lego') + + // Find the toast. + // Look out for the toast message + await expect(submittingToastMessage).toBeVisible() + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) + + // Expect 2 success toasts. + await expect(successToastMessage).toHaveCount(2, { + timeout: 15000, + }) + await expect(page.getByText('a 2x4 lego')).toBeVisible() + await expect(page.getByText('a 2x6 lego')).toBeVisible() }) + + test('you can reject text-to-cad output and it does nothing', async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + await sendPromptFromCommandBar(page, 'a 2x4 lego') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) + + const successToastMessage = page.getByText(`Text-to-CAD successful`) + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) + + // Hit copy to clipboard. + const rejectButton = page.getByRole('button', { name: 'Reject' }) + await expect(rejectButton).toBeVisible() + + await rejectButton.click() + + // The toast should disappear. + await expect(successToastMessage).not.toBeVisible() + + // Expect no code. + await expect(page.locator('.cm-content')).toContainText(``) }) + + test('sending a bad prompt fails, can dismiss', async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + const commandBarButton = page.getByRole('button', { name: 'Commands' }) + await expect(commandBarButton).toBeVisible() + // Click the command bar button + await commandBarButton.click() + + // Wait for the command bar to appear + const cmdSearchBar = page.getByPlaceholder('Search commands') + await expect(cmdSearchBar).toBeVisible() + + const textToCadCommand = page.getByText('Text-to-CAD') + await expect(textToCadCommand.first()).toBeVisible() + // Click the Text-to-CAD command + await textToCadCommand.first().click() + + // Enter the prompt. + const prompt = page.getByText('Prompt') + await expect(prompt.first()).toBeVisible() + + // Type the prompt. + const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF` + await page.keyboard.type(randomPrompt) + await page.waitForTimeout(1000) + await page.keyboard.press('Enter') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage).toBeVisible() + + const failureToastMessage = page.getByText( + `The prompt must clearly describe a CAD model` + ) + await expect(failureToastMessage).toBeVisible() + + await page.waitForTimeout(1000) + + // Make sure the toast did not say it was successful. + const successToastMessage = page.getByText(`Text-to-CAD successful`) + await expect(successToastMessage).not.toBeVisible() + await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() + + // Find the toast dismiss button. + const dismissButton = page.getByRole('button', { name: 'Dismiss' }) + await expect(dismissButton).toBeVisible() + await dismissButton.click() + + // The toast should disappear. + await expect(failureToastMessage).not.toBeVisible() }) + + test('sending a bad prompt fails, can start over from toast', async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + const commandBarButton = page.getByRole('button', { name: 'Commands' }) + await expect(commandBarButton).toBeVisible() + // Click the command bar button + await commandBarButton.click() + + // Wait for the command bar to appear + const cmdSearchBar = page.getByPlaceholder('Search commands') + await expect(cmdSearchBar).toBeVisible() + + const textToCadCommand = page.getByText('Text-to-CAD') + await expect(textToCadCommand.first()).toBeVisible() + // Click the Text-to-CAD command + await textToCadCommand.first().click() + + // Enter the prompt. + const prompt = page.getByText('Prompt') + await expect(prompt.first()).toBeVisible() + + const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss' + + // Type the prompt. + await page.keyboard.type(badPrompt) + await page.waitForTimeout(1000) + await page.keyboard.press('Enter') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage).toBeVisible() + + const failureToastMessage = page.getByText( + `The prompt must clearly describe a CAD model` + ) + await expect(failureToastMessage).toBeVisible() + + await page.waitForTimeout(1000) + + // Make sure the toast did not say it was successful. + const successToastMessage = page.getByText(`Text-to-CAD successful`) + await expect(successToastMessage).not.toBeVisible() + await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() + + // Click the edit prompt button to try again. + const editPromptButton = page.getByRole('button', { name: 'Edit prompt' }) + await expect(editPromptButton).toBeVisible() + await editPromptButton.click() + + // The toast should disappear. + await expect(failureToastMessage).not.toBeVisible() + + // Make sure the old prompt is still there and can be edited. + await expect(page.locator('textarea')).toContainText(badPrompt) + + // Select all and start a new prompt. + await page.keyboard.down('ControlOrMeta') + await page.keyboard.press('KeyA') + await page.keyboard.up('ControlOrMeta') + await page.keyboard.type('a 2x4 lego') + + // Submit the new prompt. + await page.keyboard.press('Enter') + + // Make sure the new prompt works. + // Find the toast. + // Look out for the toast message + await expect(submittingToastMessage).toBeVisible() + + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) + + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) }) + + test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + const commandBarButton = page.getByRole('button', { name: 'Commands' }) + await expect(commandBarButton).toBeVisible() + // Click the command bar button + await commandBarButton.click() + + // Wait for the command bar to appear + const cmdSearchBar = page.getByPlaceholder('Search commands') + await expect(cmdSearchBar).toBeVisible() + + const textToCadCommand = page.getByText('Text-to-CAD') + await expect(textToCadCommand.first()).toBeVisible() + // Click the Text-to-CAD command + await textToCadCommand.first().click() + + // Enter the prompt. + const prompt = page.getByText('Prompt') + await expect(prompt.first()).toBeVisible() + + const badPrompt = 'akjsndladflajbhflauweyf15;' + + // Type the prompt. + await page.keyboard.type(badPrompt) + await page.waitForTimeout(1000) + await page.keyboard.press('Enter') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage).toBeVisible() + + const failureToastMessage = page.getByText( + `The prompt must clearly describe a CAD model` + ) + await expect(failureToastMessage).toBeVisible() + + await page.waitForTimeout(1000) + + // Make sure the toast did not say it was successful. + const successToastMessage = page.getByText(`Text-to-CAD successful`) + await expect(successToastMessage).not.toBeVisible() + await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() + + // They should be able to try again from the command bar. + await sendPromptFromCommandBar(page, 'a 2x4 lego') + + // Find the toast. + // Look out for the toast message + await expect(submittingToastMessage).toBeVisible() + + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) + + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) + + await expect(page.getByText('Copied')).not.toBeVisible() + + // old failure toast should stick around. + await expect(failureToastMessage).toBeVisible() + await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() }) + + test('ensure you can shift+enter in the prompt box', async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + const promptWithNewline = `a 2x4\nlego` + + const commandBarButton = page.getByRole('button', { name: 'Commands' }) + await expect(commandBarButton).toBeVisible() + // Click the command bar button + await commandBarButton.click() + + // Wait for the command bar to appear + const cmdSearchBar = page.getByPlaceholder('Search commands') + await expect(cmdSearchBar).toBeVisible() + + const textToCadCommand = page.getByText('Text-to-CAD') + await expect(textToCadCommand.first()).toBeVisible() + // Click the Text-to-CAD command + await textToCadCommand.first().click() + + // Enter the prompt. + const prompt = page.getByText('Prompt') + await expect(prompt.first()).toBeVisible() + + // Type the prompt. + await page.keyboard.type('a 2x4') + await page.waitForTimeout(1000) + await page.keyboard.down('Shift') + await page.keyboard.press('Enter') + await page.keyboard.up('Shift') + await page.keyboard.type('lego') + await page.waitForTimeout(1000) + await page.keyboard.press('Enter') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage).toBeVisible({ timeout: 10000 }) + + const successToastMessage = page.getByText(`Text-to-CAD successful`) + await expect(successToastMessage).toBeVisible({ timeout: 15000 }) + + await expect(page.getByText(promptWithNewline)).toBeVisible() }) + + test('can do many at once and get many prompts back, and interact with many', { tag: ['@skipWin'] }, async ({ page, homePage }) => { // Let this test run longer since we've seen it timeout. + test.setTimeout(180_000) + // skip on windows + test.skip( + process.platform === 'win32', + 'This test is flaky, skipping for now' + ) + + const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + await sendPromptFromCommandBar(page, 'a 2x4 lego') + + await sendPromptFromCommandBar(page, 'a 2x8 lego') + + await sendPromptFromCommandBar(page, 'a 2x10 lego') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage.first()).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage.first()).toBeVisible({ + timeout: 10_000, + }) + + const successToastMessage = page.getByText(`Text-to-CAD successful`) + // We should have three success toasts. + await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 }) + + await expect(page.getByText('Copied')).not.toBeVisible() + + await expect(page.getByText(`a 2x4 lego`)).toBeVisible() + await expect(page.getByText(`a 2x8 lego`)).toBeVisible() + await expect(page.getByText(`a 2x10 lego`)).toBeVisible() + + // Ensure if you reject one, the others stay. + const rejectButton = page.getByRole('button', { name: 'Reject' }) + await expect(rejectButton.first()).toBeVisible() + // Click the reject button on the first toast. + await rejectButton.first().click() + + // The first toast should disappear, but not the others. + await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible() + await expect(page.getByText(`a 2x8 lego`)).toBeVisible() + await expect(page.getByText(`a 2x4 lego`)).toBeVisible() + + // Ensure you can copy the code for one of the models remaining. + const copyToClipboardButton = page.getByRole('button', { + name: 'Copy to clipboard', + }) + await expect(copyToClipboardButton.first()).toBeVisible() + // Click the button. + await copyToClipboardButton.first().click() + + // Expect the code to be copied. + await expect(page.getByText('Copied')).toBeVisible() + + // Click in the code editor. + await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) + + // Paste the code. + await page.keyboard.down('ControlOrMeta') + await page.keyboard.press('KeyV') + await page.keyboard.up('ControlOrMeta') + + // Expect the code to be pasted. + await expect(page.locator('.cm-content')).toContainText(`2x8`) + + // Find the toast close button. + const closeButton = page.locator('[data-negative-button="close"]').first() + await expect(closeButton).toBeVisible() + await closeButton.click() + + // Ensure the final toast remains. + await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible() + await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible() + await expect(page.getByText(`a 2x4 lego`)).toBeVisible() + + // Ensure you can copy the code for the final model. + await expect(copyToClipboardButton).toBeVisible() + // Click the button. + await copyToClipboardButton.click() + + // Expect the code to be copied. + await expect(page.getByText('Copied')).toBeVisible() + + // Click in the code editor. + await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) + + // Paste the code. + await page.keyboard.down('ControlOrMeta') + await page.keyboard.press('KeyA') + await page.keyboard.up('ControlOrMeta') + await page.keyboard.press('Backspace') + await page.keyboard.down('ControlOrMeta') + await page.keyboard.press('KeyV') + await page.keyboard.up('ControlOrMeta') + + // Expect the code to be pasted. + await expect(page.locator('.cm-content')).toContainText(`2x4`) + + // Expect the toast to disappear. + // Find the toast close button. + await expect(closeButton).toBeVisible() + await closeButton.click() + await expect(successToastMessage).not.toBeVisible() }) + + test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({ page, homePage }) => { const u = await getUtils(page) + + await page.setBodyDimensions({ width: 1000, height: 500 }) + + await homePage.goToModelingScene() + + await sendPromptFromCommandBar(page, 'a 2x4 lego') + + await sendPromptFromCommandBar(page, 'alkjsdnlajshdbfjlhsbdf a;askjdnf') + + // Find the toast. + // Look out for the toast message + const submittingToastMessage = page.getByText( + `Submitting to Text-to-CAD API...` + ) + await expect(submittingToastMessage.first()).toBeVisible() + + const generatingToastMessage = page.getByText( + `Generating parametric model...` + ) + await expect(generatingToastMessage.first()).toBeVisible({ timeout: 10000 }) + + const successToastMessage = page.getByText(`Text-to-CAD successful`) + // We should have three success toasts. + await expect(successToastMessage).toHaveCount(1, { timeout: 15000 }) + + await expect(page.getByText('Copied')).not.toBeVisible() + + const failureToastMessage = page.getByText( + `The prompt must clearly describe a CAD model` + ) + await expect(failureToastMessage).toBeVisible() + + // Make sure the toast did not say it was successful. + await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible() + + await expect(page.getByText(`a 2x4 lego`)).toBeVisible() + + // Ensure if you dismiss the error the others stay. + const dismissButton = page.getByRole('button', { name: 'Dismiss' }) + await expect(dismissButton).toBeVisible() + // Click the dismiss button on the first toast. + await dismissButton.first().click() + + // Make sure the failure toast disappears. + await expect(failureToastMessage).not.toBeVisible() + await expect(page.getByText(`Text-to-CAD failed`)).not.toBeVisible() + + // The first toast should disappear, but not the others. + await expect(page.getByText(`a 2x4 lego`)).toBeVisible() + + // Ensure you can copy the code for one of the models remaining. + const copyToClipboardButton = page.getByRole('button', { + name: 'Copy to clipboard', + }) + await expect(copyToClipboardButton.first()).toBeVisible() + // Click the button. + await copyToClipboardButton.first().click() + + // Expect the code to be copied. + await expect(page.getByText('Copied')).toBeVisible() + + // Click in the code editor. + await page.locator('.cm-content').click({ position: { x: 10, y: 10 } }) + + // Paste the code. + await page.keyboard.down('ControlOrMeta') + await page.keyboard.press('KeyV') + await page.keyboard.up('ControlOrMeta') + + // Expect the code to be pasted. + await expect(page.locator('.cm-content')).toContainText(`2x4`) + + // Find the toast close button. + const closeButton = page + .getByRole('status') + .locator('div') + .filter({ hasText: 'Text-to-CAD successfulPrompt' }) + .first() + .getByRole('button', { name: 'Close' }) + await expect(closeButton).toBeVisible() + await closeButton.click() + + // Expect the toast to disappear. + await expect(page.getByText('Copied')).not.toBeVisible() + await expect(successToastMessage).not.toBeVisible() }) }) async function sendPromptFromCommandBar(page: Page, promptStr: string) { @@ -672,13 +626,14 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) { const commandBarButton = page.getByRole('button', { name: 'Commands' }) await expect(commandBarButton).toBeVisible() // Click the command bar button + await commandBarButton.hover() await commandBarButton.click() // Wait for the command bar to appear const cmdSearchBar = page.getByPlaceholder('Search commands') await expect(cmdSearchBar).toBeVisible() - const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API ') + const textToCadCommand = page.getByText('Text-to-CAD') await expect(textToCadCommand.first()).toBeVisible() // Click the Text-to-CAD command await textToCadCommand.first().click() @@ -711,7 +666,7 @@ test( test ) - await page.setViewportSize({ width: 1200, height: 500 }) + await page.setBodyDimensions({ width: 1200, height: 500 }) // Locators const projectMenuButton = page