alumnium 0.16.0 → 0.17.0
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/dist/Alumni.d.ts +2 -1
- package/dist/Alumni.js +49 -26
- package/dist/Area.d.ts +2 -1
- package/dist/Area.js +43 -6
- package/dist/Model.js +5 -5
- package/dist/accessibility/UIAutomator2AccessibilityTree.d.ts +1 -0
- package/dist/accessibility/UIAutomator2AccessibilityTree.js +6 -1
- package/dist/accessibility/XCUITestAccessibilityTree.d.ts +2 -0
- package/dist/accessibility/XCUITestAccessibilityTree.js +31 -2
- package/dist/clients/ApiModels.d.ts +1 -0
- package/dist/clients/HttpClient.d.ts +4 -1
- package/dist/clients/HttpClient.js +1 -1
- package/dist/drivers/AppiumDriver.d.ts +9 -6
- package/dist/drivers/AppiumDriver.js +52 -53
- package/dist/drivers/BaseDriver.d.ts +5 -2
- package/dist/drivers/PlaywrightDriver.d.ts +8 -1
- package/dist/drivers/PlaywrightDriver.js +55 -15
- package/dist/drivers/SeleniumDriver.d.ts +8 -2
- package/dist/drivers/SeleniumDriver.js +77 -6
- package/dist/drivers/index.d.ts +1 -2
- package/dist/drivers/scripts/waiter.js +16 -5
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/result.d.ts +8 -0
- package/dist/result.js +1 -0
- package/dist/tools/BaseTool.d.ts +1 -1
- package/dist/tools/BaseTool.js +9 -3
- package/dist/tools/ClickTool.js +3 -1
- package/dist/tools/ExecuteJavascriptTool.d.ts +12 -0
- package/dist/tools/ExecuteJavascriptTool.js +19 -0
- package/dist/tools/Field.d.ts +6 -0
- package/dist/tools/Field.js +1 -0
- package/dist/tools/HoverTool.d.ts +3 -2
- package/dist/tools/NavigateToUrlTool.d.ts +12 -0
- package/dist/tools/NavigateToUrlTool.js +19 -0
- package/dist/tools/ScrollTool.d.ts +12 -0
- package/dist/tools/ScrollTool.js +19 -0
- package/dist/tools/SelectTool.d.ts +3 -2
- package/dist/tools/UploadTool.d.ts +16 -0
- package/dist/tools/UploadTool.js +34 -0
- package/dist/tools/toolToSchemaConverter.d.ts +3 -0
- package/dist/tools/toolToSchemaConverter.js +3 -0
- package/dist/utils/retry.d.ts +3 -3
- package/dist/utils/retry.js +15 -4
- package/package.json +1 -1
package/dist/Alumni.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { Data } from "./clients/typecasting.js";
|
|
|
7
7
|
import { BaseDriver } from "./drivers/BaseDriver.js";
|
|
8
8
|
import { Element } from "./drivers/index.js";
|
|
9
9
|
import { Model } from "./Model.js";
|
|
10
|
+
import { DoResult } from "./result.js";
|
|
10
11
|
import { ToolClass } from "./tools/BaseTool.js";
|
|
11
12
|
export interface AlumniOptions {
|
|
12
13
|
url?: string;
|
|
@@ -25,7 +26,7 @@ export declare class Alumni {
|
|
|
25
26
|
private model;
|
|
26
27
|
constructor(driver: WebDriver | Page | Browser, options?: AlumniOptions);
|
|
27
28
|
quit(): Promise<void>;
|
|
28
|
-
do(goal: string): Promise<
|
|
29
|
+
do(goal: string): Promise<DoResult>;
|
|
29
30
|
check(statement: string, options?: VisionOptions): Promise<string>;
|
|
30
31
|
get(data: string, options?: VisionOptions): Promise<Data>;
|
|
31
32
|
find(description: string): Promise<Element>;
|
package/dist/Alumni.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
1
10
|
import { WebDriver } from "selenium-webdriver";
|
|
2
11
|
import { Area } from "./Area.js";
|
|
3
12
|
import { Cache } from "./Cache.js";
|
|
@@ -8,16 +17,12 @@ import { SeleniumDriver } from "./drivers/SeleniumDriver.js";
|
|
|
8
17
|
import { AssertionError } from "./errors/AssertionError.js";
|
|
9
18
|
import { Model } from "./Model.js";
|
|
10
19
|
import { BaseTool } from "./tools/BaseTool.js";
|
|
11
|
-
import { ClickTool } from "./tools/ClickTool.js";
|
|
12
|
-
import { DragAndDropTool } from "./tools/DragAndDropTool.js";
|
|
13
|
-
import { HoverTool } from "./tools/HoverTool.js";
|
|
14
|
-
import { PressKeyTool } from "./tools/PressKeyTool.js";
|
|
15
|
-
import { SelectTool } from "./tools/SelectTool.js";
|
|
16
|
-
import { TypeTool } from "./tools/TypeTool.js";
|
|
17
20
|
import { getLogger } from "./utils/logger.js";
|
|
21
|
+
import { retry } from "./utils/retry.js";
|
|
18
22
|
const logger = getLogger(["Alumni"]);
|
|
19
23
|
export class Alumni {
|
|
20
24
|
constructor(driver, options = {}) {
|
|
25
|
+
this.tools = {};
|
|
21
26
|
this.url = options.url || "http://localhost:8013";
|
|
22
27
|
this.model = options.model || Model.current;
|
|
23
28
|
// Wrap driver or use directly if already wrapped
|
|
@@ -35,20 +40,11 @@ export class Alumni {
|
|
|
35
40
|
else {
|
|
36
41
|
throw new Error(`Unsupported driver type '${typeof driver}'`);
|
|
37
42
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
SelectTool,
|
|
44
|
-
PressKeyTool,
|
|
45
|
-
DragAndDropTool,
|
|
46
|
-
};
|
|
47
|
-
// Add extra tools if provided
|
|
48
|
-
if (options.extraTools) {
|
|
49
|
-
for (const tool of options.extraTools) {
|
|
50
|
-
this.tools[tool.name] = tool;
|
|
51
|
-
}
|
|
43
|
+
for (const tool of new Set([
|
|
44
|
+
...this.driver.supportedTools,
|
|
45
|
+
...(options.extraTools || []),
|
|
46
|
+
])) {
|
|
47
|
+
this.tools[tool.name] = tool;
|
|
52
48
|
}
|
|
53
49
|
// Initialize HTTP client
|
|
54
50
|
this.client = new HttpClient(this.url, this.model, this.driver.platform, this.tools);
|
|
@@ -62,7 +58,8 @@ export class Alumni {
|
|
|
62
58
|
}
|
|
63
59
|
async do(goal) {
|
|
64
60
|
const initialAccessibilityTree = await this.driver.getAccessibilityTree();
|
|
65
|
-
const steps = await this.client.planActions(goal, initialAccessibilityTree.toStr());
|
|
61
|
+
const { explanation, steps } = await this.client.planActions(goal, initialAccessibilityTree.toStr());
|
|
62
|
+
const executedSteps = [];
|
|
66
63
|
for (let idx = 0; idx < steps.length; idx++) {
|
|
67
64
|
const step = steps[idx];
|
|
68
65
|
// Use initial tree for first step, fresh tree for subsequent steps
|
|
@@ -70,11 +67,14 @@ export class Alumni {
|
|
|
70
67
|
? initialAccessibilityTree
|
|
71
68
|
: await this.driver.getAccessibilityTree();
|
|
72
69
|
const actorResponse = await this.client.executeAction(goal, step, accessibilityTree.toStr());
|
|
73
|
-
|
|
70
|
+
const calledTools = [];
|
|
74
71
|
for (const toolCall of actorResponse) {
|
|
75
|
-
await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
|
|
72
|
+
const calledTool = await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
|
|
73
|
+
calledTools.push(calledTool);
|
|
76
74
|
}
|
|
75
|
+
executedSteps.push({ name: step, tools: calledTools });
|
|
77
76
|
}
|
|
77
|
+
return { explanation, steps: executedSteps };
|
|
78
78
|
}
|
|
79
79
|
async check(statement, options = {}) {
|
|
80
80
|
const screenshot = options.vision
|
|
@@ -92,9 +92,8 @@ export class Alumni {
|
|
|
92
92
|
? await this.driver.screenshot()
|
|
93
93
|
: undefined;
|
|
94
94
|
const accessibilityTree = await this.driver.getAccessibilityTree();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return value;
|
|
95
|
+
const [explanation, value] = await this.client.retrieve(data, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
|
|
96
|
+
return value === null ? explanation : value;
|
|
98
97
|
}
|
|
99
98
|
async find(description) {
|
|
100
99
|
const accessibilityTree = await this.driver.getAccessibilityTree();
|
|
@@ -117,3 +116,27 @@ export class Alumni {
|
|
|
117
116
|
return await this.client.getStats();
|
|
118
117
|
}
|
|
119
118
|
}
|
|
119
|
+
__decorate([
|
|
120
|
+
retry(),
|
|
121
|
+
__metadata("design:type", Function),
|
|
122
|
+
__metadata("design:paramtypes", [String]),
|
|
123
|
+
__metadata("design:returntype", Promise)
|
|
124
|
+
], Alumni.prototype, "do", null);
|
|
125
|
+
__decorate([
|
|
126
|
+
retry(),
|
|
127
|
+
__metadata("design:type", Function),
|
|
128
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
129
|
+
__metadata("design:returntype", Promise)
|
|
130
|
+
], Alumni.prototype, "check", null);
|
|
131
|
+
__decorate([
|
|
132
|
+
retry(),
|
|
133
|
+
__metadata("design:type", Function),
|
|
134
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
135
|
+
__metadata("design:returntype", Promise)
|
|
136
|
+
], Alumni.prototype, "get", null);
|
|
137
|
+
__decorate([
|
|
138
|
+
retry(),
|
|
139
|
+
__metadata("design:type", Function),
|
|
140
|
+
__metadata("design:paramtypes", [String]),
|
|
141
|
+
__metadata("design:returntype", Promise)
|
|
142
|
+
], Alumni.prototype, "find", null);
|
package/dist/Area.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { HttpClient } from "./clients/HttpClient.js";
|
|
|
4
4
|
import { Data } from "./clients/typecasting.js";
|
|
5
5
|
import { BaseDriver } from "./drivers/BaseDriver.js";
|
|
6
6
|
import { Element } from "./drivers/index.js";
|
|
7
|
+
import { DoResult } from "./result.js";
|
|
7
8
|
import { ToolClass } from "./tools/BaseTool.js";
|
|
8
9
|
export declare class Area {
|
|
9
10
|
id: number;
|
|
@@ -13,7 +14,7 @@ export declare class Area {
|
|
|
13
14
|
private tools;
|
|
14
15
|
private client;
|
|
15
16
|
constructor(id: number, description: string, accessibilityTree: BaseAccessibilityTree, driver: BaseDriver, tools: Record<string, ToolClass>, client: HttpClient);
|
|
16
|
-
do(goal: string): Promise<
|
|
17
|
+
do(goal: string): Promise<DoResult>;
|
|
17
18
|
check(statement: string, options?: VisionOptions): Promise<string>;
|
|
18
19
|
get(data: string, options?: VisionOptions): Promise<Data>;
|
|
19
20
|
find(description: string): Promise<Element>;
|
package/dist/Area.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
1
10
|
import { AssertionError } from "./errors/AssertionError.js";
|
|
2
11
|
import { BaseTool } from "./tools/BaseTool.js";
|
|
12
|
+
import { retry } from "./utils/retry.js";
|
|
3
13
|
export class Area {
|
|
4
14
|
constructor(id, description, accessibilityTree, driver, tools, client) {
|
|
5
15
|
this.id = id;
|
|
@@ -10,14 +20,18 @@ export class Area {
|
|
|
10
20
|
this.client = client;
|
|
11
21
|
}
|
|
12
22
|
async do(goal) {
|
|
13
|
-
const steps = await this.client.planActions(goal, this.accessibilityTree.toStr());
|
|
23
|
+
const { explanation, steps } = await this.client.planActions(goal, this.accessibilityTree.toStr());
|
|
24
|
+
const executedSteps = [];
|
|
14
25
|
for (const step of steps) {
|
|
15
26
|
const actorResponse = await this.client.executeAction(goal, step, this.accessibilityTree.toStr());
|
|
16
|
-
|
|
27
|
+
const calledTools = [];
|
|
17
28
|
for (const toolCall of actorResponse) {
|
|
18
|
-
await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
|
|
29
|
+
const calledTool = await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
|
|
30
|
+
calledTools.push(calledTool);
|
|
19
31
|
}
|
|
32
|
+
executedSteps.push({ name: step, tools: calledTools });
|
|
20
33
|
}
|
|
34
|
+
return { explanation, steps: executedSteps };
|
|
21
35
|
}
|
|
22
36
|
async check(statement, options = {}) {
|
|
23
37
|
const screenshot = options.vision
|
|
@@ -33,12 +47,35 @@ export class Area {
|
|
|
33
47
|
const screenshot = options.vision
|
|
34
48
|
? await this.driver.screenshot()
|
|
35
49
|
: undefined;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return value;
|
|
50
|
+
const [explanation, value] = await this.client.retrieve(data, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
|
|
51
|
+
return value === null ? explanation : value;
|
|
39
52
|
}
|
|
40
53
|
async find(description) {
|
|
41
54
|
const response = await this.client.findElement(description, this.accessibilityTree.toStr());
|
|
42
55
|
return this.driver.findElement(response.id);
|
|
43
56
|
}
|
|
44
57
|
}
|
|
58
|
+
__decorate([
|
|
59
|
+
retry(),
|
|
60
|
+
__metadata("design:type", Function),
|
|
61
|
+
__metadata("design:paramtypes", [String]),
|
|
62
|
+
__metadata("design:returntype", Promise)
|
|
63
|
+
], Area.prototype, "do", null);
|
|
64
|
+
__decorate([
|
|
65
|
+
retry(),
|
|
66
|
+
__metadata("design:type", Function),
|
|
67
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
68
|
+
__metadata("design:returntype", Promise)
|
|
69
|
+
], Area.prototype, "check", null);
|
|
70
|
+
__decorate([
|
|
71
|
+
retry(),
|
|
72
|
+
__metadata("design:type", Function),
|
|
73
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
74
|
+
__metadata("design:returntype", Promise)
|
|
75
|
+
], Area.prototype, "get", null);
|
|
76
|
+
__decorate([
|
|
77
|
+
retry(),
|
|
78
|
+
__metadata("design:type", Function),
|
|
79
|
+
__metadata("design:paramtypes", [String]),
|
|
80
|
+
__metadata("design:returntype", Promise)
|
|
81
|
+
], Area.prototype, "find", null);
|
package/dist/Model.js
CHANGED
|
@@ -15,17 +15,17 @@ export var Provider;
|
|
|
15
15
|
export class ModelName {
|
|
16
16
|
}
|
|
17
17
|
ModelName.DEFAULT = {
|
|
18
|
-
[Provider.AZURE_OPENAI]: "gpt-
|
|
18
|
+
[Provider.AZURE_OPENAI]: "gpt-5-nano",
|
|
19
19
|
[Provider.ANTHROPIC]: "claude-haiku-4-5-20251001",
|
|
20
20
|
[Provider.AWS_ANTHROPIC]: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
21
21
|
[Provider.AWS_META]: "us.meta.llama4-maverick-17b-instruct-v1:0",
|
|
22
|
-
[Provider.DEEPSEEK]: "deepseek-
|
|
22
|
+
[Provider.DEEPSEEK]: "deepseek-reasoner",
|
|
23
23
|
[Provider.GITHUB]: "gpt-4o-mini",
|
|
24
|
-
[Provider.GOOGLE]: "gemini-
|
|
24
|
+
[Provider.GOOGLE]: "gemini-3-flash-preview",
|
|
25
25
|
[Provider.MISTRALAI]: "mistral-medium-2505",
|
|
26
26
|
[Provider.OLLAMA]: "mistral-small3.1",
|
|
27
|
-
[Provider.OPENAI]: "gpt-
|
|
28
|
-
[Provider.XAI]: "grok-4-fast-
|
|
27
|
+
[Provider.OPENAI]: "gpt-5-nano-2025-08-07",
|
|
28
|
+
[Provider.XAI]: "grok-4-1-fast-reasoning",
|
|
29
29
|
};
|
|
30
30
|
export class Model {
|
|
31
31
|
constructor(provider, name) {
|
|
@@ -5,6 +5,7 @@ export declare class UIAutomator2AccessibilityTree extends BaseAccessibilityTree
|
|
|
5
5
|
private nextRawId;
|
|
6
6
|
private raw;
|
|
7
7
|
constructor(xmlString: string);
|
|
8
|
+
private static fromXml;
|
|
8
9
|
toStr(): string;
|
|
9
10
|
private addRawIds;
|
|
10
11
|
elementById(rawId: number): AccessibilityElement;
|
|
@@ -12,6 +12,11 @@ export class UIAutomator2AccessibilityTree extends BaseAccessibilityTree {
|
|
|
12
12
|
const cleanedXmlContent = cleanedLines.join("\n");
|
|
13
13
|
this.xmlString = `<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>\n<root>\n${cleanedXmlContent}\n</root>`;
|
|
14
14
|
}
|
|
15
|
+
static fromXml(xmlString) {
|
|
16
|
+
const instance = new UIAutomator2AccessibilityTree("");
|
|
17
|
+
instance.raw = xmlString;
|
|
18
|
+
return instance;
|
|
19
|
+
}
|
|
15
20
|
toStr() {
|
|
16
21
|
if (this.raw !== null) {
|
|
17
22
|
return this.raw;
|
|
@@ -53,7 +58,7 @@ export class UIAutomator2AccessibilityTree extends BaseAccessibilityTree {
|
|
|
53
58
|
return this;
|
|
54
59
|
}
|
|
55
60
|
const scopedXml = this.elementToString(element, 0);
|
|
56
|
-
return
|
|
61
|
+
return UIAutomator2AccessibilityTree.fromXml(scopedXml);
|
|
57
62
|
}
|
|
58
63
|
findElementByRawId(xmlString, targetRawId) {
|
|
59
64
|
const elements = this.parseSimpleXml(xmlString);
|
|
@@ -5,11 +5,13 @@ export declare class XCUITestAccessibilityTree extends BaseAccessibilityTree {
|
|
|
5
5
|
private nextRawId;
|
|
6
6
|
private raw;
|
|
7
7
|
constructor(xmlString: string);
|
|
8
|
+
private static fromXml;
|
|
8
9
|
toStr(): string;
|
|
9
10
|
private addRawIds;
|
|
10
11
|
elementById(rawId: number): AccessibilityElement;
|
|
11
12
|
scopeToArea(rawId: number): XCUITestAccessibilityTree;
|
|
12
13
|
private findElementByRawId;
|
|
13
14
|
private elementToString;
|
|
15
|
+
private decodeHtmlEntities;
|
|
14
16
|
private parseSimpleXml;
|
|
15
17
|
}
|
|
@@ -7,6 +7,11 @@ export class XCUITestAccessibilityTree extends BaseAccessibilityTree {
|
|
|
7
7
|
this.raw = null;
|
|
8
8
|
this.xmlString = xmlString;
|
|
9
9
|
}
|
|
10
|
+
static fromXml(xmlString) {
|
|
11
|
+
const instance = new XCUITestAccessibilityTree("");
|
|
12
|
+
instance.raw = xmlString;
|
|
13
|
+
return instance;
|
|
14
|
+
}
|
|
10
15
|
toStr() {
|
|
11
16
|
if (this.raw !== null) {
|
|
12
17
|
return this.raw;
|
|
@@ -48,7 +53,7 @@ export class XCUITestAccessibilityTree extends BaseAccessibilityTree {
|
|
|
48
53
|
return this;
|
|
49
54
|
}
|
|
50
55
|
const scopedXml = this.elementToString(element, 0);
|
|
51
|
-
return
|
|
56
|
+
return XCUITestAccessibilityTree.fromXml(scopedXml);
|
|
52
57
|
}
|
|
53
58
|
findElementByRawId(xmlString, targetRawId) {
|
|
54
59
|
const elements = this.parseSimpleXml(xmlString);
|
|
@@ -95,6 +100,29 @@ export class XCUITestAccessibilityTree extends BaseAccessibilityTree {
|
|
|
95
100
|
}
|
|
96
101
|
return result;
|
|
97
102
|
}
|
|
103
|
+
decodeHtmlEntities(str) {
|
|
104
|
+
const htmlEntities = {
|
|
105
|
+
"&": "&",
|
|
106
|
+
"<": "<",
|
|
107
|
+
">": ">",
|
|
108
|
+
""": '"',
|
|
109
|
+
"'": "'",
|
|
110
|
+
};
|
|
111
|
+
let decoded = str;
|
|
112
|
+
let previous;
|
|
113
|
+
// Keep decoding until no more entities are found (handles double-encoded entities)
|
|
114
|
+
const MAX_DECODE_ITERATIONS = 10;
|
|
115
|
+
let iterations = 0;
|
|
116
|
+
do {
|
|
117
|
+
previous = decoded;
|
|
118
|
+
decoded = decoded.replace(/&(?:amp|lt|gt|quot|apos);/g, (entity) => htmlEntities[entity] || entity);
|
|
119
|
+
iterations++;
|
|
120
|
+
if (iterations >= MAX_DECODE_ITERATIONS) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
} while (previous !== decoded);
|
|
124
|
+
return decoded;
|
|
125
|
+
}
|
|
98
126
|
parseSimpleXml(xmlString) {
|
|
99
127
|
const elements = [];
|
|
100
128
|
const stack = [];
|
|
@@ -121,7 +149,8 @@ export class XCUITestAccessibilityTree extends BaseAccessibilityTree {
|
|
|
121
149
|
let attrMatch;
|
|
122
150
|
attrRegex.lastIndex = 0;
|
|
123
151
|
while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
|
|
124
|
-
|
|
152
|
+
// Decode HTML entities in attribute values
|
|
153
|
+
attributes[attrMatch[1]] = this.decodeHtmlEntities(attrMatch[2]);
|
|
125
154
|
}
|
|
126
155
|
const elem = {
|
|
127
156
|
tag: tagName,
|
|
@@ -14,7 +14,10 @@ export declare class HttpClient {
|
|
|
14
14
|
private fetchWithTimeout;
|
|
15
15
|
private ensureSession;
|
|
16
16
|
quit(): Promise<void>;
|
|
17
|
-
planActions(goal: string, accessibilityTree: string): Promise<
|
|
17
|
+
planActions(goal: string, accessibilityTree: string): Promise<{
|
|
18
|
+
explanation: string;
|
|
19
|
+
steps: string[];
|
|
20
|
+
}>;
|
|
18
21
|
addExample(goal: string, actions: string[]): Promise<void>;
|
|
19
22
|
clearExamples(): Promise<void>;
|
|
20
23
|
executeAction(goal: string, step: string, accessibilityTree: string): Promise<StepResponse["actions"]>;
|
|
@@ -71,7 +71,7 @@ export class HttpClient {
|
|
|
71
71
|
body: JSON.stringify(requestBody),
|
|
72
72
|
});
|
|
73
73
|
const responseData = (await response.json());
|
|
74
|
-
return responseData.steps;
|
|
74
|
+
return { explanation: responseData.explanation, steps: responseData.steps };
|
|
75
75
|
}
|
|
76
76
|
async addExample(goal, actions) {
|
|
77
77
|
await this.ensureSession();
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { Browser
|
|
1
|
+
import type { Browser } from "webdriverio";
|
|
2
2
|
import { BaseAccessibilityTree } from "../accessibility/BaseAccessibilityTree.js";
|
|
3
|
+
import { ToolClass } from "../tools/BaseTool.js";
|
|
3
4
|
import { BaseDriver } from "./BaseDriver.js";
|
|
4
5
|
import { Key } from "./keys.js";
|
|
5
6
|
export declare class AppiumDriver extends BaseDriver {
|
|
6
7
|
private driver;
|
|
7
8
|
platform: "xcuitest" | "uiautomator2";
|
|
8
|
-
supportedTools: Set<
|
|
9
|
+
supportedTools: Set<ToolClass>;
|
|
9
10
|
autoswitchContexts: boolean;
|
|
10
11
|
delay: number;
|
|
11
12
|
doubleFetchPageSource: boolean;
|
|
@@ -16,15 +17,17 @@ export declare class AppiumDriver extends BaseDriver {
|
|
|
16
17
|
dragAndDrop(fromId: number, toId: number): Promise<void>;
|
|
17
18
|
pressKey(key: Key): Promise<void>;
|
|
18
19
|
back(): Promise<void>;
|
|
20
|
+
visit(url: string): Promise<void>;
|
|
21
|
+
scrollTo(id: number): Promise<void>;
|
|
19
22
|
quit(): Promise<void>;
|
|
20
23
|
screenshot(): Promise<string>;
|
|
21
|
-
select(_id: number, _option: string): Promise<void>;
|
|
22
|
-
swipe(_id: number): Promise<void>;
|
|
23
24
|
title(): Promise<string>;
|
|
24
25
|
type(id: number, text: string): Promise<void>;
|
|
25
26
|
url(): Promise<string>;
|
|
26
|
-
findElement(id: number): Promise<
|
|
27
|
-
|
|
27
|
+
findElement(id: number): Promise<WebdriverIO.Element>;
|
|
28
|
+
executeScript(script: string): Promise<void>;
|
|
28
29
|
private ensureNativeAppContext;
|
|
29
30
|
private ensureWebviewContext;
|
|
31
|
+
private hideKeyboard;
|
|
32
|
+
private scrollIntoView;
|
|
30
33
|
}
|
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import { Key as SeleniumKey } from "selenium-webdriver";
|
|
2
2
|
import { UIAutomator2AccessibilityTree } from "../accessibility/UIAutomator2AccessibilityTree.js";
|
|
3
3
|
import { XCUITestAccessibilityTree } from "../accessibility/XCUITestAccessibilityTree.js";
|
|
4
|
+
import { ClickTool } from "../tools/ClickTool.js";
|
|
5
|
+
import { DragAndDropTool } from "../tools/DragAndDropTool.js";
|
|
6
|
+
import { PressKeyTool } from "../tools/PressKeyTool.js";
|
|
7
|
+
import { TypeTool } from "../tools/TypeTool.js";
|
|
4
8
|
import { BaseDriver } from "./BaseDriver.js";
|
|
5
9
|
import { Key } from "./keys.js";
|
|
6
10
|
export class AppiumDriver extends BaseDriver {
|
|
7
11
|
constructor(driver) {
|
|
8
12
|
super();
|
|
9
13
|
this.supportedTools = new Set([
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"TypeTool",
|
|
14
|
+
ClickTool,
|
|
15
|
+
DragAndDropTool,
|
|
16
|
+
PressKeyTool,
|
|
17
|
+
TypeTool,
|
|
15
18
|
]);
|
|
16
19
|
this.autoswitchContexts = true;
|
|
17
20
|
this.delay = 0;
|
|
18
21
|
this.doubleFetchPageSource = false;
|
|
19
22
|
this.hideKeyboardAfterTyping = false;
|
|
20
23
|
this.driver = driver;
|
|
21
|
-
if (this.driver.capabilities
|
|
22
|
-
"uiautomator2") {
|
|
24
|
+
if (this.driver.capabilities.platformName?.toLowerCase() === "android") {
|
|
23
25
|
this.platform = "uiautomator2";
|
|
24
26
|
}
|
|
25
27
|
else {
|
|
@@ -37,7 +39,7 @@ export class AppiumDriver extends BaseDriver {
|
|
|
37
39
|
await this.driver.getPageSource();
|
|
38
40
|
}
|
|
39
41
|
const xmlString = await this.driver.getPageSource();
|
|
40
|
-
if (this.
|
|
42
|
+
if (this.platform === "uiautomator2") {
|
|
41
43
|
return new UIAutomator2AccessibilityTree(xmlString);
|
|
42
44
|
}
|
|
43
45
|
else {
|
|
@@ -47,13 +49,14 @@ export class AppiumDriver extends BaseDriver {
|
|
|
47
49
|
async click(id) {
|
|
48
50
|
await this.ensureNativeAppContext();
|
|
49
51
|
const element = await this.findElement(id);
|
|
52
|
+
await this.scrollIntoView(element);
|
|
50
53
|
await element.click();
|
|
51
54
|
}
|
|
52
55
|
async dragAndDrop(fromId, toId) {
|
|
53
56
|
await this.ensureNativeAppContext();
|
|
54
57
|
const fromElement = await this.findElement(fromId);
|
|
55
58
|
const toElement = await this.findElement(toId);
|
|
56
|
-
|
|
59
|
+
await this.scrollIntoView(fromElement);
|
|
57
60
|
await fromElement.dragAndDrop(toElement);
|
|
58
61
|
}
|
|
59
62
|
async pressKey(key) {
|
|
@@ -79,20 +82,19 @@ export class AppiumDriver extends BaseDriver {
|
|
|
79
82
|
async back() {
|
|
80
83
|
await this.driver.back();
|
|
81
84
|
}
|
|
85
|
+
async visit(url) {
|
|
86
|
+
await this.driver.url(url);
|
|
87
|
+
}
|
|
88
|
+
async scrollTo(id) {
|
|
89
|
+
const element = await this.findElement(id);
|
|
90
|
+
await this.scrollIntoView(element);
|
|
91
|
+
}
|
|
82
92
|
async quit() {
|
|
83
93
|
// WebdriverIO handles session termination automatically.
|
|
84
94
|
}
|
|
85
95
|
async screenshot() {
|
|
86
96
|
return await this.driver.takeScreenshot();
|
|
87
97
|
}
|
|
88
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
89
|
-
async select(_id, _option) {
|
|
90
|
-
// TODO: Implement select functionality and the tool
|
|
91
|
-
}
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
93
|
-
async swipe(_id) {
|
|
94
|
-
// TODO: Implement swipe functionality and the tool
|
|
95
|
-
}
|
|
96
98
|
async title() {
|
|
97
99
|
await this.ensureWebviewContext();
|
|
98
100
|
try {
|
|
@@ -105,32 +107,10 @@ export class AppiumDriver extends BaseDriver {
|
|
|
105
107
|
async type(id, text) {
|
|
106
108
|
await this.ensureNativeAppContext();
|
|
107
109
|
const element = await this.findElement(id);
|
|
108
|
-
await
|
|
110
|
+
await this.scrollIntoView(element);
|
|
109
111
|
await element.setValue(text);
|
|
110
|
-
if (this.hideKeyboardAfterTyping) {
|
|
111
|
-
|
|
112
|
-
await this.driver.hideKeyboard();
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
const location = await element.getLocation();
|
|
116
|
-
await this.driver.performActions([
|
|
117
|
-
{
|
|
118
|
-
type: "pointer",
|
|
119
|
-
id: "finger1",
|
|
120
|
-
parameters: { pointerType: "touch" },
|
|
121
|
-
actions: [
|
|
122
|
-
{
|
|
123
|
-
type: "pointerMove",
|
|
124
|
-
duration: 0,
|
|
125
|
-
x: location.x,
|
|
126
|
-
y: location.y - 20,
|
|
127
|
-
},
|
|
128
|
-
{ type: "pointerDown", button: 0 },
|
|
129
|
-
{ type: "pointerUp", button: 0 },
|
|
130
|
-
],
|
|
131
|
-
},
|
|
132
|
-
]);
|
|
133
|
-
}
|
|
112
|
+
if (this.hideKeyboardAfterTyping && (await this.driver.isKeyboardShown())) {
|
|
113
|
+
await this.hideKeyboard();
|
|
134
114
|
}
|
|
135
115
|
}
|
|
136
116
|
async url() {
|
|
@@ -161,8 +141,7 @@ export class AppiumDriver extends BaseDriver {
|
|
|
161
141
|
predicate += ` AND ${propsStr}`;
|
|
162
142
|
}
|
|
163
143
|
console.debug(`Finding element by predicate: ${predicate}`);
|
|
164
|
-
|
|
165
|
-
return foundElement;
|
|
144
|
+
return this.driver.$(`-ios predicate string:${predicate}`).getElement();
|
|
166
145
|
}
|
|
167
146
|
else {
|
|
168
147
|
// Use XPath for UIAutomator2
|
|
@@ -170,23 +149,19 @@ export class AppiumDriver extends BaseDriver {
|
|
|
170
149
|
const props = {};
|
|
171
150
|
if (element.androidResourceId)
|
|
172
151
|
props["resource-id"] = element.androidResourceId;
|
|
173
|
-
if (element.androidText)
|
|
174
|
-
props["text"] = element.androidText;
|
|
175
|
-
if (element.androidContentDesc)
|
|
176
|
-
props["content-desc"] = element.androidContentDesc;
|
|
177
152
|
if (element.androidBounds)
|
|
178
153
|
props["bounds"] = element.androidBounds;
|
|
179
154
|
if (Object.keys(props).length > 0) {
|
|
180
155
|
const conditions = Object.entries(props).map(([k, v]) => `@${k}="${v}"`);
|
|
181
156
|
xpath += `[${conditions.join(" and ")}]`;
|
|
182
157
|
}
|
|
183
|
-
|
|
158
|
+
console.debug(`Finding element by xpath: ${xpath}`);
|
|
159
|
+
return this.driver.$(xpath).getElement();
|
|
184
160
|
}
|
|
185
161
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
throw new Error("Hover is not supported by AppiumDriver");
|
|
162
|
+
async executeScript(script) {
|
|
163
|
+
await this.ensureWebviewContext();
|
|
164
|
+
await this.driver.execute(script);
|
|
190
165
|
}
|
|
191
166
|
async ensureNativeAppContext() {
|
|
192
167
|
if (!this.autoswitchContexts) {
|
|
@@ -212,4 +187,28 @@ export class AppiumDriver extends BaseDriver {
|
|
|
212
187
|
}
|
|
213
188
|
}
|
|
214
189
|
}
|
|
190
|
+
async hideKeyboard() {
|
|
191
|
+
if (this.platform === "uiautomator2") {
|
|
192
|
+
await this.driver.hideKeyboard();
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
// Tap to the top left corner of the keyboard to dismiss it
|
|
196
|
+
const keyboard = this.driver.$("-ios predicate string:type == 'XCUIElementTypeKeyboard'");
|
|
197
|
+
const { width, height } = await keyboard.getSize();
|
|
198
|
+
await keyboard.click({
|
|
199
|
+
x: -Math.ceil(width / 2),
|
|
200
|
+
y: -Math.ceil(height / 2),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async scrollIntoView(element) {
|
|
205
|
+
if (this.platform === "uiautomator2") {
|
|
206
|
+
await element.scrollIntoView();
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
await this.driver.execute("mobile: scrollToElement", {
|
|
210
|
+
elementId: element.elementId,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
215
214
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { BaseAccessibilityTree } from "../accessibility/BaseAccessibilityTree.js";
|
|
2
|
+
import { ToolClass } from "../tools/BaseTool.js";
|
|
2
3
|
import { Element } from "./index.js";
|
|
3
4
|
import { Key } from "./keys.js";
|
|
4
5
|
export declare abstract class BaseDriver {
|
|
5
6
|
abstract platform: string;
|
|
7
|
+
abstract supportedTools: Set<ToolClass>;
|
|
6
8
|
abstract getAccessibilityTree(): Promise<BaseAccessibilityTree>;
|
|
7
9
|
abstract click(id: number): void | Promise<void>;
|
|
8
10
|
abstract dragAndDrop(fromId: number, toId: number): void | Promise<void>;
|
|
@@ -10,10 +12,11 @@ export declare abstract class BaseDriver {
|
|
|
10
12
|
abstract quit(): void | Promise<void>;
|
|
11
13
|
abstract back(): void | Promise<void>;
|
|
12
14
|
abstract screenshot(): string | Promise<string>;
|
|
13
|
-
abstract select(id: number, option: string): void | Promise<void>;
|
|
14
15
|
abstract title(): string | Promise<string>;
|
|
15
16
|
abstract type(id: number, text: string): void | Promise<void>;
|
|
16
17
|
abstract url(): string | Promise<string>;
|
|
17
18
|
abstract findElement(id: number): Element | Promise<Element>;
|
|
18
|
-
abstract
|
|
19
|
+
abstract visit(url: string): void | Promise<void>;
|
|
20
|
+
abstract scrollTo(id: number): void | Promise<void>;
|
|
21
|
+
abstract executeScript(script: string): void | Promise<void>;
|
|
19
22
|
}
|