askui 0.18.0 → 0.19.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 (61) hide show
  1. package/dist/cjs/core/ai-element/ai-element-collection.d.ts +10 -0
  2. package/dist/cjs/core/ai-element/ai-element-collection.js +78 -0
  3. package/dist/cjs/core/ai-element/ai-element-error.d.ts +2 -0
  4. package/dist/cjs/core/ai-element/ai-element-error.js +6 -0
  5. package/dist/cjs/core/ai-element/ai-element.d.ts +27 -0
  6. package/dist/cjs/core/ai-element/ai-element.js +31 -0
  7. package/dist/cjs/core/model/custom-element-json.d.ts +32 -15
  8. package/dist/cjs/core/model/custom-element.d.ts +3 -2
  9. package/dist/cjs/core/model/custom-element.js +4 -2
  10. package/dist/cjs/core/reporting/step-reporter.js +15 -6
  11. package/dist/cjs/execution/dsl.d.ts +488 -113
  12. package/dist/cjs/execution/dsl.js +519 -113
  13. package/dist/cjs/execution/index.d.ts +1 -1
  14. package/dist/cjs/execution/index.js +11 -3
  15. package/dist/cjs/execution/inference-client.js +9 -4
  16. package/dist/cjs/execution/ui-control-client-dependency-builder.d.ts +1 -0
  17. package/dist/cjs/execution/ui-control-client-dependency-builder.js +6 -3
  18. package/dist/cjs/execution/ui-control-client.d.ts +101 -16
  19. package/dist/cjs/execution/ui-control-client.js +142 -52
  20. package/dist/cjs/execution/ui-controller-client-interface.d.ts +2 -0
  21. package/dist/cjs/execution/ui-controller-client.d.ts +1 -0
  22. package/dist/cjs/execution/ui-controller-client.js +11 -1
  23. package/dist/cjs/execution/ui-controller-not-connected-error.d.ts +4 -0
  24. package/dist/cjs/execution/ui-controller-not-connected-error.js +12 -0
  25. package/dist/cjs/main.d.ts +1 -1
  26. package/dist/cjs/main.js +12 -3
  27. package/dist/cjs/utils/analytics/analytics.d.ts +3 -1
  28. package/dist/cjs/utils/analytics/analytics.js +5 -0
  29. package/dist/cjs/utils/http/custom-errors/index.js +1 -1
  30. package/dist/cjs/utils/http/http-client-got.js +46 -20
  31. package/dist/esm/core/ai-element/ai-element-collection.d.ts +10 -0
  32. package/dist/esm/core/ai-element/ai-element-collection.js +71 -0
  33. package/dist/esm/core/ai-element/ai-element-error.d.ts +2 -0
  34. package/dist/esm/core/ai-element/ai-element-error.js +2 -0
  35. package/dist/esm/core/ai-element/ai-element.d.ts +27 -0
  36. package/dist/esm/core/ai-element/ai-element.js +28 -0
  37. package/dist/esm/core/model/custom-element-json.d.ts +32 -15
  38. package/dist/esm/core/model/custom-element.d.ts +3 -2
  39. package/dist/esm/core/model/custom-element.js +4 -2
  40. package/dist/esm/core/reporting/step-reporter.js +15 -6
  41. package/dist/esm/execution/dsl.d.ts +488 -113
  42. package/dist/esm/execution/dsl.js +519 -113
  43. package/dist/esm/execution/index.d.ts +1 -1
  44. package/dist/esm/execution/index.js +1 -1
  45. package/dist/esm/execution/inference-client.js +9 -4
  46. package/dist/esm/execution/ui-control-client-dependency-builder.d.ts +1 -0
  47. package/dist/esm/execution/ui-control-client-dependency-builder.js +6 -3
  48. package/dist/esm/execution/ui-control-client.d.ts +101 -16
  49. package/dist/esm/execution/ui-control-client.js +142 -49
  50. package/dist/esm/execution/ui-controller-client-interface.d.ts +2 -0
  51. package/dist/esm/execution/ui-controller-client.d.ts +1 -0
  52. package/dist/esm/execution/ui-controller-client.js +11 -1
  53. package/dist/esm/execution/ui-controller-not-connected-error.d.ts +4 -0
  54. package/dist/esm/execution/ui-controller-not-connected-error.js +8 -0
  55. package/dist/esm/main.d.ts +1 -1
  56. package/dist/esm/main.js +1 -1
  57. package/dist/esm/utils/analytics/analytics.d.ts +3 -1
  58. package/dist/esm/utils/analytics/analytics.js +5 -0
  59. package/dist/esm/utils/http/custom-errors/index.js +1 -1
  60. package/dist/esm/utils/http/http-client-got.js +46 -20
  61. package/package.json +1 -1
