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.
- package/dist/cjs/core/ai-element/ai-element-collection.d.ts +10 -0
- package/dist/cjs/core/ai-element/ai-element-collection.js +78 -0
- package/dist/cjs/core/ai-element/ai-element-error.d.ts +2 -0
- package/dist/cjs/core/ai-element/ai-element-error.js +6 -0
- package/dist/cjs/core/ai-element/ai-element.d.ts +27 -0
- package/dist/cjs/core/ai-element/ai-element.js +31 -0
- package/dist/cjs/core/model/custom-element-json.d.ts +32 -15
- package/dist/cjs/core/model/custom-element.d.ts +3 -2
- package/dist/cjs/core/model/custom-element.js +4 -2
- package/dist/cjs/core/reporting/step-reporter.js +15 -6
- package/dist/cjs/execution/dsl.d.ts +488 -113
- package/dist/cjs/execution/dsl.js +519 -113
- package/dist/cjs/execution/index.d.ts +1 -1
- package/dist/cjs/execution/index.js +11 -3
- package/dist/cjs/execution/inference-client.js +9 -4
- package/dist/cjs/execution/ui-control-client-dependency-builder.d.ts +1 -0
- package/dist/cjs/execution/ui-control-client-dependency-builder.js +6 -3
- package/dist/cjs/execution/ui-control-client.d.ts +101 -16
- package/dist/cjs/execution/ui-control-client.js +142 -52
- package/dist/cjs/execution/ui-controller-client-interface.d.ts +2 -0
- package/dist/cjs/execution/ui-controller-client.d.ts +1 -0
- package/dist/cjs/execution/ui-controller-client.js +11 -1
- package/dist/cjs/execution/ui-controller-not-connected-error.d.ts +4 -0
- package/dist/cjs/execution/ui-controller-not-connected-error.js +12 -0
- package/dist/cjs/main.d.ts +1 -1
- package/dist/cjs/main.js +12 -3
- package/dist/cjs/utils/analytics/analytics.d.ts +3 -1
- package/dist/cjs/utils/analytics/analytics.js +5 -0
- package/dist/cjs/utils/http/custom-errors/index.js +1 -1
- package/dist/cjs/utils/http/http-client-got.js +46 -20
- package/dist/esm/core/ai-element/ai-element-collection.d.ts +10 -0
- package/dist/esm/core/ai-element/ai-element-collection.js +71 -0
- package/dist/esm/core/ai-element/ai-element-error.d.ts +2 -0
- package/dist/esm/core/ai-element/ai-element-error.js +2 -0
- package/dist/esm/core/ai-element/ai-element.d.ts +27 -0
- package/dist/esm/core/ai-element/ai-element.js +28 -0
- package/dist/esm/core/model/custom-element-json.d.ts +32 -15
- package/dist/esm/core/model/custom-element.d.ts +3 -2
- package/dist/esm/core/model/custom-element.js +4 -2
- package/dist/esm/core/reporting/step-reporter.js +15 -6
- package/dist/esm/execution/dsl.d.ts +488 -113
- package/dist/esm/execution/dsl.js +519 -113
- package/dist/esm/execution/index.d.ts +1 -1
- package/dist/esm/execution/index.js +1 -1
- package/dist/esm/execution/inference-client.js +9 -4
- package/dist/esm/execution/ui-control-client-dependency-builder.d.ts +1 -0
- package/dist/esm/execution/ui-control-client-dependency-builder.js +6 -3
- package/dist/esm/execution/ui-control-client.d.ts +101 -16
- package/dist/esm/execution/ui-control-client.js +142 -49
- package/dist/esm/execution/ui-controller-client-interface.d.ts +2 -0
- package/dist/esm/execution/ui-controller-client.d.ts +1 -0
- package/dist/esm/execution/ui-controller-client.js +11 -1
- package/dist/esm/execution/ui-controller-not-connected-error.d.ts +4 -0
- package/dist/esm/execution/ui-controller-not-connected-error.js +8 -0
- package/dist/esm/main.d.ts +1 -1
- package/dist/esm/main.js +1 -1
- package/dist/esm/utils/analytics/analytics.d.ts +3 -1
- package/dist/esm/utils/analytics/analytics.js +5 -0
- package/dist/esm/utils/http/custom-errors/index.js +1 -1
- package/dist/esm/utils/http/http-client-got.js +46 -20
- package/package.json +1 -1
package/dist/cjs/main.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { UiController } from './lib';
|
|
2
|
-
export
|
|
2
|
+
export * from './execution';
|
|
3
3
|
export { Instruction, Reporter, ReporterConfig, Snapshot, SnapshotDetailLevel, Step, StepStatus, StepStatusEnd, } from './core/reporting';
|
|
4
4
|
export { Annotation } from './core/annotation/annotation';
|
|
5
5
|
export { DetectedElement } from './core/model/annotation-result/detected-element';
|
package/dist/cjs/main.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
5
|
+
}) : (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
o[k2] = m[k];
|
|
8
|
+
}));
|
|
9
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
10
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
11
|
+
};
|
|
2
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LogLevels = exports.DetectedElement = exports.Annotation = exports.
|
|
13
|
+
exports.LogLevels = exports.DetectedElement = exports.Annotation = exports.UiController = void 0;
|
|
4
14
|
var lib_1 = require("./lib");
|
|
5
15
|
Object.defineProperty(exports, "UiController", { enumerable: true, get: function () { return lib_1.UiController; } });
|
|
6
|
-
|
|
7
|
-
Object.defineProperty(exports, "UiControlClient", { enumerable: true, get: function () { return execution_1.UiControlClient; } });
|
|
16
|
+
__exportStar(require("./execution"), exports);
|
|
8
17
|
var annotation_1 = require("./core/annotation/annotation");
|
|
9
18
|
Object.defineProperty(exports, "Annotation", { enumerable: true, get: function () { return annotation_1.Annotation; } });
|
|
10
19
|
var detected_element_1 = require("./core/model/annotation-result/detected-element");
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { AnalyticsInterface } from './analytics-interface';
|
|
2
|
-
import { Context } from '
|
|
2
|
+
import { Context } from '../../execution/context';
|
|
3
3
|
export declare class Analytics implements AnalyticsInterface {
|
|
4
|
+
private static clientSessionId;
|
|
5
|
+
private clientId;
|
|
4
6
|
private userIdentifier;
|
|
5
7
|
getAnalyticsHeaders(context: Context): Promise<Record<string, string>>;
|
|
6
8
|
getAnalyticsCookies(): Promise<Record<string, string>>;
|
|
@@ -14,16 +14,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.Analytics = void 0;
|
|
16
16
|
const os_1 = __importDefault(require("os"));
|
|
17
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
17
18
|
const user_identifier_1 = require("./user-identifier");
|
|
18
19
|
const installation_timestamp_1 = require("./installation-timestamp");
|
|
19
20
|
class Analytics {
|
|
20
21
|
constructor() {
|
|
22
|
+
this.clientId = crypto_1.default.randomUUID();
|
|
21
23
|
this.userIdentifier = new user_identifier_1.UserIdentifier();
|
|
22
24
|
}
|
|
23
25
|
getAnalyticsHeaders(context) {
|
|
24
26
|
return __awaiter(this, void 0, void 0, function* () {
|
|
25
27
|
const userID = yield this.userIdentifier.userId();
|
|
26
28
|
const headers = {
|
|
29
|
+
'askui-client-id': this.clientId,
|
|
30
|
+
'askui-client-session-id': Analytics.clientSessionId,
|
|
27
31
|
'askui-is-ci': String(context.isCi),
|
|
28
32
|
'askui-user-agent': `os:${os_1.default.platform()};arch:${os_1.default.arch()}`,
|
|
29
33
|
'askui-user-id': userID,
|
|
@@ -45,3 +49,4 @@ class Analytics {
|
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
exports.Analytics = Analytics;
|
|
52
|
+
Analytics.clientSessionId = crypto_1.default.randomUUID();
|
|
@@ -8,7 +8,7 @@ const unkown_http_client_error_1 = require("./unkown-http-client-error");
|
|
|
8
8
|
var general_http_client_error_1 = require("./general-http-client-error");
|
|
9
9
|
Object.defineProperty(exports, "GeneralHttpClientError", { enumerable: true, get: function () { return general_http_client_error_1.GeneralHttpClientError; } });
|
|
10
10
|
function httpClientErrorHandler(responseCode, errorMessage) {
|
|
11
|
-
const diplayedMessage = `HTTP Status Code: ${responseCode}. Message
|
|
11
|
+
const diplayedMessage = `HTTP Status Code: ${responseCode}. Message:\n${errorMessage}`;
|
|
12
12
|
if (responseCode >= 400 && responseCode < 500) {
|
|
13
13
|
if (responseCode === 401 || responseCode === 403) {
|
|
14
14
|
return new authentication_http_client_error_1.AuthenticationHttpClientError(diplayedMessage);
|
|
@@ -18,6 +18,22 @@ const tough_cookie_1 = require("tough-cookie");
|
|
|
18
18
|
const lib_1 = require("../../lib");
|
|
19
19
|
const credentials_1 = require("./credentials");
|
|
20
20
|
const custom_errors_1 = require("./custom-errors");
|
|
21
|
+
function buildRetryLog(requestUrl, errorCode, statusCode, errorMessage, delayInMs, delayReason, attemptCount) {
|
|
22
|
+
const failureReasons = [];
|
|
23
|
+
if (statusCode !== undefined && statusCode >= 400) {
|
|
24
|
+
failureReasons.push(`status code ${statusCode}`);
|
|
25
|
+
}
|
|
26
|
+
if (errorCode !== undefined) {
|
|
27
|
+
failureReasons.push(`error code ${errorCode}`);
|
|
28
|
+
}
|
|
29
|
+
if (failureReasons.length === 0) {
|
|
30
|
+
failureReasons.push('unknown error');
|
|
31
|
+
}
|
|
32
|
+
const requestText = requestUrl ? `Request to ${requestUrl}` : 'Request';
|
|
33
|
+
return (`${requestText} failed with ${failureReasons.join(', ')}.`
|
|
34
|
+
+ ` Retrying in ${delayInMs} ms... (based on ${delayReason}; attempt ${attemptCount})`
|
|
35
|
+
+ `\nFull message:\n${errorMessage}`);
|
|
36
|
+
}
|
|
21
37
|
class HttpClientGot {
|
|
22
38
|
constructor(token, customHeaders, cookies = {}, proxyAgents) {
|
|
23
39
|
this.token = token;
|
|
@@ -33,33 +49,43 @@ class HttpClientGot {
|
|
|
33
49
|
buildGotExtendOptions(proxyAgents) {
|
|
34
50
|
const gotExtendOptions = {
|
|
35
51
|
retry: {
|
|
36
|
-
calculateDelay: ({ attemptCount, retryOptions, error,
|
|
52
|
+
calculateDelay: ({ attemptCount, retryOptions, error, retryAfter, }) => {
|
|
37
53
|
var _a, _b, _c;
|
|
38
|
-
if (
|
|
39
|
-
|
|
54
|
+
if (!this.shouldRetryOnError(error)) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
if (attemptCount > retryOptions.limit) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
const hasMethod = retryOptions.methods.includes(error.options.method);
|
|
61
|
+
const hasErrorCode = retryOptions.errorCodes.includes(error.code);
|
|
62
|
+
const hasStatusCode = error.response
|
|
63
|
+
&& retryOptions.statusCodes.includes(error.response.statusCode);
|
|
64
|
+
if (!hasMethod || (!hasErrorCode && !hasStatusCode)) {
|
|
40
65
|
return 0;
|
|
41
66
|
}
|
|
42
|
-
if (error.response
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
if (error.response) {
|
|
68
|
+
if (retryAfter) {
|
|
69
|
+
if (retryOptions.maxRetryAfter === undefined
|
|
70
|
+
|| retryAfter > retryOptions.maxRetryAfter) {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
lib_1.logger.debug(buildRetryLog((_a = error.request) === null || _a === void 0 ? void 0 : _a.requestUrl, error.code, error.response.statusCode, error.message, retryAfter, 'retry-after header', attemptCount));
|
|
74
|
+
return retryAfter;
|
|
75
|
+
}
|
|
76
|
+
if (error.response.statusCode === 413) {
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
46
79
|
}
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
const baseDelayInMs = 1000;
|
|
81
|
+
const noiseToPreventCollisions = Math.random() * 100;
|
|
82
|
+
const delayInMs = Math.min(Math.pow(2, (attemptCount - 1)) * baseDelayInMs + noiseToPreventCollisions, Number.MAX_SAFE_INTEGER);
|
|
83
|
+
lib_1.logger.debug(buildRetryLog((_b = error.request) === null || _b === void 0 ? void 0 : _b.requestUrl, error.code, (_c = error.response) === null || _c === void 0 ? void 0 : _c.statusCode, error.message, delayInMs, 'retry-after header', attemptCount));
|
|
84
|
+
return delayInMs;
|
|
49
85
|
},
|
|
50
|
-
errorCodes: [
|
|
51
|
-
'ETIMEDOUT',
|
|
52
|
-
'ECONNRESET',
|
|
53
|
-
'EADDRINUSE',
|
|
54
|
-
'ECONNREFUSED',
|
|
55
|
-
'EPIPE',
|
|
56
|
-
'ENOTFOUND',
|
|
57
|
-
'ENETUNREACH',
|
|
58
|
-
'EAI_AGAIN',
|
|
59
|
-
],
|
|
60
86
|
limit: 5,
|
|
87
|
+
maxRetryAfter: 10 * 60,
|
|
61
88
|
methods: ['POST', 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE'],
|
|
62
|
-
statusCodes: [408, 413, 429, 500, 502, 503, 504, 521, 522, 524],
|
|
63
89
|
},
|
|
64
90
|
};
|
|
65
91
|
if (proxyAgents) {
|
|
@@ -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,71 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import os from 'os';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import fs from 'fs-extra';
|
|
13
|
+
import { AIElement } from './ai-element';
|
|
14
|
+
import { logger } from '../../lib';
|
|
15
|
+
import { AIElementError } from './ai-element-error';
|
|
16
|
+
export class AIElementCollection {
|
|
17
|
+
constructor(elements) {
|
|
18
|
+
this.elements = elements;
|
|
19
|
+
}
|
|
20
|
+
static collectForWorkspaceId(workspaceId) {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
logger.debug(`Collecting AIElements for workspace '${workspaceId}' ...`);
|
|
23
|
+
if (workspaceId === undefined) {
|
|
24
|
+
throw new AIElementError("Value of 'workspaceId' must be defined.");
|
|
25
|
+
}
|
|
26
|
+
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);
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
})));
|
|
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}'.`);
|
|
49
|
+
}
|
|
50
|
+
return new AIElementCollection(validAIElements);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
getByName(name) {
|
|
54
|
+
if (name === '') {
|
|
55
|
+
throw new AIElementError("Parameter 'name' must be non-empty. This might be due to corrupted metadata.");
|
|
56
|
+
}
|
|
57
|
+
logger.debug(`Getting all CustomElementJson with the name '${name}' ...`);
|
|
58
|
+
const elements = this.elements.filter((element) => element.hasName(name));
|
|
59
|
+
if (elements.length === 0) {
|
|
60
|
+
throw new AIElementError(`No AIElement with the name '${name}' was found.`);
|
|
61
|
+
}
|
|
62
|
+
return elements.map((element) => element.toCustomElement());
|
|
63
|
+
}
|
|
64
|
+
getByNames(names) {
|
|
65
|
+
if (names.length === 0) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
return names.flatMap((name) => this.getByName(name));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
AIElementCollection.AI_ELEMENT_FOLDER = path.join(os.homedir(), '.askui', 'SnippingTool', 'AIElement');
|
|
@@ -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,28 @@
|
|
|
1
|
+
import { logger } from '../../lib';
|
|
2
|
+
import { AIElementError } from './ai-element-error';
|
|
3
|
+
class AIElement {
|
|
4
|
+
constructor(name, imagePath, mask) {
|
|
5
|
+
this.name = name;
|
|
6
|
+
this.imagePath = imagePath;
|
|
7
|
+
this.mask = mask;
|
|
8
|
+
}
|
|
9
|
+
static fromJson(json, imagePath) {
|
|
10
|
+
var _a;
|
|
11
|
+
if (json.version === 1) {
|
|
12
|
+
return new AIElement(json.name, imagePath, (_a = json.image) === null || _a === void 0 ? void 0 : _a.mask);
|
|
13
|
+
}
|
|
14
|
+
throw new AIElementError(`Unsupported AIElement version '${json.version}'.`);
|
|
15
|
+
}
|
|
16
|
+
toCustomElement() {
|
|
17
|
+
logger.debug('Converting AIElement to CustomElementJson.');
|
|
18
|
+
return {
|
|
19
|
+
customImage: this.imagePath,
|
|
20
|
+
mask: this.mask,
|
|
21
|
+
name: this.name,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
hasName(name) {
|
|
25
|
+
return this.name === name;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export { 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
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
|
10
|
-
* what the custom element looks like during test execution as
|
|
11
|
-
* our machine learning models cannot find it, even with the
|
|
12
|
-
* provided. This is especially true for the resolution used
|
|
13
|
-
* the `CustomElementJson.customImage` which should
|
|
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
|
|
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**:
|
|
67
|
-
*
|
|
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);
|
|
@@ -10,10 +10,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
import { array, number, object, ValidationError, } from 'yup';
|
|
11
11
|
import { Base64Image } from '../../utils/base_64_image/base-64-image';
|
|
12
12
|
export class CustomElement {
|
|
13
|
-
constructor(customImage, name, threshold, rotationDegreePerStep, imageCompareFormat, mask) {
|
|
13
|
+
constructor(customImage, name, threshold, stopThreshold, rotationDegreePerStep, imageCompareFormat, mask) {
|
|
14
14
|
this.customImage = customImage;
|
|
15
15
|
this.name = name;
|
|
16
16
|
this.threshold = threshold;
|
|
17
|
+
this.stopThreshold = stopThreshold;
|
|
17
18
|
this.rotationDegreePerStep = rotationDegreePerStep;
|
|
18
19
|
this.imageCompareFormat = imageCompareFormat;
|
|
19
20
|
this.mask = mask;
|
|
@@ -32,7 +33,7 @@ export class CustomElement {
|
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
static fromJson(ceJson) {
|
|
35
|
-
return new CustomElement(ceJson.customImage, ceJson.name, ceJson.threshold, ceJson.rotationDegreePerStep, ceJson.imageCompareFormat, ceJson.mask);
|
|
36
|
+
return new CustomElement(ceJson.customImage, ceJson.name, ceJson.threshold, ceJson.stopThreshold, ceJson.rotationDegreePerStep, ceJson.imageCompareFormat, ceJson.mask);
|
|
36
37
|
}
|
|
37
38
|
validate() {
|
|
38
39
|
try {
|
|
@@ -46,5 +47,6 @@ export class CustomElement {
|
|
|
46
47
|
CustomElement.schema = object({
|
|
47
48
|
mask: array().optional().min(3, 'mask must contain at least 3 points'),
|
|
48
49
|
rotationDegreePerStep: number().optional().min(0).lessThan(360),
|
|
50
|
+
stopThreshold: number().optional().min(0).max(1),
|
|
49
51
|
threshold: number().optional().min(0).max(1),
|
|
50
52
|
});
|
|
@@ -55,37 +55,46 @@ export class StepReporter {
|
|
|
55
55
|
onStepBegin(snapshot) {
|
|
56
56
|
return __awaiter(this, void 0, void 0, function* () {
|
|
57
57
|
if (this._currentStep === undefined) {
|
|
58
|
-
|
|
58
|
+
logger.error('Cannot begin step if step is undefined.');
|
|
59
|
+
return Promise.resolve();
|
|
59
60
|
}
|
|
60
61
|
if (this._currentStep.status !== 'pending') {
|
|
61
|
-
|
|
62
|
+
logger.error('Cannot begin step that is not pending.');
|
|
63
|
+
return Promise.resolve();
|
|
62
64
|
}
|
|
63
65
|
this._currentStep = this._currentStep.onBegin(snapshot);
|
|
64
66
|
this.enqueueReporterCalls('onStepBegin', this._currentStep);
|
|
67
|
+
return Promise.resolve();
|
|
65
68
|
});
|
|
66
69
|
}
|
|
67
70
|
onStepRetry(snapshot, error) {
|
|
68
71
|
return __awaiter(this, void 0, void 0, function* () {
|
|
69
72
|
if (this._currentStep === undefined) {
|
|
70
|
-
|
|
73
|
+
logger.error('Cannot retry step if step is undefined.');
|
|
74
|
+
return Promise.resolve();
|
|
71
75
|
}
|
|
72
76
|
if (this._currentStep.status !== 'running') {
|
|
73
|
-
|
|
77
|
+
logger.error('Cannot retry step that has not been running.');
|
|
78
|
+
return Promise.resolve();
|
|
74
79
|
}
|
|
75
80
|
this._currentStep = this._currentStep.onRetry(snapshot, error);
|
|
76
81
|
this.enqueueReporterCalls('onStepRetry', this._currentStep);
|
|
82
|
+
return Promise.resolve();
|
|
77
83
|
});
|
|
78
84
|
}
|
|
79
85
|
onStepEnd(snapshot, error) {
|
|
80
86
|
return __awaiter(this, void 0, void 0, function* () {
|
|
81
87
|
if (this._currentStep === undefined) {
|
|
82
|
-
|
|
88
|
+
logger.error('Cannot end step if step is undefined.');
|
|
89
|
+
return Promise.resolve();
|
|
83
90
|
}
|
|
84
91
|
if (this._currentStep.status !== 'running') {
|
|
85
|
-
|
|
92
|
+
logger.error('Cannot end step that has not been running.');
|
|
93
|
+
return Promise.resolve();
|
|
86
94
|
}
|
|
87
95
|
this._currentStep = this._currentStep.onEnd(snapshot, error);
|
|
88
96
|
this.enqueueReporterCalls('onStepEnd', this._currentStep);
|
|
97
|
+
return Promise.resolve();
|
|
89
98
|
});
|
|
90
99
|
}
|
|
91
100
|
}
|