import { Page, Locator, expect } from '@playwright/test'; import { testConfig } from '../core/test-config'; import { testLogger } from '../core/test-logger'; import { ScreenshotHelper } from '../helpers/screenshot-helper'; export class BasePage { protected page: Page; protected screenshotHelper: ScreenshotHelper; protected baseURL: string; protected timeout: { default: number; navigation: number; element: number; network: number; }; constructor(page: Page) { this.page = page; this.screenshotHelper = new ScreenshotHelper(page); this.baseURL = testConfig.getBaseURL(); this.timeout = testConfig.getEnvironment().timeout; } async navigate(path: string = ''): Promise { const url = path.startsWith('http') ? path : `${this.baseURL}${path}`; testLogger.info(`导航到页面: ${url}`); try { await this.page.goto(url, { timeout: this.timeout.navigation, waitUntil: 'networkidle' }); await this.page.waitForLoadState('networkidle', { timeout: this.timeout.network }); testLogger.info(`页面加载完成: ${url}`); } catch (error) { testLogger.error(`页面导航失败: ${url}`, error as Error); await this.screenshotHelper.takeScreenshot('navigation-error'); throw error; } } async reload(): Promise { testLogger.info('重新加载页面'); try { await this.page.reload({ timeout: this.timeout.navigation, waitUntil: 'networkidle' }); await this.page.waitForLoadState('networkidle', { timeout: this.timeout.network }); testLogger.info('页面重新加载完成'); } catch (error) { testLogger.error('页面重新加载失败', error as Error); await this.screenshotHelper.takeScreenshot('reload-error'); throw error; } } async goBack(): Promise { testLogger.info('返回上一页'); await this.page.goBack(); } async goForward(): Promise { testLogger.info('前进到下一页'); await this.page.goForward(); } async waitForLoad(timeout?: number): Promise { const loadTimeout = timeout || this.timeout.navigation; testLogger.debug(`等待页面加载完成, 超时时间: ${loadTimeout}ms`); try { await this.page.waitForLoadState('networkidle', { timeout: loadTimeout }); testLogger.debug('页面加载完成'); } catch (error) { testLogger.error('等待页面加载超时', error as Error); await this.screenshotHelper.takeScreenshot('wait-load-error'); throw error; } } async waitForURL(urlPattern: string | RegExp, timeout?: number): Promise { const waitTimeout = timeout || this.timeout.navigation; testLogger.debug(`等待URL匹配: ${urlPattern}, 超时时间: ${waitTimeout}ms`); try { await this.page.waitForURL(urlPattern, { timeout: waitTimeout }); testLogger.debug(`URL匹配成功: ${this.page.url()}`); } catch (error) { testLogger.error(`等待URL匹配失败: ${urlPattern}`, error as Error); await this.screenshotHelper.takeScreenshot('wait-url-error'); throw error; } } async waitForElement(selector: string, timeout?: number): Promise { const waitTimeout = timeout || this.timeout.element; testLogger.debug(`等待元素可见: ${selector}, 超时时间: ${waitTimeout}ms`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: waitTimeout }); testLogger.debug(`元素可见: ${selector}`); return locator; } catch (error) { testLogger.error(`等待元素超时: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('wait-element-error'); throw error; } } async waitForElementHidden(selector: string, timeout?: number): Promise { const waitTimeout = timeout || this.timeout.element; testLogger.debug(`等待元素隐藏: ${selector}, 超时时间: ${waitTimeout}ms`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'hidden', timeout: waitTimeout }); testLogger.debug(`元素已隐藏: ${selector}`); } catch (error) { testLogger.error(`等待元素隐藏超时: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('wait-hidden-error'); throw error; } } async click(selector: string, options?: { timeout?: number; force?: boolean }): Promise { const clickTimeout = options?.timeout || this.timeout.element; testLogger.debug(`点击元素: ${selector}`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: clickTimeout }); await locator.click({ timeout: clickTimeout, force: options?.force }); testLogger.debug(`元素点击成功: ${selector}`); } catch (error) { testLogger.error(`点击元素失败: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('click-error'); throw error; } } async fill(selector: string, value: string, options?: { timeout?: number }): Promise { const fillTimeout = options?.timeout || this.timeout.element; testLogger.debug(`填充输入框: ${selector}, 值: ${value}`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: fillTimeout }); await locator.fill(value, { timeout: fillTimeout }); testLogger.debug(`输入框填充成功: ${selector}`); } catch (error) { testLogger.error(`填充输入框失败: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('fill-error'); throw error; } } async selectOption(selector: string, value: string | string[], options?: { timeout?: number }): Promise { const selectTimeout = options?.timeout || this.timeout.element; testLogger.debug(`选择下拉选项: ${selector}, 值: ${value}`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: selectTimeout }); await locator.selectOption(value, { timeout: selectTimeout }); testLogger.debug(`下拉选项选择成功: ${selector}`); } catch (error) { testLogger.error(`选择下拉选项失败: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('select-error'); throw error; } } async check(selector: string, options?: { timeout?: number }): Promise { const checkTimeout = options?.timeout || this.timeout.element; testLogger.debug(`勾选复选框: ${selector}`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: checkTimeout }); await locator.check({ timeout: checkTimeout }); testLogger.debug(`复选框勾选成功: ${selector}`); } catch (error) { testLogger.error(`勾选复选框失败: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('check-error'); throw error; } } async uncheck(selector: string, options?: { timeout?: number }): Promise { const uncheckTimeout = options?.timeout || this.timeout.element; testLogger.debug(`取消勾选复选框: ${selector}`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: uncheckTimeout }); await locator.uncheck({ timeout: uncheckTimeout }); testLogger.debug(`复选框取消勾选成功: ${selector}`); } catch (error) { testLogger.error(`取消勾选复选框失败: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('uncheck-error'); throw error; } } async getText(selector: string, options?: { timeout?: number }): Promise { const textTimeout = options?.timeout || this.timeout.element; testLogger.debug(`获取元素文本: ${selector}`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: textTimeout }); const text = await locator.textContent(); testLogger.debug(`元素文本: ${selector} = ${text}`); return text || ''; } catch (error) { testLogger.error(`获取元素文本失败: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('get-text-error'); throw error; } } async getAttribute(selector: string, attributeName: string, options?: { timeout?: number }): Promise { const attrTimeout = options?.timeout || this.timeout.element; testLogger.debug(`获取元素属性: ${selector}, 属性名: ${attributeName}`); try { const locator = this.page.locator(selector); await locator.waitFor({ state: 'visible', timeout: attrTimeout }); const attribute = await locator.getAttribute(attributeName); testLogger.debug(`元素属性: ${selector}[${attributeName}] = ${attribute}`); return attribute; } catch (error) { testLogger.error(`获取元素属性失败: ${selector}`, error as Error); await this.screenshotHelper.takeScreenshot('get-attribute-error'); throw error; } } async isVisible(selector: string): Promise { try { const locator = this.page.locator(selector); const visible = await locator.isVisible({ timeout: 5000 }); testLogger.debug(`元素可见性: ${selector} = ${visible}`); return visible; } catch (error) { testLogger.debug(`元素不可见: ${selector}`); return false; } } async isEnabled(selector: string): Promise { try { const locator = this.page.locator(selector); const enabled = await locator.isEnabled({ timeout: 5000 }); testLogger.debug(`元素可用性: ${selector} = ${enabled}`); return enabled; } catch (error) { testLogger.debug(`元素不可用: ${selector}`); return false; } } async waitForTimeout(ms: number): Promise { testLogger.debug(`等待 ${ms}ms`); await this.page.waitForTimeout(ms); } async executeScript(script: string, ...args: any[]): Promise { testLogger.debug('执行JavaScript脚本'); try { const result = await this.page.evaluate(script, ...args); testLogger.debug('JavaScript脚本执行成功'); return result; } catch (error) { testLogger.error('JavaScript脚本执行失败', error as Error); throw error; } } async scrollToElement(selector: string): Promise { testLogger.debug(`滚动到元素: ${selector}`); try { const locator = this.page.locator(selector); await locator.scrollIntoViewIfNeeded(); testLogger.debug(`滚动到元素成功: ${selector}`); } catch (error) { testLogger.error(`滚动到元素失败: ${selector}`, error as Error); throw error; } } async takeScreenshot(name: string): Promise { return await this.screenshotHelper.takeScreenshot(name); } async takeElementScreenshot(selector: string, name: string): Promise { return await this.screenshotHelper.takeElementScreenshot(selector, name); } getCurrentURL(): string { return this.page.url(); } getTitle(): Promise { return this.page.title(); } async expectVisible(selector: string, timeout?: number): Promise { const locator = await this.waitForElement(selector, timeout); await expect(locator).toBeVisible(); } async expectHidden(selector: string, timeout?: number): Promise { const locator = this.page.locator(selector); await expect(locator).toBeHidden({ timeout }); } async expectText(selector: string, expectedText: string, timeout?: number): Promise { const locator = await this.waitForElement(selector, timeout); await expect(locator).toHaveText(expectedText); } async expectValue(selector: string, expectedValue: string, timeout?: number): Promise { const locator = await this.waitForElement(selector, timeout); await expect(locator).toHaveValue(expectedValue); } async expectAttribute(selector: string, attributeName: string, expectedValue: string, timeout?: number): Promise { const locator = await this.waitForElement(selector, timeout); await expect(locator).toHaveAttribute(attributeName, expectedValue); } async expectCount(selector: string, expectedCount: number, timeout?: number): Promise { const locator = this.page.locator(selector); await expect(locator).toHaveCount(expectedCount, { timeout }); } }