mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-03 20:59:22 +08:00
Co-authored-by: gujiaming <52187003+AtomsH4@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: kangfenmao <kangfenmao@qq.com> Signed-off-by: gujiaming <52187003+AtomsH4@users.noreply.github.com>
104 lines
3.2 KiB
TypeScript
104 lines
3.2 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
|
|
/**
|
|
* Wait for the application to be fully ready.
|
|
* The app uses PersistGate which may delay initial render.
|
|
* Layout can be either Sidebar-based or TabsContainer-based depending on settings.
|
|
*/
|
|
export async function waitForAppReady(page: Page, timeout: number = 60000): Promise<void> {
|
|
// First, wait for React root to be attached
|
|
await page.waitForSelector('#root', { state: 'attached', timeout })
|
|
|
|
// Wait for main app content to render
|
|
// The app may show either:
|
|
// 1. Sidebar layout (navbarPosition === 'left')
|
|
// 2. TabsContainer layout (default)
|
|
// 3. Home page content
|
|
await page.waitForSelector(
|
|
[
|
|
'#home-page', // Home page container
|
|
'[class*="Sidebar"]', // Sidebar component
|
|
'[class*="TabsContainer"]', // Tabs container
|
|
'[class*="home-navbar"]', // Home navbar
|
|
'[class*="Container"]' // Generic generated container class
|
|
].join(', '),
|
|
{
|
|
state: 'visible',
|
|
timeout
|
|
}
|
|
)
|
|
|
|
// Additional wait for React to fully hydrate
|
|
await page.waitForLoadState('domcontentloaded')
|
|
}
|
|
|
|
/**
|
|
* Wait for navigation to a specific path.
|
|
* The app uses HashRouter, so paths are prefixed with #.
|
|
*/
|
|
export async function waitForNavigation(page: Page, path: string, timeout: number = 15000): Promise<void> {
|
|
await page.waitForURL(`**/#${path}**`, { timeout })
|
|
}
|
|
|
|
/**
|
|
* Wait for the chat interface to be ready.
|
|
*/
|
|
export async function waitForChatReady(page: Page, timeout: number = 30000): Promise<void> {
|
|
await page.waitForSelector(
|
|
['#home-page', '[class*="Chat"]', '[class*="Inputbar"]', '[class*="home-tabs"]'].join(', '),
|
|
{ state: 'visible', timeout }
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Wait for the settings page to load.
|
|
*/
|
|
export async function waitForSettingsLoad(page: Page, timeout: number = 30000): Promise<void> {
|
|
await page.waitForSelector(['[class*="SettingsPage"]', '[class*="Settings"]', 'a[href*="/settings/"]'].join(', '), {
|
|
state: 'visible',
|
|
timeout
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Wait for a modal/dialog to appear.
|
|
*/
|
|
export async function waitForModal(page: Page, timeout: number = 10000): Promise<void> {
|
|
await page.waitForSelector('.ant-modal, [role="dialog"], .ant-drawer', { state: 'visible', timeout })
|
|
}
|
|
|
|
/**
|
|
* Wait for a modal/dialog to close.
|
|
*/
|
|
export async function waitForModalClose(page: Page, timeout: number = 10000): Promise<void> {
|
|
await page.waitForSelector('.ant-modal, [role="dialog"], .ant-drawer', { state: 'hidden', timeout })
|
|
}
|
|
|
|
/**
|
|
* Wait for loading state to complete.
|
|
*/
|
|
export async function waitForLoadingComplete(page: Page, timeout: number = 30000): Promise<void> {
|
|
const spinner = page.locator('.ant-spin, [class*="Loading"], [class*="Spinner"]')
|
|
if ((await spinner.count()) > 0) {
|
|
await spinner.first().waitFor({ state: 'hidden', timeout })
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for a notification/toast to appear.
|
|
*/
|
|
export async function waitForNotification(page: Page, timeout: number = 10000): Promise<void> {
|
|
await page.waitForSelector('.ant-notification, .ant-message, [class*="Notification"]', {
|
|
state: 'visible',
|
|
timeout
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Sleep for a specified duration.
|
|
* Use sparingly - prefer explicit waits when possible.
|
|
*/
|
|
export async function sleep(ms: number): Promise<void> {
|
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
}
|