alumnium 0.16.0 → 0.18.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 (64) hide show
  1. package/dist/Alumni.d.ts +6 -1
  2. package/dist/Alumni.js +73 -32
  3. package/dist/Area.d.ts +2 -1
  4. package/dist/Area.js +53 -10
  5. package/dist/Model.d.ts +1 -0
  6. package/dist/Model.js +7 -5
  7. package/dist/accessibility/AccessibilityElement.d.ts +4 -1
  8. package/dist/accessibility/AccessibilityElement.js +4 -1
  9. package/dist/accessibility/ChromiumAccessibilityTree.d.ts +3 -0
  10. package/dist/accessibility/ChromiumAccessibilityTree.js +86 -9
  11. package/dist/accessibility/UIAutomator2AccessibilityTree.d.ts +1 -0
  12. package/dist/accessibility/UIAutomator2AccessibilityTree.js +6 -1
  13. package/dist/accessibility/XCUITestAccessibilityTree.d.ts +2 -0
  14. package/dist/accessibility/XCUITestAccessibilityTree.js +31 -2
  15. package/dist/clients/ApiModels.d.ts +20 -0
  16. package/dist/clients/HttpClient.d.ts +15 -6
  17. package/dist/clients/HttpClient.js +36 -8
  18. package/dist/drivers/AppiumDriver.d.ts +16 -6
  19. package/dist/drivers/AppiumDriver.js +84 -61
  20. package/dist/drivers/BaseDriver.d.ts +19 -9
  21. package/dist/drivers/PlaywrightDriver.d.ts +35 -3
  22. package/dist/drivers/PlaywrightDriver.js +481 -41
  23. package/dist/drivers/SeleniumDriver.d.ts +22 -3
  24. package/dist/drivers/SeleniumDriver.js +272 -27
  25. package/dist/drivers/index.d.ts +1 -2
  26. package/dist/drivers/scripts/waiter.js +133 -19
  27. package/dist/index.d.ts +6 -0
  28. package/dist/index.js +5 -0
  29. package/dist/result.d.ts +9 -0
  30. package/dist/result.js +1 -0
  31. package/dist/tools/BaseTool.d.ts +1 -1
  32. package/dist/tools/BaseTool.js +9 -3
  33. package/dist/tools/ClickTool.js +2 -1
  34. package/dist/tools/DragSliderTool.d.ts +14 -0
  35. package/dist/tools/DragSliderTool.js +25 -0
  36. package/dist/tools/ExecuteJavascriptTool.d.ts +12 -0
  37. package/dist/tools/ExecuteJavascriptTool.js +19 -0
  38. package/dist/tools/Field.d.ts +6 -0
  39. package/dist/tools/Field.js +1 -0
  40. package/dist/tools/HoverTool.d.ts +3 -2
  41. package/dist/tools/NavigateToUrlTool.d.ts +12 -0
  42. package/dist/tools/NavigateToUrlTool.js +19 -0
  43. package/dist/tools/PressKeyTool.js +1 -1
  44. package/dist/tools/PrintToPdfTool.d.ts +12 -0
  45. package/dist/tools/PrintToPdfTool.js +19 -0
  46. package/dist/tools/{SelectTool.d.ts → ScrollTool.d.ts} +1 -3
  47. package/dist/tools/ScrollTool.js +19 -0
  48. package/dist/tools/SwitchToNextTabTool.d.ts +6 -0
  49. package/dist/tools/SwitchToNextTabTool.js +7 -0
  50. package/dist/tools/SwitchToPreviousTabTool.d.ts +6 -0
  51. package/dist/tools/SwitchToPreviousTabTool.js +7 -0
  52. package/dist/tools/TypeTool.js +1 -1
  53. package/dist/tools/UploadTool.d.ts +16 -0
  54. package/dist/tools/UploadTool.js +37 -0
  55. package/dist/tools/WaitForElementTool.d.ts +14 -0
  56. package/dist/tools/WaitForElementTool.js +33 -0
  57. package/dist/tools/WaitTool.d.ts +12 -0
  58. package/dist/tools/WaitTool.js +26 -0
  59. package/dist/tools/toolToSchemaConverter.d.ts +3 -0
  60. package/dist/tools/toolToSchemaConverter.js +3 -0
  61. package/dist/utils/retry.d.ts +3 -3
  62. package/dist/utils/retry.js +15 -4
  63. package/package.json +1 -1
  64. package/dist/tools/SelectTool.js +0 -21
