mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-30 06:12:03 +00:00
Wrap route.fetch() and response.json() in try/catch blocks to handle cases where the response is disposed before it can be accessed. Falls back to route.continue() to let the original request proceed normally. This fixes the intermittent "Response has been disposed" error in open-existing-project.spec.ts that occurs due to timing issues in CI.
211 lines
7.5 KiB
TypeScript
211 lines
7.5 KiB
TypeScript
/**
|
|
* 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,
|
|
authenticateForTests,
|
|
handleLoginScreenIfPresent,
|
|
waitForNetworkIdle,
|
|
sanitizeForTestId,
|
|
} 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
|
|
},
|
|
],
|
|
});
|
|
|
|
// Intercept settings API BEFORE any navigation to prevent restoring a currentProject
|
|
// AND inject our test project into the projects list
|
|
await page.route('**/api/settings/global', async (route) => {
|
|
let response;
|
|
try {
|
|
response = await route.fetch();
|
|
} catch {
|
|
// If fetch fails, continue with original request
|
|
await route.continue();
|
|
return;
|
|
}
|
|
|
|
let json;
|
|
try {
|
|
json = await response.json();
|
|
} catch {
|
|
// If response is disposed, continue with original request
|
|
await route.continue();
|
|
return;
|
|
}
|
|
|
|
if (json.settings) {
|
|
// Remove currentProjectId to prevent restoring a project
|
|
json.settings.currentProjectId = null;
|
|
|
|
// Inject the test project into settings
|
|
const testProject = {
|
|
id: projectId,
|
|
name: projectName,
|
|
path: projectPath,
|
|
lastOpened: new Date(Date.now() - 86400000).toISOString(),
|
|
};
|
|
|
|
// Add to existing projects (or create array)
|
|
const existingProjects = json.settings.projects || [];
|
|
const hasProject = existingProjects.some((p: any) => p.id === projectId);
|
|
if (!hasProject) {
|
|
json.settings.projects = [testProject, ...existingProjects];
|
|
}
|
|
}
|
|
await route.fulfill({ response, json });
|
|
});
|
|
|
|
// Now navigate to the app
|
|
await authenticateForTests(page);
|
|
// 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 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 });
|
|
|
|
// Look for our test project by name OR any available project
|
|
// First try our specific project, if not found, use the first available project card
|
|
let recentProjectCard = page.getByText(projectName).first();
|
|
let targetProjectName = projectName;
|
|
|
|
const isOurProjectVisible = await recentProjectCard
|
|
.isVisible({ timeout: 3000 })
|
|
.catch(() => false);
|
|
|
|
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^="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()) || '';
|
|
recentProjectCard = firstProjectCard;
|
|
}
|
|
|
|
await recentProjectCard.click();
|
|
|
|
// 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 the project switcher button
|
|
// Use ends-with selector since data-testid format is: project-switcher-{id}-{sanitizedName}
|
|
if (targetProjectName) {
|
|
const sanitizedName = sanitizeForTestId(targetProjectName);
|
|
await expect(page.locator(`[data-testid$="-${sanitizedName}"]`)).toBeVisible({
|
|
timeout: 15000,
|
|
});
|
|
}
|
|
|
|
// Only verify filesystem if we opened our specific test project
|
|
// (not a fallback project from previous test runs)
|
|
if (targetProjectName === projectName) {
|
|
// 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
|
|
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);
|
|
}
|
|
});
|
|
});
|