feat(core): allow custom midscene_run dir (#631)

* feat(core): support custom midscene_run dir

* feat(report): add search functionality to PlaywrightCaseSelector component

* refactor(shared): simplify base directory resolution and remove unused environment variable

* feat(shared): integrate shared environment variables across multiple packages

* refactor(shared): update base directory resolution to use dynamic midscene_run directory

* fix(puppeteer): increase screenshot timeout from 3s to 10s for improved reliability
This commit is contained in:
Leyang 2025-04-24 22:54:52 +08:00 committed by GitHub
parent f85cd6cd1b
commit ca644d8914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 225 additions and 102 deletions

View File

@ -1,6 +1,6 @@
import './App.less';
import { overrideAIConfig } from '@midscene/core/env';
import { SCRCPY_SERVER_PORT } from '@midscene/shared/constants';
import { overrideAIConfig } from '@midscene/shared/env';
import {
EnvConfig,
Logo,

View File

@ -16,6 +16,7 @@
"@midscene/web": "workspace:*",
"@midscene/core": "workspace:*",
"@midscene/report": "workspace:*",
"@midscene/shared": "workspace:*",
"antd": "^5.21.6",
"dayjs": "1.11.11",
"react": "^18.3.1",

View File

@ -1,5 +1,5 @@
import type { UIContext } from '@midscene/core';
import { overrideAIConfig } from '@midscene/core/env';
import { overrideAIConfig } from '@midscene/shared/env';
import {
ContextPreview,
type PlaygroundResult,

View File

@ -15,6 +15,7 @@
"@midscene/core": "workspace:*",
"@midscene/visualizer": "workspace:*",
"@midscene/web": "workspace:*",
"@midscene/shared": "workspace:*",
"@modern-js/runtime": "2.60.6",
"@rsbuild/core": "^1.3.1",
"@rsbuild/plugin-less": "^1.1.1",

View File

@ -1,8 +1,9 @@
import { DownOutlined } from '@ant-design/icons';
import { DownOutlined, SearchOutlined } from '@ant-design/icons';
import type { GroupedActionDump } from '@midscene/core';
import { iconForStatus, timeCostStrElement } from '@midscene/visualizer';
import { Dropdown } from 'antd';
import { Dropdown, Input } from 'antd';
import type React from 'react';
import { useMemo, useState } from 'react';
import type { ExecutionDumpWithPlaywrightAttributes } from '../types';
interface PlaywrightCaseSelectorProps {
@ -18,6 +19,9 @@ export function PlaywrightCaseSelector({
}: PlaywrightCaseSelectorProps): JSX.Element | null {
if (!dumps || dumps.length <= 1) return null;
const [searchText, setSearchText] = useState('');
const [dropdownVisible, setDropdownVisible] = useState(false);
const nameForDump = (dump: GroupedActionDump) =>
`${dump.groupName} - ${dump.groupDescription}`;
@ -44,7 +48,14 @@ export function PlaywrightCaseSelector({
return rowContent;
};
const items = (dumps || []).map((dump, index) => {
const filteredDumps = useMemo(() => {
if (!searchText) return dumps || [];
return (dumps || []).filter((dump) =>
nameForDump(dump).toLowerCase().includes(searchText.toLowerCase()),
);
}, [dumps, searchText]);
const items = filteredDumps.map((dump, index) => {
return {
key: index,
label: (
@ -54,6 +65,7 @@ export function PlaywrightCaseSelector({
if (onSelect) {
onSelect(dump);
}
setDropdownVisible(false);
}}
>
<div>{contentForDump(dump, index)}</div>
@ -69,10 +81,40 @@ export function PlaywrightCaseSelector({
)
: 'Select a case';
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value);
};
const dropdownRender = (menu: React.ReactNode) => (
<div>
<div style={{ padding: '8px' }}>
<Input
placeholder="Search test case"
value={searchText}
onChange={handleSearchChange}
prefix={<SearchOutlined />}
allowClear
autoFocus
/>
</div>
{menu}
</div>
);
return (
<div className="playwright-case-selector">
<Dropdown menu={{ items }}>
<a onClick={(e) => e.preventDefault()}>
<Dropdown
menu={{ items }}
dropdownRender={dropdownRender}
onOpenChange={setDropdownVisible}
open={dropdownVisible}
>
<a
onClick={(e) => {
e.preventDefault();
setDropdownVisible(!dropdownVisible);
}}
>
{btnName} <DownOutlined />
</a>
</Dropdown>

View File

@ -1,5 +1,5 @@
import type { UIContext } from '@midscene/core';
import { overrideAIConfig } from '@midscene/core/env';
import { overrideAIConfig } from '@midscene/shared/env';
import {
ContextPreview,
Logo,

View File

@ -440,6 +440,14 @@ Set the `MIDSCENE_DEBUG_AI_PROFILE` variable to view the execution time and usag
export MIDSCENE_DEBUG_AI_PROFILE=1
```
### Customize the run artifact directory
Set the `MIDSCENE_RUN_DIR` variable to customize the run artifact directory.
```bash
export MIDSCENE_RUN_DIR=midscene_run # The default value is the midscene_run in the current working directory, you can set it to an absolute path or a relative path
```
### Using LangSmith
LangSmith is a platform for debugging large language models. To integrate LangSmith, follow these steps:

View File

@ -14,7 +14,7 @@ You can check the supported models in [Choose a model](./choose-a-model)
`adb` is a command-line tool that allows you to communicate with an Android device. There are two ways to install `adb`:
- way 1: use [Android Studio](https://developer.android.com/studio?hl=zh-cn) to install
- way 1: use [Android Studio](https://developer.android.com/studio) to install
- way 2: use [Android command-line tools](https://developer.android.com/studio#command-line-tools-only) to install
Verify adb is installed successfully:
@ -32,6 +32,22 @@ Installed as /usr/local/bin//adb
Running on Darwin 24.3.0 (arm64)
```
### Set environment variable ANDROID_HOME
Reference [Android environment variables](https://developer.android.com/tools/variables), set the environment variable `ANDROID_HOME`.
Verify the `ANDROID_HOME` variable is set successfully:
```bash
echo $ANDROID_HOME
```
When the command has any output, the `ANDROID_HOME` variable is set successfully:
```log
/Users/your_username/Library/Android/sdk
```
### Connect Android device with adb
In the developer options of the system settings, enable the 'USB debugging' of the Android device, if the 'USB debugging (secure settings)' exists, also enable it, then connect the Android device with a USB cable

View File

@ -124,7 +124,10 @@ export AZURE_OPENAI_DEPLOYMENT="gpt-4o"
You can also override the config by javascript. Remember to call this before running Midscene codes.
```typescript
import { overrideAIConfig } from "@midscene/core/env";
import { overrideAIConfig } from "@midscene/web/puppeteer";
// or import { overrideAIConfig } from "@midscene/web/playwright";
// or import { overrideAIConfig } from "@midscene/android";
overrideAIConfig({
MIDSCENE_MODEL_NAME: "...",

View File

@ -439,6 +439,14 @@ overrideAIConfig({
export MIDSCENE_DEBUG_AI_PROFILE=1
```
### 自定义运行产物目录
设置 `MIDSCENE_RUN_DIR` 变量,你可以自定义运行产物目录。
```bash
export MIDSCENE_RUN_DIR=midscene_run # 默认值为当前运行目录下的 midscene_run 目录,支持设置为绝对路径或者相对于当前目录的相对路径
```
### 使用 LangSmith
LangSmith 是一个用于调试大语言模型的平台。想要集成 LangSmith请按以下步骤操作

View File

@ -32,6 +32,22 @@ Installed as /usr/local/bin//adb
Running on Darwin 24.3.0 (arm64)
```
### 设置环境 ANDROID_HOME 变量
参考[Android 环境变量](https://developer.android.com/tools/variables?hl=zh-cn),设置环境变量 `ANDROID_HOME`。
验证 `ANDROID_HOME` 变量是否设置成功:
```bash
echo $ANDROID_HOME
```
当上述命令有输出时,表示 `ANDROID_HOME` 变量设置成功:
```log
/Users/your_username/Library/Android/sdk
```
### 连接 Android 设备
在 Android 设备的开发者选项中,启用 'USB 调试',如果存在 'USB 调试(安全设置)',也启用它,然后使用 USB 线连接 Android 设备。

View File

@ -125,7 +125,10 @@ export AZURE_OPENAI_DEPLOYMENT="gpt-4o"
你也可以在运行 Midscene 代码之前,使用 Javascript 来配置 AI 服务。
```typescript
import { overrideAIConfig } from "@midscene/core/env";
import { overrideAIConfig } from "@midscene/web/puppeteer";
// 或者 import { overrideAIConfig } from "@midscene/web/playwright";
// 或者 import { overrideAIConfig } from "@midscene/android";
overrideAIConfig({
MIDSCENE_MODEL_NAME: "...",

View File

@ -3,6 +3,7 @@
"private": true,
"version": "0.15.3",
"scripts": {
"dev": "nx run-many --target=build:watch --exclude=android-playground,chrome-extension,@midscene/report,doc --verbose",
"build": "nx run-many --target=build --exclude=doc --verbose",
"test": "nx run-many --target=test --projects=@midscene/core=@midscene/shared,@midscene/visualizer,@midscene/web,@midscene/cli --verbose",
"test:ai": "nx run-many --target=test:ai --projects=@midscene/core,@midscene/web --verbose",

View File

@ -1,7 +1,7 @@
import { PageAgent, type PageAgentOpt } from '@midscene/web/agent';
import { AndroidDevice } from '../page';
import { vlLocateMode } from '@midscene/core/env';
import { vlLocateMode } from '@midscene/shared/env';
import { getConnectedDevices } from '../utils';
import { debugPage } from '../page';

View File

@ -1,3 +1,4 @@
export { AndroidDevice } from './page';
export { AndroidAgent, agentFromAdbDevice } from './agent';
export { getConnectedDevices } from './utils';
export { overrideAIConfig } from '@midscene/shared/env';

View File

@ -7,7 +7,6 @@ export default defineConfig({
buildConfig: {
input: {
index: 'src/index.ts',
env: 'src/env.ts',
utils: 'src/utils.ts',
tree: 'src/tree.ts',
'ai-model': 'src/ai-model/index.ts',

View File

@ -10,7 +10,6 @@
"files": ["dist", "report", "README.md"],
"exports": {
".": "./dist/lib/index.js",
"./env": "./dist/lib/env.js",
"./utils": "./dist/lib/utils.js",
"./ai-model": "./dist/lib/ai-model.js",
"./tree": "./dist/lib/tree.js"
@ -18,7 +17,6 @@
"typesVersions": {
"*": {
".": ["./dist/types/index.d.ts"],
"env": ["./dist/types/env.d.ts"],
"utils": ["./dist/types/utils.d.ts"],
"ai-model": ["./dist/types/ai-model.d.ts"],
"tree": ["./dist/types/tree.d.ts"]

View File

@ -1,4 +1,3 @@
import { MIDSCENE_MODEL_NAME, getAIConfig } from '@/env';
import type {
ExecutionDump,
ExecutionTask,
@ -9,6 +8,7 @@ import type {
ExecutorContext,
} from '@/types';
import { getVersion } from '@/utils';
import { MIDSCENE_MODEL_NAME, getAIConfig } from '@midscene/shared/env';
import { assert } from '@midscene/shared/utils';
export class Executor {

View File

@ -17,9 +17,9 @@ import {
getModelName,
} from './service-caller/index';
import { vlLocateMode } from '@/env';
import type { PlanningLocateParam } from '@/types';
import { NodeType } from '@midscene/shared/constants';
import { vlLocateMode } from '@midscene/shared/env';
import { treeToList } from '@midscene/shared/extractor';
import { compositeElementInfoImg } from '@midscene/shared/img';
import { getDebug } from '@midscene/shared/logger';

View File

@ -1,9 +1,3 @@
import {
MIDSCENE_USE_QWEN_VL,
MIDSCENE_USE_VLM_UI_TARS,
getAIConfigInBoolean,
vlLocateMode,
} from '@/env';
import type {
AIAssertionResponse,
AIDataExtractionResponse,
@ -20,6 +14,12 @@ import type {
Size,
UIContext,
} from '@/types';
import {
MIDSCENE_USE_QWEN_VL,
MIDSCENE_USE_VLM_UI_TARS,
getAIConfigInBoolean,
vlLocateMode,
} from '@midscene/shared/env';
import { cropByRect, paddingToMatchBlockByBase64 } from '@midscene/shared/img';
import { getDebug } from '@midscene/shared/logger';
import { assert } from '@midscene/shared/utils';

View File

@ -1,5 +1,5 @@
import { vlLocateMode } from '@/env';
import type { PageType, PlanningAIResponse, UIContext } from '@/types';
import { vlLocateMode } from '@midscene/shared/env';
import { paddingToMatchBlockByBase64 } from '@midscene/shared/img';
import { assert } from '@midscene/shared/utils';
import {

View File

@ -1,4 +1,4 @@
import type { vlLocateMode } from '../../env';
import type { vlLocateMode } from '@midscene/shared/env';
export function bboxDescription(vlMode: ReturnType<typeof vlLocateMode>) {
if (vlMode === 'gemini') {
return '2d bounding box as [ymin, xmin, ymax, xmax]';

View File

@ -1,6 +1,6 @@
import { PromptTemplate } from '@langchain/core/prompts';
import type { vlLocateMode } from '@midscene/shared/env';
import type { ResponseFormatJSONSchema } from 'openai/resources';
import type { vlLocateMode } from '../../env';
import { bboxDescription } from './common';
export function systemPromptToLocateElement(
vlMode: ReturnType<typeof vlLocateMode>,

View File

@ -1,6 +1,6 @@
import type { vlLocateMode } from '@/env';
import type { PageType } from '@/types';
import { PromptTemplate } from '@langchain/core/prompts';
import type { vlLocateMode } from '@midscene/shared/env';
import type { ResponseFormatJSONSchema } from 'openai/resources';
import { bboxDescription } from './common';
import { samplePageDescription } from './util';

View File

@ -1,5 +1,5 @@
import type { vlLocateMode } from '@/env';
import { PromptTemplate } from '@langchain/core/prompts';
import type { vlLocateMode } from '@midscene/shared/env';
import { bboxDescription } from './common';
export function systemPromptToLocateSection(

View File

@ -1,7 +1,7 @@
import { vlLocateMode } from '@/env';
import { imageInfoOfBase64 } from '@/image/index';
import type { BaseElement, ElementTreeNode, Size, UIContext } from '@/types';
import { NodeType } from '@midscene/shared/constants';
import { vlLocateMode } from '@midscene/shared/env';
import { descriptionOfTree, treeToList } from '@midscene/shared/extractor';
import { assert } from '@midscene/shared/utils';
import { generateHashId } from '@midscene/shared/utils';

View File

@ -4,13 +4,6 @@ import {
DefaultAzureCredential,
getBearerTokenProvider,
} from '@azure/identity';
import { enableDebug, getDebug } from '@midscene/shared/logger';
import { assert } from '@midscene/shared/utils';
import { ifInBrowser } from '@midscene/shared/utils';
import dJSON from 'dirty-json';
import OpenAI, { AzureOpenAI } from 'openai';
import type { ChatCompletionMessageParam } from 'openai/resources';
import { SocksProxyAgent } from 'socks-proxy-agent';
import {
ANTHROPIC_API_KEY,
AZURE_OPENAI_API_VERSION,
@ -38,7 +31,14 @@ import {
getAIConfigInBoolean,
getAIConfigInJson,
vlLocateMode,
} from '../../env';
} from '@midscene/shared/env';
import { enableDebug, getDebug } from '@midscene/shared/logger';
import { assert } from '@midscene/shared/utils';
import { ifInBrowser } from '@midscene/shared/utils';
import dJSON from 'dirty-json';
import OpenAI, { AzureOpenAI } from 'openai';
import type { ChatCompletionMessageParam } from 'openai/resources';
import { SocksProxyAgent } from 'socks-proxy-agent';
import { AIActionType } from '../common';
import { assertSchema } from '../prompt/assertion';
import { locatorSchema } from '../prompt/llm-locator';

View File

@ -9,7 +9,7 @@ export {
AiAssert,
} from './ai-model/index';
export { getAIConfig, MIDSCENE_MODEL_NAME } from './env';
export { getAIConfig, MIDSCENE_MODEL_NAME } from '@midscene/shared/env';
export type * from './types';
export default Insight;

View File

@ -1,11 +1,6 @@
import { callAiFn } from '@/ai-model/common';
import { AiExtractElementInfo, AiLocateElement } from '@/ai-model/index';
import { AiAssert, AiLocateSection } from '@/ai-model/inspect';
import {
MIDSCENE_FORCE_DEEP_THINK,
getAIConfigInBoolean,
vlLocateMode,
} from '@/env';
import type {
AIElementResponse,
AISingleElementResponse,
@ -23,6 +18,11 @@ import type {
Rect,
UIContext,
} from '@/types';
import {
MIDSCENE_FORCE_DEEP_THINK,
getAIConfigInBoolean,
vlLocateMode,
} from '@midscene/shared/env';
import { getDebug } from '@midscene/shared/logger';
import { assert } from '@midscene/shared/utils';
import { emitInsightDump } from './utils';

View File

@ -1,4 +1,3 @@
import { MIDSCENE_MODEL_NAME, getAIConfig, vlLocateMode } from '@/env';
import type {
DumpMeta,
DumpSubscriber,
@ -6,6 +5,11 @@ import type {
PartialInsightDumpFromSDK,
} from '@/types';
import { getVersion } from '@/utils';
import {
MIDSCENE_MODEL_NAME,
getAIConfig,
vlLocateMode,
} from '@midscene/shared/env';
import { uuid } from '@midscene/shared/utils';
export function emitInsightDump(

View File

@ -4,19 +4,19 @@ import { tmpdir } from 'node:os';
import * as path from 'node:path';
import { dirname } from 'node:path';
import {
defaultRunDirName,
getMidsceneRunSubDir,
logDir,
runDirName,
} from '@midscene/shared/common';
import { getRunningPkgInfo } from '@midscene/shared/fs';
import { assert, getGlobalScope } from '@midscene/shared/utils';
import { ifInBrowser, uuid } from '@midscene/shared/utils';
import {
MIDSCENE_DEBUG_MODE,
MIDSCENE_OPENAI_INIT_CONFIG_JSON,
getAIConfig,
getAIConfigInJson,
} from './env';
} from '@midscene/shared/env';
import { getRunningPkgInfo } from '@midscene/shared/fs';
import { assert, getGlobalScope } from '@midscene/shared/utils';
import { ifInBrowser, uuid } from '@midscene/shared/utils';
import type { Rect, ReportDumpWithAttributes } from './types';
let logEnvReady = false;
@ -176,19 +176,25 @@ export function writeLogFile(opts: {
// gitIgnore in the parent directory
const gitIgnorePath = path.join(targetDir, '../../.gitignore');
const gitPath = path.join(targetDir, '../../.git');
let gitIgnoreContent = '';
if (existsSync(gitIgnorePath)) {
gitIgnoreContent = readFileSync(gitIgnorePath, 'utf-8');
if (existsSync(gitPath)) {
// if the git path exists, we need to add the log folder to the git ignore file
if (existsSync(gitIgnorePath)) {
gitIgnoreContent = readFileSync(gitIgnorePath, 'utf-8');
}
// ignore the log folder
if (!gitIgnoreContent.includes(`${defaultRunDirName}/`)) {
writeFileSync(
gitIgnorePath,
`${gitIgnoreContent}\n# Midscene.js dump files\n${defaultRunDirName}/dump\n${defaultRunDirName}/report\n${defaultRunDirName}/tmp\n${defaultRunDirName}/log\n`,
'utf-8',
);
}
}
// ignore the log folder
if (!gitIgnoreContent.includes(`${runDirName}/`)) {
writeFileSync(
gitIgnorePath,
`${gitIgnoreContent}\n# Midscene.js dump files\n${runDirName}/dump\n${runDirName}/report\n${runDirName}/tmp\n${runDirName}/log\n`,
'utf-8',
);
}
logEnvReady = true;
}

View File

@ -1,7 +1,7 @@
import { distance } from '@/ai-model/prompt/util';
import { vlLocateMode } from '@/env';
import Insight from '@/insight';
import { sleep } from '@/utils';
import { vlLocateMode } from '@midscene/shared/env';
import { getContextFromFixture } from 'tests/evaluation';
import { describe, expect, test, vi } from 'vitest';

View File

@ -1,5 +1,5 @@
import { plan } from '@/ai-model';
import { vlLocateMode } from '@/env';
import { vlLocateMode } from '@midscene/shared/env';
import { getContextFromFixture } from 'tests/evaluation';
/* eslint-disable max-lines-per-function */
import { describe, expect, it, vi } from 'vitest';

View File

@ -1,8 +1,8 @@
import { AiLocateElement } from '@/ai-model';
import { AiLocateSection } from '@/ai-model/inspect';
import { vlLocateMode } from '@/env';
import { saveBase64Image } from '@/image';
import { getTmpFile } from '@/utils';
import { vlLocateMode } from '@midscene/shared/env';
import { getContextFromFixture } from 'tests/evaluation';
import { expect, test } from 'vitest';

View File

@ -1,7 +1,7 @@
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import { vlLocateMode } from '@/env';
import { describeUserPage } from '@/index';
import { vlLocateMode } from '@midscene/shared/env';
import { base64Encoded, imageInfoOfBase64 } from '@midscene/shared/img';
export async function buildContext(targetDir: string): Promise<{

View File

@ -10,7 +10,7 @@ import {
getAIConfigInJson,
overrideAIConfig,
vlLocateMode,
} from '@/env';
} from '@midscene/shared/env';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
describe('env', () => {

View File

@ -1,11 +1,11 @@
import { describeUserPage } from '@/ai-model/prompt/util';
import { vlLocateMode } from '@/env';
import { vlLocateMode } from '@midscene/shared/env';
import { getContextFromFixture } from 'tests/evaluation';
import { describe, expect, it, vi } from 'vitest';
// Mock vlLocateMode to return false during tests
vi.mock('@/env', async () => {
const actual = await vi.importActual('@/env');
vi.mock('@midscene/shared/env', async () => {
const actual = await vi.importActual('@midscene/shared/env');
return {
...actual,
vlLocateMode: () => false,

View File

@ -13,7 +13,6 @@ import {
preprocessDoubaoBboxJson,
safeParseJson,
} from '@/ai-model/service-caller';
import { getAIConfig, overrideAIConfig, vlLocateMode } from '@/env';
import {
getLogDir,
getTmpDir,
@ -22,6 +21,11 @@ import {
reportHTMLContent,
writeDumpReport,
} from '@/utils';
import {
getAIConfig,
overrideAIConfig,
vlLocateMode,
} from '@midscene/shared/env';
import { describe, expect, it } from 'vitest';
describe('utils', () => {

View File

@ -7,7 +7,7 @@ import type {
plan,
} from '@midscene/core';
import type { AiLocateSection } from '@midscene/core/ai-model';
import { vlLocateMode } from '@midscene/core/env';
import { vlLocateMode } from '@midscene/shared/env';
import type { TestCase } from '../tests/util';
type ActualResult =

View File

@ -4,8 +4,8 @@ import Insight, {
MIDSCENE_MODEL_NAME,
getAIConfig,
} from '@midscene/core';
import { vlLocateMode } from '@midscene/core/env';
import { sleep } from '@midscene/core/utils';
import { vlLocateMode } from '@midscene/shared/env';
import { saveBase64Image } from '@midscene/shared/img';
import dotenv from 'dotenv';
import { afterEach, expect, test } from 'vitest';

View File

@ -7,8 +7,8 @@ import {
plan,
} from '@midscene/core';
import { adaptBboxToRect } from '@midscene/core/ai-model';
import { vlLocateMode } from '@midscene/core/env';
import { sleep } from '@midscene/core/utils';
import { vlLocateMode } from '@midscene/shared/env';
import { saveBase64Image } from '@midscene/shared/img';
import dotenv from 'dotenv';
import { describe, expect, test } from 'vitest';

View File

@ -1,8 +1,8 @@
import { writeFileSync } from 'node:fs';
import { MIDSCENE_MODEL_NAME, type Rect, getAIConfig } from '@midscene/core';
import { AiLocateSection } from '@midscene/core/ai-model';
import { vlLocateMode } from '@midscene/core/env';
import { sleep } from '@midscene/core/utils';
import { vlLocateMode } from '@midscene/shared/env';
import { saveBase64Image } from '@midscene/shared/img';
import dotenv from 'dotenv';
import { afterAll, expect, test } from 'vitest';

View File

@ -1,7 +1,7 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import type { PlanningAIResponse, Rect } from '@midscene/core';
import { vlLocateMode } from '@midscene/core/env';
import { vlLocateMode } from '@midscene/shared/env';
import {
base64Encoded,
compositeElementInfoImg,

View File

@ -2,7 +2,7 @@ import {
MIDSCENE_MCP_USE_PUPPETEER_MODE,
getAIConfig,
getAIConfigInBoolean,
} from '@midscene/core/env';
} from '@midscene/shared/env';
import {
AgentOverChromeBridge,
allConfigFromEnv,

View File

@ -15,6 +15,7 @@ export default defineConfig({
logger: './src/logger.ts',
common: './src/common.ts',
'us-keyboard-layout': './src/us-keyboard-layout.ts',
env: './src/env.ts',
},
target: 'es2020',
dts: {

View File

@ -16,7 +16,8 @@
"./extractor-debug": "./dist/lib/extractor-debug.js",
"./keyboard-layout": "./dist/lib/us-keyboard-layout.js",
"./logger": "./dist/lib/logger.js",
"./common": "./dist/lib/common.js"
"./common": "./dist/lib/common.js",
"./env": "./dist/lib/env.js"
},
"typesVersions": {
"*": {
@ -29,7 +30,8 @@
"extractor-debug": ["./dist/types/extractor-debug.d.ts"],
"keyboard-layout": ["./dist/types/us-keyboard-layout.d.ts"],
"logger": ["./dist/types/logger.d.ts"],
"common": ["./dist/types/common.d.ts"]
"common": ["./dist/types/common.d.ts"],
"env": ["./dist/types/env.d.ts"]
}
},
"files": ["dist", "src", "README.md"],

View File

@ -1,20 +1,29 @@
import { existsSync, mkdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { MIDSCENE_RUN_DIR, getAIConfig } from './env';
export const runDirName = 'midscene_run';
export const defaultRunDirName = 'midscene_run';
// Define locally for now to avoid import issues
export const isNodeEnv =
typeof process !== 'undefined' &&
process.versions != null &&
process.versions.node != null;
export const getMidsceneRunDir = () => {
if (!isNodeEnv) {
return '';
}
return getAIConfig(MIDSCENE_RUN_DIR) || defaultRunDirName;
};
export const getMidsceneRunBaseDir = () => {
if (!isNodeEnv) {
return '';
}
let basePath = path.join(process.cwd(), runDirName);
let basePath = path.resolve(process.cwd(), getMidsceneRunDir());
// Create a base directory
if (!existsSync(basePath)) {
@ -22,7 +31,7 @@ export const getMidsceneRunBaseDir = () => {
mkdirSync(basePath, { recursive: true });
} catch (error) {
// console.error(`Failed to create ${runDirName} directory: ${error}`);
basePath = path.join(tmpdir(), runDirName);
basePath = path.join(tmpdir(), defaultRunDirName);
mkdirSync(basePath, { recursive: true });
}
}

View File

@ -41,6 +41,8 @@ export const AZURE_OPENAI_DEPLOYMENT = 'AZURE_OPENAI_DEPLOYMENT';
export const MIDSCENE_USE_ANTHROPIC_SDK = 'MIDSCENE_USE_ANTHROPIC_SDK';
export const ANTHROPIC_API_KEY = 'ANTHROPIC_API_KEY';
export const MIDSCENE_RUN_DIR = 'MIDSCENE_RUN_DIR';
// @deprecated
export const OPENAI_USE_AZURE = 'OPENAI_USE_AZURE';
@ -94,6 +96,7 @@ export const allConfigFromEnv = () => {
process.env[AZURE_OPENAI_DEPLOYMENT] || undefined,
[MIDSCENE_MCP_USE_PUPPETEER_MODE]:
process.env[MIDSCENE_MCP_USE_PUPPETEER_MODE] || undefined,
[MIDSCENE_RUN_DIR]: process.env[MIDSCENE_RUN_DIR] || undefined,
};
};

View File

@ -1,4 +1,4 @@
import { overrideAIConfig } from '@midscene/core/env';
import { overrideAIConfig } from '@midscene/shared/env';
import { Button, Tooltip } from 'antd';
import type React from 'react';
import { useEffect } from 'react';

View File

@ -2,4 +2,4 @@ import { AgentOverChromeBridge } from './agent-cli-side';
export { AgentOverChromeBridge };
export { overrideAIConfig, allConfigFromEnv } from '@midscene/core/env';
export { overrideAIConfig, allConfigFromEnv } from '@midscene/shared/env';

View File

@ -2,7 +2,7 @@ import { ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED } from '../common/utils';
import { ChromeExtensionProxyPageAgent } from './agent';
import ChromeExtensionProxyPage from './page';
export { overrideAIConfig } from '@midscene/core/env';
export { overrideAIConfig } from '@midscene/shared/env';
export {
ChromeExtensionProxyPage,

View File

@ -14,7 +14,6 @@ import {
} from '@midscene/core';
import { ScriptPlayer, parseYamlScript } from '@/yaml/index';
import { vlLocateMode } from '@midscene/core/env';
import {
groupedActionDumpFileExt,
reportHTMLContent,
@ -25,6 +24,7 @@ import {
DEFAULT_WAIT_FOR_NAVIGATION_TIMEOUT,
DEFAULT_WAIT_FOR_NETWORK_IDLE_TIMEOUT,
} from '@midscene/shared/constants';
import { vlLocateMode } from '@midscene/shared/env';
import { getDebug } from '@midscene/shared/logger';
import { assert } from '@midscene/shared/utils';
import { PageTaskExecutor } from '../common/tasks';

View File

@ -6,9 +6,9 @@ import type {
PlanningAIResponse,
} from '@midscene/core';
import type { vlmPlanning } from '@midscene/core/ai-model';
import { getAIConfigInBoolean } from '@midscene/core/env';
import { stringifyDumpData, writeLogFile } from '@midscene/core/utils';
import { getMidsceneRunSubDir } from '@midscene/shared/common';
import { getAIConfigInBoolean } from '@midscene/shared/env';
import { getRunningPkgInfo } from '@midscene/shared/fs';
import { getDebug } from '@midscene/shared/logger';
import { ifInBrowser } from '@midscene/shared/utils';

View File

@ -4,8 +4,8 @@ import type {
PlaywrightParserOpt,
UIContext,
} from '@midscene/core';
import { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from '@midscene/core/env';
import { uploadTestInfoToServer } from '@midscene/core/utils';
import { MIDSCENE_REPORT_TAG_NAME, getAIConfig } from '@midscene/shared/env';
import type { ElementInfo } from '@midscene/shared/extractor';
import { traverseTree, treeToList } from '@midscene/shared/extractor';
import { resizeImgBase64 } from '@midscene/shared/img';

View File

@ -3,9 +3,9 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import type { Server } from 'node:http';
import { join } from 'node:path';
import { ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED } from '@/common/utils';
import { overrideAIConfig } from '@midscene/core/env';
import { getTmpDir } from '@midscene/core/utils';
import { PLAYGROUND_SERVER_PORT } from '@midscene/shared/constants';
import { overrideAIConfig } from '@midscene/shared/env';
import { ifInBrowser } from '@midscene/shared/utils';
import cors from 'cors';
import dotenv from 'dotenv';

View File

@ -4,7 +4,7 @@ import { WebPage as PlaywrightWebPage } from './page';
export type { PlayWrightAiFixtureType } from './ai-fixture';
export { PlaywrightAiFixture } from './ai-fixture';
export { overrideAIConfig } from '@midscene/core/env';
export { overrideAIConfig } from '@midscene/shared/env';
export { WebPage as PlaywrightWebPage } from './page';
export class PlaywrightAgent extends PageAgent<PlaywrightWebPage> {

View File

@ -1,5 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import { getMidsceneRunSubDir } from '@midscene/shared/common';
import inquirer from 'inquirer';
interface Task {
@ -57,7 +58,7 @@ const formatTasks = (tasks: Task[]): string => {
// Main function
export const getTask = async (): Promise<void> => {
const targetDir = path.join(process.cwd(), 'midscene_run/cache');
const targetDir = getMidsceneRunSubDir('cache');
const jsonFiles = getJsonFiles(targetDir);
if (jsonFiles.length === 0) {

View File

@ -137,7 +137,7 @@ export class Page<
const buffer = await (this.underlyingPage as PlaywrightPage).screenshot({
type: imgType,
quality,
timeout: 3 * 1000,
timeout: 10 * 1000,
});
base64 = `data:image/jpeg;base64,${buffer.toString('base64')}`;
} else {

View File

@ -28,7 +28,7 @@ export class PuppeteerAgent extends PageAgent<PuppeteerWebPage> {
}
}
export { overrideAIConfig } from '@midscene/core/env';
export { overrideAIConfig } from '@midscene/shared/env';
// Do NOT export this since it requires puppeteer
// export { puppeteerAgentForTarget } from './agent-launcher';

View File

@ -1,7 +1,7 @@
import path from 'node:path';
import { PuppeteerAgent } from '@/puppeteer';
import { vlLocateMode } from '@midscene/core/env';
import { sleep } from '@midscene/core/utils';
import { vlLocateMode } from '@midscene/shared/env';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { launchPage } from './utils';

18
pnpm-lock.yaml generated
View File

@ -150,6 +150,9 @@ importers:
'@midscene/report':
specifier: workspace:*
version: link:../report
'@midscene/shared':
specifier: workspace:*
version: link:../../packages/shared
'@midscene/visualizer':
specifier: workspace:*
version: link:../../packages/visualizer
@ -211,6 +214,9 @@ importers:
'@midscene/core':
specifier: workspace:*
version: link:../../packages/core
'@midscene/shared':
specifier: workspace:*
version: link:../../packages/shared
'@midscene/visualizer':
specifier: workspace:*
version: link:../../packages/visualizer
@ -16245,7 +16251,7 @@ snapshots:
'@modern-js/utils': 2.32.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@rspack/core': 0.2.12(type-fest@3.13.1)(webpack@5.99.5)
'@rspack/dev-client': 0.2.12(react-refresh@0.14.0)(type-fest@3.13.1)(webpack@5.99.5)
'@rspack/plugin-html': 0.2.12(@rspack/core@0.2.12(type-fest@3.13.1)(webpack@5.99.5))
'@rspack/plugin-html': 0.2.12(@rspack/core@0.2.12(type-fest@3.13.1)(webpack@5.95.0))
'@swc/helpers': 0.5.1
caniuse-lite: 1.0.30001714
core-js: 3.30.2
@ -18493,16 +18499,6 @@ snapshots:
optionalDependencies:
'@rspack/core': 0.2.12(type-fest@3.13.1)(webpack@5.95.0)
'@rspack/plugin-html@0.2.12(@rspack/core@0.2.12(type-fest@3.13.1)(webpack@5.99.5))':
dependencies:
'@types/html-minifier-terser': 7.0.0
html-minifier-terser: 7.0.0
lodash.template: 4.5.0
parse5: 7.1.1
tapable: 2.2.1
optionalDependencies:
'@rspack/core': 0.2.12(type-fest@3.13.1)(webpack@5.99.5)
'@rspack/plugin-react-refresh@1.0.1(react-refresh@0.16.0)':
dependencies:
error-stack-parser: 2.1.4