import { Page, expect } from '@playwright/test'; import { testLogger } from '../core/test-logger'; export async function waitForElement(page: Page, selector: string, timeout: number = 10000): Promise { testLogger.debug(`等待元素: ${selector}, 超时: ${timeout}ms`); try { await page.waitForSelector(selector, { state: 'visible', timeout }); testLogger.debug(`元素已可见: ${selector}`); } catch (error) { testLogger.error(`等待元素超时: ${selector}`, error as Error); throw error; } } export async function waitForElementHidden(page: Page, selector: string, timeout: number = 10000): Promise { testLogger.debug(`等待元素隐藏: ${selector}, 超时: ${timeout}ms`); try { await page.waitForSelector(selector, { state: 'hidden', timeout }); testLogger.debug(`元素已隐藏: ${selector}`); } catch (error) { testLogger.error(`等待元素隐藏超时: ${selector}`, error as Error); throw error; } } export async function waitForText(page: Page, selector: string, text: string, timeout: number = 10000): Promise { testLogger.debug(`等待文本: ${selector} 包含 "${text}", 超时: ${timeout}ms`); try { const locator = page.locator(selector); await expect(locator).toHaveText(text, { timeout }); testLogger.debug(`文本已出现: ${selector} 包含 "${text}"`); } catch (error) { testLogger.error(`等待文本超时: ${selector} 包含 "${text}"`, error as Error); throw error; } } export async function waitForURL(page: Page, urlPattern: string | RegExp, timeout: number = 10000): Promise { testLogger.debug(`等待URL匹配: ${urlPattern}, 超时: ${timeout}ms`); try { await page.waitForURL(urlPattern, { timeout }); testLogger.debug(`URL已匹配: ${page.url()}`); } catch (error) { testLogger.error(`等待URL超时: ${urlPattern}`, error as Error); throw error; } } export async function clickElement(page: Page, selector: string, options?: { timeout?: number; force?: boolean }): Promise { testLogger.debug(`点击元素: ${selector}`); try { const locator = page.locator(selector); await locator.click(options); testLogger.debug(`元素点击成功: ${selector}`); } catch (error) { testLogger.error(`点击元素失败: ${selector}`, error as Error); throw error; } } export async function fillInput(page: Page, selector: string, value: string, options?: { timeout?: number }): Promise { testLogger.debug(`填充输入框: ${selector}, 值: ${value}`); try { const locator = page.locator(selector); await locator.fill(value, options); testLogger.debug(`输入框填充成功: ${selector}`); } catch (error) { testLogger.error(`填充输入框失败: ${selector}`, error as Error); throw error; } } export async function selectOption(page: Page, selector: string, value: string | string[]): Promise { testLogger.debug(`选择下拉选项: ${selector}, 值: ${value}`); try { const locator = page.locator(selector); await locator.selectOption(value); testLogger.debug(`下拉选项选择成功: ${selector}`); } catch (error) { testLogger.error(`选择下拉选项失败: ${selector}`, error as Error); throw error; } } export async function checkCheckbox(page: Page, selector: string): Promise { testLogger.debug(`勾选复选框: ${selector}`); try { const locator = page.locator(selector); await locator.check(); testLogger.debug(`复选框勾选成功: ${selector}`); } catch (error) { testLogger.error(`勾选复选框失败: ${selector}`, error as Error); throw error; } } export async function uncheckCheckbox(page: Page, selector: string): Promise { testLogger.debug(`取消勾选复选框: ${selector}`); try { const locator = page.locator(selector); await locator.uncheck(); testLogger.debug(`复选框取消勾选成功: ${selector}`); } catch (error) { testLogger.error(`取消勾选复选框失败: ${selector}`, error as Error); throw error; } } export async function getText(page: Page, selector: string): Promise { testLogger.debug(`获取元素文本: ${selector}`); try { const locator = page.locator(selector); const text = await locator.textContent(); testLogger.debug(`元素文本: ${selector} = ${text}`); return text || ''; } catch (error) { testLogger.error(`获取元素文本失败: ${selector}`, error as Error); throw error; } } export async function getAttribute(page: Page, selector: string, attributeName: string): Promise { testLogger.debug(`获取元素属性: ${selector}, 属性名: ${attributeName}`); try { const locator = page.locator(selector); const attribute = await locator.getAttribute(attributeName); testLogger.debug(`元素属性: ${selector}[${attributeName}] = ${attribute}`); return attribute; } catch (error) { testLogger.error(`获取元素属性失败: ${selector}`, error as Error); throw error; } } export async function isVisible(page: Page, selector: string): Promise { try { const locator = page.locator(selector); return await locator.isVisible({ timeout: 5000 }); } catch { return false; } } export async function isEnabled(page: Page, selector: string): Promise { try { const locator = page.locator(selector); return await locator.isEnabled({ timeout: 5000 }); } catch { return false; } } export async function isHidden(page: Page, selector: string): Promise { try { const locator = page.locator(selector); return await locator.isHidden({ timeout: 5000 }); } catch { return false; } } export async function isDisabled(page: Page, selector: string): Promise { try { const locator = page.locator(selector); return await locator.isDisabled({ timeout: 5000 }); } catch { return false; } } export async function scrollToElement(page: Page, selector: string): Promise { testLogger.debug(`滚动到元素: ${selector}`); try { const locator = page.locator(selector); await locator.scrollIntoViewIfNeeded(); testLogger.debug(`滚动到元素成功: ${selector}`); } catch (error) { testLogger.error(`滚动到元素失败: ${selector}`, error as Error); throw error; } } export async function hoverElement(page: Page, selector: string): Promise { testLogger.debug(`悬停在元素上: ${selector}`); try { const locator = page.locator(selector); await locator.hover(); testLogger.debug(`悬停成功: ${selector}`); } catch (error) { testLogger.error(`悬停失败: ${selector}`, error as Error); throw error; } } export async function doubleClickElement(page: Page, selector: string): Promise { testLogger.debug(`双击元素: ${selector}`); try { const locator = page.locator(selector); await locator.dblclick(); testLogger.debug(`双击成功: ${selector}`); } catch (error) { testLogger.error(`双击失败: ${selector}`, error as Error); throw error; } } export async function rightClickElement(page: Page, selector: string): Promise { testLogger.debug(`右键点击元素: ${selector}`); try { const locator = page.locator(selector); await locator.click({ button: 'right' }); testLogger.debug(`右键点击成功: ${selector}`); } catch (error) { testLogger.error(`右键点击失败: ${selector}`, error as Error); throw error; } } export async function uploadFile(page: Page, selector: string, filePath: string): Promise { testLogger.debug(`上传文件: ${selector}, 路径: ${filePath}`); try { const locator = page.locator(selector); await locator.setInputFiles(filePath); testLogger.debug(`文件上传成功: ${filePath}`); } catch (error) { testLogger.error(`文件上传失败: ${filePath}`, error as Error); throw error; } } export async function clearInput(page: Page, selector: string): Promise { testLogger.debug(`清空输入框: ${selector}`); try { const locator = page.locator(selector); await locator.clear(); testLogger.debug(`输入框已清空: ${selector}`); } catch (error) { testLogger.error(`清空输入框失败: ${selector}`, error as Error); throw error; } } export async function pressKey(page: Page, key: string): Promise { testLogger.debug(`按键: ${key}`); try { await page.keyboard.press(key); testLogger.debug(`按键成功: ${key}`); } catch (error) { testLogger.error(`按键失败: ${key}`, error as Error); throw error; } } export async function typeText(page: Page, selector: string, text: string, delay?: number): Promise { testLogger.debug(`输入文本: ${selector}, 文本: ${text}`); try { const locator = page.locator(selector); await locator.type(text, { delay }); testLogger.debug(`文本输入成功: ${selector}`); } catch (error) { testLogger.error(`文本输入失败: ${selector}`, error as Error); throw error; } } export async function waitForNetworkIdle(page: Page, timeout: number = 30000): Promise { testLogger.debug(`等待网络空闲, 超时: ${timeout}ms`); try { await page.waitForLoadState('networkidle', { timeout }); testLogger.debug('网络已空闲'); } catch (error) { testLogger.error('等待网络空闲超时', error as Error); throw error; } } export async function waitForLoadState(page: Page, state: 'load' | 'domcontentloaded' | 'networkidle' = 'load', timeout: number = 30000): Promise { testLogger.debug(`等待加载状态: ${state}, 超时: ${timeout}ms`); try { await page.waitForLoadState(state, { timeout }); testLogger.debug(`加载状态已达到: ${state}`); } catch (error) { testLogger.error(`等待加载状态超时: ${state}`, error as Error); throw error; } } export async function executeScript(page: Page, script: string, ...args: any[]): Promise { testLogger.debug('执行JavaScript脚本'); try { const result = await page.evaluate(script, ...args); testLogger.debug('JavaScript脚本执行成功'); return result; } catch (error) { testLogger.error('JavaScript脚本执行失败', error as Error); throw error; } } export async function takeScreenshot(page: Page, name: string, fullPage: boolean = false): Promise { testLogger.debug(`截图: ${name}, 全页: ${fullPage}`); try { const path = `test-results/screenshots/${name}-${Date.now()}.png`; await page.screenshot({ path, fullPage }); testLogger.debug(`截图已保存: ${path}`); return path; } catch (error) { testLogger.error(`截图失败: ${name}`, error as Error); throw error; } } export async function waitForTimeout(ms: number): Promise { testLogger.debug(`等待 ${ms}ms`); await new Promise(resolve => setTimeout(resolve, ms)); } export async function retryOperation( operation: () => Promise, maxRetries: number = 3, delay: number = 1000, description: string = '操作' ): Promise { let lastError: Error | null = null; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { testLogger.debug(`${description} 尝试 ${attempt}/${maxRetries}`); const result = await operation(); if (attempt > 1) { testLogger.info(`${description} 在第 ${attempt} 次尝试后成功`); } return result; } catch (error) { lastError = error as Error; testLogger.warn(`${description} 第 ${attempt} 次尝试失败: ${error}`); if (attempt < maxRetries) { await waitForTimeout(delay); } } } throw lastError || new Error(`${description} 在 ${maxRetries} 次尝试后仍然失败`); } export async function waitUntil( condition: () => boolean | Promise, timeout: number = 10000, interval: number = 100, description: string = '条件' ): Promise { const startTime = Date.now(); while (Date.now() - startTime < timeout) { const result = await condition(); if (result) { testLogger.debug(`${description} 已满足`); return; } await waitForTimeout(interval); } throw new Error(`${description} 在 ${timeout}ms 内未满足`); } export function generateRandomString(length: number = 10): string { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; } export function generateRandomEmail(): string { const username = generateRandomString(8).toLowerCase(); const domains = ['example.com', 'test.com', 'demo.com']; const domain = domains[Math.floor(Math.random() * domains.length)]; return `${username}@${domain}`; } export function generateRandomPhoneNumber(): string { const prefix = ['138', '139', '150', '151', '186', '188']; const selectedPrefix = prefix[Math.floor(Math.random() * prefix.length)]; const suffix = Math.floor(Math.random() * 100000000).toString().padStart(8, '0'); return `${selectedPrefix}${suffix}`; } export function generateRandomId(): string { return `${Date.now()}-${generateRandomString(6)}`; } export function formatDate(date: Date, format: string = 'YYYY-MM-DD'): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return format .replace('YYYY', year.toString()) .replace('MM', month) .replace('DD', day) .replace('HH', hours) .replace('mm', minutes) .replace('ss', seconds); } export function parseDate(dateString: string, format: string = 'YYYY-MM-DD'): Date { const parts = dateString.match(/(\d+)/g); if (!parts) { throw new Error(`无效的日期格式: ${dateString}`); } const year = parseInt(parts[0], 10); const month = parseInt(parts[1], 10) - 1; const day = parseInt(parts[2], 10); return new Date(year, month, day); } export function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } export function debounce(func: Function, wait: number): Function { let timeout: NodeJS.Timeout | null = null; return function(...args: any[]) { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { func.apply(this, args); }, wait); }; } export function throttle(func: Function, limit: number): Function { let inThrottle: boolean = false; return function(...args: any[]) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } }; } export function deepClone(obj: T): T { return JSON.parse(JSON.stringify(obj)); } export function isEmpty(value: any): boolean { if (value === null || value === undefined) { return true; } if (typeof value === 'string' || Array.isArray(value)) { return value.length === 0; } if (typeof value === 'object') { return Object.keys(value).length === 0; } return false; } export function isNotEmpty(value: any): boolean { return !isEmpty(value); } export function pick, K extends keyof T>(obj: T, keys: K[]): Pick { const result = {} as Pick; for (const key of keys) { if (key in obj) { result[key] = obj[key]; } } return result; } export function omit, K extends keyof T>(obj: T, keys: K[]): Omit { const result = { ...obj }; for (const key of keys) { delete result[key]; } return result; }