import { Page, expect } from '@playwright/test'; import { getByTestId, getButtonByText } from './elements'; import { waitForSplashScreenToDisappear } from './waiting'; /** * Get the platform-specific modifier key (Meta for Mac, Control for Windows/Linux) * This is used for keyboard shortcuts like Cmd+Enter or Ctrl+Enter */ export function getPlatformModifier(): 'Meta' | 'Control' { return process.platform === 'darwin' ? 'Meta' : 'Control'; } /** * Press the platform-specific modifier + a key (e.g., Cmd+Enter or Ctrl+Enter) */ export async function pressModifierEnter(page: Page): Promise { const modifier = getPlatformModifier(); await page.keyboard.press(`${modifier}+Enter`); } /** * Click an element by its data-testid attribute * Waits for the element to be visible before clicking to avoid flaky tests */ export async function clickElement(page: Page, testId: string): Promise { // Wait for splash screen to disappear first (safety net) await waitForSplashScreenToDisappear(page, 5000); const element = page.locator(`[data-testid="${testId}"]`); // Wait for element to be visible and stable before clicking await element.waitFor({ state: 'visible', timeout: 10000 }); await element.click(); } /** * Click a button by its text content */ export async function clickButtonByText(page: Page, text: string): Promise { const button = await getButtonByText(page, text); await button.click(); } /** * Fill an input field by its data-testid attribute */ export async function fillInput(page: Page, testId: string, value: string): Promise { const input = await getByTestId(page, testId); await input.fill(value); } /** * Press a keyboard shortcut key */ export async function pressShortcut(page: Page, key: string): Promise { await page.keyboard.press(key); } /** * Navigate to a URL with authentication * This wrapper ensures authentication happens before navigation */ export async function gotoWithAuth(page: Page, url: string): Promise { const { authenticateForTests } = await import('../api/client'); await authenticateForTests(page); await page.goto(url); } /** * Handle login screen if it appears after navigation * Returns true if login was handled, false if no login screen was found */ export async function handleLoginScreenIfPresent(page: Page): Promise { // Check for login screen by waiting for either login input or app-container to be visible // Use data-testid selector (preferred) with fallback to the old selector 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 loggedOutPage = page.getByRole('heading', { name: /logged out/i }); const goToLoginButton = page.locator('button:has-text("Go to login")'); const maxWaitMs = 15000; // Race between login screen, logged-out page, a delayed redirect to /login, and actual content const result = await Promise.race([ page .waitForURL((url) => url.pathname.includes('/login'), { timeout: maxWaitMs }) .then(() => 'login-redirect' as const) .catch(() => null), loginInput .waitFor({ state: 'visible', timeout: maxWaitMs }) .then(() => 'login-input' as const) .catch(() => null), loggedOutPage .waitFor({ state: 'visible', timeout: maxWaitMs }) .then(() => 'logged-out' as const) .catch(() => null), appContent .first() .waitFor({ state: 'visible', timeout: maxWaitMs }) .then(() => 'app-content' as const) .catch(() => null), ]); // Handle logged-out page - click "Go to login" button and then login if (result === 'logged-out') { await goToLoginButton.click(); await page.waitForLoadState('load'); // Now handle the login screen return handleLoginScreenIfPresent(page); } const loginVisible = result === 'login-redirect' || result === 'login-input'; if (loginVisible) { const apiKey = process.env.AUTOMAKER_API_KEY || 'test-api-key-for-e2e-tests'; await loginInput.fill(apiKey); // Wait a moment for the button to become enabled await page.waitForTimeout(100); // Wait for button to be enabled (it's disabled when input is empty) const loginButton = page .locator('[data-testid="login-submit-button"], button:has-text("Login")') .first(); await expect(loginButton).toBeEnabled({ timeout: 5000 }); await loginButton.click(); // Wait for navigation away from login - either to content or URL change await Promise.race([ page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15000 }), appContent.first().waitFor({ state: 'visible', timeout: 15000 }), ]).catch(() => {}); // Wait for page to load await page.waitForLoadState('load'); return true; } return false; } /** * Press a number key (0-9) on the keyboard */ export async function pressNumberKey(page: Page, num: number): Promise { await page.keyboard.press(num.toString()); } /** * Focus on an input element to test that shortcuts don't fire when typing */ export async function focusOnInput(page: Page, testId: string): Promise { const input = page.locator(`[data-testid="${testId}"]`); await input.focus(); } /** * Close any open dialog by pressing Escape * Waits for dialog to be removed from DOM rather than using arbitrary timeout */ export async function closeDialogWithEscape(page: Page): Promise { await page.keyboard.press('Escape'); // Wait for any dialog overlay to disappear await page .locator('[data-radix-dialog-overlay], [role="dialog"]') .waitFor({ state: 'hidden', timeout: 5000 }) .catch(() => { // Dialog may have already closed or not exist }); }