Skip to content

Commit d2978e0

Browse files
zifengjiangjeffjiangquanru
authored
feat(android, harmony): add terminate action to support force-stopping apps and update documentation (#2214)
* feat(android,harmony): Added a terminate action to support forced application termination and supplemented documentation. Added a terminate interface for Android and Harmony Agent. Registered the Terminate action in the device action space, supporting forced termination of applications by package name or bundle name. Completed unit tests for agent/device, covering success, URI truncation, and failure branches. * fix(core): map yaml terminate shortcut to uri param * fix(harmony): improve terminate app resolution * ci(workflow): add workflow dispatch to ci and lint * fix(visualizer): capitalize action names in playground dropdown The actionNameForType function did not handle lowercase interfaceAlias values (e.g. "launch", "terminate", "runHdcShell"), causing inconsistent capitalization in the Device-Specific APIs dropdown. --------- Co-authored-by: jeffjiang <jeffjiang@didiglobal.com> Co-authored-by: quanruzhuoxiu <quanruzhuoxiu@gmail.com>
1 parent d3b29be commit d2978e0

22 files changed

Lines changed: 480 additions & 14 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches:
66
- main
77
pull_request:
8+
workflow_dispatch:
89

910
permissions:
1011
contents: read

.github/workflows/lint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- main
77
pull_request:
88
types: [opened, synchronize, reopened]
9+
workflow_dispatch:
910

1011
permissions:
1112
contents: read

apps/site/docs/en/android-api-reference.mdx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Use this doc when you need to customize Midscene's Android automation or review
1717
- `Pinch` &mdash; Two-finger pinch gesture. Use `scale > 1` to zoom in, `scale < 1` to zoom out.
1818
- `ClearInput` &mdash; Clear the contents of an input field.
1919
- `Launch` &mdash; Open a web URL or `package/.Activity` string.
20+
- `Terminate` &mdash; Force-stop an app by package name.
2021
- `RunAdbShell` &mdash; Execute raw `adb shell` commands.
2122
- `AndroidBackButton` &mdash; Trigger the system back action.
2223
- `AndroidHomeButton` &mdash; Return to the home screen.
@@ -148,7 +149,7 @@ const agent = new AndroidAgent(device, {
148149
:::info
149150

150151
- Use one agent per device connection.
151-
- Android-only helpers such as `launch` and `runAdbShell` are also exposed in YAML scripts. See [Android platform-specific actions](./automate-with-scripts-in-yaml#the-android-part).
152+
- Android-only helpers such as `launch`, `terminate`, and `runAdbShell` are also exposed in YAML scripts. See [Android platform-specific actions](./automate-with-scripts-in-yaml#the-android-part).
152153
- For shared interaction methods, see [API reference (Common)](./api#interaction-methods).
153154

154155
:::
@@ -180,6 +181,20 @@ const result = await agent.runAdbShell('dumpsys battery');
180181
console.log(result);
181182
```
182183

184+
#### `agent.terminate()`
185+
186+
Terminate (force-stop) a running Android app.
187+
188+
```ts
189+
function terminate(uri: string): Promise<void>;
190+
```
191+
192+
- `uri: string` &mdash; Package name, app name in `appNameMapping`, or `package/.Activity` (only the package part is used).
193+
194+
```ts
195+
await agent.terminate('com.android.settings');
196+
```
197+
183198
#### Navigation helpers
184199

185200
- `agent.back(): Promise<void>` &mdash; Trigger the Android system Back action.

apps/site/docs/en/automate-with-scripts-in-yaml.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,20 @@ tasks:
276276
- launch: https://www.example.com
277277
```
278278

279+
**`terminate` - Terminate App**
280+
281+
Terminate (force-stop) a running Android app by package name.
282+
283+
```yaml
284+
android:
285+
deviceId: 'test-device'
286+
287+
tasks:
288+
- name: Terminate Settings app
289+
flow:
290+
- terminate: com.android.settings
291+
```
292+
279293
### The `ios` part
280294

281295
```yaml

apps/site/docs/en/harmony-api-reference.mdx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ When you need to customize device behavior, integrate Midscene into a framework,
1616
- `ClearInput` — Clear input field contents.
1717
- ~~`Pinch`~~ — Not supported. The HarmonyOS `uitest` framework does not provide multi-touch input APIs.
1818
- `Launch` — Open a HarmonyOS app (bundle name).
19+
- `Terminate` — Force-stop a HarmonyOS app by bundle name.
1920
- `RunHdcShell` — Execute a raw `hdc shell` command.
2021
- `HarmonyBackButton` — Trigger system back.
2122
- `HarmonyHomeButton` — Return to home screen.
@@ -113,7 +114,7 @@ const agent = new HarmonyAgent(device, {
113114
:::info
114115

115116
- One device connection corresponds to one Agent.
116-
- HarmonyOS-specific helpers like `launch` and `runHdcShell` can also be used in YAML scripts. See [HarmonyOS platform-specific actions](./automate-with-scripts-in-yaml#the-harmony-part).
117+
- HarmonyOS-specific helpers like `launch`, `terminate`, and `runHdcShell` can also be used in YAML scripts. See [HarmonyOS platform-specific actions](./automate-with-scripts-in-yaml#the-harmony-part).
117118
- For common interaction methods, see [API Reference (Common)](./api#interaction-methods).
118119

119120
:::
@@ -150,6 +151,20 @@ const result = await agent.runHdcShell('hidumper -s RenderService -a screen');
150151
console.log(result);
151152
```
152153

154+
#### `agent.terminate()`
155+
156+
Terminate (force-stop) a running HarmonyOS app.
157+
158+
```ts
159+
function terminate(uri: string): Promise<void>;
160+
```
161+
162+
- `uri: string` — Bundle name, app name in `appNameMapping`, or `bundle/Ability` (only the bundle part is used).
163+
164+
```ts
165+
await agent.terminate('com.huawei.hmos.settings');
166+
```
167+
153168
#### Navigation Helpers
154169

155170
- `agent.back(): Promise<void>` — Trigger HarmonyOS system back.

apps/site/docs/zh/android-api-reference.mdx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- `Pinch` —— 双指缩放手势。`scale > 1` 放大,`scale < 1` 缩小。
1818
- `ClearInput` —— 清空输入框内容。
1919
- `Launch` —— 打开网页或 `package/.Activity`
20+
- `Terminate` —— 按包名强制停止应用。
2021
- `RunAdbShell` —— 执行原始 `adb shell` 命令。
2122
- `AndroidBackButton` —— 触发系统返回。
2223
- `AndroidHomeButton` —— 回到桌面。
@@ -147,7 +148,7 @@ const agent = new AndroidAgent(device, {
147148
:::info
148149

149150
- 一个设备连接对应一个 Agent。
150-
- `launch``runAdbShell` 等 Android 专属辅助函数也可在 YAML 脚本中使用,语法见 [Android 平台特定动作](./automate-with-scripts-in-yaml#the-android-part)
151+
- `launch``terminate``runAdbShell` 等 Android 专属辅助函数也可在 YAML 脚本中使用,语法见 [Android 平台特定动作](./automate-with-scripts-in-yaml#the-android-part)
151152
- 通用交互方法请查阅 [API 参考(通用)](./api#interaction-methods)
152153

153154
:::
@@ -179,6 +180,20 @@ const result = await agent.runAdbShell('dumpsys battery');
179180
console.log(result);
180181
```
181182

183+
#### `agent.terminate()`
184+
185+
终止(强制停止)正在运行的 Android 应用。
186+
187+
```ts
188+
function terminate(uri: string): Promise<void>;
189+
```
190+
191+
- `uri: string` —— 应用包名、`appNameMapping` 中的应用名称,或 `package/.Activity`(仅使用包名部分)。
192+
193+
```ts
194+
await agent.terminate('com.android.settings');
195+
```
196+
182197
#### 导航辅助
183198

184199
- `agent.back(): Promise<void>` —— 触发 Android 系统的返回操作。

apps/site/docs/zh/automate-with-scripts-in-yaml.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,20 @@ tasks:
278278
- launch: https://www.example.com
279279
```
280280

281+
**`terminate` - 终止应用**
282+
283+
通过包名终止(强制停止)正在运行的 Android 应用。
284+
285+
```yaml
286+
android:
287+
deviceId: 'test-device'
288+
289+
tasks:
290+
- name: 终止设置应用
291+
flow:
292+
- terminate: com.android.settings
293+
```
294+
281295
### `ios` 部分
282296

283297
```yaml

apps/site/docs/zh/harmony-api-reference.mdx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
- `ClearInput` —— 清空输入框内容。
1717
- ~~`Pinch`~~ —— 不支持。HarmonyOS `uitest` 框架未提供多触点输入 API。
1818
- `Launch` —— 打开 HarmonyOS 应用(bundle name)。
19+
- `Terminate` —— 按 bundle name 强制停止应用。
1920
- `RunHdcShell` —— 执行原始 `hdc shell` 命令。
2021
- `HarmonyBackButton` —— 触发系统返回。
2122
- `HarmonyHomeButton` —— 回到桌面。
@@ -113,7 +114,7 @@ const agent = new HarmonyAgent(device, {
113114
:::info
114115

115116
- 一个设备连接对应一个 Agent。
116-
- `launch``runHdcShell` 等 HarmonyOS 专属辅助函数也可在 YAML 脚本中使用,语法见 [HarmonyOS 平台特定动作](./automate-with-scripts-in-yaml#harmony-部分)
117+
- `launch``terminate``runHdcShell` 等 HarmonyOS 专属辅助函数也可在 YAML 脚本中使用,语法见 [HarmonyOS 平台特定动作](./automate-with-scripts-in-yaml#harmony-部分)
117118
- 通用交互方法请查阅 [API 参考(通用)](./api#interaction-methods)
118119

119120
:::
@@ -150,6 +151,20 @@ const result = await agent.runHdcShell('hidumper -s RenderService -a screen');
150151
console.log(result);
151152
```
152153

154+
#### `agent.terminate()`
155+
156+
终止(强制停止)正在运行的 HarmonyOS 应用。
157+
158+
```ts
159+
function terminate(uri: string): Promise<void>;
160+
```
161+
162+
- `uri: string` —— 应用 bundle name、`appNameMapping` 中的应用名称,或 `bundle/Ability`(仅使用 bundle name 部分)。
163+
164+
```ts
165+
await agent.terminate('com.huawei.hmos.settings');
166+
```
167+
153168
#### 导航辅助
154169

155170
- `agent.back(): Promise<void>` —— 触发 HarmonyOS 系统的返回操作。

packages/android/src/agent.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
type DeviceActionAndroidRecentAppsButton,
1212
type DeviceActionLaunch,
1313
type DeviceActionRunAdbShell,
14+
type DeviceActionTerminate,
1415
} from './device';
1516
import { getConnectedDevices } from './utils';
1617

@@ -92,6 +93,16 @@ export class AndroidAgent extends PageAgent<AndroidDevice> {
9293
return action({ uri });
9394
}
9495

96+
/**
97+
* Terminate (force-stop) an Android app by package name
98+
* @param uri - Package name or app name to terminate
99+
*/
100+
async terminate(uri: string): Promise<void> {
101+
const action =
102+
this.wrapActionInActionSpace<DeviceActionTerminate>('Terminate');
103+
return action({ uri });
104+
}
105+
95106
/**
96107
* Execute ADB shell command on Android device
97108
* @param command - ADB shell command to execute

packages/android/src/device.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,27 @@ ${Object.keys(size)
609609
return this;
610610
}
611611

612+
/**
613+
* Terminate (force-stop) an Android app by package name.
614+
* Supports app name resolution via setAppNameMapping.
615+
* If uri contains "/" (e.g. com.example.app/.MainActivity), only the package part is used.
616+
*/
617+
public async terminate(uri: string): Promise<void> {
618+
const packagePart = uri.includes('/') ? uri.split('/')[0] : uri;
619+
const resolved = this.resolvePackageName(packagePart) ?? packagePart;
620+
const adb = await this.getAdb();
621+
try {
622+
debugDevice(`Terminating app: ${resolved}`);
623+
await adb.shell(`am force-stop ${resolved}`);
624+
debugDevice(`Successfully terminated: ${resolved}`);
625+
} catch (error: any) {
626+
debugDevice(`Error terminating ${resolved}: ${error}`);
627+
throw new Error(`Failed to terminate ${resolved}: ${error.message}`, {
628+
cause: error,
629+
});
630+
}
631+
}
632+
612633
async execYadb(
613634
keyboardContent: string,
614635
options?: { overwrite?: boolean },
@@ -1989,17 +2010,28 @@ const launchParamSchema = z.object({
19892010
),
19902011
});
19912012

2013+
const terminateParamSchema = z.object({
2014+
uri: z
2015+
.string()
2016+
.describe(
2017+
'Package name or app name to terminate. Use the exact package name, e.g. com.android.settings.',
2018+
),
2019+
});
2020+
19922021
type RunAdbShellParam = z.infer<typeof runAdbShellParamSchema>;
19932022
type LaunchParam = z.infer<typeof launchParamSchema>;
2023+
type TerminateParam = z.infer<typeof terminateParamSchema>;
19942024

19952025
export type DeviceActionRunAdbShell = DeviceAction<RunAdbShellParam, string>;
19962026
export type DeviceActionLaunch = DeviceAction<LaunchParam, void>;
2027+
export type DeviceActionTerminate = DeviceAction<TerminateParam, void>;
19972028

19982029
const createPlatformActions = (
19992030
device: AndroidDevice,
20002031
): {
20012032
RunAdbShell: DeviceActionRunAdbShell;
20022033
Launch: DeviceActionLaunch;
2034+
Terminate: DeviceActionTerminate;
20032035
AndroidBackButton: DeviceActionAndroidBackButton;
20042036
AndroidHomeButton: DeviceActionAndroidHomeButton;
20052037
AndroidRecentAppsButton: DeviceActionAndroidRecentAppsButton;
@@ -2040,6 +2072,18 @@ const createPlatformActions = (
20402072
await device.launch(param.uri);
20412073
},
20422074
}),
2075+
Terminate: defineAction<typeof terminateParamSchema, TerminateParam, void>({
2076+
name: 'Terminate',
2077+
description: 'Terminate (force-stop) an Android app by package name',
2078+
interfaceAlias: 'terminate',
2079+
paramSchema: terminateParamSchema,
2080+
call: async (param) => {
2081+
if (!param.uri || param.uri.trim() === '') {
2082+
throw new Error('Terminate requires a non-empty uri parameter');
2083+
}
2084+
await device.terminate(param.uri);
2085+
},
2086+
}),
20432087
AndroidBackButton: defineAction({
20442088
name: 'AndroidBackButton',
20452089
description: 'Trigger the system "back" operation on Android devices',

0 commit comments

Comments
 (0)