assistsx-js 0.0.1 → 0.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-DEV.md +350 -0
- package/README.md +91 -347
- package/dist/AssistsX.js +3 -3
- package/dist/Node.d.ts +1 -1
- package/dist/Node.js +1 -1
- package/package.json +1 -1
- package/src/AssistsX.ts +3 -3
- package/src/Node.ts +1 -1
package/README-DEV.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
### 基本用法
|
|
2
|
+
|
|
3
|
+
```typescript
|
|
4
|
+
import { AssistsX, Node } from 'assistsx-js';
|
|
5
|
+
|
|
6
|
+
// 获取所有节点
|
|
7
|
+
const nodes: Node[] = AssistsX.getAllNodes();
|
|
8
|
+
|
|
9
|
+
// 通过ID查找节点
|
|
10
|
+
const nodeList: Node[] = AssistsX.findById('target_id');
|
|
11
|
+
|
|
12
|
+
// 点击第一个节点
|
|
13
|
+
if (nodeList.length > 0) {
|
|
14
|
+
AssistsX.click(nodeList[0]);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 执行手势点击
|
|
18
|
+
AssistsX.gestureClick(100, 200, 50); // 在(100,200)位置点击,持续50ms
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 节点对象的常用操作
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
const node: Node = nodeList[0];
|
|
25
|
+
|
|
26
|
+
// 设置节点文本
|
|
27
|
+
node.setNodeText('new text');
|
|
28
|
+
|
|
29
|
+
// 节点点击
|
|
30
|
+
node.click();
|
|
31
|
+
|
|
32
|
+
// 节点长按
|
|
33
|
+
node.longClick();
|
|
34
|
+
|
|
35
|
+
// 节点滚动
|
|
36
|
+
node.scrollForward();
|
|
37
|
+
node.scrollBackward();
|
|
38
|
+
|
|
39
|
+
// 获取节点在屏幕中的位置
|
|
40
|
+
const bounds = node.getBoundsInScreen();
|
|
41
|
+
console.log(bounds);
|
|
42
|
+
|
|
43
|
+
// 节点截图
|
|
44
|
+
const imagePath = await node.takeScreenshot();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 复杂查找与遍历
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// 通过文本查找节点
|
|
51
|
+
const textNodes = AssistsX.findByText('确定');
|
|
52
|
+
|
|
53
|
+
// 通过标签查找节点
|
|
54
|
+
const tagNodes = AssistsX.findByTags('android.widget.Button', { filterText: '提交' });
|
|
55
|
+
|
|
56
|
+
// 获取节点的所有子节点
|
|
57
|
+
const children = node.getChildren();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 主要类型说明
|
|
61
|
+
|
|
62
|
+
### AssistsX
|
|
63
|
+
|
|
64
|
+
- 提供静态方法用于节点查找、手势操作、系统操作等。
|
|
65
|
+
- 常用方法:
|
|
66
|
+
- `getAllNodes`:获取所有节点
|
|
67
|
+
- `findById`、`findByText`、`findByTags`:多种查找方式
|
|
68
|
+
- `click`、`longClick`、`gestureClick`:节点点击与手势
|
|
69
|
+
- `takeScreenshotNodes`:节点截图
|
|
70
|
+
- `launchApp`、`back`、`home`、`notifications`、`recentApps`:系统操作
|
|
71
|
+
|
|
72
|
+
### Node
|
|
73
|
+
|
|
74
|
+
- 表示界面上的一个可交互元素,包含属性和操作方法。
|
|
75
|
+
- 主要属性:
|
|
76
|
+
- `nodeId`、`text`、`des`、`viewId`、`className`
|
|
77
|
+
- `isScrollable`、`isClickable`、`isEnabled`
|
|
78
|
+
- 主要方法:
|
|
79
|
+
- `setNodeText(text: string)`:设置文本
|
|
80
|
+
- `click()`、`longClick()`:点击/长按
|
|
81
|
+
- `scrollForward()`、`scrollBackward()`:滚动
|
|
82
|
+
- `getBoundsInScreen()`:获取屏幕位置
|
|
83
|
+
- `takeScreenshot()`:节点截图
|
|
84
|
+
- `findById`、`findByText`、`findByTags`:在当前节点范围内查找
|
|
85
|
+
|
|
86
|
+
## 典型场景
|
|
87
|
+
|
|
88
|
+
### 1. 自动化测试
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// 启动应用
|
|
92
|
+
AssistsX.launchApp('com.example.app');
|
|
93
|
+
|
|
94
|
+
// 等待并点击登录按钮
|
|
95
|
+
const loginBtn = AssistsX.findByText('登录');
|
|
96
|
+
if (loginBtn.length > 0) {
|
|
97
|
+
loginBtn[0].click();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 检查是否包含某文本
|
|
101
|
+
const hasText = AssistsX.containsText('登录成功');
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. 界面元素操作
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// 查找输入框并输入文本
|
|
108
|
+
const input = AssistsX.findById('input_field');
|
|
109
|
+
if (input.length > 0) {
|
|
110
|
+
input[0].setNodeText('测试文本');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 查找并点击提交按钮
|
|
114
|
+
const submit = AssistsX.findByText('提交');
|
|
115
|
+
if (submit.length > 0) {
|
|
116
|
+
submit[0].click();
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 3. 手势操作
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// 简单点击
|
|
124
|
+
AssistsX.gestureClick(100, 200, 50);
|
|
125
|
+
|
|
126
|
+
// 节点手势点击(支持偏移)
|
|
127
|
+
const node = AssistsX.findById('target')[0];
|
|
128
|
+
await node.nodeGestureClick({
|
|
129
|
+
offsetX: 10,
|
|
130
|
+
offsetY: 10,
|
|
131
|
+
clickDuration: 50
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// 双击操作
|
|
135
|
+
await node.nodeGestureClickByDouble({
|
|
136
|
+
clickInterval: 200
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 4. 滚动和导航
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// 系统导航
|
|
144
|
+
AssistsX.back(); // 返回
|
|
145
|
+
AssistsX.home(); // 主页
|
|
146
|
+
AssistsX.notifications(); // 通知栏
|
|
147
|
+
|
|
148
|
+
// 滚动操作
|
|
149
|
+
const scrollable = AssistsX.findByTags('android.widget.ScrollView')[0];
|
|
150
|
+
scrollable.scrollForward();
|
|
151
|
+
scrollable.scrollBackward();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 步骤器(Step)使用说明
|
|
155
|
+
|
|
156
|
+
步骤器提供了一种结构化的方式来组织和执行自动化操作,支持步骤的生命周期管理、状态控制和界面操作。
|
|
157
|
+
|
|
158
|
+
### 基本用法
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { Step } from 'assistsx-js';
|
|
162
|
+
|
|
163
|
+
// 定义步骤实现
|
|
164
|
+
async function loginStep(step: Step): Promise<Step | undefined> {
|
|
165
|
+
// 启动应用
|
|
166
|
+
step.launchApp('com.example.app');
|
|
167
|
+
|
|
168
|
+
// 查找用户名输入框并输入
|
|
169
|
+
const usernameInput = step.findById('username_input');
|
|
170
|
+
if (usernameInput.length > 0) {
|
|
171
|
+
usernameInput[0].setNodeText('user123');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 查找密码输入框并输入
|
|
175
|
+
const passwordInput = step.findById('password_input');
|
|
176
|
+
if (passwordInput.length > 0) {
|
|
177
|
+
passwordInput[0].setNodeText('password123');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 点击登录按钮
|
|
181
|
+
const loginButton = step.findByText('登录');
|
|
182
|
+
if (loginButton.length > 0) {
|
|
183
|
+
loginButton[0].click();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 返回 undefined 表示步骤结束
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 运行步骤
|
|
191
|
+
await Step.run(loginStep, {
|
|
192
|
+
tag: 'login', // 步骤标签
|
|
193
|
+
data: { user: 'test' }, // 步骤数据
|
|
194
|
+
delayMs: 1000 // 步骤延迟
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 步骤链式调用
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
async function step1(step: Step): Promise<Step | undefined> {
|
|
202
|
+
// 执行某些操作
|
|
203
|
+
// ...
|
|
204
|
+
|
|
205
|
+
// 返回下一个步骤
|
|
206
|
+
return step.next(step2, { tag: 'step2' });
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function step2(step: Step): Promise<Step | undefined> {
|
|
210
|
+
// 执行某些操作
|
|
211
|
+
// ...
|
|
212
|
+
|
|
213
|
+
// 重复当前步骤
|
|
214
|
+
if (needRepeat) {
|
|
215
|
+
return step.repeat();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 返回下一个步骤
|
|
219
|
+
return step.next(step3, { tag: 'step3' });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function step3(step: Step): Promise<Step | undefined> {
|
|
223
|
+
// 执行最后的操作
|
|
224
|
+
// ...
|
|
225
|
+
|
|
226
|
+
// 步骤结束
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 运行步骤链
|
|
231
|
+
await Step.run(step1, { tag: 'step1' });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 异步操作处理
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
async function asyncStep(step: Step): Promise<Step | undefined> {
|
|
238
|
+
// 等待异步操作
|
|
239
|
+
await step.delay(1000);
|
|
240
|
+
|
|
241
|
+
// 执行异步方法
|
|
242
|
+
const result = await step.await(async () => {
|
|
243
|
+
// 异步操作
|
|
244
|
+
return 'result';
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// 节点截图
|
|
248
|
+
const node = step.findById('target')[0];
|
|
249
|
+
const imagePath = await step.takeScreenshotByNode(node);
|
|
250
|
+
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Step API 文档
|
|
256
|
+
|
|
257
|
+
### 静态方法
|
|
258
|
+
|
|
259
|
+
#### `Step.run(impl, options)`
|
|
260
|
+
运行步骤实现。
|
|
261
|
+
- `impl`: `(step: Step) => Promise<Step | undefined>` - 步骤实现函数
|
|
262
|
+
- `options`:
|
|
263
|
+
- `tag?: string` - 步骤标签
|
|
264
|
+
- `data?: any` - 步骤数据
|
|
265
|
+
- `delayMs?: number` - 步骤延迟时间(毫秒),默认 1000
|
|
266
|
+
|
|
267
|
+
#### `Step.stop()`
|
|
268
|
+
停止当前步骤执行。
|
|
269
|
+
|
|
270
|
+
### 实例属性
|
|
271
|
+
|
|
272
|
+
- `stepId: string` - 步骤ID
|
|
273
|
+
- `repeatCount: number` - 步骤重复执行次数
|
|
274
|
+
- `tag?: string` - 步骤标签
|
|
275
|
+
- `data?: any` - 步骤数据
|
|
276
|
+
- `delayMs: number` - 步骤延迟时间(毫秒)
|
|
277
|
+
|
|
278
|
+
### 实例方法
|
|
279
|
+
|
|
280
|
+
#### 步骤控制
|
|
281
|
+
- `next(impl, options)` - 创建下一个步骤
|
|
282
|
+
- `repeat(options)` - 重复当前步骤
|
|
283
|
+
- `delay(ms)` - 延迟执行
|
|
284
|
+
- `await<T>(method)` - 等待异步方法执行完成
|
|
285
|
+
|
|
286
|
+
#### 节点操作
|
|
287
|
+
- `getAllNodes(options)` - 获取所有符合条件的节点
|
|
288
|
+
- `findById(id, options)` - 通过ID查找节点
|
|
289
|
+
- `findByText(text, options)` - 通过文本查找节点
|
|
290
|
+
- `findByTags(className, options)` - 通过标签查找节点
|
|
291
|
+
- `findByTextAllMatch(text)` - 查找所有匹配文本的节点
|
|
292
|
+
- `findFirstParentByTags(className)` - 查找第一个匹配标签的父节点
|
|
293
|
+
|
|
294
|
+
#### 界面操作
|
|
295
|
+
- `takeScreenshotByNode(node, delay)` - 对单个节点进行截图
|
|
296
|
+
- `takeScreenshotNodes(nodes, delay)` - 对多个节点进行截图
|
|
297
|
+
- `gestureClick(x, y, duration)` - 执行点击手势
|
|
298
|
+
- `back()` - 返回操作
|
|
299
|
+
- `home()` - 回到主页
|
|
300
|
+
- `notifications()` - 打开通知栏
|
|
301
|
+
- `recentApps()` - 显示最近应用
|
|
302
|
+
|
|
303
|
+
#### 应用控制
|
|
304
|
+
- `launchApp(packageName)` - 启动应用
|
|
305
|
+
- `getPackageName()` - 获取当前应用包名
|
|
306
|
+
|
|
307
|
+
#### 其他操作
|
|
308
|
+
- `containsText(text)` - 检查是否包含指定文本
|
|
309
|
+
- `getAllText()` - 获取所有文本
|
|
310
|
+
- `getScreenSize()` - 获取屏幕尺寸
|
|
311
|
+
- `getAppScreenSize()` - 获取应用窗口尺寸
|
|
312
|
+
|
|
313
|
+
## 最佳实践
|
|
314
|
+
|
|
315
|
+
1. 步骤组织
|
|
316
|
+
- 将复杂的自动化流程拆分为多个步骤
|
|
317
|
+
- 每个步骤专注于完成特定的任务
|
|
318
|
+
- 使用有意义的标签和数据来标识步骤
|
|
319
|
+
|
|
320
|
+
2. 错误处理
|
|
321
|
+
```typescript
|
|
322
|
+
async function robustStep(step: Step): Promise<Step | undefined> {
|
|
323
|
+
try {
|
|
324
|
+
const node = step.findById('target')[0];
|
|
325
|
+
if (!node) {
|
|
326
|
+
throw new Error('Target node not found');
|
|
327
|
+
}
|
|
328
|
+
// ... 其他操作
|
|
329
|
+
} catch (error) {
|
|
330
|
+
console.error(`Step failed: ${error.message}`);
|
|
331
|
+
// 可以选择重试或执行其他步骤
|
|
332
|
+
return step.repeat({ delayMs: 2000 });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
3. 步骤复用
|
|
338
|
+
```typescript
|
|
339
|
+
// 创建可复用的步骤
|
|
340
|
+
function createLoginStep(username: string, password: string) {
|
|
341
|
+
return async function(step: Step): Promise<Step | undefined> {
|
|
342
|
+
// ... 登录逻辑
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 在不同地方复用
|
|
348
|
+
await Step.run(createLoginStep('user1', 'pass1'));
|
|
349
|
+
await Step.run(createLoginStep('user2', 'pass2'));
|
|
350
|
+
```
|
package/README.md
CHANGED
|
@@ -1,388 +1,132 @@
|
|
|
1
|
-
# AssistsX
|
|
1
|
+
# AssistsX JS
|
|
2
2
|
|
|
3
|
-
Android
|
|
3
|
+
一个支持通过Web端实现Android平台自动化脚本的JS库,支持元素节点查找、获取节点文本、节点截图、执行手势动作、提供步骤器实现复杂自动化业务等一系列自动化脚本开发能力支持
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# AssistsX JS运行平台
|
|
6
|
+
开发的自动化脚本需要运行在Android端[AssistsX](https://www.pgyer.com/SqGaCx8C)中,所以开发前需要先在手机安装[AssistsX](https://www.pgyer.com/SqGaCx8C)
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
npm install assistsx
|
|
9
|
-
```
|
|
8
|
+
**扫码下载**
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
<img width="112" alt="image" src="https://github.com/user-attachments/assets/c28ecc41-01f8-4e52-9ddc-80dc5c6d0ed5" />
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
**下载链接:[https://www.pgyer.com/SqGaCx8C](https://www.pgyer.com/SqGaCx8C)**
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
import { AssistsX, Node } from 'assistsx';
|
|
14
|
+
> 下载安装后会默认安装一个示例插件,可长按删除
|
|
17
15
|
|
|
18
|
-
|
|
19
|
-
const nodes: Node[] = AssistsX.getAllNodes();
|
|
16
|
+
# 示例源码安装运行
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
const nodeList: Node[] = AssistsX.findById('target_id');
|
|
18
|
+
源码:[https://github.com/ven-coder/assistsx-js-simple](https://github.com/ven-coder/assistsx-js-simple)
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
if (nodeList.length > 0) {
|
|
26
|
-
AssistsX.click(nodeList[0]);
|
|
27
|
-
}
|
|
20
|
+
## 本地安装运行
|
|
28
21
|
|
|
29
|
-
|
|
30
|
-
AssistsX.gestureClick(100, 200, 50); // 在(100,200)位置点击,持续50ms
|
|
31
|
-
```
|
|
22
|
+
### 1. 安装[AssistsX](https://www.pgyer.com/SqGaCx8C)
|
|
32
23
|
|
|
33
|
-
|
|
24
|
+
安装前请先在手机安装[AssistsX](https://www.pgyer.com/SqGaCx8C)
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
### 2. 插件包传至手机
|
|
27
|
+
下载本示例源码后(*前提`npm`环境配置好*)
|
|
28
|
+
1. 终端依次运行命令:`npm install`,`npm run build`
|
|
29
|
+
2. 编译完成后将`dist`文件夹压缩为`.zip`文件,然后将`.zip`文件传至手机。
|
|
30
|
+
3. 手机打开[AssistsX](https://www.pgyer.com/SqGaCx8C),点击+号->本地添加->选择传至手机的`zip`压缩包
|
|
37
31
|
|
|
38
|
-
// 设置节点文本
|
|
39
|
-
node.setNodeText('new text');
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
node.click();
|
|
33
|
+
<img src="https://github.com/user-attachments/assets/7dc27910-be61-473b-8900-f09c16ca5f46" width="250">
|
|
43
34
|
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
## 局域网加载运行
|
|
36
|
+
> 确保手机与电脑处于同一个局域网
|
|
37
|
+
### 1. 安装[AssistsX](https://www.pgyer.com/SqGaCx8C)
|
|
38
|
+
加载运行前请先在手机安装[AssistsX](https://www.pgyer.com/SqGaCx8C)
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
node.scrollBackward();
|
|
40
|
+
### 2. 启动项目
|
|
41
|
+
下载本示例源码后,终端依次运行命令:`npm install`,`npm run dev`
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
console.log(bounds);
|
|
54
|
-
|
|
55
|
-
// 节点截图
|
|
56
|
-
const imagePath = await node.takeScreenshot();
|
|
57
|
-
```
|
|
43
|
+
### 3. 加载插件
|
|
44
|
+
手机打开[AssistsX](https://www.pgyer.com/SqGaCx8C),点击+号->扫描局域网->点击扫描到插件+号
|
|
58
45
|
|
|
59
|
-
|
|
46
|
+
<img src="https://github.com/user-attachments/assets/d0f24763-266e-4e3c-bd64-a63be9e6c68c" width="250"/>
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
// 获取节点的所有子节点
|
|
69
|
-
const children = node.getChildren();
|
|
48
|
+
# 快速开始
|
|
49
|
+
## 1. 创建项目
|
|
50
|
+
- 创建`vite`模版项目:`npm create vite@latest assistsx-helloword -- --template vue`
|
|
51
|
+
- 安装`assistsx-js`依赖:`npm install assistsx-js@latest`
|
|
52
|
+
## 2. 创建插件配置
|
|
53
|
+
在项目目录`public`下创建文件`assistsx_plugin_config.json`文件,将以下`json`复制粘贴到文件中
|
|
70
54
|
```
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
- `takeScreenshotNodes`:节点截图
|
|
82
|
-
- `launchApp`、`back`、`home`、`notifications`、`recentApps`:系统操作
|
|
83
|
-
|
|
84
|
-
### Node
|
|
85
|
-
|
|
86
|
-
- 表示界面上的一个可交互元素,包含属性和操作方法。
|
|
87
|
-
- 主要属性:
|
|
88
|
-
- `nodeId`、`text`、`des`、`viewId`、`className`
|
|
89
|
-
- `isScrollable`、`isClickable`、`isEnabled`
|
|
90
|
-
- 主要方法:
|
|
91
|
-
- `setNodeText(text: string)`:设置文本
|
|
92
|
-
- `click()`、`longClick()`:点击/长按
|
|
93
|
-
- `scrollForward()`、`scrollBackward()`:滚动
|
|
94
|
-
- `getBoundsInScreen()`:获取屏幕位置
|
|
95
|
-
- `takeScreenshot()`:节点截图
|
|
96
|
-
- `findById`、`findByText`、`findByTags`:在当前节点范围内查找
|
|
97
|
-
|
|
98
|
-
## 典型场景
|
|
99
|
-
|
|
100
|
-
### 1. 自动化测试
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
// 启动应用
|
|
104
|
-
AssistsX.launchApp('com.example.app');
|
|
105
|
-
|
|
106
|
-
// 等待并点击登录按钮
|
|
107
|
-
const loginBtn = AssistsX.findByText('登录');
|
|
108
|
-
if (loginBtn.length > 0) {
|
|
109
|
-
loginBtn[0].click();
|
|
55
|
+
{
|
|
56
|
+
"name": "AssistsX示例",
|
|
57
|
+
"version": "1.0.0",
|
|
58
|
+
"description": "AssistsX示例",
|
|
59
|
+
"isShowOverlay": true,
|
|
60
|
+
"needScreenCapture": true,
|
|
61
|
+
"packageName": "com.assistsx.example",
|
|
62
|
+
"main": "index.html",
|
|
63
|
+
"icon": "vite.svg",
|
|
64
|
+
"overlayTitle": "AssistsX示例"
|
|
110
65
|
}
|
|
111
|
-
|
|
112
|
-
// 检查是否包含某文本
|
|
113
|
-
const hasText = AssistsX.containsText('登录成功');
|
|
114
66
|
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const input = AssistsX.findById('input_field');
|
|
121
|
-
if (input.length > 0) {
|
|
122
|
-
input[0].setNodeText('测试文本');
|
|
67
|
+
## 3. 编写脚本插件
|
|
68
|
+
写一个最简单的,点击微信搜索进入搜索页面
|
|
69
|
+
```agsl
|
|
70
|
+
const handleClick = () => {
|
|
71
|
+
AssistsX.findById("com.tencent.mm:id/jha")[0].click()
|
|
123
72
|
}
|
|
73
|
+
```
|
|
124
74
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
submit[0].click();
|
|
129
|
-
}
|
|
75
|
+
增加一个测试按钮调用这个方法
|
|
76
|
+
```agsl
|
|
77
|
+
<button type="button" @click="handleClick">测试按钮</button>
|
|
130
78
|
```
|
|
131
79
|
|
|
132
|
-
|
|
80
|
+
## 4. 加载插件
|
|
81
|
+
1. 通过[AssistsX](https://www.pgyer.com/SqGaCx8C)局域网加载插件
|
|
82
|
+
> 加载插件前需要配置项目允许局域网访问,在文件`vite.config.js`添加以下配置
|
|
83
|
+
```
|
|
84
|
+
export default defineConfig({
|
|
85
|
+
plugins: [vue()],
|
|
86
|
+
server: {
|
|
87
|
+
host: '0.0.0.0', // 允许局域网访问
|
|
88
|
+
port: 5173
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
```
|
|
92
|
+
运行项目`npm run dev`以便`AssistsX`直接加载
|
|
133
93
|
|
|
134
|
-
|
|
135
|
-
// 简单点击
|
|
136
|
-
AssistsX.gestureClick(100, 200, 50);
|
|
94
|
+
1. 打开`AssistsX`,扫描局域网插件添加
|
|
137
95
|
|
|
138
|
-
|
|
139
|
-
const node = AssistsX.findById('target')[0];
|
|
140
|
-
await node.nodeGestureClick({
|
|
141
|
-
offsetX: 10,
|
|
142
|
-
offsetY: 10,
|
|
143
|
-
clickDuration: 50
|
|
144
|
-
});
|
|
96
|
+
<img src="https://github.com/user-attachments/assets/d0f24763-266e-4e3c-bd64-a63be9e6c68c" width="250"/>
|
|
145
97
|
|
|
146
|
-
|
|
147
|
-
await node.nodeGestureClickByDouble({
|
|
148
|
-
clickInterval: 200
|
|
149
|
-
});
|
|
150
|
-
```
|
|
98
|
+
3. 测试插件:点击开始,打开微信消息列表,点击测试按钮
|
|
151
99
|
|
|
152
|
-
|
|
100
|
+
<img src="https://github.com/user-attachments/assets/e6e59149-ed78-42de-81a7-c3476b5472e6" width="250"/>
|
|
153
101
|
|
|
154
|
-
```typescript
|
|
155
|
-
// 系统导航
|
|
156
|
-
AssistsX.back(); // 返回
|
|
157
|
-
AssistsX.home(); // 主页
|
|
158
|
-
AssistsX.notifications(); // 通知栏
|
|
159
102
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
scrollable.scrollForward();
|
|
163
|
-
scrollable.scrollBackward();
|
|
164
|
-
```
|
|
103
|
+
<br/>
|
|
104
|
+
<br/>
|
|
165
105
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
1. 使用前提
|
|
169
|
-
- 确保 Android WebView 中已注入 `assistsx` 对象
|
|
170
|
-
- 确保有足够的权限(如辅助功能权限)
|
|
171
|
-
|
|
172
|
-
2. 性能建议
|
|
173
|
-
- 避免频繁查找节点,建议复用查找结果
|
|
174
|
-
- 截图等耗时操作请合理使用
|
|
175
|
-
|
|
176
|
-
3. 异步操作
|
|
177
|
-
- 截图、手势等操作为异步方法,需使用 `await`
|
|
178
|
-
- 建议使用 try-catch 处理异常
|
|
179
|
-
|
|
180
|
-
4. 节点状态
|
|
181
|
-
- 操作前建议检查节点的 `isEnabled`、`isClickable` 等状态
|
|
182
|
-
- 使用 `isVisible` 确认节点是否可见
|
|
183
|
-
|
|
184
|
-
## API 文档
|
|
185
|
-
|
|
186
|
-
详细的 API 文档请参考源码中的 TypeScript 类型定义和注释说明。
|
|
187
|
-
|
|
188
|
-
## 问题反馈
|
|
189
|
-
|
|
190
|
-
如有问题或建议,欢迎提交 Issue 或 Pull Request。
|
|
191
|
-
|
|
192
|
-
## 步骤器(Step)使用说明
|
|
193
|
-
|
|
194
|
-
步骤器提供了一种结构化的方式来组织和执行自动化操作,支持步骤的生命周期管理、状态控制和界面操作。
|
|
195
|
-
|
|
196
|
-
### 基本用法
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
import { Step } from 'assistsx';
|
|
200
|
-
|
|
201
|
-
// 定义步骤实现
|
|
202
|
-
async function loginStep(step: Step): Promise<Step | undefined> {
|
|
203
|
-
// 启动应用
|
|
204
|
-
step.launchApp('com.example.app');
|
|
205
|
-
|
|
206
|
-
// 查找用户名输入框并输入
|
|
207
|
-
const usernameInput = step.findById('username_input');
|
|
208
|
-
if (usernameInput.length > 0) {
|
|
209
|
-
usernameInput[0].setNodeText('user123');
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// 查找密码输入框并输入
|
|
213
|
-
const passwordInput = step.findById('password_input');
|
|
214
|
-
if (passwordInput.length > 0) {
|
|
215
|
-
passwordInput[0].setNodeText('password123');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// 点击登录按钮
|
|
219
|
-
const loginButton = step.findByText('登录');
|
|
220
|
-
if (loginButton.length > 0) {
|
|
221
|
-
loginButton[0].click();
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// 返回 undefined 表示步骤结束
|
|
225
|
-
return undefined;
|
|
226
|
-
}
|
|
106
|
+
**[API开发文档](https://github.com/ven-coder/assistsx-js/blob/main/README-DEV.md)**
|
|
227
107
|
|
|
228
|
-
|
|
229
|
-
await Step.run(loginStep, {
|
|
230
|
-
tag: 'login', // 步骤标签
|
|
231
|
-
data: { user: 'test' }, // 步骤数据
|
|
232
|
-
delayMs: 1000 // 步骤延迟
|
|
233
|
-
});
|
|
234
|
-
```
|
|
108
|
+
## 🙋有问题欢迎反馈交流
|
|
235
109
|
|
|
236
|
-
|
|
110
|
+
| QQ交流群| 个人微信 |
|
|
111
|
+
|:---------:|:-----------:|
|
|
112
|
+
| <img src="https://github.com/user-attachments/assets/732c38a5-7473-44ca-be76-d1fabb27aa5d" width=200/> | <img src="https://github.com/user-attachments/assets/b805f5a0-223b-415d-a34b-7659aa0bdf0a" width=200/>
|
|
237
113
|
|
|
238
|
-
|
|
239
|
-
async function step1(step: Step): Promise<Step | undefined> {
|
|
240
|
-
// 执行某些操作
|
|
241
|
-
// ...
|
|
242
|
-
|
|
243
|
-
// 返回下一个步骤
|
|
244
|
-
return step.next(step2, { tag: 'step2' });
|
|
245
|
-
}
|
|
114
|
+
# 💝 支持开源
|
|
246
115
|
|
|
247
|
-
|
|
248
|
-
// 执行某些操作
|
|
249
|
-
// ...
|
|
250
|
-
|
|
251
|
-
// 重复当前步骤
|
|
252
|
-
if (needRepeat) {
|
|
253
|
-
return step.repeat();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// 返回下一个步骤
|
|
257
|
-
return step.next(step3, { tag: 'step3' });
|
|
258
|
-
}
|
|
116
|
+
开源不易,您的支持是我坚持的动力!
|
|
259
117
|
|
|
260
|
-
|
|
261
|
-
// 执行最后的操作
|
|
262
|
-
// ...
|
|
263
|
-
|
|
264
|
-
// 步骤结束
|
|
265
|
-
return undefined;
|
|
266
|
-
}
|
|
118
|
+
如果AssistsX JS对您的项目有帮助,可以通过以下方式支持我喔:
|
|
267
119
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
120
|
+
### ⭐ Star支持
|
|
121
|
+
- 给项目点个Star,让更多开发者发现这个框架
|
|
122
|
+
- 分享给身边的朋友和同事
|
|
271
123
|
|
|
272
|
-
###
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
// 执行异步方法
|
|
280
|
-
const result = await step.await(async () => {
|
|
281
|
-
// 异步操作
|
|
282
|
-
return 'result';
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// 节点截图
|
|
286
|
-
const node = step.findById('target')[0];
|
|
287
|
-
const imagePath = await step.takeScreenshotByNode(node);
|
|
288
|
-
|
|
289
|
-
return undefined;
|
|
290
|
-
}
|
|
291
|
-
```
|
|
124
|
+
### 💰 赞助支持
|
|
125
|
+
- [爱发电支持](https://afdian.com/a/vencoder) - 您的每一份支持都是我们前进的动力
|
|
126
|
+
- 一杯Coffee的微信赞赏
|
|
127
|
+
|
|
128
|
+
<img width="200" alt="image" src="https://github.com/user-attachments/assets/3862a40c-631c-4ab0-b1e7-00ec3e3e00ad" />
|
|
129
|
+
|
|
130
|
+
**定制开发可联系个人微信: x39598**
|
|
292
131
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
### 静态方法
|
|
296
|
-
|
|
297
|
-
#### `Step.run(impl, options)`
|
|
298
|
-
运行步骤实现。
|
|
299
|
-
- `impl`: `(step: Step) => Promise<Step | undefined>` - 步骤实现函数
|
|
300
|
-
- `options`:
|
|
301
|
-
- `tag?: string` - 步骤标签
|
|
302
|
-
- `data?: any` - 步骤数据
|
|
303
|
-
- `delayMs?: number` - 步骤延迟时间(毫秒),默认 1000
|
|
304
|
-
|
|
305
|
-
#### `Step.stop()`
|
|
306
|
-
停止当前步骤执行。
|
|
307
|
-
|
|
308
|
-
### 实例属性
|
|
309
|
-
|
|
310
|
-
- `stepId: string` - 步骤ID
|
|
311
|
-
- `repeatCount: number` - 步骤重复执行次数
|
|
312
|
-
- `tag?: string` - 步骤标签
|
|
313
|
-
- `data?: any` - 步骤数据
|
|
314
|
-
- `delayMs: number` - 步骤延迟时间(毫秒)
|
|
315
|
-
|
|
316
|
-
### 实例方法
|
|
317
|
-
|
|
318
|
-
#### 步骤控制
|
|
319
|
-
- `next(impl, options)` - 创建下一个步骤
|
|
320
|
-
- `repeat(options)` - 重复当前步骤
|
|
321
|
-
- `delay(ms)` - 延迟执行
|
|
322
|
-
- `await<T>(method)` - 等待异步方法执行完成
|
|
323
|
-
|
|
324
|
-
#### 节点操作
|
|
325
|
-
- `getAllNodes(options)` - 获取所有符合条件的节点
|
|
326
|
-
- `findById(id, options)` - 通过ID查找节点
|
|
327
|
-
- `findByText(text, options)` - 通过文本查找节点
|
|
328
|
-
- `findByTags(className, options)` - 通过标签查找节点
|
|
329
|
-
- `findByTextAllMatch(text)` - 查找所有匹配文本的节点
|
|
330
|
-
- `findFirstParentByTags(className)` - 查找第一个匹配标签的父节点
|
|
331
|
-
|
|
332
|
-
#### 界面操作
|
|
333
|
-
- `takeScreenshotByNode(node, delay)` - 对单个节点进行截图
|
|
334
|
-
- `takeScreenshotNodes(nodes, delay)` - 对多个节点进行截图
|
|
335
|
-
- `gestureClick(x, y, duration)` - 执行点击手势
|
|
336
|
-
- `back()` - 返回操作
|
|
337
|
-
- `home()` - 回到主页
|
|
338
|
-
- `notifications()` - 打开通知栏
|
|
339
|
-
- `recentApps()` - 显示最近应用
|
|
340
|
-
|
|
341
|
-
#### 应用控制
|
|
342
|
-
- `launchApp(packageName)` - 启动应用
|
|
343
|
-
- `getPackageName()` - 获取当前应用包名
|
|
344
|
-
|
|
345
|
-
#### 其他操作
|
|
346
|
-
- `containsText(text)` - 检查是否包含指定文本
|
|
347
|
-
- `getAllText()` - 获取所有文本
|
|
348
|
-
- `getScreenSize()` - 获取屏幕尺寸
|
|
349
|
-
- `getAppScreenSize()` - 获取应用窗口尺寸
|
|
350
|
-
|
|
351
|
-
## 最佳实践
|
|
352
|
-
|
|
353
|
-
1. 步骤组织
|
|
354
|
-
- 将复杂的自动化流程拆分为多个步骤
|
|
355
|
-
- 每个步骤专注于完成特定的任务
|
|
356
|
-
- 使用有意义的标签和数据来标识步骤
|
|
357
|
-
|
|
358
|
-
2. 错误处理
|
|
359
|
-
```typescript
|
|
360
|
-
async function robustStep(step: Step): Promise<Step | undefined> {
|
|
361
|
-
try {
|
|
362
|
-
const node = step.findById('target')[0];
|
|
363
|
-
if (!node) {
|
|
364
|
-
throw new Error('Target node not found');
|
|
365
|
-
}
|
|
366
|
-
// ... 其他操作
|
|
367
|
-
} catch (error) {
|
|
368
|
-
console.error(`Step failed: ${error.message}`);
|
|
369
|
-
// 可以选择重试或执行其他步骤
|
|
370
|
-
return step.repeat({ delayMs: 2000 });
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
3. 步骤复用
|
|
376
|
-
```typescript
|
|
377
|
-
// 创建可复用的步骤
|
|
378
|
-
function createLoginStep(username: string, password: string) {
|
|
379
|
-
return async function(step: Step): Promise<Step | undefined> {
|
|
380
|
-
// ... 登录逻辑
|
|
381
|
-
return undefined;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// 在不同地方复用
|
|
386
|
-
await Step.run(createLoginStep('user1', 'pass1'));
|
|
387
|
-
await Step.run(createLoginStep('user2', 'pass2'));
|
|
388
|
-
```
|
|
132
|
+
**感谢所有的支持者,得到你们的支持我将会更加完善开源库的能力!** 🚀
|
package/dist/AssistsX.js
CHANGED
|
@@ -204,7 +204,7 @@ export class AssistsX {
|
|
|
204
204
|
* @returns 节点数组
|
|
205
205
|
*/
|
|
206
206
|
static findByTextAllMatch(text) {
|
|
207
|
-
const response = this.call(CallMethod.findByTextAllMatch, { args: text });
|
|
207
|
+
const response = this.call(CallMethod.findByTextAllMatch, { args: { text } });
|
|
208
208
|
return Node.fromJSONArray(response.getDataOrDefault("[]"));
|
|
209
209
|
}
|
|
210
210
|
/**
|
|
@@ -213,7 +213,7 @@ export class AssistsX {
|
|
|
213
213
|
* @returns 是否包含
|
|
214
214
|
*/
|
|
215
215
|
static containsText(text) {
|
|
216
|
-
const response = this.call(CallMethod.containsText, { args: text });
|
|
216
|
+
const response = this.call(CallMethod.containsText, { args: { text } });
|
|
217
217
|
return response.getDataOrDefault(false);
|
|
218
218
|
}
|
|
219
219
|
/**
|
|
@@ -230,7 +230,7 @@ export class AssistsX {
|
|
|
230
230
|
* @returns 父节点
|
|
231
231
|
*/
|
|
232
232
|
static findFirstParentByTags(className) {
|
|
233
|
-
const response = this.call(CallMethod.findFirstParentByTags, { args: className });
|
|
233
|
+
const response = this.call(CallMethod.findFirstParentByTags, { args: { className } });
|
|
234
234
|
return Node.create(response.getDataOrDefault("{}"));
|
|
235
235
|
}
|
|
236
236
|
/**
|
package/dist/Node.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ export declare class Node {
|
|
|
93
93
|
* @param filterDes 描述过滤
|
|
94
94
|
* @returns 节点数组
|
|
95
95
|
*/
|
|
96
|
-
findByTags(className: string, { filterText, filterViewId, filterDes }
|
|
96
|
+
findByTags(className: string, { filterText, filterViewId, filterDes }?: {
|
|
97
97
|
filterText?: string;
|
|
98
98
|
filterViewId?: string;
|
|
99
99
|
filterDes?: string;
|
package/dist/Node.js
CHANGED
|
@@ -54,7 +54,7 @@ export class Node {
|
|
|
54
54
|
* @param filterDes 描述过滤
|
|
55
55
|
* @returns 节点数组
|
|
56
56
|
*/
|
|
57
|
-
findByTags(className, { filterText, filterViewId, filterDes }) {
|
|
57
|
+
findByTags(className, { filterText, filterViewId, filterDes } = {}) {
|
|
58
58
|
Step.assert(this.stepId);
|
|
59
59
|
const result = AssistsX.findByTags(className, { filterText, filterViewId, filterDes, node: this });
|
|
60
60
|
Step.assignIdsToNodes(result, this.stepId);
|
package/package.json
CHANGED
package/src/AssistsX.ts
CHANGED
|
@@ -230,7 +230,7 @@ export class AssistsX {
|
|
|
230
230
|
* @returns 节点数组
|
|
231
231
|
*/
|
|
232
232
|
public static findByTextAllMatch(text: string): Node[] {
|
|
233
|
-
const response = this.call(CallMethod.findByTextAllMatch, { args: text });
|
|
233
|
+
const response = this.call(CallMethod.findByTextAllMatch, { args: { text } });
|
|
234
234
|
return Node.fromJSONArray(response.getDataOrDefault("[]"));
|
|
235
235
|
}
|
|
236
236
|
|
|
@@ -240,7 +240,7 @@ export class AssistsX {
|
|
|
240
240
|
* @returns 是否包含
|
|
241
241
|
*/
|
|
242
242
|
public static containsText(text: string): boolean {
|
|
243
|
-
const response = this.call(CallMethod.containsText, { args: text });
|
|
243
|
+
const response = this.call(CallMethod.containsText, { args: { text } });
|
|
244
244
|
return response.getDataOrDefault(false);
|
|
245
245
|
}
|
|
246
246
|
|
|
@@ -259,7 +259,7 @@ export class AssistsX {
|
|
|
259
259
|
* @returns 父节点
|
|
260
260
|
*/
|
|
261
261
|
public static findFirstParentByTags(className: string): Node {
|
|
262
|
-
const response = this.call(CallMethod.findFirstParentByTags, { args: className });
|
|
262
|
+
const response = this.call(CallMethod.findFirstParentByTags, { args: { className } });
|
|
263
263
|
return Node.create(response.getDataOrDefault("{}"));
|
|
264
264
|
}
|
|
265
265
|
|
package/src/Node.ts
CHANGED
|
@@ -116,7 +116,7 @@ export class Node {
|
|
|
116
116
|
* @param filterDes 描述过滤
|
|
117
117
|
* @returns 节点数组
|
|
118
118
|
*/
|
|
119
|
-
public findByTags(className: string, { filterText, filterViewId, filterDes }: { filterText?: string, filterViewId?: string, filterDes?: string, }): Node[] {
|
|
119
|
+
public findByTags(className: string, { filterText, filterViewId, filterDes }: { filterText?: string, filterViewId?: string, filterDes?: string, } = {}): Node[] {
|
|
120
120
|
Step.assert(this.stepId);
|
|
121
121
|
const result = AssistsX.findByTags(className, { filterText, filterViewId, filterDes, node: this });
|
|
122
122
|
Step.assignIdsToNodes(result, this.stepId);
|