| import { Page, Locator } from '@playwright/test'; |
| import { |
| clickElement, |
| fillInput, |
| handleLoginScreenIfPresent, |
| closeDialogWithEscape, |
| } from '../core/interactions'; |
| import { waitForElement, waitForElementHidden } from '../core/waiting'; |
| import { getByTestId } from '../core/elements'; |
| import { expect } from '@playwright/test'; |
| import { authenticateForTests } from '../api/client'; |
|
|
| |
| |
| |
| export async function getMemoryFileList(page: Page): Promise<Locator> { |
| return page.locator('[data-testid="memory-file-list"]'); |
| } |
|
|
| |
| |
| |
| export async function clickMemoryFile(page: Page, fileName: string): Promise<void> { |
| const fileButton = page.locator(`[data-testid="memory-file-${fileName}"]`); |
| await fileButton.click(); |
| } |
|
|
| |
| |
| |
| export async function getMemoryEditor(page: Page): Promise<Locator> { |
| return page.locator('[data-testid="memory-editor"]'); |
| } |
|
|
| |
| |
| |
| export async function getMemoryEditorContent(page: Page): Promise<string> { |
| const editor = await getByTestId(page, 'memory-editor'); |
| return await editor.inputValue(); |
| } |
|
|
| |
| |
| |
| export async function setMemoryEditorContent(page: Page, content: string): Promise<void> { |
| const editor = await getByTestId(page, 'memory-editor'); |
| await editor.fill(content); |
| } |
|
|
| |
| |
| |
| export async function openCreateMemoryDialog(page: Page): Promise<void> { |
| await clickElement(page, 'create-memory-button'); |
| await waitForElement(page, 'create-memory-dialog'); |
| } |
|
|
| |
| |
| |
| export async function createMemoryFile( |
| page: Page, |
| filename: string, |
| content: string |
| ): Promise<void> { |
| await openCreateMemoryDialog(page); |
| await fillInput(page, 'new-memory-name', filename); |
| await fillInput(page, 'new-memory-content', content); |
| await clickElement(page, 'confirm-create-memory'); |
| await waitForElementHidden(page, 'create-memory-dialog'); |
| } |
|
|
| |
| |
| |
| export async function deleteSelectedMemoryFile(page: Page): Promise<void> { |
| await clickElement(page, 'delete-memory-file'); |
| await waitForElement(page, 'delete-memory-dialog'); |
| await clickElement(page, 'confirm-delete-memory'); |
| await waitForElementHidden(page, 'delete-memory-dialog'); |
| } |
|
|
| |
| |
| |
| export async function saveMemoryFile(page: Page): Promise<void> { |
| await clickElement(page, 'save-memory-file'); |
| |
| |
| |
| await page.waitForFunction( |
| () => { |
| const btn = document.querySelector('[data-testid="save-memory-file"]'); |
| if (!btn) return false; |
| const stateText = [ |
| btn.textContent ?? '', |
| btn.getAttribute('aria-label') ?? '', |
| btn.getAttribute('title') ?? '', |
| ] |
| .join(' ') |
| .toLowerCase(); |
| return stateText.includes('saved'); |
| }, |
| { timeout: 5000 } |
| ); |
| } |
|
|
| |
| |
| |
| export async function toggleMemoryPreviewMode(page: Page): Promise<void> { |
| await clickElement(page, 'toggle-preview-mode'); |
| } |
|
|
| |
| |
| |
| |
| export async function waitForMemoryFile( |
| page: Page, |
| filename: string, |
| timeout: number = 15000 |
| ): Promise<void> { |
| await expect(async () => { |
| const locator = page.locator(`[data-testid="memory-file-${filename}"]`); |
| await expect(locator).toBeVisible(); |
| }).toPass({ timeout, intervals: [200, 500, 1000] }); |
| } |
|
|
| |
| |
| |
| |
| export async function selectMemoryFile( |
| page: Page, |
| filename: string, |
| timeout: number = 15000 |
| ): Promise<void> { |
| const fileButton = await getByTestId(page, `memory-file-${filename}`); |
|
|
| |
| |
| |
| const innerTimeout = Math.min(2000, Math.floor(timeout / 3)); |
| await expect(async () => { |
| |
| await fileButton.evaluate((el) => (el as HTMLButtonElement).click()); |
| |
| const contentLocator = page.locator( |
| '[data-testid="memory-editor"], [data-testid="markdown-preview"]' |
| ); |
| await expect(contentLocator).toBeVisible({ timeout: innerTimeout }); |
| }).toPass({ timeout, intervals: [200, 500, 1000] }); |
| } |
|
|
| |
| |
| |
| |
| export async function waitForMemoryContentToLoad( |
| page: Page, |
| timeout: number = 15000 |
| ): Promise<void> { |
| const innerTimeout = Math.min(2000, Math.floor(timeout / 3)); |
| await expect(async () => { |
| const contentLocator = page.locator( |
| '[data-testid="memory-editor"], [data-testid="markdown-preview"]' |
| ); |
| await expect(contentLocator).toBeVisible({ timeout: innerTimeout }); |
| }).toPass({ timeout, intervals: [200, 500, 1000] }); |
| } |
|
|
| |
| |
| |
| |
| export async function switchMemoryToEditMode(page: Page): Promise<void> { |
| |
| await waitForMemoryContentToLoad(page); |
|
|
| const markdownPreview = await getByTestId(page, 'markdown-preview'); |
| const isPreview = await markdownPreview.isVisible().catch(() => false); |
|
|
| if (isPreview) { |
| await clickElement(page, 'toggle-preview-mode'); |
| await page.waitForSelector('[data-testid="memory-editor"]', { |
| timeout: 5000, |
| }); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export async function refreshMemoryList(page: Page): Promise<void> { |
| |
| const desktopRefresh = page.locator('[data-testid="refresh-memory-button"]'); |
| const mobileRefresh = page.locator('[data-testid="refresh-memory-button-mobile"]'); |
| if (await desktopRefresh.isVisible().catch(() => false)) { |
| await desktopRefresh.click(); |
| } else { |
| await clickElement(page, 'header-actions-panel-trigger'); |
| await mobileRefresh.click(); |
| } |
| |
| await page.waitForTimeout(150); |
| } |
|
|
| |
| |
| |
| |
| export async function navigateToMemory(page: Page): Promise<void> { |
| |
| await authenticateForTests(page); |
|
|
| |
| await page.goto('/memory', { waitUntil: 'domcontentloaded' }); |
|
|
| |
| await handleLoginScreenIfPresent(page); |
|
|
| |
| |
| const viewSelector = |
| '[data-testid="memory-view"], [data-testid="memory-view-no-project"], [data-testid="memory-view-loading"]'; |
| await page.locator(viewSelector).first().waitFor({ state: 'visible', timeout: 15000 }); |
|
|
| |
| const noProject = page.locator('[data-testid="memory-view-no-project"]'); |
| if (await noProject.isVisible().catch(() => false)) { |
| |
| await page |
| .locator('[data-testid="memory-view"], [data-testid="memory-view-loading"]') |
| .first() |
| .waitFor({ state: 'visible', timeout: 5000 }) |
| .catch(() => { |
| throw new Error( |
| 'Memory view showed "No project selected". Ensure setupProjectWithFixture runs before navigateToMemory and store has time to hydrate.' |
| ); |
| }); |
| } |
|
|
| |
| const loadingElement = page.locator('[data-testid="memory-view-loading"]'); |
| if (await loadingElement.isVisible().catch(() => false)) { |
| await loadingElement.waitFor({ state: 'hidden', timeout: 10000 }); |
| } |
|
|
| |
| await waitForElement(page, 'memory-view', { timeout: 15000 }); |
|
|
| |
| |
| const backdrop = page.locator('[data-testid="sidebar-backdrop"]'); |
| if (await backdrop.isVisible().catch(() => false)) { |
| await backdrop.evaluate((el) => (el as HTMLElement).click()); |
| } |
|
|
| |
| |
| const sandboxAcceptBtn = page.locator('button:has-text("I Accept the Risks")'); |
| const sandboxVisible = await sandboxAcceptBtn |
| .waitFor({ state: 'visible', timeout: 1000 }) |
| .then(() => true) |
| .catch(() => false); |
| if (sandboxVisible) { |
| await sandboxAcceptBtn.click(); |
| await page |
| .locator('[role="dialog"][data-state="open"]') |
| .first() |
| .waitFor({ state: 'hidden', timeout: 3000 }) |
| .catch(() => {}); |
| } else { |
| await closeDialogWithEscape(page, { timeout: 2000 }); |
| } |
|
|
| |
| await page |
| .locator('[data-testid="header-actions-panel-trigger"]') |
| .waitFor({ state: 'visible', timeout: 5000 }) |
| .catch(() => {}); |
| } |
|
|