mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-19 22:53:08 +00:00
Improve pull request flow, add branch selection for worktree creation, fix auto-mode concurrency count (#787)
* Changes from fix/fetch-before-pull-fetch * feat: Improve pull request flow, add branch selection for worktree creation, fix for automode concurrency count * feat: Add validation for remote names and improve error handling * Address PR comments and mobile layout fixes * ``` refactor: Extract PR target resolution logic into dedicated service ``` * feat: Add app shell UI and improve service imports. Address PR comments * fix: Improve security validation and cache handling in git operations * feat: Add GET /list endpoint and improve parameter handling * chore: Improve validation, accessibility, and error handling across apps * chore: Format vite server port configuration * fix: Add error handling for gh pr list command and improve offline fallbacks * fix: Preserve existing PR creation time and improve remote handling
This commit is contained in:
@@ -25,6 +25,10 @@ import {
|
||||
|
||||
const TEST_TEMP_DIR = createTempDirPath('manual-review-test');
|
||||
|
||||
// Generate deterministic projectId once at test module load so both
|
||||
// setupRealProject and the route interceptor use the same ID
|
||||
const TEST_PROJECT_ID = `project-manual-review-${Date.now()}`;
|
||||
|
||||
test.describe('Feature Manual Review Flow', () => {
|
||||
let projectPath: string;
|
||||
const projectName = `test-project-${Date.now()}`;
|
||||
@@ -71,34 +75,45 @@ test.describe('Feature Manual Review Flow', () => {
|
||||
});
|
||||
|
||||
test('should manually verify a feature in waiting_approval column', async ({ page }) => {
|
||||
// Set up the project in localStorage
|
||||
await setupRealProject(page, projectPath, projectName, { setAsCurrent: true });
|
||||
// Set up the project in localStorage with a deterministic projectId
|
||||
await setupRealProject(page, projectPath, projectName, {
|
||||
setAsCurrent: true,
|
||||
projectId: TEST_PROJECT_ID,
|
||||
});
|
||||
|
||||
// Intercept settings API to ensure our test project remains current
|
||||
// and doesn't get overridden by server settings
|
||||
// and doesn't get overridden by server settings.
|
||||
await page.route('**/api/settings/global', async (route) => {
|
||||
const method = route.request().method();
|
||||
if (method === 'PUT') {
|
||||
// Allow settings sync writes to pass through
|
||||
return route.continue();
|
||||
}
|
||||
const response = await route.fetch();
|
||||
const json = await response.json();
|
||||
if (json.settings) {
|
||||
// Set our test project as the current project
|
||||
const testProject = {
|
||||
id: `project-${projectName}`,
|
||||
name: projectName,
|
||||
path: projectPath,
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Add to projects if not already there
|
||||
const existingProjects = json.settings.projects || [];
|
||||
const hasProject = existingProjects.some(
|
||||
(p: { id: string; path: string }) => p.path === projectPath
|
||||
);
|
||||
if (!hasProject) {
|
||||
|
||||
// Find existing project by path to preserve any server-generated IDs
|
||||
let testProject = existingProjects.find((p: { path: string }) => p.path === projectPath);
|
||||
|
||||
if (!testProject) {
|
||||
// Project not in server response yet — use the same deterministic TEST_PROJECT_ID
|
||||
// that was seeded in automaker-settings-cache.currentProjectId via setupRealProject.
|
||||
testProject = {
|
||||
id: TEST_PROJECT_ID,
|
||||
name: projectName,
|
||||
path: projectPath,
|
||||
lastOpened: new Date().toISOString(),
|
||||
};
|
||||
json.settings.projects = [testProject, ...existingProjects];
|
||||
}
|
||||
|
||||
// Set as current project
|
||||
// Set as current project using the matching project's ID
|
||||
json.settings.currentProjectId = testProject.id;
|
||||
// Ensure CI runs don't redirect to /setup
|
||||
json.settings.setupComplete = true;
|
||||
json.settings.isFirstRun = false;
|
||||
}
|
||||
await route.fulfill({ response, json });
|
||||
});
|
||||
@@ -140,7 +155,7 @@ test.describe('Feature Manual Review Flow', () => {
|
||||
priority: 2,
|
||||
};
|
||||
|
||||
const API_BASE_URL = process.env.VITE_SERVER_URL || 'http://localhost:3008';
|
||||
const API_BASE_URL = process.env.SERVER_URL || 'http://localhost:3008';
|
||||
const createResponse = await page.request.post(`${API_BASE_URL}/api/features/create`, {
|
||||
data: { projectPath, feature },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -35,16 +35,42 @@ test.describe('Project Creation', () => {
|
||||
// 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 method = route.request().method();
|
||||
if (method === 'PUT') {
|
||||
// Allow settings sync writes to pass through
|
||||
return route.continue();
|
||||
}
|
||||
const response = await route.fetch();
|
||||
const json = await response.json();
|
||||
// Remove currentProjectId and clear projects to prevent auto-open
|
||||
if (json.settings) {
|
||||
json.settings.currentProjectId = null;
|
||||
json.settings.projects = [];
|
||||
// Ensure setup is marked complete to prevent redirect to /setup on fresh CI
|
||||
json.settings.setupComplete = true;
|
||||
json.settings.isFirstRun = false;
|
||||
// Preserve lastProjectDir so the new project modal knows where to create projects
|
||||
json.settings.lastProjectDir = TEST_TEMP_DIR;
|
||||
}
|
||||
await route.fulfill({ response, json });
|
||||
});
|
||||
|
||||
// Mock workspace config API to return a valid default directory.
|
||||
// In CI, ALLOWED_ROOT_DIRECTORY is unset and Documents path is unavailable,
|
||||
// so without this mock, getDefaultWorkspaceDirectory() returns null and the
|
||||
// "Will be created at:" text never renders in the new project modal.
|
||||
await page.route('**/api/workspace/config', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
configured: false,
|
||||
defaultDir: TEST_TEMP_DIR,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
await authenticateForTests(page);
|
||||
|
||||
// Navigate directly to dashboard to avoid auto-open logic
|
||||
|
||||
@@ -20,6 +20,67 @@ test.describe('Projects Overview Dashboard', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Set up mock projects state
|
||||
await setupMockMultipleProjects(page, 3);
|
||||
|
||||
// Intercept settings API to preserve mock project data and prevent
|
||||
// the server's settings from overriding our test setup.
|
||||
// Without this, background reconciliation can clear the mock projects.
|
||||
await page.route('**/api/settings/global', async (route) => {
|
||||
const method = route.request().method();
|
||||
if (method === 'PUT') {
|
||||
// Allow settings sync writes to pass through
|
||||
return route.continue();
|
||||
}
|
||||
const response = await route.fetch();
|
||||
const json = await response.json();
|
||||
if (json.settings) {
|
||||
// Always overwrite projects with mock data so CI-provided projects
|
||||
// that don't contain 'test-project-1' can't break hydration.
|
||||
json.settings.projects = [
|
||||
{
|
||||
id: 'test-project-1',
|
||||
name: 'Test Project 1',
|
||||
path: '/mock/test-project-1',
|
||||
lastOpened: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'test-project-2',
|
||||
name: 'Test Project 2',
|
||||
path: '/mock/test-project-2',
|
||||
lastOpened: new Date(Date.now() - 86400000).toISOString(),
|
||||
},
|
||||
{
|
||||
id: 'test-project-3',
|
||||
name: 'Test Project 3',
|
||||
path: '/mock/test-project-3',
|
||||
lastOpened: new Date(Date.now() - 172800000).toISOString(),
|
||||
},
|
||||
];
|
||||
json.settings.currentProjectId = 'test-project-1';
|
||||
json.settings.setupComplete = true;
|
||||
json.settings.isFirstRun = false;
|
||||
}
|
||||
await route.fulfill({ response, json });
|
||||
});
|
||||
|
||||
// Mock the initialize-project endpoint for mock paths that don't exist on disk.
|
||||
// This prevents auto-open from failing when it tries to verify the project directory.
|
||||
await page.route('**/api/project/initialize', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true }),
|
||||
});
|
||||
});
|
||||
|
||||
// Mock features list for mock project paths (they don't exist on disk)
|
||||
await page.route('**/api/features/list**', async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ success: true, features: [] }),
|
||||
});
|
||||
});
|
||||
|
||||
await authenticateForTests(page);
|
||||
});
|
||||
|
||||
|
||||
@@ -64,6 +64,10 @@ export async function gotoWithAuth(page: Page, url: string): Promise<void> {
|
||||
await page.goto(url);
|
||||
}
|
||||
|
||||
/** Selector matching any top-level app view by data-testid, used to detect that the app has loaded. */
|
||||
const APP_CONTENT_SELECTOR =
|
||||
'[data-testid="welcome-view"], [data-testid="dashboard-view"], [data-testid="board-view"], [data-testid="context-view"], [data-testid="agent-view"], [data-testid="overview-view"]';
|
||||
|
||||
/**
|
||||
* Handle login screen if it appears after navigation
|
||||
* Returns true if login was handled, false if no login screen was found
|
||||
@@ -74,9 +78,7 @@ export async function handleLoginScreenIfPresent(page: Page): Promise<boolean> {
|
||||
const loginInput = page
|
||||
.locator('[data-testid="login-api-key-input"], input[type="password"][placeholder*="API key"]')
|
||||
.first();
|
||||
const appContent = page.locator(
|
||||
'[data-testid="welcome-view"], [data-testid="dashboard-view"], [data-testid="board-view"], [data-testid="context-view"], [data-testid="agent-view"]'
|
||||
);
|
||||
const appContent = page.locator(APP_CONTENT_SELECTOR);
|
||||
const loggedOutPage = page.getByRole('heading', { name: /logged out/i });
|
||||
const goToLoginButton = page.locator('button:has-text("Go to login")');
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user