mirror of
https://github.com/web-infra-dev/midscene.git
synced 2025-12-28 23:49:32 +00:00
feat: add static-server into cli (#153)
This commit is contained in:
parent
3117584e57
commit
324a337bac
@ -1,6 +1,6 @@
|
||||
# Command Line Tools
|
||||
|
||||
`@midscene/cli` is the command line version of Midscene. It is suitable for executing very simple tasks or experiencing the basics of Midscene.
|
||||
`@midscene/cli` is the command line version of Midscene. It is suitable for executing very simple tasks. For example, you can write a one-line npm script to check if the build result can launch normally, or extract some data from the web page and write the result into a JSON file.
|
||||
|
||||
:::info Demo Project
|
||||
you can check the demo project of command line tools here: [https://github.com/web-infra-dev/midscene-example/blob/main/command-line](https://github.com/web-infra-dev/midscene-example/blob/main/command-line)
|
||||
@ -21,13 +21,7 @@ export OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
## Examples
|
||||
|
||||
Use headed mode (i.e. visible browser) to visit bing.com and search for 'weather today'
|
||||
|
||||
```bash
|
||||
npx @midscene/cli --headed --url https://wwww.bing.com --action "type 'weather today', hit enter" --sleep 3000
|
||||
```
|
||||
|
||||
visit github status page and save the status to ./status.json
|
||||
Visit github status page and save the status to `./status.json`
|
||||
|
||||
```bash
|
||||
npx @midscene/cli --url https://www.githubstatus.com/ \
|
||||
@ -35,6 +29,18 @@ npx @midscene/cli --url https://www.githubstatus.com/ \
|
||||
--query '{name: string, status: string}[], service status of github page'
|
||||
```
|
||||
|
||||
Serve the `./dist` path statically and check if the `index.html` can launch normally
|
||||
|
||||
```bash
|
||||
npx @midscene/cli --serve ./dist --url index.html --assert 'page title is "My App"'
|
||||
```
|
||||
|
||||
Use headed mode (i.e. visible browser) to visit bing.com and search for 'weather today'
|
||||
|
||||
```bash
|
||||
npx @midscene/cli --headed --url https://wwww.bing.com --action "type 'weather today', hit enter" --sleep 3000
|
||||
```
|
||||
|
||||
Or you may install @midscene/cli globally before calling
|
||||
|
||||
```bash
|
||||
@ -77,3 +83,4 @@ Actions (the order matters, can be used multiple times):
|
||||
1. Always put options before any action param.
|
||||
2. The order of action parameters matters. For example, `--action "some action" --query "some data"` means that the action is taken first, followed by a query.
|
||||
3. If you have some more complex requirements, such as loop operations, using the SDK version (instead of this cli) is an easier way to achieve them.
|
||||
4. Midscene CLI reads the `.env` file by dotenv in the current working directory, allowing you to place some configuration in it.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 命令行工具
|
||||
|
||||
`@midscene/cli` 是 Midscene 的命令行版本。它适合执行简单的任务或体验 Midscene 的基础功能。
|
||||
`@midscene/cli` 是 Midscene 的命令行版本。它非常适合执行简单的任务。比如你可以用它校验编译后的产物是否能正常启动,或者是从某些页面中提取信息并写入一个 JSON 文件。
|
||||
|
||||
:::info 样例项目
|
||||
你可以在这里看到使用命令行工具的样例项目:[https://github.com/web-infra-dev/midscene-example/blob/main/command-line](https://github.com/web-infra-dev/midscene-example/blob/main/command-line)
|
||||
@ -25,16 +25,26 @@ export OPENAI_API_KEY="sk-abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
## 示例
|
||||
|
||||
```bash
|
||||
# headed 模式(即可见浏览器)访问 baidu.com 并搜索“天气”
|
||||
npx @midscene/cli --headed --url https://www.baidu.com --action "输入 '天气', 敲回车" --sleep 3000
|
||||
访问 Github 状态页面并将状态保存到 `./status.json`
|
||||
|
||||
# 访问 Github 状态页面并将状态保存到 ./status.json
|
||||
```bash
|
||||
npx @midscene/cli --url https://www.githubstatus.com/ \
|
||||
--query-output status.json \
|
||||
--query '{serviceName: string, status: string}[], github 页面的服务状态,返回服务名称'
|
||||
```
|
||||
|
||||
为 `./dist` 目录启动静态服务,并检查 `index.html` 能否正常启动
|
||||
|
||||
```bash
|
||||
npx @midscene/cli --serve ./dist --url index.html --assert '页面标题是 "My App"'
|
||||
```
|
||||
|
||||
用 headed 模式(即可见浏览器)访问 baidu.com 并搜索“天气”
|
||||
|
||||
```bash
|
||||
npx @midscene/cli --headed --url https://www.baidu.com --action "输入 '天气', 敲回车" --sleep 3000
|
||||
```
|
||||
|
||||
你也可以先全局安装 @midscene/cli 再调用
|
||||
|
||||
```bash
|
||||
@ -76,3 +86,4 @@ Actions (参数顺序很重要,可以支持多次使用):
|
||||
1. Options 参数(任务信息)应始终放在 Actions 参数之前。
|
||||
2. Actions 参数的顺序很重要。例如,`--action "某操作" --query "某数据"` 表示先执行操作,然后再查询。
|
||||
3. 如果有更复杂的需求,比如循环操作,使用 SDK 版本(而不是这个命令行工具)会更合适。
|
||||
4. Midscene Cli 会用 dotenv 读取当前路径下的 `.env` 配置文件,你可以将环境配置放在其中
|
||||
@ -9,7 +9,8 @@
|
||||
"e2e": "nx run @midscene/web:e2e --verbose",
|
||||
"e2e:cache": "nx run @midscene/web:e2e:cache --verbose",
|
||||
"e2e:report": "nx run @midscene/web:e2e:report --verbose",
|
||||
"test:ai:all": "npm run e2e && npm run e2e:cache && npm run e2e:report && npm run test:ai",
|
||||
"e2e:visualizer": "nx run @midscene/visualizer:e2e --verbose",
|
||||
"test:ai:all": "npm run e2e && npm run e2e:cache && npm run e2e:report && npm run test:ai && npm run e2e:visualizer",
|
||||
"prepare": "pnpm run build && simple-git-hooks",
|
||||
"check-dependency-version": "check-dependency-version-consistency .",
|
||||
"lint": "npx biome check . --diagnostic-level=warn --no-errors-on-unmatched --fix",
|
||||
|
||||
@ -2,8 +2,4 @@
|
||||
|
||||
require('../dist/lib/help.js');
|
||||
|
||||
if (process.argv.indexOf('playground') !== -1) {
|
||||
require('../dist/lib/playground.js');
|
||||
} else {
|
||||
require('../dist/lib/index.js');
|
||||
}
|
||||
require('../dist/lib/index.js');
|
||||
|
||||
@ -8,9 +8,7 @@ export default defineConfig({
|
||||
input: {
|
||||
index: 'src/index.ts',
|
||||
help: 'src/help.ts',
|
||||
playground: 'src/playground.ts',
|
||||
},
|
||||
// input: ['src/utils.ts', 'src/index.ts', 'src/image/index.ts'],
|
||||
externals: ['node:buffer'],
|
||||
target: 'es6',
|
||||
},
|
||||
|
||||
@ -22,12 +22,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@midscene/web": "workspace:*",
|
||||
"dotenv": "16.4.5",
|
||||
"http-server": "14.1.1",
|
||||
"minimist": "1.2.5",
|
||||
"ora-classic": "5.4.2",
|
||||
"puppeteer": "23.0.2",
|
||||
"yargs": "17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modern-js/module-tools": "2.60.6",
|
||||
"@types/minimist": "1.2.5",
|
||||
"@types/node": "^18.0.0",
|
||||
"@types/yargs": "17.0.32",
|
||||
"execa": "9.3.0",
|
||||
|
||||
@ -1,12 +1,29 @@
|
||||
export type ArgumentValueType = string | boolean | number;
|
||||
import type minimist from 'minimist';
|
||||
|
||||
export interface Argument {
|
||||
export type ArgumentValueType = string | boolean | number;
|
||||
export function findOnlyItemInArgs(
|
||||
args: minimist.ParsedArgs,
|
||||
name: string,
|
||||
): string | boolean | number | undefined {
|
||||
const found = args[name];
|
||||
if (found === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(found) && found.length > 1) {
|
||||
throw new Error(`Multiple values found for ${name}`);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
export interface OrderedArgumentItem {
|
||||
name: string;
|
||||
value: ArgumentValueType;
|
||||
}
|
||||
|
||||
export function parse(args: string[]): Argument[] {
|
||||
const orderedArgs: Argument[] = [];
|
||||
export function orderMattersParse(args: string[]): OrderedArgumentItem[] {
|
||||
const orderedArgs: OrderedArgumentItem[] = [];
|
||||
args.forEach((arg, index) => {
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.substring(2);
|
||||
@ -24,19 +41,3 @@ export function parse(args: string[]): Argument[] {
|
||||
|
||||
return orderedArgs;
|
||||
}
|
||||
|
||||
export function findOnlyItemInArgs(
|
||||
args: Argument[],
|
||||
name: string,
|
||||
): ArgumentValueType {
|
||||
const found = args.filter((arg) => arg.name === name);
|
||||
if (found.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (found.length > 1) {
|
||||
throw new Error(`Multiple values found for ${name}`);
|
||||
}
|
||||
|
||||
return found[0].value;
|
||||
}
|
||||
|
||||
@ -8,7 +8,8 @@ if (process.argv.indexOf('--help') !== -1) {
|
||||
Usage: midscene [options] [actions]
|
||||
|
||||
Options:
|
||||
--url <url> The URL to visit, required
|
||||
--serve <root-directory> Serve the local path as a static server, optional
|
||||
--url <url> The URL to visit, required. If --serve is provided, provide the path to the file to visit
|
||||
--user-agent <ua> The user agent to use, optional
|
||||
--viewport-width <width> The width of the viewport, optional
|
||||
--viewport-height <height> The height of the viewport, optional
|
||||
@ -34,6 +35,9 @@ if (process.argv.indexOf('--help') !== -1) {
|
||||
--query-output status.json \\
|
||||
--query '{name: string, status: string}[], service status of github page'
|
||||
|
||||
# serve the current directory and visit the index.html file
|
||||
midscene --serve . --url "index.html" --assert "the content title is 'My App'"
|
||||
|
||||
Examples with Chinese Prompts
|
||||
# headed 模式(即可见浏览器)访问 baidu.com 并搜索“天气”
|
||||
midscene --headed --url "https://www.baidu.com" --action "在搜索框输入 '天气', 敲回车" --wait-for 界面上出现了天气信息
|
||||
@ -42,10 +46,6 @@ if (process.argv.indexOf('--help') !== -1) {
|
||||
midscene --url "https://www.githubstatus.com/" \\
|
||||
--query-output status.json \\
|
||||
--query '{serviceName: string, status: string}[], github 页面的服务状态,返回服务名称'
|
||||
|
||||
|
||||
To launch a playground server, run the following command:
|
||||
midscene playground
|
||||
`);
|
||||
process.exit(0);
|
||||
} else if (process.argv.indexOf('--version') !== -1) {
|
||||
|
||||
6
packages/cli/src/http-server.d.ts
vendored
Normal file
6
packages/cli/src/http-server.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
declare module 'http-server' {
|
||||
export function createServer(options: http.ServerOptions): {
|
||||
server: http.Server;
|
||||
listen: (port: number, host: string, callback: () => void) => void;
|
||||
};
|
||||
}
|
||||
@ -1,28 +1,38 @@
|
||||
import assert from 'node:assert';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { PuppeteerAgent } from '@midscene/web/puppeteer';
|
||||
import { createServer } from 'http-server';
|
||||
import minimist from 'minimist';
|
||||
import ora from 'ora-classic';
|
||||
import puppeteer from 'puppeteer';
|
||||
import { type ArgumentValueType, findOnlyItemInArgs, parse } from './args';
|
||||
import { findOnlyItemInArgs, orderMattersParse } from './args';
|
||||
import 'dotenv/config';
|
||||
|
||||
let spinner: ora.Ora | undefined;
|
||||
const stepString = (name: string, param?: any) => {
|
||||
let paramStr;
|
||||
if (typeof param === 'object') {
|
||||
paramStr = JSON.stringify(param, null, 2);
|
||||
} else if (name === 'sleep') {
|
||||
paramStr = `${param}ms`;
|
||||
} else {
|
||||
paramStr = param;
|
||||
const stepString = (name: string, param?: any, line2?: string) => {
|
||||
const paramToString = (data: any) => {
|
||||
if (name === 'sleep') {
|
||||
return `${data}ms`;
|
||||
}
|
||||
if (typeof data === 'object') {
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
return String(data);
|
||||
};
|
||||
|
||||
let paramStr = paramToString(param);
|
||||
if (line2) {
|
||||
paramStr = `${paramStr}\n ${line2}`;
|
||||
}
|
||||
return `${name}\n ${paramStr ? `${paramStr}` : ''}`;
|
||||
};
|
||||
|
||||
const printStep = (name: string, param?: any) => {
|
||||
const printStep = (name: string, param?: any, line2?: string) => {
|
||||
if (spinner) {
|
||||
spinner.stop();
|
||||
}
|
||||
console.log(`- ${stepString(name, param)}`);
|
||||
console.log(`- ${stepString(name, param, line2)}`);
|
||||
};
|
||||
|
||||
const updateSpin = (text: string) => {
|
||||
@ -35,8 +45,23 @@ const updateSpin = (text: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const launchServer = async (
|
||||
dir: string,
|
||||
): Promise<ReturnType<typeof createServer>> => {
|
||||
// https://github.com/http-party/http-server/blob/master/bin/http-server
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = createServer({
|
||||
root: dir,
|
||||
});
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
resolve(server);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const preferenceArgs = {
|
||||
url: 'url',
|
||||
serve: 'serve',
|
||||
headed: 'headed',
|
||||
viewportWidth: 'viewport-width',
|
||||
viewportHeight: 'viewport-height',
|
||||
@ -61,8 +86,7 @@ const defaultUA =
|
||||
const welcome = '\nWelcome to @midscene/cli\n';
|
||||
console.log(welcome);
|
||||
|
||||
const args = parse(process.argv);
|
||||
|
||||
const args = minimist(process.argv);
|
||||
if (findOnlyItemInArgs(args, 'version')) {
|
||||
const versionFromPkgJson = require('../package.json').version;
|
||||
console.log(`@midscene/cli version ${versionFromPkgJson}`);
|
||||
@ -70,45 +94,84 @@ if (findOnlyItemInArgs(args, 'version')) {
|
||||
}
|
||||
|
||||
// check each arg is either in the preferenceArgs or actionArgs
|
||||
args.forEach((arg) => {
|
||||
Object.keys(args).forEach((arg) => {
|
||||
if (arg === '_') return;
|
||||
assert(
|
||||
Object.values(preferenceArgs).includes(arg.name) ||
|
||||
Object.values(actionArgs).includes(arg.name),
|
||||
`Unknown argument: ${arg.name}`,
|
||||
Object.values(preferenceArgs).includes(arg) ||
|
||||
Object.values(actionArgs).includes(arg),
|
||||
`Unknown argument: ${arg}`,
|
||||
);
|
||||
});
|
||||
|
||||
// prepare the viewport config
|
||||
const preferHeaded = findOnlyItemInArgs(args, preferenceArgs.headed);
|
||||
const userExpectWidth = findOnlyItemInArgs(args, preferenceArgs.viewportWidth);
|
||||
const userExpectHeight = findOnlyItemInArgs(
|
||||
args,
|
||||
preferenceArgs.viewportHeight,
|
||||
);
|
||||
const userExpectDpr = findOnlyItemInArgs(args, preferenceArgs.viewportScale);
|
||||
const defaultDpr = process.platform === 'darwin' ? 2 : 1;
|
||||
const viewportConfig = {
|
||||
width: typeof userExpectWidth === 'number' ? userExpectWidth : 1280,
|
||||
height: typeof userExpectHeight === 'number' ? userExpectHeight : 1280,
|
||||
deviceScaleFactor:
|
||||
typeof userExpectDpr === 'number' ? userExpectDpr : defaultDpr,
|
||||
};
|
||||
const url = findOnlyItemInArgs(args, preferenceArgs.url);
|
||||
assert(url, 'URL is required');
|
||||
assert(typeof url === 'string', 'URL must be a string');
|
||||
|
||||
const preferredUA = findOnlyItemInArgs(args, preferenceArgs.useragent);
|
||||
const ua = typeof preferredUA === 'string' ? preferredUA : defaultUA;
|
||||
|
||||
printStep(preferenceArgs.url, url);
|
||||
printStep(preferenceArgs.useragent, ua);
|
||||
printStep('viewport', JSON.stringify(viewportConfig));
|
||||
if (preferHeaded) {
|
||||
printStep(preferenceArgs.headed, 'true');
|
||||
}
|
||||
|
||||
Promise.resolve(
|
||||
(async () => {
|
||||
// prepare the static server
|
||||
const staticServerConfig = findOnlyItemInArgs(args, 'serve');
|
||||
let staticServerUrl: string | undefined;
|
||||
if (staticServerConfig) {
|
||||
const serverDir =
|
||||
typeof staticServerConfig === 'string'
|
||||
? staticServerConfig
|
||||
: process.cwd();
|
||||
const finalServerDir = resolve(process.cwd(), serverDir);
|
||||
|
||||
const staticServerResult = await launchServer(finalServerDir);
|
||||
const server = staticServerResult.server;
|
||||
const serverAddress = server.address();
|
||||
staticServerUrl = `http://${serverAddress?.address}:${serverAddress?.port}`;
|
||||
printStep('static server', finalServerDir, staticServerUrl);
|
||||
}
|
||||
|
||||
// prepare the viewport config
|
||||
const preferHeaded = findOnlyItemInArgs(args, preferenceArgs.headed);
|
||||
const userExpectWidth = findOnlyItemInArgs(
|
||||
args,
|
||||
preferenceArgs.viewportWidth,
|
||||
);
|
||||
const userExpectHeight = findOnlyItemInArgs(
|
||||
args,
|
||||
preferenceArgs.viewportHeight,
|
||||
);
|
||||
const userExpectDpr = findOnlyItemInArgs(
|
||||
args,
|
||||
preferenceArgs.viewportScale,
|
||||
);
|
||||
const defaultDpr = process.platform === 'darwin' ? 2 : 1;
|
||||
const viewportConfig = {
|
||||
width: typeof userExpectWidth === 'number' ? userExpectWidth : 1280,
|
||||
height: typeof userExpectHeight === 'number' ? userExpectHeight : 1280,
|
||||
deviceScaleFactor:
|
||||
typeof userExpectDpr === 'number' ? userExpectDpr : defaultDpr,
|
||||
};
|
||||
const url = findOnlyItemInArgs(args, preferenceArgs.url) as
|
||||
| string
|
||||
| undefined;
|
||||
let urlToVisit: string | undefined;
|
||||
if (staticServerUrl) {
|
||||
if (typeof url !== 'undefined') {
|
||||
if (url.startsWith('/')) {
|
||||
urlToVisit = `${staticServerUrl}${url}`;
|
||||
} else {
|
||||
urlToVisit = `${staticServerUrl}/${url}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
urlToVisit = url;
|
||||
}
|
||||
assert(urlToVisit, 'URL is required');
|
||||
assert(typeof urlToVisit === 'string', 'URL must be a string');
|
||||
|
||||
const preferredUA = findOnlyItemInArgs(args, preferenceArgs.useragent);
|
||||
const ua = typeof preferredUA === 'string' ? preferredUA : defaultUA;
|
||||
|
||||
printStep(preferenceArgs.url, urlToVisit);
|
||||
printStep(preferenceArgs.useragent, ua);
|
||||
printStep('viewport', JSON.stringify(viewportConfig));
|
||||
if (preferHeaded) {
|
||||
printStep(preferenceArgs.headed, 'true');
|
||||
}
|
||||
|
||||
// launch the browser
|
||||
updateSpin(stepString('launch', 'puppeteer'));
|
||||
const browser = await puppeteer.launch({
|
||||
headless: !preferHeaded,
|
||||
@ -120,24 +183,27 @@ Promise.resolve(
|
||||
|
||||
let errorWhenRunning: Error | undefined;
|
||||
let argName: string;
|
||||
let argValue: ArgumentValueType;
|
||||
let argValue: string | boolean | number | undefined;
|
||||
let agent: PuppeteerAgent | undefined;
|
||||
try {
|
||||
updateSpin(stepString('launch', url));
|
||||
await page.goto(url);
|
||||
updateSpin(stepString('waitForNetworkIdle', url));
|
||||
updateSpin(stepString('launch', urlToVisit));
|
||||
await page.goto(urlToVisit);
|
||||
updateSpin(stepString('waitForNetworkIdle', urlToVisit));
|
||||
await page.waitForNetworkIdle();
|
||||
printStep('launched', url);
|
||||
printStep('launched', urlToVisit);
|
||||
|
||||
agent = new PuppeteerAgent(page, {
|
||||
autoPrintReportMsg: false,
|
||||
});
|
||||
|
||||
const orderedArgs = orderMattersParse(process.argv);
|
||||
|
||||
let index = 0;
|
||||
let outputPath: string | undefined;
|
||||
let actionStarted = false;
|
||||
while (index <= args.length - 1) {
|
||||
const arg = args[index];
|
||||
|
||||
while (index <= orderedArgs.length - 1) {
|
||||
const arg = orderedArgs[index];
|
||||
argName = arg.name;
|
||||
argValue = arg.value;
|
||||
updateSpin(stepString(argName, String(argValue)));
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
// import { PlaygroundServer } from '@midscene/web/playground';
|
||||
|
||||
// const server = new PlaygroundServer();
|
||||
// Promise.resolve()
|
||||
// .then(() => server.launch())
|
||||
// .then(() => {
|
||||
// console.log(
|
||||
// `Midscene playground server is running on http://localhost:${server.port}`,
|
||||
// );
|
||||
// });
|
||||
@ -1,34 +1,16 @@
|
||||
import { findOnlyItemInArgs, parse } from '@/args';
|
||||
import { findOnlyItemInArgs, orderMattersParse } from '@/args';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
describe('args', () => {
|
||||
test('should parse arguments', async () => {
|
||||
const input = [
|
||||
'--url',
|
||||
'https://example.com',
|
||||
'--width',
|
||||
'500',
|
||||
'--action',
|
||||
'click',
|
||||
'--assert',
|
||||
'this is an assertion',
|
||||
'--query-output',
|
||||
'output.json',
|
||||
'--query',
|
||||
'title',
|
||||
'--query',
|
||||
'content',
|
||||
'--prefer-cache',
|
||||
];
|
||||
|
||||
const result = parse(input);
|
||||
|
||||
expect(result).toMatchSnapshot();
|
||||
|
||||
expect(findOnlyItemInArgs(result, 'url')).toBe('https://example.com');
|
||||
expect(findOnlyItemInArgs(result, 'prefer-cache')).toBe(true);
|
||||
expect(
|
||||
findOnlyItemInArgs({ url: 'https://example.com', _: [] }, 'url'),
|
||||
).toBe('https://example.com');
|
||||
expect(() => {
|
||||
findOnlyItemInArgs(result, 'query');
|
||||
findOnlyItemInArgs(
|
||||
{ url: 'https://example.com', _: [], query: [1, 2] },
|
||||
'query',
|
||||
);
|
||||
}).toThrowError('Multiple values found for query');
|
||||
});
|
||||
|
||||
@ -39,9 +21,13 @@ describe('args', () => {
|
||||
'--url',
|
||||
'https://example.com',
|
||||
'--action',
|
||||
'--sleep',
|
||||
'20',
|
||||
'--sleep',
|
||||
'10',
|
||||
];
|
||||
|
||||
const result = parse(input);
|
||||
const result = orderMattersParse(input);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
@ -52,6 +38,14 @@ describe('args', () => {
|
||||
name: 'action',
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
name: 'sleep',
|
||||
value: 20,
|
||||
},
|
||||
{
|
||||
name: 'sleep',
|
||||
value: 10,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -36,4 +36,17 @@ describe.skipIf(process.platform !== 'darwin')('bin', () => {
|
||||
expect(existsSync(randomFileName)).toBeTruthy();
|
||||
unlinkSync(randomFileName);
|
||||
});
|
||||
|
||||
test('serve', async () => {
|
||||
const params = [
|
||||
'--serve',
|
||||
'./tests/server_root',
|
||||
'--url',
|
||||
'index.html',
|
||||
'--assert',
|
||||
'the content title is "My App"',
|
||||
];
|
||||
const { failed } = await execa(cliBin, params);
|
||||
expect(failed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
2
packages/cli/tests/server_root/index.html
Normal file
2
packages/cli/tests/server_root/index.html
Normal file
@ -0,0 +1,2 @@
|
||||
<h1>My App</h1>
|
||||
<p>This is a test page</p>
|
||||
4
packages/visualizer/.gitignore
vendored
4
packages/visualizer/.gitignore
vendored
@ -2,3 +2,7 @@ unpacked-extension/lib/
|
||||
unpacked-extension/scripts/
|
||||
unpacked-extension/pages/
|
||||
|
||||
|
||||
# Midscene.js dump files
|
||||
midscene_run/report
|
||||
midscene_run/dump
|
||||
|
||||
@ -20,7 +20,8 @@
|
||||
"build:watch": "modern build -w",
|
||||
"serve": "http-server ./dist/ -p 3000",
|
||||
"new": "modern new",
|
||||
"upgrade": "modern upgrade"
|
||||
"upgrade": "modern upgrade",
|
||||
"e2e": "./scripts/check-html.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/icons": "5.3.7",
|
||||
|
||||
6
packages/visualizer/scripts/check-html.sh
Executable file
6
packages/visualizer/scripts/check-html.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#! /bin/bash
|
||||
|
||||
node ../cli/bin/midscene --serve ./dist/report --url demo.html \
|
||||
--action "Click the 'Insight / Locate' on Left" \
|
||||
--sleep 300 \
|
||||
--assert "There is a 'Open in Playground' button on the page"
|
||||
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@ -84,6 +84,15 @@ importers:
|
||||
'@midscene/web':
|
||||
specifier: workspace:*
|
||||
version: link:../web-integration
|
||||
dotenv:
|
||||
specifier: 16.4.5
|
||||
version: 16.4.5
|
||||
http-server:
|
||||
specifier: 14.1.1
|
||||
version: 14.1.1
|
||||
minimist:
|
||||
specifier: 1.2.5
|
||||
version: 1.2.5
|
||||
ora-classic:
|
||||
specifier: 5.4.2
|
||||
version: 5.4.2
|
||||
@ -97,6 +106,9 @@ importers:
|
||||
'@modern-js/module-tools':
|
||||
specifier: 2.60.6
|
||||
version: 2.60.6(typescript@5.0.4)
|
||||
'@types/minimist':
|
||||
specifier: 1.2.5
|
||||
version: 1.2.5
|
||||
'@types/node':
|
||||
specifier: ^18.0.0
|
||||
version: 18.19.62
|
||||
@ -3498,9 +3510,6 @@ packages:
|
||||
'@types/node@22.8.5':
|
||||
resolution: {integrity: sha512-5iYk6AMPtsMbkZqCO1UGF9W5L38twq11S2pYWkybGHH2ogPUvXWNlQqJBzuEZWKj/WRH+QTeiv6ySWqJtvIEgA==}
|
||||
|
||||
'@types/node@22.9.0':
|
||||
resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==}
|
||||
|
||||
'@types/normalize-package-data@2.4.4':
|
||||
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
|
||||
|
||||
@ -13992,7 +14001,7 @@ snapshots:
|
||||
|
||||
'@types/conventional-commits-parser@5.0.0':
|
||||
dependencies:
|
||||
'@types/node': 22.9.0
|
||||
'@types/node': 18.19.62
|
||||
optional: true
|
||||
|
||||
'@types/cors@2.8.12': {}
|
||||
@ -14142,11 +14151,6 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
|
||||
'@types/node@22.9.0':
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
optional: true
|
||||
|
||||
'@types/normalize-package-data@2.4.4': {}
|
||||
|
||||
'@types/parse-json@4.0.2': {}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user