/** * 通用自动化执行工具 * 根据YAML配置自动化执行任意网站的操作流程 * * 用法: * npm run run -- windsurf # 执行Windsurf自动化 * npm run run -- stripe # 执行Stripe自动化 * npm run run -- <网站名> # 执行任意网站自动化 * * 配置文件位置:configs/sites/<网站名>.yaml */ import * as fs from 'fs'; import * as path from 'path'; import * as yaml from 'js-yaml'; import { AdsPowerProvider } from '../src/providers/adspower/AdsPowerProvider'; import { PlaywrightStealthProvider } from '../src/providers/playwright-stealth/PlaywrightStealthProvider'; import { WorkflowEngine } from '../src/workflow/WorkflowEngine'; import { ISiteAdapter, EmptyAdapter } from '../src/adapters/ISiteAdapter'; import { IBrowserProvider } from '../src/core/interfaces/IBrowserProvider'; interface WorkflowConfig { site: string; workflow: any[]; errorHandling?: any; variables?: any; url?: string; siteConfig?: { url?: string; name?: string; [key: string]: any; }; } class AutomationRunner { private siteName: string; private configPath: string; private adapterPath: string; private adapter: ISiteAdapter | null = null; constructor(siteName: string) { this.siteName = siteName; this.configPath = path.join(__dirname, '../configs/sites', `${siteName}.yaml`); this.adapterPath = path.join(__dirname, '../configs/sites', `${siteName}-adapter.ts`); } async run() { console.log('🚀 Browser Automation Executor\n'); console.log(`Site: ${this.siteName}`); console.log(`Config: ${this.configPath}\n`); // 1. 检查配置文件 if (!fs.existsSync(this.configPath)) { console.error(`❌ Config file not found: ${this.configPath}`); console.log('\n💡 Available configs:'); this.listAvailableConfigs(); process.exit(1); } // 2. 加载配置 const config = this.loadConfig(); console.log(`✅ Loaded workflow with ${config.workflow.length} steps\n`); // 2.5. 加载Adapter(如果存在) await this.loadAdapter(); // 3. 初始化Provider const provider = await this.initializeProvider(); try { // 4. 启动浏览器 const result = await provider.launch(); console.log('✅ Browser launched successfully\n'); // 5. 准备Context const context = this.buildContext(result, config); // 5.5. 初始化Adapter if (this.adapter) { await this.adapter.initialize(context); if (this.adapter.beforeWorkflow) { await this.adapter.beforeWorkflow(context); } } // 6. 创建并执行WorkflowEngine const engine = new WorkflowEngine( config.workflow, context, provider.getActionFactory() ); console.log('▶️ Starting workflow execution...\n'); const workflowResult = await engine.execute(); // 7. 显示结果 this.displayResults(workflowResult, config.workflow.length); // 7.5. Adapter后处理 if (this.adapter && this.adapter.afterWorkflow) { await this.adapter.afterWorkflow(context, workflowResult); } // 8. 等待查看 console.log('⏸️ Waiting 5 seconds before closing...'); await new Promise(resolve => setTimeout(resolve, 5000)); } catch (error: any) { console.error('\n❌ Fatal error:', error.message); console.error(error.stack); } finally { await this.cleanup(provider); } } private loadConfig(): WorkflowConfig { const content = fs.readFileSync(this.configPath, 'utf8'); return yaml.load(content) as WorkflowConfig; } /** * 动态加载网站适配器 */ private async loadAdapter(): Promise { // 检查是否存在adapter文件(支持.ts和.js) const tsPath = this.adapterPath; const jsPath = this.adapterPath.replace('.ts', '.js'); let adapterPath: string | null = null; if (fs.existsSync(tsPath)) { adapterPath = tsPath; } else if (fs.existsSync(jsPath)) { adapterPath = jsPath; } if (!adapterPath) { console.log(`⚠️ No adapter found for ${this.siteName}, using empty adapter`); this.adapter = new EmptyAdapter(); return; } try { console.log(`🔌 Loading adapter: ${path.basename(adapterPath)}`); // 动态导入adapter const adapterModule = await import(adapterPath); const AdapterClass = adapterModule.default; if (!AdapterClass) { throw new Error('Adapter must have a default export'); } this.adapter = new AdapterClass(); console.log(`✅ Adapter loaded: ${this.adapter!.name}\n`); } catch (error: any) { console.error(`❌ Failed to load adapter: ${error.message}`); console.log('Using empty adapter as fallback\n'); this.adapter = new EmptyAdapter(); } } private async initializeProvider(): Promise { const browserProvider = process.argv[2] || 'adspower'; console.log(`🌐 Initializing ${browserProvider} Provider...`); if (browserProvider === 'playwright' || browserProvider === 'playwright-stealth') { return new PlaywrightStealthProvider({ headless: false, viewport: { width: 1920, height: 1080 } }); } else { return new AdsPowerProvider({ profileId: process.env.ADSPOWER_USER_ID, siteName: this.siteName.charAt(0).toUpperCase() + this.siteName.slice(1) }); } } private buildContext(launchResult: any, config: WorkflowConfig): any { // 创建空context - Adapter负责填充数据 return { page: launchResult.page, browser: launchResult.browser, logger: console, data: { account: {}, // Adapter会在beforeWorkflow中填充 ...config.variables }, siteConfig: config.siteConfig || { url: config.url || '', name: config.site || this.siteName }, config: config, siteName: this.siteName, // 注入adapter的handlers adapter: this.adapter ? this.adapter.getHandlers() : {} }; } private displayResults(result: any, totalSteps: number): void { console.log('\n' + '='.repeat(60)); console.log('📊 Workflow Execution Summary'); console.log('='.repeat(60)); console.log(`Site: ${this.siteName}`); console.log(`Status: ${result.success ? '✅ SUCCESS' : '❌ FAILED'}`); console.log(`Steps Completed: ${result.steps}/${totalSteps}`); console.log(`Duration: ${(result.duration / 1000).toFixed(2)}s`); console.log(`Errors: ${result.errors.length}`); if (result.errors.length > 0) { console.log('\n❌ Errors:'); result.errors.forEach((err: any, i: number) => { console.log(` ${i + 1}. Step ${err.step} (${err.name}): ${err.error}`); }); } console.log('='.repeat(60) + '\n'); } private async cleanup(provider: IBrowserProvider): Promise { try { console.log('\n🔒 Closing browser...'); await provider.close(); console.log('✅ Browser closed successfully'); // 清理adapter资源 if (this.adapter && this.adapter.cleanup) { await this.adapter.cleanup(); } } catch (e: any) { console.error('⚠️ Error closing browser:', e.message); } } private listAvailableConfigs(): void { const configsDir = path.join(__dirname, '../configs/sites'); if (!fs.existsSync(configsDir)) { console.log(' (No configs directory found)'); return; } const files = fs.readdirSync(configsDir) .filter(f => f.endsWith('.yaml') || f.endsWith('.yml')) .map(f => f.replace(/\.(yaml|yml)$/, '')); if (files.length === 0) { console.log(' (No config files found)'); } else { files.forEach(name => console.log(` - ${name}`)); } } } // 主程序 async function main() { const args = process.argv.slice(2); // 参数格式:node cli/run.js // 示例:node cli/run.js adspower k1728p8l windsurf if (args.length < 3) { console.error('❌ Usage: node cli/run.js '); console.error('\nArguments:'); console.error(' browser-provider 浏览器提供商 (adspower, playwright, etc)'); console.error(' browser-params 浏览器参数 (AdsPower的profileId, 或 - 表示默认)'); console.error(' site 网站名称 (windsurf, stripe, etc)\n'); console.error('Examples:'); console.error(' node cli/run.js adspower k1728p8l windsurf'); console.error(' node cli/run.js playwright - windsurf'); console.error(' node cli/run.js adspower j9abc123 stripe\n'); process.exit(1); } const browserProvider = args[0]; const browserParams = args[1]; const siteName = args[2]; console.log('🚀 Browser Automation Executor\n'); console.log(`Browser: ${browserProvider}`); console.log(`Params: ${browserParams}`); console.log(`Site: ${siteName}\n`); // 设置浏览器参数 if (browserProvider === 'adspower') { if (browserParams !== '-') { process.env.ADSPOWER_USER_ID = browserParams; console.log(`📍 AdsPower Profile: ${browserParams}`); } } const runner = new AutomationRunner(siteName); try { await runner.run(); console.log('\n✅ Automation completed successfully!'); process.exit(0); } catch (error: any) { console.error('\n❌ Automation failed:', error.message); console.error(error.stack); process.exit(1); } } main();