import BaseAction from '../core/BaseAction'; import { ValidationError, ElementNotFoundError } from '../../../core/errors/CustomErrors'; /** * 导航动作 - 打开页面 */ class NavigateAction extends BaseAction { async execute(): Promise { const url = this.replaceVariables(this.config.url); const options = this.config.options || { waitUntil: 'networkidle2', timeout: 30000 }; // 重试配置 const maxRetries = this.config.maxRetries || 5; const retryDelay = this.config.retryDelay || 3000; const totalTimeout = this.config.totalTimeout || 180000; // 默认3分钟 const startTime = Date.now(); let lastError: any = null; for (let attempt = 0; attempt < maxRetries; attempt++) { // 检查总超时 if (Date.now() - startTime > totalTimeout) { this.log('error', `总超时 ${totalTimeout}ms,停止重试`); break; } try { if (attempt > 0) { this.log('info', `第 ${attempt + 1} 次尝试导航...`); } else { this.log('info', `导航到: ${url}`); } // 尝试导航 await this.page.goto(url, options); // 验证页面URL是否正确(避免重定向到会员中心等) const currentUrl = this.page.url(); if (this.config.verifyUrl && !currentUrl.includes(this.config.verifyUrl)) { throw new ValidationError( `页面跳转异常`, `URL包含: ${this.config.verifyUrl}`, `实际URL: ${currentUrl}`, { expectedUrl: this.config.verifyUrl, actualUrl: currentUrl } ); } // 验证关键元素存在(确保页面加载正确) if (this.config.verifyElements) { await this.verifyElements(this.config.verifyElements); } this.log('info', `✓ 页面加载完成${attempt > 0 ? ` (尝试 ${attempt + 1} 次)` : ''}`); // 模拟人类阅读页面(1-3秒) await this.readPageDelay(); // 可选的额外等待时间 if (this.config.waitAfter) { await new Promise(resolve => setTimeout(resolve, this.config.waitAfter)); } return { success: true, url: currentUrl }; } catch (error: any) { lastError = error; this.log('warn', `导航失败 (尝试 ${attempt + 1}/${maxRetries}): ${error.message}`); // 如果不是最后一次尝试,等待后重试 if (attempt < maxRetries - 1) { this.log('debug', `等待 ${retryDelay}ms 后重试...`); await new Promise(resolve => setTimeout(resolve, retryDelay)); } } } // 所有重试都失败 this.log('error', `导航失败: ${lastError.message}`); throw lastError; } /** * 验证关键元素存在 */ async verifyElements(selectors: string[]): Promise { this.log('debug', '验证页面元素...'); for (const selector of selectors) { try { await this.page.waitForSelector(selector, { timeout: 10000 }); } catch (error: any) { throw new ElementNotFoundError(selector, { action: 'navigate', operation: 'verifyElements', url: this.page.url() }); } } this.log('debug', `✓ 已验证 ${selectors.length} 个关键元素`); } } export default NavigateAction;