/** * 页面 E2E 核心路径执行器(Task 93)。 * * 把 Similar ASIN / 店铺抓取 / 采集数据三个页面的 E2E 核心路径收敛为 * 可注入依赖的确定性流程:解析文件(parse)→ 建任务(createTask)→ * 轮询到终态(poll)→ 刷新历史(refreshHistory),各步由 deps 提供实现, * 页面接入时传真实接口即可复用同一套语义。 * * 流程语义: * - parse 返回 0 表示无可处理数据:安全跳过后续步骤,不创建无效资源; * - poll 返回非终态时按最大次数轮询,超过 maxPollAttempts 降级返回当前 * 状态(不抛错、不无限轮询、不刷新历史); * - 任一步抛错:执行立即终止,cleanup 必被调用(失败与成功路径都执行), * 调用方可重新执行同一 deps 恢复; * - 输入校验 fail-fast:deps 缺函数、parse 返回非法 taskId、maxPollAttempts * 非法均抛错且零状态变更。 */ export interface PageE2EDeps { /** 解析文件/参数,返回 taskId;返回 0 表示无可处理数据 */ parse: () => Promise /** 创建任务 */ createTask: (taskId: number) => Promise /** 单次轮询任务进度,返回状态字符串 */ poll: (taskId: number) => Promise /** 任务到达终态后刷新历史列表(可选) */ refreshHistory?: () => Promise /** 释放临时资源(URL、控制器、计时器等),失败与成功路径都会调用(可选) */ cleanup?: () => Promise } export interface PageE2EOptions { /** 轮询次数上限,必须为正数,默认 60 */ maxPollAttempts?: number /** 判定终态,默认 SUCCESS/FAILED */ isTerminal?: (status: string) => boolean } export interface PageE2EResult { taskId: number status: string /** 已完成的步骤(按执行顺序) */ completedSteps: string[] /** 实际轮询次数 */ attempts: number } export async function runPageE2ECorePath( deps: PageE2EDeps, options: PageE2EOptions = {}, ): Promise { if (typeof deps !== 'object' || deps == null) { throw new Error('deps 必须是对象') } if (typeof deps.parse !== 'function') { throw new Error('parse 必须是函数') } if (typeof deps.createTask !== 'function') { throw new Error('createTask 必须是函数') } if (typeof deps.poll !== 'function') { throw new Error('poll 必须是函数') } const maxPollAttempts = options.maxPollAttempts ?? 60 if (!(maxPollAttempts > 0)) { throw new Error('maxPollAttempts 必须为正数: ' + maxPollAttempts) } const isTerminal = options.isTerminal ?? ((status: string) => status === 'SUCCESS' || status === 'FAILED') const completedSteps: string[] = [] let status = '' let attempts = 0 try { const taskId = await deps.parse() if (taskId === 0) { completedSteps.push('parse') return { taskId: 0, status: '', completedSteps, attempts: 0 } } if (typeof taskId !== 'number' || !Number.isFinite(taskId) || taskId <= 0) { throw new Error('parse 必须返回正整数 taskId: ' + taskId) } completedSteps.push('parse') await deps.createTask(taskId) completedSteps.push('create-task') while (attempts < maxPollAttempts) { attempts += 1 status = await deps.poll(taskId) completedSteps.push('poll') if (isTerminal(status)) { await deps.refreshHistory?.() completedSteps.push('refresh-history') return { taskId, status, completedSteps, attempts } } } return { taskId, status, completedSteps, attempts } } finally { await deps.cleanup?.() } }