From 0c508ce1305f3da3e3b3f87c2d9bcb021a84b02d Mon Sep 17 00:00:00 2001 From: Test User Date: Mon, 22 Dec 2025 12:49:48 -0500 Subject: [PATCH] feat: add end-to-end testing guide and project creation tests - Introduced a comprehensive E2E Testing Guide outlining best practices for Playwright tests, including principles for test isolation, element selection, and setup utilities. - Added new test files for project creation and opening existing projects, ensuring functionality for creating blank projects and projects from GitHub templates. - Implemented utility functions for setting up test states and managing localStorage, enhancing maintainability and reducing boilerplate in tests. --- apps/ui/tests/e2e-testing-guide.md | 306 +++++++++++++++++++++++++ apps/ui/tests/open-project.spec.ts | 125 ++++++++++ apps/ui/tests/project-creation.spec.ts | 188 +++++++++++++++ apps/ui/tests/utils/project/setup.ts | 165 ++++++++++++- 4 files changed, 781 insertions(+), 3 deletions(-) create mode 100644 apps/ui/tests/e2e-testing-guide.md create mode 100644 apps/ui/tests/open-project.spec.ts create mode 100644 apps/ui/tests/project-creation.spec.ts diff --git a/apps/ui/tests/e2e-testing-guide.md b/apps/ui/tests/e2e-testing-guide.md new file mode 100644 index 00000000..8b07dd53 --- /dev/null +++ b/apps/ui/tests/e2e-testing-guide.md @@ -0,0 +1,306 @@ +# E2E Testing Guide + +Best practices and patterns for writing reliable, non-flaky Playwright e2e tests in this codebase. + +## Core Principles + +1. **No arbitrary timeouts** - Never use `page.waitForTimeout()`. Always wait for specific conditions. +2. **Use data-testid attributes** - Prefer `[data-testid="..."]` selectors over CSS classes or text content. +3. **Clean up after tests** - Use unique temp directories and clean them up in `afterAll`. +4. **Test isolation** - Each test should be independent and not rely on state from other tests. + +## Setting Up Test State + +### Use Setup Utilities (Recommended) + +Use the provided utility functions to set up localStorage state. These utilities hide the internal store structure and version details, making tests more maintainable. + +```typescript +import { setupWelcomeView, setupRealProject } from './utils'; + +// Show welcome view with workspace directory configured +await setupWelcomeView(page, { workspaceDir: TEST_TEMP_DIR }); + +// Show welcome view with recent projects +await setupWelcomeView(page, { + workspaceDir: TEST_TEMP_DIR, + recentProjects: [ + { + id: 'project-123', + name: 'My Project', + path: '/path/to/project', + lastOpened: new Date().toISOString(), + }, + ], +}); + +// Set up a real project on the filesystem +await setupRealProject(page, projectPath, projectName, { + setAsCurrent: true, // Opens board view (default) +}); +``` + +### Why Use Utilities Instead of Raw localStorage + +1. **Version management** - Store versions are centralized in one place +2. **Less brittle** - If store structure changes, update one file instead of every test +3. **Cleaner tests** - Focus on test logic, not setup boilerplate +4. **Type safety** - Utilities provide typed interfaces for test data + +### Manual LocalStorage Setup (Advanced) + +If you need custom setup not covered by utilities, use `page.addInitScript()`. +Store versions are defined in `tests/utils/project/setup.ts`: + +- `APP_STORE`: version 2 (matches `app-store.ts`) +- `SETUP_STORE`: version 0 (matches `setup-store.ts` default) + +### Temp Directory Management + +Create unique temp directories for test isolation: + +```typescript +import { createTempDirPath, cleanupTempDir } from './utils'; + +const TEST_TEMP_DIR = createTempDirPath('my-test-name'); + +test.describe('My Tests', () => { + test.beforeAll(async () => { + if (!fs.existsSync(TEST_TEMP_DIR)) { + fs.mkdirSync(TEST_TEMP_DIR, { recursive: true }); + } + }); + + test.afterAll(async () => { + cleanupTempDir(TEST_TEMP_DIR); + }); +}); +``` + +## Waiting for Elements + +### Prefer `toBeVisible()` over `waitForSelector()` + +```typescript +// Good - uses Playwright's auto-waiting with expect +await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 }); + +// Avoid - manual waiting +await page.waitForSelector('[data-testid="welcome-view"]'); +``` + +### Wait for network idle after navigation + +```typescript +await page.goto('/'); +await page.waitForLoadState('networkidle'); +``` + +### Use appropriate timeouts + +- Quick UI updates: 5000ms (default) +- Page loads/navigation: 10000ms +- Async operations (API calls, file system): 15000ms + +```typescript +// Fast UI element +await expect(button).toBeVisible(); + +// Page load +await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 10000 }); + +// Async operation completion +await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); +``` + +## Element Selection + +### Use data-testid attributes + +```typescript +// Good - stable selector +const button = page.locator('[data-testid="create-new-project"]'); + +// Avoid - brittle selectors +const button = page.locator('.btn-primary'); +const button = page.getByText('Create'); +``` + +### Scope selectors when needed + +When text appears in multiple places, scope to a parent: + +```typescript +// Bad - might match multiple elements +await expect(page.getByText(projectName)).toBeVisible(); + +// Good - scoped to specific container +await expect(page.locator('[data-testid="project-selector"]').getByText(projectName)).toBeVisible(); +``` + +### Handle strict mode violations + +If a selector matches multiple elements: + +```typescript +// Use .first() if you need the first match +await page.locator('[data-testid="item"]').first().click(); + +// Or scope to a unique parent +await page.locator('[data-testid="sidebar"]').locator('[data-testid="item"]').click(); +``` + +## Clicking Elements + +### Always verify visibility before clicking + +```typescript +const button = page.locator('[data-testid="submit"]'); +await expect(button).toBeVisible(); +await button.click(); +``` + +### Handle dialogs that may close quickly + +Some dialogs may appear briefly or auto-close. Don't rely on clicking them: + +```typescript +// Instead of trying to close a dialog that might disappear: +// await expect(dialog).toBeVisible(); +// await closeButton.click(); // May fail if dialog closes first + +// Just verify the end state: +await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); +``` + +## Filesystem Verification + +Verify files were created after async operations: + +```typescript +// Wait for UI to confirm operation completed first +await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); + +// Then verify filesystem +const projectPath = path.join(TEST_TEMP_DIR, projectName); +expect(fs.existsSync(projectPath)).toBe(true); + +const appSpecPath = path.join(projectPath, '.automaker', 'app_spec.txt'); +expect(fs.existsSync(appSpecPath)).toBe(true); + +const content = fs.readFileSync(appSpecPath, 'utf-8'); +expect(content).toContain(projectName); +``` + +## Test Structure + +### Use descriptive test names + +```typescript +test('should create a new blank project from welcome view', async ({ page }) => { + // ... +}); +``` + +### Group related tests with describe blocks + +```typescript +test.describe('Project Creation', () => { + test('should create a new blank project from welcome view', ...); + test('should create a project from template', ...); +}); +``` + +### Use serial mode when tests depend on each other + +```typescript +test.describe.configure({ mode: 'serial' }); +``` + +## Common Patterns + +### Waiting for either of two outcomes + +When multiple outcomes are possible (e.g., dialog or direct navigation): + +```typescript +// Wait for either the dialog or the board view +await Promise.race([ + initDialog.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {}), + boardView.waitFor({ state: 'visible', timeout: 10000 }).catch(() => {}), +]); + +// Then handle whichever appeared +if (await initDialog.isVisible()) { + await closeButton.click(); +} + +await expect(boardView).toBeVisible(); +``` + +### Generating unique test data + +```typescript +const projectName = `test-project-${Date.now()}`; +``` + +## Running Tests + +```bash +# Run all tests +npm run test + +# Run specific test file +npm run test -- project-creation.spec.ts + +# Run with headed browser (see what's happening) +npm run test:headed -- project-creation.spec.ts + +# Run multiple times to check for flakiness +npm run test -- project-creation.spec.ts --repeat-each=5 +``` + +## Debugging Failed Tests + +1. Check the screenshot in `test-results/` +2. Read the error context markdown file in `test-results/` +3. Run with `--headed` to watch the test +4. Add `await page.pause()` to pause execution at a specific point + +## Available Test Utilities + +Import from `./utils`: + +### State Setup Utilities + +- `setupWelcomeView(page, options?)` - Set up empty state showing welcome view + - `options.workspaceDir` - Pre-configure workspace directory + - `options.recentProjects` - Add projects to recent list (not current) +- `setupRealProject(page, path, name, options?)` - Set up state with a real filesystem project + - `options.setAsCurrent` - Open board view (default: true) + - `options.additionalProjects` - Add more projects to list +- `setupMockProject(page)` - Set up mock project for unit-style tests +- `setupComplete(page)` - Mark setup wizard as complete + +### Filesystem Utilities + +- `createTempDirPath(prefix)` - Create unique temp directory path +- `cleanupTempDir(path)` - Remove temp directory +- `createTestGitRepo(tempDir)` - Create a git repo for testing + +### Waiting Utilities + +- `waitForNetworkIdle(page)` - Wait for network to be idle +- `waitForElement(page, testId)` - Wait for element by test ID + +### Async File Verification + +Use `expect().toPass()` for polling filesystem operations: + +```typescript +await expect(async () => { + expect(fs.existsSync(filePath)).toBe(true); +}).toPass({ timeout: 10000 }); +``` + +See `tests/utils/index.ts` for the full list of available utilities. diff --git a/apps/ui/tests/open-project.spec.ts b/apps/ui/tests/open-project.spec.ts new file mode 100644 index 00000000..87885604 --- /dev/null +++ b/apps/ui/tests/open-project.spec.ts @@ -0,0 +1,125 @@ +/** + * Open Project End-to-End Test + * + * Tests opening an existing project directory from the welcome view. + * This verifies that: + * 1. An existing directory can be opened as a project + * 2. The .automaker directory is initialized if it doesn't exist + * 3. The project is loaded and shown in the board view + */ + +import { test, expect } from '@playwright/test'; +import * as fs from 'fs'; +import * as path from 'path'; +import { createTempDirPath, cleanupTempDir, setupWelcomeView } from './utils'; + +// Create unique temp dir for this test run +const TEST_TEMP_DIR = createTempDirPath('open-project-test'); + +test.describe('Open Project', () => { + test.beforeAll(async () => { + // Create test temp directory + if (!fs.existsSync(TEST_TEMP_DIR)) { + fs.mkdirSync(TEST_TEMP_DIR, { recursive: true }); + } + }); + + test.afterAll(async () => { + // Cleanup temp directory + cleanupTempDir(TEST_TEMP_DIR); + }); + + test('should open an existing project directory from recent projects', async ({ page }) => { + const projectName = `existing-project-${Date.now()}`; + const projectPath = path.join(TEST_TEMP_DIR, projectName); + const projectId = `project-${Date.now()}`; + + // Create the project directory with some files to simulate an existing codebase + fs.mkdirSync(projectPath, { recursive: true }); + + // Create a package.json to simulate a real project + fs.writeFileSync( + path.join(projectPath, 'package.json'), + JSON.stringify( + { + name: projectName, + version: '1.0.0', + description: 'A test project for e2e testing', + }, + null, + 2 + ) + ); + + // Create a README.md + fs.writeFileSync(path.join(projectPath, 'README.md'), `# ${projectName}\n\nA test project.`); + + // Create a src directory with an index.ts file + fs.mkdirSync(path.join(projectPath, 'src'), { recursive: true }); + fs.writeFileSync( + path.join(projectPath, 'src', 'index.ts'), + 'export const hello = () => console.log("Hello World");' + ); + + // Set up welcome view with the project in recent projects (but NOT as current project) + await setupWelcomeView(page, { + recentProjects: [ + { + id: projectId, + name: projectName, + path: projectPath, + lastOpened: new Date(Date.now() - 86400000).toISOString(), // 1 day ago + }, + ], + }); + + // Navigate to the app + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + // Wait for welcome view to be visible + await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 }); + + // Verify we see the "Recent Projects" section + await expect(page.getByText('Recent Projects')).toBeVisible({ timeout: 5000 }); + + // Click on the recent project to open it + const recentProjectCard = page.locator(`[data-testid="recent-project-${projectId}"]`); + await expect(recentProjectCard).toBeVisible(); + await recentProjectCard.click(); + + // Wait for the board view to appear (project was opened) + await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); + + // Verify the project name appears in the project selector (sidebar) + await expect( + page.locator('[data-testid="project-selector"]').getByText(projectName) + ).toBeVisible({ timeout: 5000 }); + + // Verify .automaker directory was created (initialized for the first time) + // Use polling since file creation may be async + const automakerDir = path.join(projectPath, '.automaker'); + await expect(async () => { + expect(fs.existsSync(automakerDir)).toBe(true); + }).toPass({ timeout: 10000 }); + + // Verify the required structure was created by initializeProject: + // - .automaker/categories.json + // - .automaker/features directory + // - .automaker/context directory + // Note: app_spec.txt is NOT created automatically for existing projects + const categoriesPath = path.join(automakerDir, 'categories.json'); + await expect(async () => { + expect(fs.existsSync(categoriesPath)).toBe(true); + }).toPass({ timeout: 10000 }); + + // Verify subdirectories were created + expect(fs.existsSync(path.join(automakerDir, 'features'))).toBe(true); + expect(fs.existsSync(path.join(automakerDir, 'context'))).toBe(true); + + // Verify the original project files still exist (weren't modified) + expect(fs.existsSync(path.join(projectPath, 'package.json'))).toBe(true); + expect(fs.existsSync(path.join(projectPath, 'README.md'))).toBe(true); + expect(fs.existsSync(path.join(projectPath, 'src', 'index.ts'))).toBe(true); + }); +}); diff --git a/apps/ui/tests/project-creation.spec.ts b/apps/ui/tests/project-creation.spec.ts new file mode 100644 index 00000000..9d71d4ff --- /dev/null +++ b/apps/ui/tests/project-creation.spec.ts @@ -0,0 +1,188 @@ +/** + * Project Creation End-to-End Tests + * + * Tests the project creation flows: + * 1. Creating a new blank project from the welcome view + * 2. Creating a new project from a GitHub template + */ + +import { test, expect } from '@playwright/test'; +import * as fs from 'fs'; +import * as path from 'path'; +import { createTempDirPath, cleanupTempDir, setupWelcomeView } from './utils'; + +// Create unique temp dir for this test run +const TEST_TEMP_DIR = createTempDirPath('project-creation-test'); + +test.describe('Project Creation', () => { + test.beforeAll(async () => { + // Create test temp directory + if (!fs.existsSync(TEST_TEMP_DIR)) { + fs.mkdirSync(TEST_TEMP_DIR, { recursive: true }); + } + }); + + test.afterAll(async () => { + // Cleanup temp directory + cleanupTempDir(TEST_TEMP_DIR); + }); + + test('should create a new blank project from welcome view', async ({ page }) => { + const projectName = `test-project-${Date.now()}`; + + // Set up welcome view with workspace directory pre-configured + await setupWelcomeView(page, { workspaceDir: TEST_TEMP_DIR }); + + // Navigate to the app + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + // Wait for welcome view to be visible + await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 }); + + // Click the "Create New Project" dropdown button + const createButton = page.locator('[data-testid="create-new-project"]'); + await expect(createButton).toBeVisible(); + await createButton.click(); + + // Click "Quick Setup" option from the dropdown + const quickSetupOption = page.locator('[data-testid="quick-setup-option"]'); + await expect(quickSetupOption).toBeVisible(); + await quickSetupOption.click(); + + // Wait for the new project modal to appear + const modal = page.locator('[data-testid="new-project-modal"]'); + await expect(modal).toBeVisible({ timeout: 5000 }); + + // Enter the project name + const projectNameInput = page.locator('[data-testid="project-name-input"]'); + await expect(projectNameInput).toBeVisible(); + await projectNameInput.fill(projectName); + + // Verify the workspace directory is shown (from our pre-configured localStorage) + // Wait for workspace to be loaded (it shows "Will be created at:" when ready) + await expect(page.getByText('Will be created at:')).toBeVisible({ timeout: 5000 }); + + // Click the Create Project button + const createProjectButton = page.locator('[data-testid="confirm-create-project"]'); + await expect(createProjectButton).toBeVisible(); + await createProjectButton.click(); + + // Wait for project creation to complete + // The app may show an init dialog briefly and then navigate to board view + // We just need to verify we end up on the board view with our project + + // Wait for the board view - this confirms the project was created and opened + await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 }); + + // Verify the project name appears in the project selector (sidebar) + await expect( + page.locator('[data-testid="project-selector"]').getByText(projectName) + ).toBeVisible({ timeout: 5000 }); + + // Verify the project was created in the filesystem + const projectPath = path.join(TEST_TEMP_DIR, projectName); + expect(fs.existsSync(projectPath)).toBe(true); + + // Verify .automaker directory was created + const automakerDir = path.join(projectPath, '.automaker'); + expect(fs.existsSync(automakerDir)).toBe(true); + + // Verify app_spec.txt was created + const appSpecPath = path.join(automakerDir, 'app_spec.txt'); + expect(fs.existsSync(appSpecPath)).toBe(true); + + // Verify the app_spec.txt contains the project name + const appSpecContent = fs.readFileSync(appSpecPath, 'utf-8'); + expect(appSpecContent).toContain(projectName); + }); + + test('should create a new project from GitHub template', async ({ page }) => { + // Increase timeout for this test since git clone takes time + test.setTimeout(60000); + + const projectName = `template-project-${Date.now()}`; + + // Set up welcome view with workspace directory pre-configured + await setupWelcomeView(page, { workspaceDir: TEST_TEMP_DIR }); + + // Navigate to the app + await page.goto('/'); + await page.waitForLoadState('networkidle'); + + // Wait for welcome view to be visible + await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 }); + + // Click the "Create New Project" dropdown button + const createButton = page.locator('[data-testid="create-new-project"]'); + await expect(createButton).toBeVisible(); + await createButton.click(); + + // Click "Quick Setup" option from the dropdown + const quickSetupOption = page.locator('[data-testid="quick-setup-option"]'); + await expect(quickSetupOption).toBeVisible(); + await quickSetupOption.click(); + + // Wait for the new project modal to appear + const modal = page.locator('[data-testid="new-project-modal"]'); + await expect(modal).toBeVisible({ timeout: 5000 }); + + // Enter the project name first + const projectNameInput = page.locator('[data-testid="project-name-input"]'); + await expect(projectNameInput).toBeVisible(); + await projectNameInput.fill(projectName); + + // Wait for workspace directory to be loaded + await expect(page.getByText('Will be created at:')).toBeVisible({ timeout: 5000 }); + + // Click on the "Starter Kit" tab + const starterKitTab = modal.getByText('Starter Kit'); + await expect(starterKitTab).toBeVisible(); + await starterKitTab.click(); + + // Select the first template (Automaker Starter Kit) + const firstTemplate = page.locator('[data-testid="template-automaker-starter-kit"]'); + await expect(firstTemplate).toBeVisible(); + await firstTemplate.click(); + + // Verify the template is selected (check mark should appear) + await expect(firstTemplate.locator('.lucide-check')).toBeVisible(); + + // Click the Create Project button + const createProjectButton = page.locator('[data-testid="confirm-create-project"]'); + await expect(createProjectButton).toBeVisible(); + await createProjectButton.click(); + + // Wait for git clone to complete and board view to appear + // This takes longer due to the git clone operation + await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 45000 }); + + // Verify the project name appears in the project selector (sidebar) + await expect( + page.locator('[data-testid="project-selector"]').getByText(projectName) + ).toBeVisible({ timeout: 5000 }); + + // Verify the project was cloned in the filesystem + const projectPath = path.join(TEST_TEMP_DIR, projectName); + expect(fs.existsSync(projectPath)).toBe(true); + + // Verify .automaker directory was created + const automakerDir = path.join(projectPath, '.automaker'); + expect(fs.existsSync(automakerDir)).toBe(true); + + // Verify app_spec.txt was created with template info + const appSpecPath = path.join(automakerDir, 'app_spec.txt'); + expect(fs.existsSync(appSpecPath)).toBe(true); + const appSpecContent = fs.readFileSync(appSpecPath, 'utf-8'); + expect(appSpecContent).toContain(projectName); + expect(appSpecContent).toContain('Automaker Starter Kit'); + + // Verify the template files were cloned (check for package.json which should exist in the template) + const packageJsonPath = path.join(projectPath, 'package.json'); + expect(fs.existsSync(packageJsonPath)).toBe(true); + + // Verify it's a git repository (cloned from GitHub) + const gitDir = path.join(projectPath, '.git'); + expect(fs.existsSync(gitDir)).toBe(true); + }); +}); diff --git a/apps/ui/tests/utils/project/setup.ts b/apps/ui/tests/utils/project/setup.ts index ee24b376..23f87d8d 100644 --- a/apps/ui/tests/utils/project/setup.ts +++ b/apps/ui/tests/utils/project/setup.ts @@ -1,5 +1,164 @@ import { Page } from '@playwright/test'; +/** + * Store version constants - centralized to avoid hardcoding across tests + * These MUST match the versions used in the actual stores + */ +const STORE_VERSIONS = { + APP_STORE: 2, // Must match app-store.ts persist version + SETUP_STORE: 0, // setup-store.ts doesn't specify a version, so zustand defaults to 0 +} as const; + +/** + * Project interface for test setup + */ +export interface TestProject { + id: string; + name: string; + path: string; + lastOpened?: string; +} + +/** + * Options for setting up the welcome view + */ +export interface WelcomeViewSetupOptions { + /** Directory path to pre-configure as the workspace directory */ + workspaceDir?: string; + /** Recent projects to show (but not as current project) */ + recentProjects?: TestProject[]; +} + +/** + * Set up localStorage to show the welcome view with no current project + * This is the cleanest way to test project creation flows + * + * @param page - Playwright page + * @param options - Configuration options + */ +export async function setupWelcomeView( + page: Page, + options?: WelcomeViewSetupOptions +): Promise { + await page.addInitScript( + ({ + opts, + versions, + }: { + opts: WelcomeViewSetupOptions | undefined; + versions: typeof STORE_VERSIONS; + }) => { + // Set up empty app state (no current project) - shows welcome view + const appState = { + state: { + projects: opts?.recentProjects || [], + currentProject: null, + currentView: 'welcome', + theme: 'dark', + sidebarOpen: true, + apiKeys: { anthropic: '', google: '' }, + chatSessions: [], + chatHistoryOpen: false, + maxConcurrency: 3, + }, + version: versions.APP_STORE, + }; + localStorage.setItem('automaker-storage', JSON.stringify(appState)); + + // Mark setup as complete to skip the setup wizard + const setupState = { + state: { + isFirstRun: false, + setupComplete: true, + skipClaudeSetup: false, + }, + version: versions.SETUP_STORE, + }; + localStorage.setItem('automaker-setup', JSON.stringify(setupState)); + + // Set workspace directory if provided + if (opts?.workspaceDir) { + localStorage.setItem('automaker:lastProjectDir', opts.workspaceDir); + } + }, + { opts: options, versions: STORE_VERSIONS } + ); +} + +/** + * Set up localStorage with a project at a real filesystem path + * Use this when testing with actual files on disk + * + * @param page - Playwright page + * @param projectPath - Absolute path to the project directory + * @param projectName - Display name for the project + * @param options - Additional options + */ +export async function setupRealProject( + page: Page, + projectPath: string, + projectName: string, + options?: { + /** Set as current project (opens board view) or just add to recent projects */ + setAsCurrent?: boolean; + /** Additional recent projects to include */ + additionalProjects?: TestProject[]; + } +): Promise { + await page.addInitScript( + ({ + path, + name, + opts, + versions, + }: { + path: string; + name: string; + opts: typeof options; + versions: typeof STORE_VERSIONS; + }) => { + const projectId = `project-${Date.now()}`; + const project: TestProject = { + id: projectId, + name: name, + path: path, + lastOpened: new Date().toISOString(), + }; + + const allProjects = [project, ...(opts?.additionalProjects || [])]; + const currentProject = opts?.setAsCurrent !== false ? project : null; + + const appState = { + state: { + projects: allProjects, + currentProject: currentProject, + currentView: currentProject ? 'board' : 'welcome', + theme: 'dark', + sidebarOpen: true, + apiKeys: { anthropic: '', google: '' }, + chatSessions: [], + chatHistoryOpen: false, + maxConcurrency: 3, + }, + version: versions.APP_STORE, + }; + localStorage.setItem('automaker-storage', JSON.stringify(appState)); + + // Mark setup as complete + const setupState = { + state: { + isFirstRun: false, + setupComplete: true, + skipClaudeSetup: false, + }, + version: versions.SETUP_STORE, + }; + localStorage.setItem('automaker-setup', JSON.stringify(setupState)); + }, + { path: projectPath, name: projectName, opts: options, versions: STORE_VERSIONS } + ); +} + /** * Set up a mock project in localStorage to bypass the welcome screen * This simulates having opened a project before @@ -595,7 +754,7 @@ export async function setupFirstRun(page: Page): Promise { * Set up the app to skip the setup wizard (setup already complete) */ export async function setupComplete(page: Page): Promise { - await page.addInitScript(() => { + await page.addInitScript((versions: typeof STORE_VERSIONS) => { // Mark setup as complete const setupState = { state: { @@ -604,11 +763,11 @@ export async function setupComplete(page: Page): Promise { currentStep: 'complete', skipClaudeSetup: false, }, - version: 2, // Must match app-store.ts persist version + version: versions.SETUP_STORE, }; localStorage.setItem('automaker-setup', JSON.stringify(setupState)); - }); + }, STORE_VERSIONS); } /**