diff --git a/apps/ui/tests/features/feature-manual-review-flow.spec.ts b/apps/ui/tests/features/feature-manual-review-flow.spec.ts index a74b39be..9456b215 100644 --- a/apps/ui/tests/features/feature-manual-review-flow.spec.ts +++ b/apps/ui/tests/features/feature-manual-review-flow.spec.ts @@ -123,8 +123,17 @@ test.describe('Feature Manual Review Flow', () => { await waitForNetworkIdle(page); await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 10000 }); - // Verify we're on the correct project - await expect(page.getByText(projectName).first()).toBeVisible({ timeout: 10000 }); + // Expand sidebar if collapsed to see project name + const expandSidebarButton = page.locator('button:has-text("Expand sidebar")'); + if (await expandSidebarButton.isVisible()) { + await expandSidebarButton.click(); + await page.waitForTimeout(300); + } + + // Verify we're on the correct project (project name appears in sidebar button) + await expect(page.getByRole('button', { name: new RegExp(projectName) })).toBeVisible({ + timeout: 10000, + }); // Create the feature via HTTP API (writes to disk) const feature = { diff --git a/apps/ui/tests/projects/board-background-persistence.spec.ts b/apps/ui/tests/projects/board-background-persistence.spec.ts index f7d35dec..bc3b8c3b 100644 --- a/apps/ui/tests/projects/board-background-persistence.spec.ts +++ b/apps/ui/tests/projects/board-background-persistence.spec.ts @@ -122,9 +122,7 @@ test.describe('Board Background Persistence', () => { ], }); - await authenticateForTests(page); - - // Intercept settings API to use our test projects and clear currentProjectId + // Intercept settings API BEFORE authenticateForTests (which navigates to the page) // This ensures the app shows the welcome view with our test projects await page.route('**/api/settings/global', async (route) => { const response = await route.fetch(); @@ -151,6 +149,8 @@ test.describe('Board Background Persistence', () => { await route.fulfill({ response, json }); }); + await authenticateForTests(page); + // Track API calls to /api/settings/project to verify settings are being loaded const settingsApiCalls: Array<{ url: string; method: string; body: string }> = []; page.on('request', (request) => { @@ -163,24 +163,31 @@ test.describe('Board Background Persistence', () => { } }); - // Navigate to the app - await page.goto('/'); + // Navigate directly to dashboard to avoid auto-open which would bypass the project selection + await page.goto('/dashboard'); await page.waitForLoadState('load'); await handleLoginScreenIfPresent(page); - // Wait for welcome view - await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 }); + // Wait for dashboard view + await expect(page.locator('[data-testid="dashboard-view"]')).toBeVisible({ timeout: 10000 }); // Open project A (has background settings) - const projectACard = page.locator(`[data-testid="recent-project-${projectAId}"]`); + const projectACard = page.locator(`[data-testid="project-card-${projectAId}"]`); await expect(projectACard).toBeVisible(); await projectACard.click(); // Wait for board view await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); - // Verify project A is current (check header paragraph which is always visible) - await expect(page.locator('[data-testid="board-view"]').getByText(projectAName)).toBeVisible({ + // Ensure sidebar is expanded before checking project name + const expandSidebarButton = page.locator('button:has-text("Expand sidebar")'); + if (await expandSidebarButton.isVisible()) { + await expandSidebarButton.click(); + await page.waitForTimeout(300); + } + + // Verify project A is current (project name appears in sidebar button) + await expect(page.getByRole('button', { name: new RegExp(projectAName) })).toBeVisible({ timeout: 5000, }); @@ -196,13 +203,6 @@ test.describe('Board Background Persistence', () => { // Wait for initial project load to stabilize await page.waitForTimeout(500); - // Ensure sidebar is expanded before interacting with project selector - const expandSidebarButton = page.locator('button:has-text("Expand sidebar")'); - if (await expandSidebarButton.isVisible()) { - await expandSidebarButton.click(); - await page.waitForTimeout(300); - } - // Switch to project B (no background) const projectSelector = page.locator('[data-testid="project-selector"]'); await expect(projectSelector).toBeVisible({ timeout: 5000 }); diff --git a/apps/ui/tests/projects/new-project-creation.spec.ts b/apps/ui/tests/projects/new-project-creation.spec.ts index 802038fc..ca95bd92 100644 --- a/apps/ui/tests/projects/new-project-creation.spec.ts +++ b/apps/ui/tests/projects/new-project-creation.spec.ts @@ -33,27 +33,29 @@ test.describe('Project Creation', () => { const projectName = `test-project-${Date.now()}`; await setupWelcomeView(page, { workspaceDir: TEST_TEMP_DIR }); - await authenticateForTests(page); - // Intercept settings API to ensure it doesn't return a currentProjectId - // This prevents settings hydration from restoring a project + // Intercept settings API BEFORE authenticateForTests (which navigates to the page) + // This prevents settings hydration from restoring a project and disables auto-open await page.route('**/api/settings/global', async (route) => { const response = await route.fetch(); const json = await response.json(); - // Remove currentProjectId to prevent restoring a project + // Remove currentProjectId and clear projects to prevent auto-open if (json.settings) { json.settings.currentProjectId = null; + json.settings.projects = []; } await route.fulfill({ response, json }); }); - // Navigate to root - await page.goto('/'); + await authenticateForTests(page); + + // Navigate directly to dashboard to avoid auto-open logic + await page.goto('/dashboard'); await page.waitForLoadState('load'); await handleLoginScreenIfPresent(page); - // Wait for welcome view to be visible - await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 15000 }); + // Wait for dashboard view + await expect(page.locator('[data-testid="dashboard-view"]')).toBeVisible({ timeout: 15000 }); await page.locator('[data-testid="create-new-project"]').click(); await page.locator('[data-testid="quick-setup-option"]').click(); @@ -67,10 +69,18 @@ test.describe('Project Creation', () => { await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); + // Expand sidebar if collapsed to see project name + const expandSidebarButton = page.locator('button:has-text("Expand sidebar")'); + if (await expandSidebarButton.isVisible()) { + await expandSidebarButton.click(); + await page.waitForTimeout(300); + } + // Wait for project to be set as current and visible on the page - // The project name appears in multiple places: project-selector, board header paragraph, etc. - // Check any element containing the project name - await expect(page.getByText(projectName).first()).toBeVisible({ timeout: 15000 }); + // The project name appears in the sidebar project selector button + await expect(page.getByRole('button', { name: new RegExp(projectName) })).toBeVisible({ + timeout: 15000, + }); // Project was created successfully if we're on board view with project name visible // Note: The actual project directory is created in the server's default workspace, diff --git a/apps/ui/tests/projects/open-existing-project.spec.ts b/apps/ui/tests/projects/open-existing-project.spec.ts index 8135538e..7111d119 100644 --- a/apps/ui/tests/projects/open-existing-project.spec.ts +++ b/apps/ui/tests/projects/open-existing-project.spec.ts @@ -113,12 +113,13 @@ test.describe('Open Project', () => { // Now navigate to the app await authenticateForTests(page); - await page.goto('/'); + // Navigate directly to dashboard to avoid auto-open which would bypass the project selection + await page.goto('/dashboard'); await page.waitForLoadState('load'); await handleLoginScreenIfPresent(page); - // Wait for welcome view to be visible - await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 15000 }); + // Wait for dashboard view + await expect(page.locator('[data-testid="dashboard-view"]')).toBeVisible({ timeout: 15000 }); // Verify we see the "Recent Projects" section await expect(page.getByText('Recent Projects')).toBeVisible({ timeout: 5000 }); @@ -135,7 +136,7 @@ test.describe('Open Project', () => { if (!isOurProjectVisible) { // Our project isn't visible - use the first available recent project card instead // This tests the "open recent project" flow even if our specific project didn't get injected - const firstProjectCard = page.locator('[data-testid^="recent-project-"]').first(); + const firstProjectCard = page.locator('[data-testid^="project-card-"]').first(); await expect(firstProjectCard).toBeVisible({ timeout: 5000 }); // Get the project name from the card to verify later targetProjectName = (await firstProjectCard.locator('p').first().textContent()) || ''; @@ -147,10 +148,19 @@ test.describe('Open Project', () => { // Wait for the board view to appear (project was opened) await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); + // Expand sidebar if collapsed to see project name + const expandSidebarButton = page.locator('button:has-text("Expand sidebar")'); + if (await expandSidebarButton.isVisible()) { + await expandSidebarButton.click(); + await page.waitForTimeout(300); + } + // Wait for a project to be set as current and visible on the page - // The project name appears in multiple places: project-selector, board header paragraph, etc. + // The project name appears in the sidebar project selector button if (targetProjectName) { - await expect(page.getByText(targetProjectName).first()).toBeVisible({ timeout: 15000 }); + await expect(page.getByRole('button', { name: new RegExp(targetProjectName) })).toBeVisible({ + timeout: 15000, + }); } // Only verify filesystem if we opened our specific test project diff --git a/apps/ui/tests/settings/settings-startup-sync-race.spec.ts b/apps/ui/tests/settings/settings-startup-sync-race.spec.ts index 4676dba4..c8543ff0 100644 --- a/apps/ui/tests/settings/settings-startup-sync-race.spec.ts +++ b/apps/ui/tests/settings/settings-startup-sync-race.spec.ts @@ -93,7 +93,9 @@ test.describe('Settings startup sync race', () => { // App should eventually render a main view after settings hydration. await page - .locator('[data-testid="welcome-view"], [data-testid="board-view"]') + .locator( + '[data-testid="welcome-view"], [data-testid="dashboard-view"], [data-testid="board-view"]' + ) .first() .waitFor({ state: 'visible', timeout: 30000 }); @@ -112,7 +114,9 @@ test.describe('Settings startup sync race', () => { await page.waitForLoadState('load'); await handleLoginScreenIfPresent(page); await page - .locator('[data-testid="welcome-view"], [data-testid="board-view"]') + .locator( + '[data-testid="welcome-view"], [data-testid="dashboard-view"], [data-testid="board-view"]' + ) .first() .waitFor({ state: 'visible', timeout: 30000 }); diff --git a/apps/ui/tests/utils/core/constants.ts b/apps/ui/tests/utils/core/constants.ts index 9e32f199..92439337 100644 --- a/apps/ui/tests/utils/core/constants.ts +++ b/apps/ui/tests/utils/core/constants.ts @@ -89,6 +89,7 @@ export const TEST_IDS = { agentView: 'agent-view', settingsView: 'settings-view', welcomeView: 'welcome-view', + dashboardView: 'dashboard-view', setupView: 'setup-view', // Board View Components diff --git a/apps/ui/tests/utils/core/interactions.ts b/apps/ui/tests/utils/core/interactions.ts index 9c52dd1f..d6ebfa66 100644 --- a/apps/ui/tests/utils/core/interactions.ts +++ b/apps/ui/tests/utils/core/interactions.ts @@ -75,28 +75,44 @@ export async function handleLoginScreenIfPresent(page: Page): Promise { .locator('[data-testid="login-api-key-input"], input[type="password"][placeholder*="API key"]') .first(); const appContent = page.locator( - '[data-testid="welcome-view"], [data-testid="board-view"], [data-testid="context-view"], [data-testid="agent-view"]' + '[data-testid="welcome-view"], [data-testid="dashboard-view"], [data-testid="board-view"], [data-testid="context-view"], [data-testid="agent-view"]' ); + const loggedOutPage = page.getByRole('heading', { name: /logged out/i }); + const goToLoginButton = page.locator('button:has-text("Go to login")'); const maxWaitMs = 15000; - // Race between login screen, a delayed redirect to /login, and actual content - const loginVisible = await Promise.race([ + // Race between login screen, logged-out page, a delayed redirect to /login, and actual content + const result = await Promise.race([ page .waitForURL((url) => url.pathname.includes('/login'), { timeout: maxWaitMs }) - .then(() => true) - .catch(() => false), + .then(() => 'login-redirect' as const) + .catch(() => null), loginInput .waitFor({ state: 'visible', timeout: maxWaitMs }) - .then(() => true) - .catch(() => false), + .then(() => 'login-input' as const) + .catch(() => null), + loggedOutPage + .waitFor({ state: 'visible', timeout: maxWaitMs }) + .then(() => 'logged-out' as const) + .catch(() => null), appContent .first() .waitFor({ state: 'visible', timeout: maxWaitMs }) - .then(() => false) - .catch(() => false), + .then(() => 'app-content' as const) + .catch(() => null), ]); + // Handle logged-out page - click "Go to login" button and then login + if (result === 'logged-out') { + await goToLoginButton.click(); + await page.waitForLoadState('load'); + // Now handle the login screen + return handleLoginScreenIfPresent(page); + } + + const loginVisible = result === 'login-redirect' || result === 'login-input'; + if (loginVisible) { const apiKey = process.env.AUTOMAKER_API_KEY || 'test-api-key-for-e2e-tests'; await loginInput.fill(apiKey); diff --git a/apps/ui/tests/utils/navigation/views.ts b/apps/ui/tests/utils/navigation/views.ts index d83f90f4..5bc96062 100644 --- a/apps/ui/tests/utils/navigation/views.ts +++ b/apps/ui/tests/utils/navigation/views.ts @@ -152,7 +152,8 @@ export async function navigateToSetup(page: Page): Promise { } /** - * Navigate to the welcome view (clear project selection) + * Navigate to the welcome/dashboard view (clear project selection) + * Note: The app redirects from / to /dashboard when no project is selected */ export async function navigateToWelcome(page: Page): Promise { // Authenticate before navigating @@ -167,7 +168,11 @@ export async function navigateToWelcome(page: Page): Promise { // Handle login redirect if needed await handleLoginScreenIfPresent(page); - await waitForElement(page, 'welcome-view', { timeout: 10000 }); + // Wait for either welcome-view or dashboard-view (app redirects to /dashboard when no project) + await page + .locator('[data-testid="welcome-view"], [data-testid="dashboard-view"]') + .first() + .waitFor({ state: 'visible', timeout: 10000 }); } /**