package/dist/Alumni.d.ts CHANGED
@@ -7,11 +7,15 @@ 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;
13
14
  model?: Model;
14
15
  extraTools?: ToolClass[];
16
+ planner?: boolean;
17
+ changeAnalysis?: boolean;
18
+ excludedAttributes?: Set<string>;
15
19
  }
16
20
  export interface VisionOptions {
17
21
  vision?: boolean;
@@ -23,9 +27,10 @@ export declare class Alumni {
23
27
  cache: Cache;
24
28
  private url;
25
29
  private model;
30
+ private changeAnalysis;
26
31
  constructor(driver: WebDriver | Page | Browser, options?: AlumniOptions);
27
32
  quit(): Promise<void>;
28
- do(goal: string): Promise<void>;
33
+ do(goal: string): Promise<DoResult>;
29
34
  check(statement: string, options?: VisionOptions): Promise<string>;
30
35
  get(data: string, options?: VisionOptions): Promise<Data>;
31
36
  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,18 +17,18 @@ 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"]);
23
+ const changeAnalysis = (process.env.ALUMNIUM_CHANGE_ANALYSIS || "false").toLowerCase() === "true";
24
+ const planner = (process.env.ALUMNIUM_PLANNER || "true").toLowerCase() === "true";
25
+ const excludedAttributes = new Set((process.env.ALUMNIUM_EXCLUDE_ATTRIBUTES || "").split(",").filter(Boolean));
19
26
  export class Alumni {
20
27
  constructor(driver, options = {}) {
28
+ this.tools = {};
21
29
  this.url = options.url || "http://localhost:8013";
22
30
  this.model = options.model || Model.current;
31
+ this.changeAnalysis = options.changeAnalysis ?? changeAnalysis;
23
32
  // Wrap driver or use directly if already wrapped
24
33
  if (driver instanceof WebDriver) {
25
34
  this.driver = new SeleniumDriver(driver);
@@ -35,23 +44,14 @@ export class Alumni {
35
44
  else {
36
45
  throw new Error(`Unsupported driver type '${typeof driver}'`);
37
46
  }
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
- }
47
+ for (const tool of new Set([
48
+ ...this.driver.supportedTools,
49
+ ...(options.extraTools || []),
50
+ ])) {
51
+ this.tools[tool.name] = tool;
52
52
  }
53
53
  // Initialize HTTP client
54
- this.client = new HttpClient(this.url, this.model, this.driver.platform, this.tools);
54
+ this.client = new HttpClient(this.url, this.model, this.driver.platform, this.tools, options.planner ?? planner, options.excludedAttributes ?? excludedAttributes);
55
55
  this.cache = new Cache(this.client);
56
56
  logger.info(`Using model: ${this.model.provider}/${this.model.name}`);
57
57
  logger.info(`Using HTTP client with server: ${this.url}`);
@@ -61,27 +61,45 @@ export class Alumni {
61
61
  await this.driver.quit();
62
62
  }
63
63
  async do(goal) {
64
+ const app = await this.driver.app();
64
65
  const initialAccessibilityTree = await this.driver.getAccessibilityTree();
65
- const steps = await this.client.planActions(goal, initialAccessibilityTree.toStr());
66
+ const beforeTree = this.changeAnalysis
67
+ ? initialAccessibilityTree.toStr()
68
+ : null;
69
+ const beforeUrl = this.changeAnalysis ? await this.driver.url() : null;
70
+ const { explanation, steps } = await this.client.planActions(goal, initialAccessibilityTree.toStr(), app);
71
+ let finalExplanation = explanation;
72
+ const executedSteps = [];
66
73
  for (let idx = 0; idx < steps.length; idx++) {
67
74
  const step = steps[idx];
68
75
  // Use initial tree for first step, fresh tree for subsequent steps
69
76
  const accessibilityTree = idx === 0
70
77
  ? initialAccessibilityTree
71
78
  : await this.driver.getAccessibilityTree();
72
- const actorResponse = await this.client.executeAction(goal, step, accessibilityTree.toStr());
73
- // Execute tool calls
74
- for (const toolCall of actorResponse) {
75
- await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
79
+ const { explanation: actorExplanation, actions } = await this.client.executeAction(goal, step, accessibilityTree.toStr(), app);
80
+ // When planner is off, explanation is just the goal — replace with actor's reasoning.
81
+ if (finalExplanation === goal) {
82
+ finalExplanation = actorExplanation;
83
+ }
84
+ const calledTools = [];
85
+ for (const toolCall of actions) {
86
+ const calledTool = await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
87
+ calledTools.push(calledTool);
76
88
  }
89
+ executedSteps.push({ name: step, tools: calledTools });
90
+ }
91
+ let changes = "";
92
+ if (this.changeAnalysis && executedSteps.length > 0) {
93
+ changes = await this.client.analyzeChanges(beforeTree, beforeUrl, (await this.driver.getAccessibilityTree()).toStr(), await this.driver.url());
77
94
  }
95
+ return { explanation: finalExplanation, steps: executedSteps, changes };
78
96
  }
79
97
  async check(statement, options = {}) {
80
98
  const screenshot = options.vision
81
99
  ? await this.driver.screenshot()
82
100
  : undefined;
83
101
  const accessibilityTree = await this.driver.getAccessibilityTree();
84
- const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
102
+ const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
85
103
  if (!value) {
86
104
  throw new AssertionError(explanation);
87
105
  }
@@ -92,18 +110,17 @@ export class Alumni {
92
110
  ? await this.driver.screenshot()
93
111
  : undefined;
94
112
  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;
113
+ const [explanation, value] = await this.client.retrieve(data, accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
114
+ return value === null ? explanation : value;
98
115
  }
99
116
  async find(description) {
100
117
  const accessibilityTree = await this.driver.getAccessibilityTree();
101
- const response = await this.client.findElement(description, accessibilityTree.toStr());
118
+ const response = await this.client.findElement(description, accessibilityTree.toStr(), await this.driver.app());
102
119
  return this.driver.findElement(response.id);
103
120
  }
104
121
  async area(description) {
105
122
  const accessibilityTree = await this.driver.getAccessibilityTree();
106
- const response = await this.client.findArea(description, accessibilityTree.toStr());
123
+ const response = await this.client.findArea(description, accessibilityTree.toStr(), await this.driver.app());
107
124
  const scopedTree = accessibilityTree.scopeToArea(response.id);
108
125
  return new Area(response.id, response.explanation, scopedTree, this.driver, this.tools, this.client);
109
126
  }
@@ -117,3 +134,27 @@ export class Alumni {
117
134
  return await this.client.getStats();
118
135
  }
119
136
  }
137
+ __decorate([
138
+ retry(),
139
+ __metadata("design:type", Function),
140
+ __metadata("design:paramtypes", [String]),
141
+ __metadata("design:returntype", Promise)
142
+ ], Alumni.prototype, "do", null);
143
+ __decorate([
144
+ retry(),
145
+ __metadata("design:type", Function),
146
+ __metadata("design:paramtypes", [String, Object]),
147
+ __metadata("design:returntype", Promise)
148
+ ], Alumni.prototype, "check", null);
149
+ __decorate([
150
+ retry(),
151
+ __metadata("design:type", Function),
152
+ __metadata("design:paramtypes", [String, Object]),
153
+ __metadata("design:returntype", Promise)
154
+ ], Alumni.prototype, "get", null);
155
+ __decorate([
156
+ retry(),
157
+ __metadata("design:type", Function),
158
+ __metadata("design:paramtypes", [String]),
159
+ __metadata("design:returntype", Promise)
160
+ ], 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,20 +20,30 @@ 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 app = await this.driver.app();
24
+ const { explanation, steps } = await this.client.planActions(goal, this.accessibilityTree.toStr(), app);
25
+ let finalExplanation = explanation;
26
+ const executedSteps = [];
14
27
  for (const step of steps) {
15
- const actorResponse = await this.client.executeAction(goal, step, this.accessibilityTree.toStr());
16
- // Execute tool calls
17
- for (const toolCall of actorResponse) {
18
- await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
28
+ const { explanation: actorExplanation, actions } = await this.client.executeAction(goal, step, this.accessibilityTree.toStr(), app);
29
+ // When planner is off, explanation is just the goal — replace with actor's reasoning.
30
+ if (finalExplanation === goal) {
31
+ finalExplanation = actorExplanation;
19
32
  }
33
+ const calledTools = [];
34
+ for (const toolCall of actions) {
35
+ const calledTool = await BaseTool.executeToolCall(toolCall, this.tools, this.driver);
36
+ calledTools.push(calledTool);
37
+ }
38
+ executedSteps.push({ name: step, tools: calledTools });
20
39
  }
40
+ return { explanation: finalExplanation, steps: executedSteps, changes: "" };
21
41
  }
22
42
  async check(statement, options = {}) {
23
43
  const screenshot = options.vision
24
44
  ? await this.driver.screenshot()
25
45
  : undefined;
26
- const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot);
46
+ const [explanation, value] = await this.client.retrieve(`Is the following true or false - ${statement}`, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
27
47
  if (!value) {
28
48
  throw new AssertionError(explanation);
29
49
  }
@@ -33,12 +53,35 @@ export class Area {
33
53
  const screenshot = options.vision
34
54
  ? await this.driver.screenshot()
35
55
  : 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;
56
+ const [explanation, value] = await this.client.retrieve(data, this.accessibilityTree.toStr(), await this.driver.title(), await this.driver.url(), screenshot, await this.driver.app());
57
+ return value === null ? explanation : value;
39
58
  }
40
59
  async find(description) {
41
- const response = await this.client.findElement(description, this.accessibilityTree.toStr());
60
+ const response = await this.client.findElement(description, this.accessibilityTree.toStr(), await this.driver.app());
42
61
  return this.driver.findElement(response.id);
43
62
  }
44
63
  }
64
+ __decorate([
65
+ retry(),
66
+ __metadata("design:type", Function),
67
+ __metadata("design:paramtypes", [String]),
68
+ __metadata("design:returntype", Promise)
69
+ ], Area.prototype, "do", null);
70
+ __decorate([
71
+ retry(),
72
+ __metadata("design:type", Function),
73
+ __metadata("design:paramtypes", [String, Object]),
74
+ __metadata("design:returntype", Promise)
75
+ ], Area.prototype, "check", null);
76
+ __decorate([
77
+ retry(),
78
+ __metadata("design:type", Function),
79
+ __metadata("design:paramtypes", [String, Object]),
80
+ __metadata("design:returntype", Promise)
81
+ ], Area.prototype, "get", null);
82
+ __decorate([
83
+ retry(),
84
+ __metadata("design:type", Function),
85
+ __metadata("design:paramtypes", [String]),
86
+ __metadata("design:returntype", Promise)
87
+ ], Area.prototype, "find", null);
package/dist/Model.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare enum Provider {
2
+ AZURE_FOUNDRY = "azure_foundry",
2
3
  AZURE_OPENAI = "azure_openai",
3
4
  ANTHROPIC = "anthropic",
4
5
  AWS_ANTHROPIC = "aws_anthropic",
package/dist/Model.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export var Provider;
2
2
  (function (Provider) {
3
+ Provider["AZURE_FOUNDRY"] = "azure_foundry";
3
4
  Provider["AZURE_OPENAI"] = "azure_openai";
4
5
  Provider["ANTHROPIC"] = "anthropic";
5
6
  Provider["AWS_ANTHROPIC"] = "aws_anthropic";
@@ -15,17 +16,18 @@ export var Provider;
15
16
  export class ModelName {
16
17
  }
17
18
  ModelName.DEFAULT = {
18
- [Provider.AZURE_OPENAI]: "gpt-4o-mini",
19
+ [Provider.AZURE_FOUNDRY]: "gpt-5-nano",
20
+ [Provider.AZURE_OPENAI]: "gpt-5-nano",
19
21
  [Provider.ANTHROPIC]: "claude-haiku-4-5-20251001",
20
22
  [Provider.AWS_ANTHROPIC]: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
21
23
  [Provider.AWS_META]: "us.meta.llama4-maverick-17b-instruct-v1:0",
22
- [Provider.DEEPSEEK]: "deepseek-chat",
24
+ [Provider.DEEPSEEK]: "deepseek-reasoner",
23
25
  [Provider.GITHUB]: "gpt-4o-mini",
24
- [Provider.GOOGLE]: "gemini-2.0-flash-001",
26
+ [Provider.GOOGLE]: "gemini-3.1-flash-lite-preview",
25
27
  [Provider.MISTRALAI]: "mistral-medium-2505",
26
28
  [Provider.OLLAMA]: "mistral-small3.1",
27
- [Provider.OPENAI]: "gpt-4o-mini-2024-07-18",
28
- [Provider.XAI]: "grok-4-fast-non-reasoning",
29
+ [Provider.OPENAI]: "gpt-5-nano-2025-08-07",
30
+ [Provider.XAI]: "grok-4-1-fast-reasoning",
29
31
  };
30
32
  export class Model {
31
33
  constructor(provider, name) {
@@ -10,5 +10,8 @@ export declare class AccessibilityElement {
10
10
  androidText?: string;
11
11
  androidContentDesc?: string;
12
12
  androidBounds?: string;
13
- constructor(id?: number, name?: string, label?: string, type?: string, value?: string, backendNodeId?: number, androidResourceId?: string, androidClass?: string, androidText?: string, androidContentDesc?: string, androidBounds?: string);
13
+ frame?: object;
14
+ locatorInfo?: Record<string, unknown>;
15
+ frameChain?: number[];
16
+ constructor(id?: number, name?: string, label?: string, type?: string, value?: string, backendNodeId?: number, androidResourceId?: string, androidClass?: string, androidText?: string, androidContentDesc?: string, androidBounds?: string, frame?: object, locatorInfo?: Record<string, unknown>, frameChain?: number[]);
14
17
  }
@@ -1,5 +1,5 @@
1
1
  export class AccessibilityElement {
2
- constructor(id, name, label, type, value, backendNodeId, androidResourceId, androidClass, androidText, androidContentDesc, androidBounds) {
2
+ constructor(id, name, label, type, value, backendNodeId, androidResourceId, androidClass, androidText, androidContentDesc, androidBounds, frame, locatorInfo, frameChain) {
3
3
  this.id = id;
4
4
  this.backendNodeId = backendNodeId;
5
5
  this.name = name;
@@ -11,5 +11,8 @@ export class AccessibilityElement {
11
11
  this.androidText = androidText;
12
12
  this.androidContentDesc = androidContentDesc;
13
13
  this.androidBounds = androidBounds;
14
+ this.frame = frame;
15
+ this.locatorInfo = locatorInfo;
16
+ this.frameChain = frameChain;
14
17
  }
15
18
  }
@@ -4,11 +4,14 @@ export declare class ChromiumAccessibilityTree extends BaseAccessibilityTree {
4
4
  private cdpResponse;
5
5
  private nextRawId;
6
6
  private raw;
7
+ private frameMap;
8
+ private frameChainMap;
7
9
  constructor(cdpResponse: unknown);
8
10
  private static fromXml;
9
11
  toStr(): string;
10
12
  private nodeToXml;
11
13
  private elementToString;
14
+ private unescapeXml;
12
15
  elementById(rawId: number): AccessibilityElement;
13
16
  scopeToArea(rawId: number): ChromiumAccessibilityTree;
14
17
  private findElementByRawId;
@@ -5,11 +5,16 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
5
5
  super();
6
6
  this.nextRawId = 0;
7
7
  this.raw = null;
8
+ this.frameMap = new Map();
9
+ this.frameChainMap = new Map(); // raw_id -> frame chain
8
10
  this.cdpResponse = cdpResponse;
9
11
  }
10
- static fromXml(xmlString) {
12
+ static fromXml(xmlString, frameMap) {
11
13
  const instance = new ChromiumAccessibilityTree({ nodes: [] });
12
14
  instance.raw = xmlString;
15
+ if (frameMap) {
16
+ instance.frameMap = frameMap;
17
+ }
13
18
  return instance;
14
19
  }
15
20
  toStr() {
@@ -17,18 +22,38 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
17
22
  return this.raw;
18
23
  }
19
24
  const nodes = this.cdpResponse.nodes;
25
+ if (!nodes || nodes.length === 0) {
26
+ this.raw = "";
27
+ return this.raw;
28
+ }
20
29
  const nodeLookup = {};
21
30
  for (const node of nodes) {
22
31
  nodeLookup[node.nodeId] = node;
23
32
  }
24
- // Build tree structure and convert to XML
25
- const rootNodes = [];
33
+ // Build mapping: backendDOMNodeId -> list of iframe child root nodes
34
+ // This allows us to inline iframe content inside their parent <Iframe> elements
35
+ const iframeChildren = new Map();
36
+ const trueRoots = [];
26
37
  for (const node of nodes) {
27
38
  if (!node.parentId) {
28
- const xmlNode = this.nodeToXml(node, nodeLookup);
29
- rootNodes.push(xmlNode);
39
+ const parentIframeId = node._parent_iframe_backend_node_id;
40
+ if (parentIframeId !== undefined) {
41
+ if (!iframeChildren.has(parentIframeId)) {
42
+ iframeChildren.set(parentIframeId, []);
43
+ }
44
+ iframeChildren.get(parentIframeId).push(node);
45
+ }
46
+ else {
47
+ trueRoots.push(node);
48
+ }
30
49
  }
31
50
  }
51
+ // Build tree structure and convert to XML (only from true roots)
52
+ const rootNodes = [];
53
+ for (const node of trueRoots) {
54
+ const xmlNode = this.nodeToXml(node, nodeLookup, iframeChildren);
55
+ rootNodes.push(xmlNode);
56
+ }
32
57
  // Combine all root nodes into a single XML string
33
58
  let xmlString = "";
34
59
  for (const root of rootNodes) {
@@ -37,7 +62,7 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
37
62
  this.raw = xmlString;
38
63
  return this.raw;
39
64
  }
40
- nodeToXml(node, nodeLookup) {
65
+ nodeToXml(node, nodeLookup, iframeChildren) {
41
66
  const role = node.role?.value || "unknown";
42
67
  const elem = {
43
68
  tag: role,
@@ -47,6 +72,24 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
47
72
  // Add our own sequential raw_id attribute
48
73
  this.nextRawId++;
49
74
  elem.attributes.raw_id = String(this.nextRawId);
75
+ // Store frame reference if present
76
+ if (node._frame) {
77
+ this.frameMap.set(this.nextRawId, node._frame);
78
+ }
79
+ // Store frame chain if present (for Selenium nested frame switching)
80
+ if (node._frame_chain) {
81
+ this.frameChainMap.set(this.nextRawId, node._frame_chain);
82
+ }
83
+ // Store Playwright node metadata
84
+ if (node._playwright_node) {
85
+ elem.attributes._playwright_node = "true";
86
+ }
87
+ if (node._locator_info) {
88
+ elem.attributes._locator_info = JSON.stringify(node._locator_info);
89
+ }
90
+ if (node._frame_url) {
91
+ elem.attributes._frame_url = node._frame_url;
92
+ }
50
93
  // Add all node attributes as XML attributes
51
94
  if (node.backendDOMNodeId !== undefined) {
52
95
  elem.attributes.backendDOMNodeId = String(node.backendDOMNodeId);
@@ -84,11 +127,19 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
84
127
  if (node.childIds) {
85
128
  for (const childId of node.childIds) {
86
129
  if (childId in nodeLookup) {
87
- const childElem = this.nodeToXml(nodeLookup[childId], nodeLookup);
130
+ const childElem = this.nodeToXml(nodeLookup[childId], nodeLookup, iframeChildren);
88
131
  elem.children.push(childElem);
89
132
  }
90
133
  }
91
134
  }
135
+ // Inline iframe content: if this element is an iframe, add its child trees
136
+ const backendNodeId = node.backendDOMNodeId;
137
+ if (backendNodeId !== undefined && iframeChildren.has(backendNodeId)) {
138
+ for (const childRoot of iframeChildren.get(backendNodeId)) {
139
+ const childElem = this.nodeToXml(childRoot, nodeLookup, iframeChildren);
140
+ elem.children.push(childElem);
141
+ }
142
+ }
92
143
  return elem;
93
144
  }
94
145
  elementToString(elem, indent) {
@@ -124,17 +175,39 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
124
175
  }
125
176
  return result;
126
177
  }
178
+ unescapeXml(value) {
179
+ return value
180
+ .replace(/&apos;/g, "'")
181
+ .replace(/&quot;/g, '"')
182
+ .replace(/&gt;/g, ">")
183
+ .replace(/&lt;/g, "<")
184
+ .replace(/&amp;/g, "&");
185
+ }
127
186
  elementById(rawId) {
128
187
  const rawXml = this.toStr();
129
188
  const element = this.findElementByRawId(rawXml, rawId);
130
189
  if (!element) {
131
190
  throw new Error(`No element with raw_id=${rawId} found`);
132
191
  }
192
+ // Handle Playwright nodes (cross-origin iframes)
193
+ if (element.attributes._playwright_node === "true") {
194
+ // Synthetic frame node
195
+ if (element.attributes._frame_url) {
196
+ return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, undefined, undefined, undefined, undefined, undefined, undefined, this.frameMap.get(rawId), { _synthetic_frame: true, _frame_url: element.attributes._frame_url });
197
+ }
198
+ // Regular Playwright node with locator info
199
+ const locatorInfo = element.attributes
200
+ ._locator_info
201
+ ? JSON.parse(element.attributes._locator_info)
202
+ : {};
203
+ return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, undefined, undefined, undefined, undefined, undefined, undefined, this.frameMap.get(rawId), locatorInfo);
204
+ }
205
+ // Existing CDP node logic
133
206
  const backendNodeIdStr = element.attributes.backendDOMNodeId;
134
207
  if (!backendNodeIdStr) {
135
208
  throw new Error(`Element with raw_id=${rawId} has no backendDOMNodeId attribute`);
136
209
  }
137
- return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, parseInt(backendNodeIdStr));
210
+ return new AccessibilityElement(undefined, undefined, undefined, element.tag, undefined, parseInt(backendNodeIdStr), undefined, undefined, undefined, undefined, undefined, this.frameMap.get(rawId), undefined, this.frameChainMap.get(rawId));
138
211
  }
139
212
  scopeToArea(rawId) {
140
213
  const rawXml = this.toStr();
@@ -143,7 +216,7 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
143
216
  return this;
144
217
  }
145
218
  const scopedXml = this.elementToString(element, 0);
146
- return ChromiumAccessibilityTree.fromXml(scopedXml);
219
+ return ChromiumAccessibilityTree.fromXml(scopedXml, this.frameMap);
147
220
  }
148
221
  findElementByRawId(xmlString, targetRawId) {
149
222
  // Parse the XML string to find the element
@@ -195,6 +268,10 @@ export class ChromiumAccessibilityTree extends BaseAccessibilityTree {
195
268
  while ((attrMatch = attrRegex.exec(attrsString)) !== null) {
196
269
  attributes[attrMatch[1]] = attrMatch[2];
197
270
  }
271
+ // Unescape XML entities in attribute values
272
+ for (const key of Object.keys(attributes)) {
273
+ attributes[key] = this.unescapeXml(attributes[key]);
274
+ }
198
275
  const elem = {
199
276
  tag: tagName,
200
277
  attributes,
@@ -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
  }