feat: allow continue-on-error in yaml (#254)

* feat: allow continue on error in yaml #252

* doc: update doc for new param

* fix: ignore yaml file in node-modules
This commit is contained in:
yuyutaotao 2025-01-08 20:59:54 +08:00 committed by GitHub
parent de92f3fc13
commit c91da56431
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 67 additions and 15 deletions

View File

@ -151,6 +151,7 @@ The `tasks` part is an array indicates the tasks to do. Remember to write a `-`
```yaml
tasks:
- name: <name>
continueOnError: <boolean> # optional, default is false
flow:
# perform an action, this is the shortcut for aiAction
- ai: <prompt>

View File

@ -151,6 +151,7 @@ target:
```yaml
tasks:
- name: <name>
continueOnError: <boolean> # 可选,错误时是否继续执行下一个任务,默认 false
flow:
# 执行一个交互,`ai` 是 `aiAction` 的简写方式
- ai: <prompt>

View File

@ -6,6 +6,7 @@ export interface MidsceneYamlScript {
export interface MidsceneYamlTask {
name: string;
flow: MidsceneYamlFlowItem[];
continueOnError?: boolean;
}
export interface MidsceneYamlScriptEnv {

View File

@ -197,26 +197,31 @@ export class ScriptPlayer {
this.setPlayerStatus('running');
let errorFlag = false;
while (taskIndex < tasks.length) {
const taskStatus = this.taskStatusList[taskIndex];
this.setTaskStatus(taskIndex, 'running' as any);
this.setTaskIndex(taskIndex);
try {
this.setTaskIndex(taskIndex);
await this.playTask(this.taskStatusList[taskIndex], this.pageAgent);
await this.playTask(taskStatus, this.pageAgent);
this.setTaskStatus(taskIndex, 'done' as any);
} catch (e) {
this.setTaskStatus(taskIndex, 'error' as any, e as Error);
this.setPlayerStatus('error');
errorFlag = true;
this.reportFile = agent.reportFile;
taskIndex++;
continue;
if (taskStatus.continueOnError) {
// nothing more to do
} else {
this.reportFile = agent.reportFile;
errorFlag = true;
break;
}
}
this.reportFile = agent.reportFile;
this.setTaskStatus(taskIndex, 'done' as any);
taskIndex++;
}
if (!errorFlag) {
if (errorFlag) {
this.setPlayerStatus('error');
} else {
this.setPlayerStatus('done');
}

View File

@ -9,7 +9,7 @@ import { describe, expect, test, vi } from 'vitest';
const serverRoot = join(__dirname, 'server_root');
const runYaml = async (yamlString: string) => {
const runYaml = async (yamlString: string, ignoreStatusAssertion = false) => {
const script = parseYamlScript(yamlString);
const statusUpdate = vi.fn();
const player = new ScriptPlayer(
@ -18,11 +18,17 @@ const runYaml = async (yamlString: string) => {
statusUpdate,
);
await player.run();
expect(statusUpdate).toHaveBeenCalled();
assert(
player.status === 'done',
player.errorInSetup?.message || 'unknown error',
);
if (!ignoreStatusAssertion) {
assert(
player.status === 'done',
player.errorInSetup?.message || 'unknown error',
);
expect(statusUpdate).toHaveBeenCalled();
}
return {
player,
statusUpdate,
};
};
const shouldRunAITest =
@ -140,6 +146,44 @@ describe.skipIf(!shouldRunAITest)(
await runYaml(yamlString);
}).rejects.toThrow(/TimeoutError/i);
});
test('stop on task error', async () => {
const yamlString = `
target:
url: https://www.baidu.com
tasks:
- name: assert1
flow:
- aiAssert: this is a food delivery service app
- name: assert2
flow:
- aiAssert: this is a search engine
`;
const { player } = await runYaml(yamlString, true);
expect(player.status).toBe('error');
expect(player.taskStatusList[0].status).toBe('error');
expect(player.taskStatusList[1].status).toBe('init');
});
test('allow continue on task error', async () => {
const yamlString = `
target:
url: https://www.baidu.com
tasks:
- name: assert1
continueOnError: true
flow:
- aiAssert: this is a food delivery service app
- name: assert2
flow:
- aiAssert: this is a search engine
`;
const { player } = await runYaml(yamlString, true);
expect(player.status).toBe('done');
expect(player.taskStatusList[0].status).toBe('error');
expect(player.taskStatusList[1].status).toBe('done');
});
},
60 * 1000,
);