开发契约
4. 开发契约: 平台与脚本的交互规范
Section titled “4. 开发契约: 平台与脚本的交互规范”4.1 执行入口: run
Section titled “4.1 执行入口: run”您的 bundle.js
必须导出一个名为 run
的 async
函数。
4.2 输入: RunOptions
Section titled “4.2 输入: RunOptions”run
函数接收一个参数对象,包含 browser
, page
, 和 context
。
4.3 核心上下文: AnbaoContext
Section titled “4.3 核心上下文: AnbaoContext”这是您获取平台能力的核心。
export interface AnbaoContext { /** * 通用输入:由用户通过 `@schema` 生成的表单动态填写的参数。 * @example * const title = context.common.video_title; * const filePath = context.common.video_file_path; // "C:\\Users\\Me\\Videos\\my_video.mp4" */ common: Record<string, any>;
/** * 当前运行实例匹配到的平台信息。 */ platform: { name: string; base_url: string };
/** * 当前运行实例所使用的 Profile 信息。 * 脚本应利用此 Profile 关联的持久化存储来管理登录状态 (Cookies)。 * 首次运行时,脚本需要实现交互式登录流程来建立初始状态。 */ profile: { name: string };
/** * 路径 API */ paths: { downloads: string; // 用户系统的“下载”目录 data: string; // 与 Profile 绑定的、可持久化读写的目录 };
/** * 结构化日志 API。 * 用于在任务日志中创建具有明确步骤和状态的日志条目。 * 这对于向用户展示清晰、可跟进的任务进度至关重要。 * * @example * context.log('开始上传视频...', 'info'); * // ... a long running operation ... * context.log('视频上传成功', 'success'); */ log: (message: string, level?: 'info' | 'warn' | 'error' | 'success' | 'debug') => void;
/** * 发送一个系统级通知。 * 这将在 Anbao Agent 的通知中心创建一个新的通知,并(如果用户允许)显示一个原生系统通知。 * * @example * context.notify({ * title: '下载完成', * content: '文件 "report.pdf" 已成功下载到您的下载目录。' * }); * * // 发送一个与任务结果相关的通知 * context.notify({ * title: '任务成功', * content: 'Bilibili 视频发布任务已成功完成。', * category: 'TaskResult' * }); */ notify: (payload: { title: string; content: string; category?: 'ScriptMessage' | 'TaskResult' }) => void;
/** * 强制退出 API。 * 当脚本遇到可预期的、业务逻辑上的失败时,应调用此函数来优雅地终止任务。 * 它会向平台报告一个明确的错误信息,而不是抛出一个通用的、未处理的异常。 * * @example * if (!videoUploaded) { * context.forceExit('视频上传失败,请检查网络连接。'); * } */ forceExit: (errorMessage?: string) => void;
/** * 请求人工介入 (Human-in-the-Loop)。 * 当脚本遇到无法自动处理的情况(如验证码)时,调用此函数。 * 它会暂停脚本,发送高优先级通知给用户,并在页面注入一个“继续”按钮。 * 用户手动处理完毕后,点击按钮,脚本将从暂停处继续执行。 * * @param options 介入请求的配置 * @returns {Promise<void>} 一个在用户点击“继续”或超时后完成的 Promise。 * * @example * try { * if (await page.locator('#captcha').isVisible()) { * throw new AutomationError('Captcha', '检测到验证码,请手动处理。'); * } * } catch (error) { * if (error instanceof AutomationError) { * await context.requestHumanIntervention({ message: error.message }); * } else { * throw error; * } * } * // 代码将从这里继续 */ requestHumanIntervention: (options: { message: string; timeout?: number; theme?: 'light' | 'dark' }) => Promise<void>;}
4.4 输出: 成功与失败
Section titled “4.4 输出: 成功与失败”脚本的执行有三种终止方式:
- 成功:
run
函数正常结束并return
一个 JSON 可序列化的对象。这是任务成功的唯一标志。 - 优雅失败: 在函数中调用
context.forceExit("错误信息")
。这将立即终止脚本,并将任务状态标记为失败,同时记录您提供的错误信息。 - 异常失败:
run
函数中throw
一个Error
对象。平台会捕获这个异常,将任务标记为失败,并记录异常的堆栈信息。
4.5 启动参数优先级
Section titled “4.5 启动参数优先级”为了向用户和开发者提供最大的灵活性,平台采用三层优先级策略来合并和应用浏览器启动参数 (launchOptions
)。最终生效的参数由以下三个来源按从高到低的优先级合并而成:
-
最高优先级:临时覆盖 (Temporary Overrides)
- 来源: 用户在计划任务列表页点击“带参运行”,或在任务失败后点击“以有头模式重试”时,在弹出的对话框中设置的参数。
- 作用: 赋予用户针对单次运行的最高控制权,主要用于调试或处理突发情况(如手动登录)。这些参数仅当次有效,不会被保存。
-
中等优先级:计划任务配置 (Schedule Configuration)
- 来源: 用户在创建或编辑计划任务时,通过“长期启动参数”选项为该特定任务设置的参数。
- 作用: 满足用户为不同计划任务设置不同默认行为的需求(例如,某个任务默认就需要有头模式)。这些参数会随计划任务一起保存。
-
最低优先级:脚本默认值 (Script’s Default)
- 来源: 脚本开发者在脚本元数据块中通过
// @launchOptions { ... }
定义的参数。 - 作用: 提供一个开箱即用的推荐配置,确保脚本在大多数情况下的正常运行。
- 来源: 脚本开发者在脚本元数据块中通过
生效逻辑: 这是一个优先级覆盖 (Priority Override) 模型,不是深度合并。高优先级的参数一旦存在,就会完全取代所有低优先级的参数。
- 如果用户设置了临时覆盖参数,则只有临时参数会生效。
- 如果没有临时参数,但用户配置了计划任务参数,则只有计划任务参数会生效。
- 只有在以上两者都不存在时,脚本默认值才会生效。
例如:如果脚本默认值为 {"slowMo": 50}
,而用户在计划任务中配置了 {"headless": false}
,则最终生效的参数只有 {"headless": false}
,slowMo
将不会被应用。
4.6 登录状态管理
Section titled “4.6 登录状态管理”对于需要登录才能操作的平台,脚本必须能够正确处理已登录、未登录和登录失效这三种状态。
核心原则:脚本自检,平台辅助
Section titled “核心原则:脚本自检,平台辅助”平台本身不会预检您的登录状态。脚本的健壮性体现在它能够自我检测环境并向用户提供清晰的指引。
- 检测:脚本在执行核心逻辑前,必须先检查页面是否处于预期的登录状态。
- 报告:如果未登录,脚本不应尝试复杂的交互,而应立即调用
context.forceExit()
并提供一条明确的错误信息,指导用户下一步该怎么做。
最佳实践:如何处理登录
Section titled “最佳实践:如何处理登录”Profile
的核心价值在于它与一个持久化存储目录 (context.paths.data
) 绑定。Playwright 的 BrowserContext
会自动将该上下文中的所有浏览数据(包括 Cookies, Local Storage 等)保存到这个目录中。
这意味着,作为开发者,您完全不需要手动读写任何凭据文件。
正确的登录处理流程如下:
- 启动时检查:在
run
函数的开头,直接导航到需要登录的页面,然后通过检查页面上的特定元素(如头像、用户名)来验证登录是否有效。Playwright 会自动从context.paths.data
加载并应用该 Profile 的 Cookies。 - 失败时清晰退出:如果验证失败,立即调用
context.forceExit()
并提供清晰的指引。
// 无需导入 'fs' 或 'path',平台已处理好一切
export async function run({ page, context }) { // 1. 导航到目标页并验证登录 // 平台已自动从 Profile 的持久化目录加载 Cookies await page.goto(`${context.platform.base_url}/dashboard`); // 假设 /dashboard 是需要登录的页面 const isLoggedIn = await page.locator('#your-avatar-or-logout-button-selector').isVisible();
// 2. 如果未登录,提供清晰的、可操作的失败信息 if (!isLoggedIn) { context.forceExit( '登录凭据失效或未初始化。请在运行历史中找到本次失败的记录,并使用【以有头模式重试】按钮来手动完成登录。', ); return; // 确保在 forceExit 后退出函数 }
// --- 主要业务逻辑 --- context.log('登录验证成功,开始执行任务。', 'info'); // ... 您的代码 ... // 您无需手动保存任何东西,用户在有头模式下登录后, // Playwright 会自动将新的登录状态保存到 Profile 目录中。}
用户的操作流程
Section titled “用户的操作流程”当脚本因为上述原因失败后,用户会在“运行历史”中看到您提供的错误信息。安宝平台的 UI 会提供一个**“以有头模式重试”**的选项,允许用户临时覆盖脚本的 @headless
设置,启动一个带界面的浏览器来完成登录。
一旦用户在有头模式下手动登录成功,Playwright 会自动将新的登录状态(Cookies 等)更新到与该 Profile 关联的持久化目录中。脚本无需任何额外操作,下一次即可在无头模式下成功运行。