askui 0.21.2 → 0.23.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.
@@ -32,6 +32,7 @@ export declare class UiControlClient extends ApiCommands {
32
32
  private workspaceId;
33
33
  private executionRuntime;
34
34
  private stepReporter;
35
+ private aiElementArgs;
35
36
  private constructor();
36
37
  static build(clientArgs?: ClientArgs): Promise<UiControlClient>;
37
38
  /**
@@ -243,12 +244,6 @@ export declare class UiControlClient extends ApiCommands {
243
244
  * @param {string} label - The textfields label.
244
245
  */
245
246
  clickTextfieldNearestTo(label: string): Promise<void>;
246
- /**
247
- * Clicks an icon based on a description.
248
- *
249
- * @param {string} description
250
- */
251
- clickIcon(description: string): Promise<void>;
252
247
  /**
253
248
  * Wait until an AskUICommand does not fail.
254
249
  *
@@ -18,11 +18,12 @@ const logger_1 = require("../lib/logger");
18
18
  const ui_control_client_dependency_builder_1 = require("./ui-control-client-dependency-builder");
19
19
  const ai_element_collection_1 = require("../core/ai-element/ai-element-collection");
20
20
  class UiControlClient extends dsl_1.ApiCommands {
21
- constructor(workspaceId, executionRuntime, stepReporter) {
21
+ constructor(workspaceId, executionRuntime, stepReporter, aiElementArgs) {
22
22
  super();
23
23
  this.workspaceId = workspaceId;
24
24
  this.executionRuntime = executionRuntime;
25
25
  this.stepReporter = stepReporter;
26
+ this.aiElementArgs = aiElementArgs;
26
27
  this.secretText = undefined;
27
28
  }
28
29
  static build() {
@@ -30,7 +31,7 @@ class UiControlClient extends dsl_1.ApiCommands {
30
31
  const builder = ui_control_client_dependency_builder_1.UiControlClientDependencyBuilder;
31
32
  const clientArgsWithDefaults = yield builder.getClientArgsWithDefaults(clientArgs);
32
33
  const { workspaceId, executionRuntime, stepReporter, } = yield builder.build(clientArgsWithDefaults);
33
- return new UiControlClient(workspaceId, executionRuntime, stepReporter);
34
+ return new UiControlClient(workspaceId, executionRuntime, stepReporter, clientArgsWithDefaults.aiElementArgs);
34
35
  });
35
36
  }
36
37
  /**
@@ -130,7 +131,7 @@ class UiControlClient extends dsl_1.ApiCommands {
130
131
  return [];
131
132
  }
132
133
  // eslint-disable-next-line max-len
133
- const workspaceAIElementCollection = yield ai_element_collection_1.AIElementCollection.collectForWorkspaceId(this.workspaceId);
134
+ const workspaceAIElementCollection = yield ai_element_collection_1.AIElementCollection.collectAIElements(this.workspaceId, this.aiElementArgs);
134
135
  return workspaceAIElementCollection.getByNames(names);
135
136
  });
136
137
  }
@@ -409,16 +410,6 @@ class UiControlClient extends dsl_1.ApiCommands {
409
410
  .exec();
410
411
  });
411
412
  }
412
- /**
413
- * Clicks an icon based on a description.
414
- *
415
- * @param {string} description
416
- */
417
- clickIcon(description) {
418
- return __awaiter(this, void 0, void 0, function* () {
419
- yield this.click().icon().matching(description).exec();
420
- });
421
- }
422
413
  /**
423
414
  * Wait until an AskUICommand does not fail.
424
415
  *
@@ -4,6 +4,7 @@ import { ModelCompositionBranch } from './model-composition-branch';
4
4
  import { Reporter } from '../core/reporting';
5
5
  import { Context } from './context';
6
6
  import { RetryStrategy } from './retry-strategies/retry-strategy';
7
+ import { AIElementArgs } from '../core/ai-element/ai-elements-args';
7
8
  /**
8
9
  * Context object to provide additional information about the context of (test) automation.
9
10
  *
@@ -47,6 +48,8 @@ export interface ContextArgs {
47
48
  * @property {(RetryStrategy | undefined)} [retryStrategy] - Default: `new LinearRetryStrategy()`.
48
49
  * Strategy for retrying failed requests to the inference server. This can help manage transient
49
50
  * errors or network issues, improving the reliability of interactions with the server.
51
+ * @property {AIElementArgs} [aiElementArgs] - Options for configuring how AI elements are
52
+ * collected.
50
53
  */
51
54
  export interface ClientArgs {
52
55
  readonly uiControllerUrl?: string;
@@ -59,11 +62,13 @@ export interface ClientArgs {
59
62
  readonly context?: ContextArgs | undefined;
60
63
  readonly inferenceServerApiVersion?: string;
61
64
  readonly retryStrategy?: RetryStrategy;
65
+ readonly aiElementArgs?: AIElementArgs;
62
66
  }
63
67
  export interface ClientArgsWithDefaults extends ClientArgs {
64
68
  readonly uiControllerUrl: string;
65
69
  readonly inferenceServerUrl: string;
66
70
  readonly context: Context;
67
71
  readonly inferenceServerApiVersion: string;
72
+ readonly aiElementArgs: AIElementArgs;
68
73
  readonly retryStrategy?: RetryStrategy;
69
74
  }
@@ -1,10 +1,12 @@
1
1
  import { AIElement } from './ai-element';
2
2
  import { CustomElementJson } from '../model/custom-element-json';
3
+ import { AIElementArgs } from './ai-elements-args';
3
4
  export declare class AIElementCollection {
4
5
  private elements;
5
6
  static AI_ELEMENT_FOLDER: string;
6
7
  constructor(elements: AIElement[]);
7
- static collectForWorkspaceId(workspaceId: string | undefined): Promise<AIElementCollection>;
8
+ static collectAIElements(workspaceId: string | undefined, aiElementArgs: AIElementArgs): Promise<AIElementCollection>;
8
9
  getByName(name: string): CustomElementJson[];
9
10
  getByNames(names: string[]): CustomElementJson[];
11
+ private static CollectAiElementsFromLocation;
10
12
  }
@@ -17,37 +17,37 @@ export class AIElementCollection {
17
17
  constructor(elements) {
18
18
  this.elements = elements;
19
19
  }
20
- static collectForWorkspaceId(workspaceId) {
20
+ static collectAIElements(workspaceId, aiElementArgs) {
21
21
  return __awaiter(this, void 0, void 0, function* () {
22
- logger.debug(`Collecting AIElements for workspace '${workspaceId}' ...`);
23
- if (workspaceId === undefined) {
22
+ if (!workspaceId) {
24
23
  throw new AIElementError("Value of 'workspaceId' must be defined.");
25
24
  }
26
25
  const workspaceAIElementFolder = path.join(AIElementCollection.AI_ELEMENT_FOLDER, workspaceId);
27
- if (!(yield fs.pathExists(workspaceAIElementFolder))) {
28
- throw new AIElementError(`Missing AIElement folder for workspace '${workspaceId}' at '${workspaceAIElementFolder}'.`);
29
- }
30
- const files = yield fs.readdir(workspaceAIElementFolder);
31
- if (files.length === 0) {
32
- throw new AIElementError(`'${workspaceAIElementFolder}' is empty. No AIElement files found for workspace '${workspaceId}'.`);
33
- }
34
- const aiElements = yield Promise.all(files
35
- .filter((file) => path.extname(file) === '.json')
36
- .map((file) => __awaiter(this, void 0, void 0, function* () {
37
- const jsonFile = path.join(workspaceAIElementFolder, file);
38
- const baseName = path.basename(jsonFile, '.json');
39
- const pngFile = path.join(workspaceAIElementFolder, `${baseName}.png`);
40
- if (yield fs.pathExists(pngFile)) {
41
- const metadata = JSON.parse(yield fs.readFile(jsonFile, 'utf-8'));
42
- return AIElement.fromJson(metadata, pngFile);
26
+ const aiElementsLocations = [
27
+ workspaceAIElementFolder,
28
+ ...aiElementArgs.additionalLocations.map((userPath) => path.resolve(userPath)),
29
+ ];
30
+ const aiElements = [];
31
+ yield Promise.all(aiElementsLocations.map((aiElementLocation) => __awaiter(this, void 0, void 0, function* () {
32
+ if (yield fs.pathExists(aiElementLocation)) {
33
+ aiElements.push(...AIElementCollection.CollectAiElementsFromLocation(aiElementLocation));
34
+ }
35
+ else {
36
+ const errorMessage = `AIElements location '${aiElementLocation}' does not exist.`;
37
+ if (aiElementArgs.onLocationNotExist === 'error') {
38
+ throw new AIElementError(errorMessage);
39
+ }
40
+ else if (aiElementArgs.onLocationNotExist === 'warn') {
41
+ logger.warn(errorMessage);
42
+ }
43
43
  }
44
- return null;
45
44
  })));
46
- const validAIElements = aiElements.filter((element) => element !== null);
47
- if (validAIElements.length === 0) {
48
- throw new AIElementError(`No AIElement files found for workspace '${workspaceId}' at '${workspaceAIElementFolder}'.`);
45
+ if (aiElements.length === 0) {
46
+ const formattedLocations = aiElementsLocations.map((aiElementsLocation) => `"${aiElementsLocation}"`).join(', ');
47
+ const errorMessage = `No AIElements found in the following location${aiElementsLocations.length > 1 ? 's' : ''}: [${formattedLocations}].`;
48
+ throw new AIElementError(errorMessage);
49
49
  }
50
- return new AIElementCollection(validAIElements);
50
+ return new AIElementCollection(aiElements);
51
51
  });
52
52
  }
53
53
  getByName(name) {
@@ -67,5 +67,28 @@ export class AIElementCollection {
67
67
  }
68
68
  return names.flatMap((name) => this.getByName(name));
69
69
  }
70
+ static CollectAiElementsFromLocation(aiElementLocation) {
71
+ const files = fs.readdirSync(aiElementLocation);
72
+ if (files.length === 0) {
73
+ return [];
74
+ }
75
+ const aiElements = files
76
+ .filter((file) => path.extname(file) === '.json')
77
+ .map((file) => {
78
+ const jsonFile = path.join(aiElementLocation, file);
79
+ const baseName = path.basename(jsonFile, '.json');
80
+ const pngFile = path.join(aiElementLocation, `${baseName}.png`);
81
+ if (fs.existsSync(pngFile)) {
82
+ const metadata = JSON.parse(fs.readFileSync(jsonFile, 'utf-8'));
83
+ return AIElement.fromJson(metadata, pngFile);
84
+ }
85
+ return null;
86
+ });
87
+ const validAIElements = aiElements.filter((element) => element !== null);
88
+ if (validAIElements.length === 0) {
89
+ logger.debug(`No valid AIElements found in '${aiElementLocation}'.`);
90
+ }
91
+ return validAIElements;
92
+ }
70
93
  }
71
94
  AIElementCollection.AI_ELEMENT_FOLDER = path.join(os.homedir(), '.askui', 'SnippingTool', 'AIElement');
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Options for configuring how AI elements are collected.
3
+ *
4
+ * @interface AIElementArgs
5
+ *
6
+ * @property {string[]} additionalLocations - Additional directories to search for AI elements.
7
+ * These directories will be checked for the presence of AI element files.
8
+ *
9
+ * @property {'Warn' | 'Error' | 'Ignore'} onLocationNotExist - Action to take when
10
+ * one or more specified directories do not exist.
11
+ * - `'warn'` → Logs a warning if a directory is missing.
12
+ * - `'error'` → Throws an error if a directory is missing.
13
+ * - `'igone'` → Skips missing directories without logging anything.
14
+ * This action is triggered when specified directories do not exist,
15
+ * not when AI elements within those directories are missing.
16
+ */
17
+ export interface AIElementArgs {
18
+ additionalLocations: string[];
19
+ onLocationNotExist?: 'warn' | 'error' | 'ignore';
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -99,28 +99,6 @@ export declare class FluentFilters extends FluentBase {
99
99
  * @return {FluentFiltersOrRelations}
100
100
  */
101
101
  button(): FluentFiltersOrRelations;
102
- /**
103
- *
104
- * @param {number} index - element index
105
- *
106
- * @return {FluentFiltersOrRelations}
107
- */
108
- row(index: number): FluentFiltersOrRelations;
109
- /**
110
- *
111
- * @param {number} index - element index
112
- *
113
- * @return {FluentFiltersOrRelations}
114
- */
115
- column(index: number): FluentFiltersOrRelations;
116
- /**
117
- *
118
- * @param {number} row_index - row index
119
- * @param {number} column_index - column index
120
- *
121
- * @return {FluentFiltersOrRelations}
122
- */
123
- cell(row_index: number, column_index: number): FluentFiltersOrRelations;
124
102
  /**
125
103
  * Filters for a UI element 'table'.
126
104
  *
@@ -387,42 +365,6 @@ export declare class FluentFilters extends FluentBase {
387
365
  * @return {FluentFiltersOrRelations}
388
366
  */
389
367
  pta(text: string): FluentFiltersOrRelations;
390
- /**
391
- * Filters elements based on a textual description.
392
- *
393
- * **What Should I Write as Matching Text**
394
- *
395
- * The text description inside the `matching()` should describe the element visually.
396
- * It understands color, some famous company/product names, general descriptions.
397
- *
398
- * **Important: _Matching only returns the best matching element when you use it with `get()`_**
399
- *
400
- * A bit of playing around to find a matching description is sometimes needed:
401
- * For example, `puzzle piece` can fail while `an icon showing a puzzle piece` might work.
402
- * Generally, the more detail the better.
403
- *
404
- * We also recommend to not restrict the type of element by using the general selector `element()` as shown in the examples below.
405
- *
406
- * **Examples:**
407
- * ```typescript
408
- * // Select the black sneaker from a bunch of sneakers
409
- * await aui.click().element().matching('a black sneaker shoe').exec();
410
- *
411
- * // Select an image that has text in it
412
- * await aui.click().element().matching('has Burger King in it').exec();
413
- * await aui.click().element().matching('has adidas in it').exec();
414
- *
415
- * // Target a logo/image by describing it
416
- * await aui.click().element().matching('a mask on purple background and a firefox logo').exec();
417
- * await aui.click().element().matching('logo looking like an apple with one bite bitten off').exec();
418
- * await aui.click().element().matching('logo looking like a seashell').exec();
419
- * ```
420
- *
421
- * @param {string} text - A description of the target element.
422
- *
423
- * @return {FluentFiltersOrRelations}
424
- */
425
- matching(text: string): FluentFiltersOrRelations;
426
368
  }
427
369
  export declare class FluentFiltersOrRelations extends FluentFilters {
428
370
  /**
@@ -933,28 +875,6 @@ export declare class FluentFiltersCondition extends FluentBase {
933
875
  * @return {FluentFiltersOrRelationsCondition}
934
876
  */
935
877
  button(): FluentFiltersOrRelationsCondition;
936
- /**
937
- *
938
- * @param {number} index - element index
939
- *
940
- * @return {FluentFiltersOrRelationsCondition}
941
- */
942
- row(index: number): FluentFiltersOrRelationsCondition;
943
- /**
944
- *
945
- * @param {number} index - element index
946
- *
947
- * @return {FluentFiltersOrRelationsCondition}
948
- */
949
- column(index: number): FluentFiltersOrRelationsCondition;
950
- /**
951
- *
952
- * @param {number} row_index - row index
953
- * @param {number} column_index - column index
954
- *
955
- * @return {FluentFiltersOrRelationsCondition}
956
- */
957
- cell(row_index: number, column_index: number): FluentFiltersOrRelationsCondition;
958
878
  /**
959
879
  * Filters for a UI element 'table'.
960
880
  *
@@ -1221,42 +1141,6 @@ export declare class FluentFiltersCondition extends FluentBase {
1221
1141
  * @return {FluentFiltersOrRelationsCondition}
1222
1142
  */
1223
1143
  pta(text: string): FluentFiltersOrRelationsCondition;
1224
- /**
1225
- * Filters elements based on a textual description.
1226
- *
1227
- * **What Should I Write as Matching Text**
1228
- *
1229
- * The text description inside the `matching()` should describe the element visually.
1230
- * It understands color, some famous company/product names, general descriptions.
1231
- *
1232
- * **Important: _Matching only returns the best matching element when you use it with `get()`_**
1233
- *
1234
- * A bit of playing around to find a matching description is sometimes needed:
1235
- * For example, `puzzle piece` can fail while `an icon showing a puzzle piece` might work.
1236
- * Generally, the more detail the better.
1237
- *
1238
- * We also recommend to not restrict the type of element by using the general selector `element()` as shown in the examples below.
1239
- *
1240
- * **Examples:**
1241
- * ```typescript
1242
- * // Select the black sneaker from a bunch of sneakers
1243
- * await aui.click().element().matching('a black sneaker shoe').exec();
1244
- *
1245
- * // Select an image that has text in it
1246
- * await aui.click().element().matching('has Burger King in it').exec();
1247
- * await aui.click().element().matching('has adidas in it').exec();
1248
- *
1249
- * // Target a logo/image by describing it
1250
- * await aui.click().element().matching('a mask on purple background and a firefox logo').exec();
1251
- * await aui.click().element().matching('logo looking like an apple with one bite bitten off').exec();
1252
- * await aui.click().element().matching('logo looking like a seashell').exec();
1253
- * ```
1254
- *
1255
- * @param {string} text - A description of the target element.
1256
- *
1257
- * @return {FluentFiltersOrRelationsCondition}
1258
- */
1259
- matching(text: string): FluentFiltersOrRelationsCondition;
1260
1144
  }
1261
1145
  export declare class FluentFiltersOrRelationsCondition extends FluentFiltersCondition {
1262
1146
  /**
@@ -2271,28 +2155,6 @@ export declare class FluentFiltersGetter extends FluentBase {
2271
2155
  * @return {FluentFiltersOrRelationsGetter}
2272
2156
  */
2273
2157
  button(): FluentFiltersOrRelationsGetter;
2274
- /**
2275
- *
2276
- * @param {number} index - element index
2277
- *
2278
- * @return {FluentFiltersOrRelationsGetter}
2279
- */
2280
- row(index: number): FluentFiltersOrRelationsGetter;
2281
- /**
2282
- *
2283
- * @param {number} index - element index
2284
- *
2285
- * @return {FluentFiltersOrRelationsGetter}
2286
- */
2287
- column(index: number): FluentFiltersOrRelationsGetter;
2288
- /**
2289
- *
2290
- * @param {number} row_index - row index
2291
- * @param {number} column_index - column index
2292
- *
2293
- * @return {FluentFiltersOrRelationsGetter}
2294
- */
2295
- cell(row_index: number, column_index: number): FluentFiltersOrRelationsGetter;
2296
2158
  /**
2297
2159
  * Filters for a UI element 'table'.
2298
2160
  *
@@ -2559,42 +2421,6 @@ export declare class FluentFiltersGetter extends FluentBase {
2559
2421
  * @return {FluentFiltersOrRelationsGetter}
2560
2422
  */
2561
2423
  pta(text: string): FluentFiltersOrRelationsGetter;
2562
- /**
2563
- * Filters elements based on a textual description.
2564
- *
2565
- * **What Should I Write as Matching Text**
2566
- *
2567
- * The text description inside the `matching()` should describe the element visually.
2568
- * It understands color, some famous company/product names, general descriptions.
2569
- *
2570
- * **Important: _Matching only returns the best matching element when you use it with `get()`_**
2571
- *
2572
- * A bit of playing around to find a matching description is sometimes needed:
2573
- * For example, `puzzle piece` can fail while `an icon showing a puzzle piece` might work.
2574
- * Generally, the more detail the better.
2575
- *
2576
- * We also recommend to not restrict the type of element by using the general selector `element()` as shown in the examples below.
2577
- *
2578
- * **Examples:**
2579
- * ```typescript
2580
- * // Select the black sneaker from a bunch of sneakers
2581
- * await aui.click().element().matching('a black sneaker shoe').exec();
2582
- *
2583
- * // Select an image that has text in it
2584
- * await aui.click().element().matching('has Burger King in it').exec();
2585
- * await aui.click().element().matching('has adidas in it').exec();
2586
- *
2587
- * // Target a logo/image by describing it
2588
- * await aui.click().element().matching('a mask on purple background and a firefox logo').exec();
2589
- * await aui.click().element().matching('logo looking like an apple with one bite bitten off').exec();
2590
- * await aui.click().element().matching('logo looking like a seashell').exec();
2591
- * ```
2592
- *
2593
- * @param {string} text - A description of the target element.
2594
- *
2595
- * @return {FluentFiltersOrRelationsGetter}
2596
- */
2597
- matching(text: string): FluentFiltersOrRelationsGetter;
2598
2424
  }
2599
2425
  export declare class FluentFiltersOrRelationsGetter extends FluentFiltersGetter {
2600
2426
  /**