mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-02 08:33:36 +00:00
Merge main into massive-terminal-upgrade
Resolves merge conflicts: - apps/server/src/routes/terminal/common.ts: Keep randomBytes import, use @automaker/utils for createLogger - apps/ui/eslint.config.mjs: Use main's explicit globals list with XMLHttpRequest and MediaQueryListEvent additions - apps/ui/src/components/views/terminal-view.tsx: Keep our terminal improvements (killAllSessions, beforeunload, better error handling) - apps/ui/src/config/terminal-themes.ts: Keep our search highlight colors for all themes - apps/ui/src/store/app-store.ts: Keep our terminal settings persistence improvements (merge function) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,32 +1,23 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Get a kanban card by feature ID
|
||||
*/
|
||||
export async function getKanbanCard(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<Locator> {
|
||||
export async function getKanbanCard(page: Page, featureId: string): Promise<Locator> {
|
||||
return page.locator(`[data-testid="kanban-card-${featureId}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a kanban column by its ID
|
||||
*/
|
||||
export async function getKanbanColumn(
|
||||
page: Page,
|
||||
columnId: string
|
||||
): Promise<Locator> {
|
||||
export async function getKanbanColumn(page: Page, columnId: string): Promise<Locator> {
|
||||
return page.locator(`[data-testid="kanban-column-${columnId}"]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of a kanban column
|
||||
*/
|
||||
export async function getKanbanColumnWidth(
|
||||
page: Page,
|
||||
columnId: string
|
||||
): Promise<number> {
|
||||
export async function getKanbanColumnWidth(page: Page, columnId: string): Promise<number> {
|
||||
const column = page.locator(`[data-testid="kanban-column-${columnId}"]`);
|
||||
const box = await column.boundingBox();
|
||||
return box?.width ?? 0;
|
||||
@@ -35,19 +26,16 @@ export async function getKanbanColumnWidth(
|
||||
/**
|
||||
* Check if a kanban column has CSS columns (masonry) layout
|
||||
*/
|
||||
export async function hasKanbanColumnMasonryLayout(
|
||||
page: Page,
|
||||
columnId: string
|
||||
): Promise<boolean> {
|
||||
export async function hasKanbanColumnMasonryLayout(page: Page, columnId: string): Promise<boolean> {
|
||||
const column = page.locator(`[data-testid="kanban-column-${columnId}"]`);
|
||||
const contentDiv = column.locator("> div").nth(1); // Second child is the content area
|
||||
const contentDiv = column.locator('> div').nth(1); // Second child is the content area
|
||||
|
||||
const columnCount = await contentDiv.evaluate((el) => {
|
||||
const style = window.getComputedStyle(el);
|
||||
return style.columnCount;
|
||||
});
|
||||
|
||||
return columnCount === "2";
|
||||
return columnCount === '2';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,11 +46,8 @@ export async function dragKanbanCard(
|
||||
featureId: string,
|
||||
targetColumnId: string
|
||||
): Promise<void> {
|
||||
const card = page.locator(`[data-testid="kanban-card-${featureId}"]`);
|
||||
const dragHandle = page.locator(`[data-testid="drag-handle-${featureId}"]`);
|
||||
const targetColumn = page.locator(
|
||||
`[data-testid="kanban-column-${targetColumnId}"]`
|
||||
);
|
||||
const targetColumn = page.locator(`[data-testid="kanban-column-${targetColumnId}"]`);
|
||||
|
||||
// Perform drag and drop
|
||||
await dragHandle.dragTo(targetColumn);
|
||||
@@ -71,15 +56,10 @@ export async function dragKanbanCard(
|
||||
/**
|
||||
* Click the view output button on a kanban card
|
||||
*/
|
||||
export async function clickViewOutput(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<void> {
|
||||
export async function clickViewOutput(page: Page, featureId: string): Promise<void> {
|
||||
// Try the running version first, then the in-progress version
|
||||
const runningBtn = page.locator(`[data-testid="view-output-${featureId}"]`);
|
||||
const inProgressBtn = page.locator(
|
||||
`[data-testid="view-output-inprogress-${featureId}"]`
|
||||
);
|
||||
const inProgressBtn = page.locator(`[data-testid="view-output-inprogress-${featureId}"]`);
|
||||
|
||||
if (await runningBtn.isVisible()) {
|
||||
await runningBtn.click();
|
||||
@@ -104,10 +84,7 @@ export async function isDragHandleVisibleForFeature(
|
||||
/**
|
||||
* Get the drag handle element for a specific feature card
|
||||
*/
|
||||
export async function getDragHandleForFeature(
|
||||
page: Page,
|
||||
featureId: string
|
||||
): Promise<Locator> {
|
||||
export async function getDragHandleForFeature(page: Page, featureId: string): Promise<Locator> {
|
||||
return page.locator(`[data-testid="drag-handle-${featureId}"]`);
|
||||
}
|
||||
|
||||
@@ -134,9 +111,7 @@ export async function fillAddFeatureDialog(
|
||||
options?: { branch?: string; category?: string }
|
||||
): Promise<void> {
|
||||
// Fill description (using the dropzone textarea)
|
||||
const descriptionInput = page
|
||||
.locator('[data-testid="add-feature-dialog"] textarea')
|
||||
.first();
|
||||
const descriptionInput = page.locator('[data-testid="add-feature-dialog"] textarea').first();
|
||||
await descriptionInput.fill(description);
|
||||
|
||||
// Fill branch if provided (it's a combobox autocomplete)
|
||||
@@ -145,36 +120,34 @@ export async function fillAddFeatureDialog(
|
||||
const otherBranchRadio = page
|
||||
.locator('[data-testid="feature-radio-group"]')
|
||||
.locator('[id="feature-other"]');
|
||||
await otherBranchRadio.waitFor({ state: "visible", timeout: 5000 });
|
||||
await otherBranchRadio.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await otherBranchRadio.click();
|
||||
// Wait for the branch input to appear
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Now click on the branch input (autocomplete)
|
||||
const branchInput = page.locator('[data-testid="feature-input"]');
|
||||
await branchInput.waitFor({ state: "visible", timeout: 5000 });
|
||||
await branchInput.waitFor({ state: 'visible', timeout: 5000 });
|
||||
await branchInput.click();
|
||||
// Wait for the popover to open
|
||||
await page.waitForTimeout(300);
|
||||
// Type in the command input
|
||||
const commandInput = page.locator("[cmdk-input]");
|
||||
const commandInput = page.locator('[cmdk-input]');
|
||||
await commandInput.fill(options.branch);
|
||||
// Press Enter to select/create the branch
|
||||
await commandInput.press("Enter");
|
||||
await commandInput.press('Enter');
|
||||
// Wait for popover to close
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
|
||||
// Fill category if provided (it's also a combobox autocomplete)
|
||||
if (options?.category) {
|
||||
const categoryButton = page.locator(
|
||||
'[data-testid="feature-category-input"]'
|
||||
);
|
||||
const categoryButton = page.locator('[data-testid="feature-category-input"]');
|
||||
await categoryButton.click();
|
||||
await page.waitForTimeout(300);
|
||||
const commandInput = page.locator("[cmdk-input]");
|
||||
const commandInput = page.locator('[cmdk-input]');
|
||||
await commandInput.fill(options.category);
|
||||
await commandInput.press("Enter");
|
||||
await commandInput.press('Enter');
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
}
|
||||
@@ -185,10 +158,9 @@ export async function fillAddFeatureDialog(
|
||||
export async function confirmAddFeature(page: Page): Promise<void> {
|
||||
await page.click('[data-testid="confirm-add-feature"]');
|
||||
// Wait for dialog to close
|
||||
await page.waitForFunction(
|
||||
() => !document.querySelector('[data-testid="add-feature-dialog"]'),
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
await page.waitForFunction(() => !document.querySelector('[data-testid="add-feature-dialog"]'), {
|
||||
timeout: 5000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -218,12 +190,9 @@ export async function getWorktreeSelector(page: Page): Promise<Locator> {
|
||||
/**
|
||||
* Click on a branch button in the worktree selector
|
||||
*/
|
||||
export async function selectWorktreeBranch(
|
||||
page: Page,
|
||||
branchName: string
|
||||
): Promise<void> {
|
||||
const branchButton = page.getByRole("button", {
|
||||
name: new RegExp(branchName, "i"),
|
||||
export async function selectWorktreeBranch(page: Page, branchName: string): Promise<void> {
|
||||
const branchButton = page.getByRole('button', {
|
||||
name: new RegExp(branchName, 'i'),
|
||||
});
|
||||
await branchButton.click();
|
||||
await page.waitForTimeout(500); // Wait for UI to update
|
||||
@@ -232,9 +201,7 @@ export async function selectWorktreeBranch(
|
||||
/**
|
||||
* Get the currently selected branch in the worktree selector
|
||||
*/
|
||||
export async function getSelectedWorktreeBranch(
|
||||
page: Page
|
||||
): Promise<string | null> {
|
||||
export async function getSelectedWorktreeBranch(page: Page): Promise<string | null> {
|
||||
// The main branch button has aria-pressed="true" when selected
|
||||
const selectedButton = page.locator(
|
||||
'[data-testid="worktree-selector"] button[aria-pressed="true"]'
|
||||
@@ -246,12 +213,9 @@ export async function getSelectedWorktreeBranch(
|
||||
/**
|
||||
* Check if a branch button is visible in the worktree selector
|
||||
*/
|
||||
export async function isWorktreeBranchVisible(
|
||||
page: Page,
|
||||
branchName: string
|
||||
): Promise<boolean> {
|
||||
const branchButton = page.getByRole("button", {
|
||||
name: new RegExp(branchName, "i"),
|
||||
export async function isWorktreeBranchVisible(page: Page, branchName: string): Promise<boolean> {
|
||||
const branchButton = page.getByRole('button', {
|
||||
name: new RegExp(branchName, 'i'),
|
||||
});
|
||||
return await branchButton.isVisible().catch(() => false);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { clickElement, fillInput } from "../core/interactions";
|
||||
import { waitForElement, waitForElementHidden } from "../core/waiting";
|
||||
import { getByTestId } from "../core/elements";
|
||||
import { expect } from "@playwright/test";
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
import { clickElement, fillInput } from '../core/interactions';
|
||||
import { waitForElement, waitForElementHidden } from '../core/waiting';
|
||||
import { getByTestId } from '../core/elements';
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Get the context file list element
|
||||
@@ -14,10 +14,7 @@ export async function getContextFileList(page: Page): Promise<Locator> {
|
||||
/**
|
||||
* Click on a context file in the list
|
||||
*/
|
||||
export async function clickContextFile(
|
||||
page: Page,
|
||||
fileName: string
|
||||
): Promise<void> {
|
||||
export async function clickContextFile(page: Page, fileName: string): Promise<void> {
|
||||
const fileButton = page.locator(`[data-testid="context-file-${fileName}"]`);
|
||||
await fileButton.click();
|
||||
}
|
||||
@@ -33,18 +30,15 @@ export async function getContextEditor(page: Page): Promise<Locator> {
|
||||
* Get the context editor content
|
||||
*/
|
||||
export async function getContextEditorContent(page: Page): Promise<string> {
|
||||
const editor = await getByTestId(page, "context-editor");
|
||||
const editor = await getByTestId(page, 'context-editor');
|
||||
return await editor.inputValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context editor content
|
||||
*/
|
||||
export async function setContextEditorContent(
|
||||
page: Page,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
const editor = await getByTestId(page, "context-editor");
|
||||
export async function setContextEditorContent(page: Page, content: string): Promise<void> {
|
||||
const editor = await getByTestId(page, 'context-editor');
|
||||
await editor.fill(content);
|
||||
}
|
||||
|
||||
@@ -52,8 +46,8 @@ export async function setContextEditorContent(
|
||||
* Open the add context file dialog
|
||||
*/
|
||||
export async function openAddContextFileDialog(page: Page): Promise<void> {
|
||||
await clickElement(page, "add-context-file");
|
||||
await waitForElement(page, "add-context-dialog");
|
||||
await clickElement(page, 'add-context-file');
|
||||
await waitForElement(page, 'add-context-dialog');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,11 +59,11 @@ export async function createContextFile(
|
||||
content: string
|
||||
): Promise<void> {
|
||||
await openAddContextFileDialog(page);
|
||||
await clickElement(page, "add-text-type");
|
||||
await fillInput(page, "new-file-name", filename);
|
||||
await fillInput(page, "new-file-content", content);
|
||||
await clickElement(page, "confirm-add-file");
|
||||
await waitForElementHidden(page, "add-context-dialog");
|
||||
await clickElement(page, 'add-text-type');
|
||||
await fillInput(page, 'new-file-name', filename);
|
||||
await fillInput(page, 'new-file-content', content);
|
||||
await clickElement(page, 'confirm-add-file');
|
||||
await waitForElementHidden(page, 'add-context-dialog');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,34 +75,32 @@ export async function createContextImage(
|
||||
imagePath: string
|
||||
): Promise<void> {
|
||||
await openAddContextFileDialog(page);
|
||||
await clickElement(page, "add-image-type");
|
||||
await fillInput(page, "new-file-name", filename);
|
||||
await clickElement(page, 'add-image-type');
|
||||
await fillInput(page, 'new-file-name', filename);
|
||||
await page.setInputFiles('[data-testid="image-upload-input"]', imagePath);
|
||||
await clickElement(page, "confirm-add-file");
|
||||
await waitForElementHidden(page, "add-context-dialog");
|
||||
await clickElement(page, 'confirm-add-file');
|
||||
await waitForElementHidden(page, 'add-context-dialog');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a context file via the UI (must be selected first)
|
||||
*/
|
||||
export async function deleteSelectedContextFile(page: Page): Promise<void> {
|
||||
await clickElement(page, "delete-context-file");
|
||||
await waitForElement(page, "delete-context-dialog");
|
||||
await clickElement(page, "confirm-delete-file");
|
||||
await waitForElementHidden(page, "delete-context-dialog");
|
||||
await clickElement(page, 'delete-context-file');
|
||||
await waitForElement(page, 'delete-context-dialog');
|
||||
await clickElement(page, 'confirm-delete-file');
|
||||
await waitForElementHidden(page, 'delete-context-dialog');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current context file
|
||||
*/
|
||||
export async function saveContextFile(page: Page): Promise<void> {
|
||||
await clickElement(page, "save-context-file");
|
||||
await clickElement(page, 'save-context-file');
|
||||
// Wait for save to complete (button shows "Saved")
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
document
|
||||
.querySelector('[data-testid="save-context-file"]')
|
||||
?.textContent?.includes("Saved"),
|
||||
document.querySelector('[data-testid="save-context-file"]')?.textContent?.includes('Saved'),
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
}
|
||||
@@ -117,7 +109,7 @@ export async function saveContextFile(page: Page): Promise<void> {
|
||||
* Toggle markdown preview mode
|
||||
*/
|
||||
export async function toggleContextPreviewMode(page: Page): Promise<void> {
|
||||
await clickElement(page, "toggle-preview-mode");
|
||||
await clickElement(page, 'toggle-preview-mode');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,8 +120,8 @@ export async function waitForContextFile(
|
||||
filename: string,
|
||||
timeout: number = 10000
|
||||
): Promise<void> {
|
||||
const locator = await getByTestId(page, `context-file-${filename}`);
|
||||
await locator.waitFor({ state: "visible", timeout });
|
||||
const locator = page.locator(`[data-testid="context-file-${filename}"]`);
|
||||
await locator.waitFor({ state: 'visible', timeout });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,13 +134,13 @@ export async function selectContextFile(
|
||||
timeout: number = 10000
|
||||
): Promise<void> {
|
||||
const fileButton = await getByTestId(page, `context-file-${filename}`);
|
||||
await fileButton.waitFor({ state: "visible", timeout });
|
||||
await fileButton.waitFor({ state: 'visible', timeout });
|
||||
|
||||
// Use JavaScript click to ensure React onClick handler fires
|
||||
await fileButton.evaluate((el) => (el as HTMLButtonElement).click());
|
||||
|
||||
// Wait for the file to be selected (toolbar with delete button becomes visible)
|
||||
const deleteButton = await getByTestId(page, "delete-context-file");
|
||||
const deleteButton = await getByTestId(page, 'delete-context-file');
|
||||
await expect(deleteButton).toBeVisible({
|
||||
timeout,
|
||||
});
|
||||
@@ -173,11 +165,11 @@ export async function switchToEditMode(page: Page): Promise<void> {
|
||||
// First wait for content to load
|
||||
await waitForFileContentToLoad(page);
|
||||
|
||||
const markdownPreview = await getByTestId(page, "markdown-preview");
|
||||
const markdownPreview = await getByTestId(page, 'markdown-preview');
|
||||
const isPreview = await markdownPreview.isVisible().catch(() => false);
|
||||
|
||||
if (isPreview) {
|
||||
await clickElement(page, "toggle-preview-mode");
|
||||
await clickElement(page, 'toggle-preview-mode');
|
||||
await page.waitForSelector('[data-testid="context-editor"]', {
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { getByTestId } from "../core/elements";
|
||||
import { waitForElement } from "../core/waiting";
|
||||
import { setupFirstRun } from "../project/setup";
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
import { getByTestId } from '../core/elements';
|
||||
import { waitForElement } from '../core/waiting';
|
||||
|
||||
/**
|
||||
* Wait for setup view to be visible
|
||||
*/
|
||||
export async function waitForSetupView(page: Page): Promise<Locator> {
|
||||
return waitForElement(page, "setup-view", { timeout: 10000 });
|
||||
return waitForElement(page, 'setup-view', { timeout: 10000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Click "Get Started" button on setup welcome step
|
||||
*/
|
||||
export async function clickSetupGetStarted(page: Page): Promise<void> {
|
||||
const button = await getByTestId(page, "setup-start-button");
|
||||
const button = await getByTestId(page, 'setup-start-button');
|
||||
await button.click();
|
||||
}
|
||||
|
||||
@@ -22,7 +21,7 @@ export async function clickSetupGetStarted(page: Page): Promise<void> {
|
||||
* Click continue on Claude setup step
|
||||
*/
|
||||
export async function clickClaudeContinue(page: Page): Promise<void> {
|
||||
const button = await getByTestId(page, "claude-next-button");
|
||||
const button = await getByTestId(page, 'claude-next-button');
|
||||
await button.click();
|
||||
}
|
||||
|
||||
@@ -30,46 +29,40 @@ export async function clickClaudeContinue(page: Page): Promise<void> {
|
||||
* Click finish on setup complete step
|
||||
*/
|
||||
export async function clickSetupFinish(page: Page): Promise<void> {
|
||||
const button = await getByTestId(page, "setup-finish-button");
|
||||
const button = await getByTestId(page, 'setup-finish-button');
|
||||
await button.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter Anthropic API key in setup
|
||||
*/
|
||||
export async function enterAnthropicApiKey(
|
||||
page: Page,
|
||||
apiKey: string
|
||||
): Promise<void> {
|
||||
export async function enterAnthropicApiKey(page: Page, apiKey: string): Promise<void> {
|
||||
// Click "Use Anthropic API Key Instead" button
|
||||
const useApiKeyButton = await getByTestId(page, "use-api-key-button");
|
||||
const useApiKeyButton = await getByTestId(page, 'use-api-key-button');
|
||||
await useApiKeyButton.click();
|
||||
|
||||
// Enter the API key
|
||||
const input = await getByTestId(page, "anthropic-api-key-input");
|
||||
const input = await getByTestId(page, 'anthropic-api-key-input');
|
||||
await input.fill(apiKey);
|
||||
|
||||
// Click save button
|
||||
const saveButton = await getByTestId(page, "save-anthropic-key-button");
|
||||
const saveButton = await getByTestId(page, 'save-anthropic-key-button');
|
||||
await saveButton.click();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter OpenAI API key in setup
|
||||
*/
|
||||
export async function enterOpenAIApiKey(
|
||||
page: Page,
|
||||
apiKey: string
|
||||
): Promise<void> {
|
||||
export async function enterOpenAIApiKey(page: Page, apiKey: string): Promise<void> {
|
||||
// Click "Enter OpenAI API Key" button
|
||||
const useApiKeyButton = await getByTestId(page, "use-openai-key-button");
|
||||
const useApiKeyButton = await getByTestId(page, 'use-openai-key-button');
|
||||
await useApiKeyButton.click();
|
||||
|
||||
// Enter the API key
|
||||
const input = await getByTestId(page, "openai-api-key-input");
|
||||
const input = await getByTestId(page, 'openai-api-key-input');
|
||||
await input.fill(apiKey);
|
||||
|
||||
// Click save button
|
||||
const saveButton = await getByTestId(page, "save-openai-key-button");
|
||||
const saveButton = await getByTestId(page, 'save-openai-key-button');
|
||||
await saveButton.click();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Page, Locator } from "@playwright/test";
|
||||
import { clickElement } from "../core/interactions";
|
||||
import { navigateToSpec } from "../navigation/views";
|
||||
import { Page, Locator } from '@playwright/test';
|
||||
import { clickElement } from '../core/interactions';
|
||||
import { navigateToSpec } from '../navigation/views';
|
||||
|
||||
/**
|
||||
* Get the spec editor element
|
||||
@@ -20,10 +20,7 @@ export async function getSpecEditorContent(page: Page): Promise<string> {
|
||||
/**
|
||||
* Set the spec editor content
|
||||
*/
|
||||
export async function setSpecEditorContent(
|
||||
page: Page,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
export async function setSpecEditorContent(page: Page, content: string): Promise<void> {
|
||||
const editor = await getSpecEditor(page);
|
||||
await editor.fill(content);
|
||||
}
|
||||
@@ -32,14 +29,14 @@ export async function setSpecEditorContent(
|
||||
* Click the save spec button
|
||||
*/
|
||||
export async function clickSaveSpec(page: Page): Promise<void> {
|
||||
await clickElement(page, "save-spec");
|
||||
await clickElement(page, 'save-spec');
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the reload spec button
|
||||
*/
|
||||
export async function clickReloadSpec(page: Page): Promise<void> {
|
||||
await clickElement(page, "reload-spec");
|
||||
await clickElement(page, 'reload-spec');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +44,7 @@ export async function clickReloadSpec(page: Page): Promise<void> {
|
||||
*/
|
||||
export async function getDisplayedSpecPath(page: Page): Promise<string | null> {
|
||||
const specView = page.locator('[data-testid="spec-view"]');
|
||||
const pathElement = specView.locator("p.text-muted-foreground").first();
|
||||
const pathElement = specView.locator('p.text-muted-foreground').first();
|
||||
return await pathElement.textContent();
|
||||
}
|
||||
|
||||
@@ -60,13 +57,17 @@ export async function navigateToSpecEditor(page: Page): Promise<void> {
|
||||
|
||||
/**
|
||||
* Get the CodeMirror editor content
|
||||
* Waits for CodeMirror to be ready and returns the content
|
||||
*/
|
||||
export async function getEditorContent(page: Page): Promise<string> {
|
||||
// CodeMirror uses a contenteditable div with class .cm-content
|
||||
const content = await page
|
||||
.locator('[data-testid="spec-editor"] .cm-content')
|
||||
.textContent();
|
||||
return content || "";
|
||||
// Wait for it to be visible and then read its textContent
|
||||
const contentElement = page.locator('[data-testid="spec-editor"] .cm-content');
|
||||
await contentElement.waitFor({ state: 'visible', timeout: 10000 });
|
||||
|
||||
// Read the content - CodeMirror should have updated its DOM by now
|
||||
const content = await contentElement.textContent();
|
||||
return content || '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -81,14 +82,14 @@ export async function setEditorContent(page: Page, content: string): Promise<voi
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Select all content (Cmd+A on Mac, Ctrl+A on others)
|
||||
const isMac = process.platform === "darwin";
|
||||
await page.keyboard.press(isMac ? "Meta+a" : "Control+a");
|
||||
const isMac = process.platform === 'darwin';
|
||||
await page.keyboard.press(isMac ? 'Meta+a' : 'Control+a');
|
||||
|
||||
// Wait for selection
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Delete the selected content first
|
||||
await page.keyboard.press("Backspace");
|
||||
await page.keyboard.press('Backspace');
|
||||
|
||||
// Wait for deletion
|
||||
await page.waitForTimeout(100);
|
||||
@@ -111,7 +112,7 @@ export async function clickSaveButton(page: Page): Promise<void> {
|
||||
await page.waitForFunction(
|
||||
() => {
|
||||
const btn = document.querySelector('[data-testid="save-spec"]');
|
||||
return btn?.textContent?.includes("Saved");
|
||||
return btn?.textContent?.includes('Saved');
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user