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.
Files changed (45) hide show
  1. package/dist/Alumni.d.ts +2 -1
  2. package/dist/Alumni.js +49 -26
  3. package/dist/Area.d.ts +2 -1
  4. package/dist/Area.js +43 -6
  5. package/dist/Model.js +5 -5
  6. package/dist/accessibility/UIAutomator2AccessibilityTree.d.ts +1 -0
  7. package/dist/accessibility/UIAutomator2AccessibilityTree.js +6 -1
  8. package/dist/accessibility/XCUITestAccessibilityTree.d.ts +2 -0
  9. package/dist/accessibility/XCUITestAccessibilityTree.js +31 -2
  10. package/dist/clients/ApiModels.d.ts +1 -0
  11. package/dist/clients/HttpClient.d.ts +4 -1
  12. package/dist/clients/HttpClient.js +1 -1
  13. package/dist/drivers/AppiumDriver.d.ts +9 -6
  14. package/dist/drivers/AppiumDriver.js +52 -53
  15. package/dist/drivers/BaseDriver.d.ts +5 -2
  16. package/dist/drivers/PlaywrightDriver.d.ts +8 -1
  17. package/dist/drivers/PlaywrightDriver.js +55 -15
  18. package/dist/drivers/SeleniumDriver.d.ts +8 -2
  19. package/dist/drivers/SeleniumDriver.js +77 -6
  20. package/dist/drivers/index.d.ts +1 -2
  21. package/dist/drivers/scripts/waiter.js +16 -5
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +3 -0
  24. package/dist/result.d.ts +8 -0
  25. package/dist/result.js +1 -0
  26. package/dist/tools/BaseTool.d.ts +1 -1
  27. package/dist/tools/BaseTool.js +9 -3
  28. package/dist/tools/ClickTool.js +3 -1
  29. package/dist/tools/ExecuteJavascriptTool.d.ts +12 -0
  30. package/dist/tools/ExecuteJavascriptTool.js +19 -0
  31. package/dist/tools/Field.d.ts +6 -0
  32. package/dist/tools/Field.js +1 -0
  33. package/dist/tools/HoverTool.d.ts +3 -2
  34. package/dist/tools/NavigateToUrlTool.d.ts +12 -0
  35. package/dist/tools/NavigateToUrlTool.js +19 -0
  36. package/dist/tools/ScrollTool.d.ts +12 -0
  37. package/dist/tools/ScrollTool.js +19 -0
  38. package/dist/tools/SelectTool.d.ts +3 -2
  39. package/dist/tools/UploadTool.d.ts +16 -0
  40. package/dist/tools/UploadTool.js +34 -0
  41. package/dist/tools/toolToSchemaConverter.d.ts +3 -0
  42. package/dist/tools/toolToSchemaConverter.js +3 -0
  43. package/dist/utils/retry.d.ts +3 -3
  44. package/dist/utils/retry.js +15 -4
  45. 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<void>;
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
- // Initialize tools
39
- this.tools = {
40
- ClickTool,
41
- TypeTool,
42
- HoverTool,
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
- // Execute tool calls
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
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
96
- const [_explanation, value] = await this.client.retrieve(data, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
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<void>;
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
- // Execute tool calls
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
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
37
- const [_explanation, value] = await this.client.retrieve(data, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
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-4o-mini",
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-chat",
22
+ [Provider.DEEPSEEK]: "deepseek-reasoner",
23
23
  [Provider.GITHUB]: "gpt-4o-mini",
24
- [Provider.GOOGLE]: "gemini-2.0-flash-001",
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-4o-mini-2024-07-18",
28
- [Provider.XAI]: "grok-4-fast-non-reasoning",
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 new UIAutomator2AccessibilityTree(scopedXml);
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 new XCUITestAccessibilityTree(scopedXml);
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
+ "&amp;": "&",
106
+ "&lt;": "<",
107
+ "&gt;": ">",
108
+ "&quot;": '"',
109
+ "&apos;": "'",
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
- attributes[attrMatch[1]] = attrMatch[2];
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,
@@ -22,6 +22,7 @@ export interface PlanRequest {
22
22
  title?: string;
23
23
  }
24
24
  export interface PlanResponse {
25
+ explanation: string;
25
26
  steps: string[];
26
27
  }
27
28
  export interface StepRequest {
@@ -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<string[]>;
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, ChainablePromiseElement } from "webdriverio";
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<string>;
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<ChainablePromiseElement>;
27
- hover(_id: number): Promise<void>;
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
- "ClickTool",
11
- "DragAndDropTool",
12
- "PressKeyTool",
13
- "SelectTool",
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["appium:automationName"]?.toLowerCase() ===
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.driver.capabilities["appium:automationName"] === "uiautomator2") {
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
- // WebdriverIO provides dragAndDrop method on elements
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 element.clearValue();
110
+ await this.scrollIntoView(element);
109
111
  await element.setValue(text);
110
- if (this.hideKeyboardAfterTyping) {
111
- if (this.platform === "uiautomator2") {
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
- const foundElement = this.driver.$(`-ios predicate string:${predicate}`);
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
- return this.driver.$(xpath);
158
+ console.debug(`Finding element by xpath: ${xpath}`);
159
+ return this.driver.$(xpath).getElement();
184
160
  }
185
161
  }
186
- // eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
187
- async hover(_id) {
188
- // Hover is not typically supported in mobile contexts
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 hover(id: number): void | Promise<void>;
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
  }