@@ -0,0 +1,10 @@
1
+ import { AIElement } from './ai-element';
2
+ import { CustomElementJson } from '../model/custom-element-json';
3
+ export declare class AIElementCollection {
4
+ private elements;
5
+ static AI_ELEMENT_FOLDER: string;
6
+ constructor(elements: AIElement[]);
7
+ static collectForWorkspaceId(workspaceId: string | undefined): Promise<AIElementCollection>;
8
+ getByName(name: string): CustomElementJson[];
9
+ getByNames(names: string[]): CustomElementJson[];
10
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AIElementCollection = void 0;
16
+ const os_1 = __importDefault(require("os"));
17
+ const path_1 = __importDefault(require("path"));
18
+ const fs_extra_1 = __importDefault(require("fs-extra"));
19
+ const ai_element_1 = require("./ai-element");
20
+ const lib_1 = require("../../lib");
21
+ const ai_element_error_1 = require("./ai-element-error");
22
+ class AIElementCollection {
23
+ constructor(elements) {
24
+ this.elements = elements;
25
+ }
26
+ static collectForWorkspaceId(workspaceId) {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ lib_1.logger.debug(`Collecting AIElements for workspace '${workspaceId}' ...`);
29
+ if (workspaceId === undefined) {
30
+ throw new ai_element_error_1.AIElementError("Value of 'workspaceId' must be defined.");
31
+ }
32
+ const workspaceAIElementFolder = path_1.default.join(AIElementCollection.AI_ELEMENT_FOLDER, workspaceId);
33
+ if (!(yield fs_extra_1.default.pathExists(workspaceAIElementFolder))) {
34
+ throw new ai_element_error_1.AIElementError(`Missing AIElement folder for workspace '${workspaceId}' at '${workspaceAIElementFolder}'.`);
35
+ }
36
+ const files = yield fs_extra_1.default.readdir(workspaceAIElementFolder);
37
+ if (files.length === 0) {
38
+ throw new ai_element_error_1.AIElementError(`'${workspaceAIElementFolder}' is empty. No AIElement files found for workspace '${workspaceId}'.`);
39
+ }
40
+ const aiElements = yield Promise.all(files
41
+ .filter((file) => path_1.default.extname(file) === '.json')
42
+ .map((file) => __awaiter(this, void 0, void 0, function* () {
43
+ const jsonFile = path_1.default.join(workspaceAIElementFolder, file);
44
+ const baseName = path_1.default.basename(jsonFile, '.json');
45
+ const pngFile = path_1.default.join(workspaceAIElementFolder, `${baseName}.png`);
46
+ if (yield fs_extra_1.default.pathExists(pngFile)) {
47
+ const metadata = JSON.parse(yield fs_extra_1.default.readFile(jsonFile, 'utf-8'));
48
+ return ai_element_1.AIElement.fromJson(metadata, pngFile);
49
+ }
50
+ return null;
51
+ })));
52
+ const validAIElements = aiElements.filter((element) => element !== null);
53
+ if (validAIElements.length === 0) {
54
+ throw new ai_element_error_1.AIElementError(`No AIElement files found for workspace '${workspaceId}' at '${workspaceAIElementFolder}'.`);
55
+ }
56
+ return new AIElementCollection(validAIElements);
57
+ });
58
+ }
59
+ getByName(name) {
60
+ if (name === '') {
61
+ throw new ai_element_error_1.AIElementError("Parameter 'name' must be non-empty. This might be due to corrupted metadata.");
62
+ }
63
+ lib_1.logger.debug(`Getting all CustomElementJson with the name '${name}' ...`);
64
+ const elements = this.elements.filter((element) => element.hasName(name));
65
+ if (elements.length === 0) {
66
+ throw new ai_element_error_1.AIElementError(`No AIElement with the name '${name}' was found.`);
67
+ }
68
+ return elements.map((element) => element.toCustomElement());
69
+ }
70
+ getByNames(names) {
71
+ if (names.length === 0) {
72
+ return [];
73
+ }
74
+ return names.flatMap((name) => this.getByName(name));
75
+ }
76
+ }
77
+ exports.AIElementCollection = AIElementCollection;
78
+ AIElementCollection.AI_ELEMENT_FOLDER = path_1.default.join(os_1.default.homedir(), '.askui', 'SnippingTool', 'AIElement');
@@ -0,0 +1,2 @@
1
+ export declare class AIElementError extends Error {
2
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AIElementError = void 0;
4
+ class AIElementError extends Error {
5
+ }
6
+ exports.AIElementError = AIElementError;
@@ -0,0 +1,27 @@
1
+ import { CustomElementJson } from '../model/custom-element-json';
2
+ interface AIElementJson {
3
+ version: number;
4
+ name: string;
5
+ image?: {
6
+ mask?: {
7
+ x: number;
8
+ y: number;
9
+ }[];
10
+ };
11
+ }
12
+ declare class AIElement {
13
+ name: string;
14
+ imagePath: string;
15
+ mask?: {
16
+ x: number;
17
+ y: number;
18
+ }[] | undefined;
19
+ constructor(name: string, imagePath: string, mask?: {
20
+ x: number;
21
+ y: number;
22
+ }[] | undefined);
23
+ static fromJson(json: AIElementJson, imagePath: string): AIElement;
24
+ toCustomElement(): CustomElementJson;
25
+ hasName(name: string): boolean;
26
+ }
27
+ export { AIElement, AIElementJson };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AIElement = void 0;
4
+ const lib_1 = require("../../lib");
5
+ const ai_element_error_1 = require("./ai-element-error");
6
+ class AIElement {
7
+ constructor(name, imagePath, mask) {
8
+ this.name = name;
9
+ this.imagePath = imagePath;
10
+ this.mask = mask;
11
+ }
12
+ static fromJson(json, imagePath) {
13
+ var _a;
14
+ if (json.version === 1) {
15
+ return new AIElement(json.name, imagePath, (_a = json.image) === null || _a === void 0 ? void 0 : _a.mask);
16
+ }
17
+ throw new ai_element_error_1.AIElementError(`Unsupported AIElement version '${json.version}'.`);
18
+ }
19
+ toCustomElement() {
20
+ lib_1.logger.debug('Converting AIElement to CustomElementJson.');
21
+ return {
22
+ customImage: this.imagePath,
23
+ mask: this.mask,
24
+ name: this.name,
25
+ };
26
+ }
27
+ hasName(name) {
28
+ return this.name === name;
29
+ }
30
+ }
31
+ exports.AIElement = AIElement;
@@ -1,17 +1,16 @@
1
1
  /**
2
2
  * Defines a 'custom element'. This is a UI element which is defined by
3
3
  * providing an image and other parameters such as degree of rotation.
4
- * It allows filtering for a UI element that is not recognized
5
- * by our machine learning models by default.
6
- * It can also be used for pixel assertions of elements using classical
7
- * [template matching](https://en.wikipedia.org/wiki/Template_matching).
4
+ * It allows filtering for a UI element based on an image instead of using
5
+ * text or element descriptions like `button().withText('Submit')` in
6
+ * `await aui.click().button().withText('Submit').exec()`.
8
7
  *
9
- * **Important:** The `CustomElementJson` needs to capture as accurately as possible
10
- * what the custom element looks like during test execution as otherwise
11
- * our machine learning models cannot find it, even with the additional data
12
- * provided. This is especially true for the resolution used while cropping
13
- * the `CustomElementJson.customImage` which should match the resolution during
14
- * test execution.
8
+ * **Important:** The `CustomElementJson` needs to capture as accurately as
9
+ * possible what the custom element looks like during test execution as
10
+ * otherwise our machine learning models cannot find it, even with the
11
+ * additional data provided. This is especially true for the resolution used
12
+ * while cropping the `CustomElementJson.customImage` which should be as
13
+ * similar as possible to the resolution during test execution.
15
14
  *
16
15
  * Rotated custom elements can be filtered for using
17
16
  * `CustomElementJson.rotationDegreePerStep`.
@@ -46,8 +45,22 @@ export interface CustomElementJson {
46
45
  * probably not what you want) and `1.0` (= elements need to look exactly
47
46
  * like defined by `CustomElementJson` which is unlikely to be achieved
48
47
  * as even minor differences count). Defaults to `0.9`.
48
+ *
49
+ * **Important**: The `threshold` impacts the prediction quality.
49
50
  */
50
51
  threshold?: number | undefined;
52
+ /**
53
+ * A threshold for when to stop searching for UI elements similar to the
54
+ * custom element. As soon as UI elements have been found that are at least
55
+ * as similar as the `stopThreshold`, the search is going to stop. After that
56
+ * elements are filtered using the `threshold`. Because of that the
57
+ * `stopThreshold` should be greater than or equal to `threshold`. It is
58
+ * primarily to be used as a speed improvement (by lowering the value).
59
+ * Takes values between `0.0` and `1.0`. Defaults to `0.9`.
60
+ *
61
+ * **Important**: The `stopThreshold` impacts the prediction speed.
62
+ */
63
+ stopThreshold?: number | undefined;
51
64
  /**
52
65
  * A step size in rotation degree. Rotates the custom image by
53
66
  * `rotationDegreePerStep` until 360° is exceeded. Range is between
@@ -60,13 +73,17 @@ export interface CustomElementJson {
60
73
  /**
61
74
  * A color compare style. Allows matching a custom element by color, e.g.,
62
75
  * instead of filtering for all icons (blue and green ones),
63
- * only the green ones captured by `customImage` are filtered for using 'RGB'.
64
- * Defaults to 'grayscale'.
76
+ * only the green ones captured by `customImage` are filtered for using
77
+ * 'RGB'. Defaults to 'grayscale'.
65
78
  *
66
- * **Important**: This increases the prediction time quite a bit. So only use
67
- * it when absolutely necessary.
79
+ * **Important**: The `imageCompareFormat` impacts the prediction time as
80
+ * well as quality. Although this highly depends on the use case, the other
81
+ * parameters and the actual scene in which to find the UI element, as a
82
+ * rule of thumb, 'edges' is likely to be a bit faster than 'grayscale' and
83
+ * 'grayscale' is likely to be a bit faster than 'RGB'. For quality it is
84
+ * most often the other way around.
68
85
  */
69
- imageCompareFormat?: 'RGB' | 'grayscale' | undefined;
86
+ imageCompareFormat?: 'RGB' | 'grayscale' | 'edges' | undefined;
70
87
  /** A polygon to match only a certain area of the custom element. */
71
88
  mask?: ({
72
89
  x: number;
@@ -3,14 +3,15 @@ export declare class CustomElement implements CustomElementJson {
3
3
  customImage: string;
4
4
  name?: string | undefined;
5
5
  threshold?: number | undefined;
6
+ stopThreshold?: number | undefined;
6
7
  rotationDegreePerStep?: number | undefined;
7
- imageCompareFormat?: "RGB" | "grayscale" | undefined;
8
+ imageCompareFormat?: "RGB" | "grayscale" | "edges" | undefined;
8
9
  mask?: {
9
10
  x: number;
10
11
  y: number;
11
12
  }[] | undefined;
12
13
  private static schema;
13
- constructor(customImage: string, name?: string | undefined, threshold?: number | undefined, rotationDegreePerStep?: number | undefined, imageCompareFormat?: "RGB" | "grayscale" | undefined, mask?: {
14
+ constructor(customImage: string, name?: string | undefined, threshold?: number | undefined, stopThreshold?: number | undefined, rotationDegreePerStep?: number | undefined, imageCompareFormat?: "RGB" | "grayscale" | "edges" | undefined, mask?: {
14
15
  x: number;
15
16
  y: number;
16
17
  }[] | undefined);
@@ -13,10 +13,11 @@ exports.CustomElement = void 0;
13
13
  const yup_1 = require("yup");
14
14
  const base_64_image_1 = require("../../utils/base_64_image/base-64-image");
15
15
  class CustomElement {
16
- constructor(customImage, name, threshold, rotationDegreePerStep, imageCompareFormat, mask) {
16
+ constructor(customImage, name, threshold, stopThreshold, rotationDegreePerStep, imageCompareFormat, mask) {
17
17
  this.customImage = customImage;
18
18
  this.name = name;
19
19
  this.threshold = threshold;
20
+ this.stopThreshold = stopThreshold;
20
21
  this.rotationDegreePerStep = rotationDegreePerStep;
21
22
  this.imageCompareFormat = imageCompareFormat;
22
23
  this.mask = mask;
@@ -35,7 +36,7 @@ class CustomElement {
35
36
  });
36
37
  }
37
38
  static fromJson(ceJson) {
38
- return new CustomElement(ceJson.customImage, ceJson.name, ceJson.threshold, ceJson.rotationDegreePerStep, ceJson.imageCompareFormat, ceJson.mask);
39
+ return new CustomElement(ceJson.customImage, ceJson.name, ceJson.threshold, ceJson.stopThreshold, ceJson.rotationDegreePerStep, ceJson.imageCompareFormat, ceJson.mask);
39
40
  }
40
41
  validate() {
41
42
  try {
@@ -50,5 +51,6 @@ exports.CustomElement = CustomElement;
50
51
  CustomElement.schema = (0, yup_1.object)({
51
52
  mask: (0, yup_1.array)().optional().min(3, 'mask must contain at least 3 points'),
52
53
  rotationDegreePerStep: (0, yup_1.number)().optional().min(0).lessThan(360),
54
+ stopThreshold: (0, yup_1.number)().optional().min(0).max(1),
53
55
  threshold: (0, yup_1.number)().optional().min(0).max(1),
54
56
  });
@@ -58,37 +58,46 @@ class StepReporter {
58
58
  onStepBegin(snapshot) {
59
59
  return __awaiter(this, void 0, void 0, function* () {
60
60
  if (this._currentStep === undefined) {
61
- throw new Error('Cannot begin step if step is undefined.');
61
+ logger_1.logger.error('Cannot begin step if step is undefined.');
62
+ return Promise.resolve();
62
63
  }
63
64
  if (this._currentStep.status !== 'pending') {
64
- throw new Error('Cannot begin step that is not pending.');
65
+ logger_1.logger.error('Cannot begin step that is not pending.');
66
+ return Promise.resolve();
65
67
  }
66
68
  this._currentStep = this._currentStep.onBegin(snapshot);
67
69
  this.enqueueReporterCalls('onStepBegin', this._currentStep);
70
+ return Promise.resolve();
68
71
  });
69
72
  }
70
73
  onStepRetry(snapshot, error) {
71
74
  return __awaiter(this, void 0, void 0, function* () {
72
75
  if (this._currentStep === undefined) {
73
- throw new Error('Cannot retry step if step is undefined.');
76
+ logger_1.logger.error('Cannot retry step if step is undefined.');
77
+ return Promise.resolve();
74
78
  }
75
79
  if (this._currentStep.status !== 'running') {
76
- throw new Error('Cannot retry step that has not been running.');
80
+ logger_1.logger.error('Cannot retry step that has not been running.');
81
+ return Promise.resolve();
77
82
  }
78
83
  this._currentStep = this._currentStep.onRetry(snapshot, error);
79
84
  this.enqueueReporterCalls('onStepRetry', this._currentStep);
85
+ return Promise.resolve();
80
86
  });
81
87
  }
82
88
  onStepEnd(snapshot, error) {
83
89
  return __awaiter(this, void 0, void 0, function* () {
84
90
  if (this._currentStep === undefined) {
85
- throw new Error('Cannot end step if step is undefined.');
91
+ logger_1.logger.error('Cannot end step if step is undefined.');
92
+ return Promise.resolve();
86
93
  }
87
94
  if (this._currentStep.status !== 'running') {
88
- throw new Error('Cannot end step that has not been running.');
95
+ logger_1.logger.error('Cannot end step that has not been running.');
96
+ return Promise.resolve();
89
97
  }
90
98
  this._currentStep = this._currentStep.onEnd(snapshot, error);
91
99
  this.enqueueReporterCalls('onStepEnd', this._currentStep);
100
+ return Promise.resolve();
92
101
  });
93
102
  }
94
103
  }