2024-12-25 20:23:12 +08:00
|
|
|
import { join } from 'node:path';
|
2025-03-07 17:20:18 +08:00
|
|
|
import { assert } from '@midscene/shared/utils';
|
2024-12-25 20:23:12 +08:00
|
|
|
|
2024-11-25 16:05:01 +08:00
|
|
|
import { randomUUID } from 'node:crypto';
|
|
|
|
|
import { existsSync } from 'node:fs';
|
2025-04-29 13:36:49 +08:00
|
|
|
import { puppeteerAgentForTarget } from '@/puppeteer/agent-launcher';
|
2024-12-25 20:23:12 +08:00
|
|
|
import { ScriptPlayer, buildYaml, parseYamlScript } from '@/yaml';
|
2025-04-11 09:36:41 +08:00
|
|
|
import type { MidsceneYamlScriptWebEnv } from '@midscene/core';
|
2024-12-25 20:23:12 +08:00
|
|
|
import { describe, expect, test, vi } from 'vitest';
|
|
|
|
|
|
|
|
|
|
const serverRoot = join(__dirname, 'server_root');
|
2024-11-25 16:05:01 +08:00
|
|
|
|
2025-01-08 20:59:54 +08:00
|
|
|
const runYaml = async (yamlString: string, ignoreStatusAssertion = false) => {
|
2024-12-25 20:23:12 +08:00
|
|
|
const script = parseYamlScript(yamlString);
|
|
|
|
|
const statusUpdate = vi.fn();
|
2025-04-11 09:36:41 +08:00
|
|
|
const player = new ScriptPlayer<MidsceneYamlScriptWebEnv>(
|
2024-12-25 20:23:12 +08:00
|
|
|
script,
|
|
|
|
|
puppeteerAgentForTarget,
|
|
|
|
|
statusUpdate,
|
|
|
|
|
);
|
2024-12-16 15:04:21 +08:00
|
|
|
await player.run();
|
2025-01-08 20:59:54 +08:00
|
|
|
if (!ignoreStatusAssertion) {
|
|
|
|
|
assert(
|
|
|
|
|
player.status === 'done',
|
|
|
|
|
player.errorInSetup?.message || 'unknown error',
|
|
|
|
|
);
|
|
|
|
|
expect(statusUpdate).toHaveBeenCalled();
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
player,
|
|
|
|
|
statusUpdate,
|
|
|
|
|
};
|
2024-11-25 16:05:01 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const shouldRunAITest =
|
|
|
|
|
process.platform !== 'linux' || process.env.AITEST === 'true';
|
|
|
|
|
|
2024-12-25 20:23:12 +08:00
|
|
|
describe('yaml utils', () => {
|
|
|
|
|
test('basic build && load', () => {
|
|
|
|
|
const script = buildYaml(
|
|
|
|
|
{
|
2025-05-16 17:16:56 +08:00
|
|
|
url: 'https://bing.com',
|
2024-12-25 20:23:12 +08:00
|
|
|
waitForNetworkIdle: {
|
|
|
|
|
timeout: 1000,
|
|
|
|
|
continueOnNetworkIdleError: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
name: 'search',
|
|
|
|
|
flow: [
|
|
|
|
|
{
|
|
|
|
|
aiAction: 'type "hello" in search box, hit enter',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
);
|
2024-11-25 16:05:01 +08:00
|
|
|
expect(script).toMatchSnapshot();
|
2024-12-25 20:23:12 +08:00
|
|
|
|
|
|
|
|
const loadedScript = parseYamlScript(script);
|
|
|
|
|
expect(loadedScript).toMatchSnapshot();
|
2024-11-25 16:05:01 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('load error with filePath', () => {
|
|
|
|
|
expect(() => {
|
2024-12-25 20:23:12 +08:00
|
|
|
parseYamlScript(
|
2024-11-25 16:05:01 +08:00
|
|
|
`
|
|
|
|
|
target:
|
|
|
|
|
a: 1
|
|
|
|
|
`,
|
|
|
|
|
'some_error_path',
|
|
|
|
|
);
|
|
|
|
|
}).toThrow(/some_error_path/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('player - bad params', async () => {
|
|
|
|
|
expect(async () => {
|
|
|
|
|
await runYaml(`
|
|
|
|
|
target:
|
|
|
|
|
serve: ${serverRoot}
|
|
|
|
|
`);
|
|
|
|
|
}).rejects.toThrow();
|
|
|
|
|
|
|
|
|
|
expect(async () => {
|
|
|
|
|
await runYaml(`
|
|
|
|
|
target:
|
|
|
|
|
serve: ${serverRoot}
|
|
|
|
|
viewportWidth: 0
|
|
|
|
|
`);
|
|
|
|
|
}).rejects.toThrow();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe.skipIf(!shouldRunAITest)(
|
|
|
|
|
'player - e2e',
|
|
|
|
|
() => {
|
2024-12-25 20:23:12 +08:00
|
|
|
test('flush output even if assertion failed', async () => {
|
2024-11-25 16:05:01 +08:00
|
|
|
const outputPath = `./midscene_run/output/${randomUUID()}.json`;
|
|
|
|
|
const yamlString = `
|
|
|
|
|
target:
|
2024-12-25 20:23:12 +08:00
|
|
|
url: https://www.bing.com
|
2024-11-25 16:05:01 +08:00
|
|
|
output: ${outputPath}
|
|
|
|
|
tasks:
|
|
|
|
|
- name: local page
|
|
|
|
|
flow:
|
|
|
|
|
- aiQuery: >
|
|
|
|
|
the background color of the page, { color: 'white' | 'black' | 'red' | 'green' | 'blue' | 'yellow' | 'purple' | 'orange' | 'pink' | 'brown' | 'gray' | 'black'
|
|
|
|
|
- name: check content
|
|
|
|
|
flow:
|
2024-12-25 20:23:12 +08:00
|
|
|
- aiAssert: this is a food delivery service app
|
2024-11-25 16:05:01 +08:00
|
|
|
`;
|
|
|
|
|
await expect(async () => {
|
|
|
|
|
await runYaml(yamlString);
|
|
|
|
|
}).rejects.toThrow();
|
|
|
|
|
|
|
|
|
|
expect(existsSync(outputPath)).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('cookie', async () => {
|
|
|
|
|
const yamlString = `
|
|
|
|
|
target:
|
|
|
|
|
url: http://httpbin.dev/cookies
|
2024-12-25 20:23:12 +08:00
|
|
|
cookie: ./tests/unit-test/fixtures/cookie/httpbin.dev_cookies.json
|
2024-11-25 16:05:01 +08:00
|
|
|
tasks:
|
|
|
|
|
- name: check cookie
|
|
|
|
|
flow:
|
|
|
|
|
- aiAssert: the value of midscene_foo is "bar"
|
|
|
|
|
`;
|
|
|
|
|
await runYaml(yamlString);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('online server - lazy response', async () => {
|
|
|
|
|
const yamlString = `
|
|
|
|
|
target:
|
|
|
|
|
url: https://httpbin.org/delay/60000
|
|
|
|
|
waitForNetworkIdle:
|
|
|
|
|
timeout: 10
|
|
|
|
|
continueOnNetworkIdleError: false
|
|
|
|
|
tasks:
|
|
|
|
|
- name: check content
|
|
|
|
|
flow:
|
|
|
|
|
- aiAssert: the response is "Hello, world!"
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
expect(async () => {
|
|
|
|
|
await runYaml(yamlString);
|
|
|
|
|
}).rejects.toThrow(/TimeoutError/i);
|
|
|
|
|
});
|
2025-01-08 20:59:54 +08:00
|
|
|
|
|
|
|
|
test('stop on task error', async () => {
|
|
|
|
|
const yamlString = `
|
|
|
|
|
target:
|
2025-05-16 17:16:56 +08:00
|
|
|
url: https://bing.com/
|
2025-01-08 20:59:54 +08:00
|
|
|
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:
|
2025-05-16 17:16:56 +08:00
|
|
|
url: https://bing.com/
|
2025-01-08 20:59:54 +08:00
|
|
|
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');
|
|
|
|
|
});
|
2024-11-25 16:05:01 +08:00
|
|
|
},
|
|
|
|
|
60 * 1000,
|
|
|
|
|
);
|