@applitools/ec-client 1.2.19 → 1.2.21
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/{cli.js → cli/cli.js} +2 -2
- package/dist/client.js +24 -19
- package/dist/commands/end-session.js +22 -0
- package/dist/commands/execute-script.js +112 -0
- package/dist/commands/find-element.js +23 -0
- package/dist/commands/start-session.js +118 -0
- package/dist/server.js +123 -0
- package/dist/tunnels/manager-server.js +1 -1
- package/dist/{queue.js → utils/queue.js} +1 -0
- package/dist/utils/router.js +49 -0
- package/package.json +8 -3
- package/types/commands/end-session.d.ts +16 -0
- package/types/commands/execute-script.d.ts +16 -0
- package/types/commands/find-element.d.ts +14 -0
- package/types/commands/start-session.d.ts +16 -0
- package/types/req-proxy.d.ts +3 -2
- package/types/types.d.ts +24 -2
- package/types/{queue.d.ts → utils/queue.d.ts} +1 -0
- package/types/utils/router.d.ts +27 -0
- package/dist/proxy-server.js +0 -232
- /package/types/{cli.d.ts → cli/cli.d.ts} +0 -0
- /package/types/{proxy-server.d.ts → server.d.ts} +0 -0
|
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
5
|
};
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const client_1 = require("../client");
|
|
8
|
+
const manager_server_1 = require("../tunnels/manager-server");
|
|
7
9
|
const yargs_1 = __importDefault(require("yargs"));
|
|
8
|
-
const client_1 = require("./client");
|
|
9
|
-
const manager_server_1 = require("./tunnels/manager-server");
|
|
10
10
|
yargs_1.default
|
|
11
11
|
.command({
|
|
12
12
|
command: '*',
|
package/dist/client.js
CHANGED
|
@@ -24,31 +24,36 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
24
24
|
};
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
26
|
exports.makeECClient = void 0;
|
|
27
|
-
const
|
|
27
|
+
const server_1 = require("./server");
|
|
28
28
|
const utils = __importStar(require("@applitools/utils"));
|
|
29
29
|
async function makeECClient({ settings, logger, } = {}) {
|
|
30
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
|
|
31
|
-
var
|
|
30
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
|
|
31
|
+
var _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25;
|
|
32
32
|
settings !== null && settings !== void 0 ? settings : (settings = {});
|
|
33
33
|
(_a = settings.serverUrl) !== null && _a !== void 0 ? _a : (settings.serverUrl = (_b = utils.general.getEnvValue('EG_SERVER_URL')) !== null && _b !== void 0 ? _b : 'https://exec-wus.applitools.com');
|
|
34
34
|
(_c = settings.proxy) !== null && _c !== void 0 ? _c : (settings.proxy = utils.general.getEnvValue('PROXY_URL') ? { url: utils.general.getEnvValue('PROXY_URL') } : undefined);
|
|
35
35
|
(_d = settings.tunnel) !== null && _d !== void 0 ? _d : (settings.tunnel = {});
|
|
36
|
-
(_e = (
|
|
37
|
-
(_f = (
|
|
38
|
-
(_h = (
|
|
39
|
-
(_j = (
|
|
40
|
-
(_l = (
|
|
41
|
-
(_m = (
|
|
42
|
-
(_p = (
|
|
43
|
-
(_r = settings.
|
|
44
|
-
(_s = (
|
|
45
|
-
(_v = (
|
|
46
|
-
(_w = (
|
|
47
|
-
(_x = (
|
|
48
|
-
(
|
|
49
|
-
(
|
|
50
|
-
(
|
|
51
|
-
|
|
36
|
+
(_e = (_7 = settings.tunnel).serverUrl) !== null && _e !== void 0 ? _e : (_7.serverUrl = utils.general.getEnvValue('EG_TUNNEL_URL'));
|
|
37
|
+
(_f = (_8 = settings.tunnel).groupSize) !== null && _f !== void 0 ? _f : (_8.groupSize = (_g = utils.general.getEnvValue('TUNNEL_GROUP_SIZE', 'number')) !== null && _g !== void 0 ? _g : 2);
|
|
38
|
+
(_h = (_9 = settings.tunnel).pool) !== null && _h !== void 0 ? _h : (_9.pool = {});
|
|
39
|
+
(_j = (_10 = settings.tunnel.pool).maxInuse) !== null && _j !== void 0 ? _j : (_10.maxInuse = (_k = utils.general.getEnvValue('TUNNEL_POOL_MAX_INUSE', 'number')) !== null && _k !== void 0 ? _k : 4);
|
|
40
|
+
(_l = (_11 = settings.tunnel.pool).timeout) !== null && _l !== void 0 ? _l : (_11.timeout = {});
|
|
41
|
+
(_m = (_12 = settings.tunnel.pool.timeout).idle) !== null && _m !== void 0 ? _m : (_12.idle = (_o = utils.general.getEnvValue('TUNNEL_POOL_TIMEOUT_IDLE', 'number')) !== null && _o !== void 0 ? _o : 10 * 60000);
|
|
42
|
+
(_p = (_13 = settings.tunnel.pool.timeout).expiration) !== null && _p !== void 0 ? _p : (_13.expiration = (_q = utils.general.getEnvValue('TUNNEL_POOL_TIMEOUT_EXPIRATION', 'number')) !== null && _q !== void 0 ? _q : 30000);
|
|
43
|
+
(_r = settings.options) !== null && _r !== void 0 ? _r : (settings.options = {});
|
|
44
|
+
(_s = (_14 = settings.options).eyesServerUrl) !== null && _s !== void 0 ? _s : (_14.eyesServerUrl = (_u = (_t = utils.general.getEnvValue('EYES_SERVER_URL')) !== null && _t !== void 0 ? _t : utils.general.getEnvValue('SERVER_URL')) !== null && _u !== void 0 ? _u : 'https://eyesapi.applitools.com');
|
|
45
|
+
(_v = (_15 = settings.options).apiKey) !== null && _v !== void 0 ? _v : (_15.apiKey = utils.general.getEnvValue('API_KEY'));
|
|
46
|
+
(_w = (_16 = settings.options).batch) !== null && _w !== void 0 ? _w : (_16.batch = {});
|
|
47
|
+
(_x = (_17 = settings.options.batch).id) !== null && _x !== void 0 ? _x : (_17.id = (_y = utils.general.getEnvValue('BATCH_ID')) !== null && _y !== void 0 ? _y : `generated-${utils.general.guid()}`);
|
|
48
|
+
(_z = (_18 = settings.options.batch).name) !== null && _z !== void 0 ? _z : (_18.name = utils.general.getEnvValue('BATCH_NAME'));
|
|
49
|
+
(_0 = (_19 = settings.options.batch).sequenceName) !== null && _0 !== void 0 ? _0 : (_19.sequenceName = utils.general.getEnvValue('BATCH_SEQUENCE'));
|
|
50
|
+
(_1 = (_20 = settings.options.batch).notifyOnCompletion) !== null && _1 !== void 0 ? _1 : (_20.notifyOnCompletion = utils.general.getEnvValue('BATCH_NOTIFY', 'boolean'));
|
|
51
|
+
(_2 = (_21 = settings.options).tunnel) !== null && _2 !== void 0 ? _2 : (_21.tunnel = utils.general.getEnvValue('TUNNEL', 'boolean'));
|
|
52
|
+
(_3 = (_22 = settings.options).useSelfHealing) !== null && _3 !== void 0 ? _3 : (_22.useSelfHealing = utils.general.getEnvValue('USE_SELF_HEALING', 'boolean'));
|
|
53
|
+
(_4 = (_23 = settings.options).sessionName) !== null && _4 !== void 0 ? _4 : (_23.sessionName = utils.general.getEnvValue('SESSION_NAME'));
|
|
54
|
+
(_5 = (_24 = settings.options).timeout) !== null && _5 !== void 0 ? _5 : (_24.timeout = utils.general.getEnvValue('EG_TIMEOUT', 'number'));
|
|
55
|
+
(_6 = (_25 = settings.options).inactivityTimeout) !== null && _6 !== void 0 ? _6 : (_25.inactivityTimeout = utils.general.getEnvValue('EG_INACTIVITY_TIMEOUT', 'number'));
|
|
56
|
+
const server = await (0, server_1.makeServer)({ settings: settings, logger });
|
|
52
57
|
return server;
|
|
53
58
|
}
|
|
54
59
|
exports.makeECClient = makeECClient;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeEndSession = void 0;
|
|
4
|
+
function makeEndSession({ req, tunnels }) {
|
|
5
|
+
return async function endSession({ session, request, response, logger, }) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
var _c;
|
|
8
|
+
logger.log(`Request was intercepted with sessionId:`, session.sessionId);
|
|
9
|
+
await req(request.url, { io: { request, response }, logger });
|
|
10
|
+
if ((_a = session.tests) === null || _a === void 0 ? void 0 : _a.current) {
|
|
11
|
+
await session.tests.current.abort({ settings: { testMetadata: session.metadata }, logger });
|
|
12
|
+
(_b = (_c = session.tests).ended) !== null && _b !== void 0 ? _b : (_c.ended = []);
|
|
13
|
+
session.tests.ended.push(session.tests.current);
|
|
14
|
+
session.tests.current = undefined;
|
|
15
|
+
}
|
|
16
|
+
if (session.tunnels && tunnels) {
|
|
17
|
+
await tunnels.release(session.tunnels);
|
|
18
|
+
logger.log(`Tunnels with id ${session.tunnels.map(tunnel => tunnel.tunnelId)} was released for session with id ${session.sessionId}`);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
exports.makeEndSession = makeEndSession;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.makeExecuteScript = void 0;
|
|
27
|
+
const driver_1 = require("@applitools/driver");
|
|
28
|
+
const spec = __importStar(require("@applitools/spec-driver-webdriver"));
|
|
29
|
+
const utils = __importStar(require("@applitools/utils"));
|
|
30
|
+
function makeExecuteScript({ req, core }) {
|
|
31
|
+
return async function executeScript({ session, request, response, logger, }) {
|
|
32
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
33
|
+
var _s, _t;
|
|
34
|
+
const requestBody = await utils.streams.toJSON(request);
|
|
35
|
+
if ((_a = requestBody.script) === null || _a === void 0 ? void 0 : _a.startsWith('applitools:')) {
|
|
36
|
+
logger.log(`Custom script execution was intercepted with body:`, requestBody);
|
|
37
|
+
const [options] = (_b = requestBody.args) !== null && _b !== void 0 ? _b : [];
|
|
38
|
+
if (requestBody.script === 'applitools:startTest') {
|
|
39
|
+
if ((_c = session.tests) === null || _c === void 0 ? void 0 : _c.current) {
|
|
40
|
+
await session.tests.current.abort({ settings: { testMetadata: session.metadata }, logger });
|
|
41
|
+
(_d = (_s = session.tests).ended) !== null && _d !== void 0 ? _d : (_s.ended = []);
|
|
42
|
+
session.tests.ended.push(session.tests.current);
|
|
43
|
+
session.tests.current = undefined;
|
|
44
|
+
}
|
|
45
|
+
const driver = await (0, driver_1.makeDriver)({
|
|
46
|
+
driver: spec.transformDriver({
|
|
47
|
+
sessionId: session.sessionId,
|
|
48
|
+
serverUrl: session.serverUrl,
|
|
49
|
+
capabilities: session.capabilities,
|
|
50
|
+
proxy: session.proxy,
|
|
51
|
+
}),
|
|
52
|
+
spec,
|
|
53
|
+
logger,
|
|
54
|
+
});
|
|
55
|
+
const environment = await driver.getEnvironment();
|
|
56
|
+
(_e = session.tests) !== null && _e !== void 0 ? _e : (session.tests = {});
|
|
57
|
+
session.tests.current = await core.openFunctionalSession({
|
|
58
|
+
settings: {
|
|
59
|
+
serverUrl: session.credentials.eyesServerUrl,
|
|
60
|
+
apiKey: session.credentials.apiKey,
|
|
61
|
+
proxy: session.proxy,
|
|
62
|
+
appName: (_g = (_f = options === null || options === void 0 ? void 0 : options.appName) !== null && _f !== void 0 ? _f : session.options.appName) !== null && _g !== void 0 ? _g : (await driver.getTitle()),
|
|
63
|
+
testName: (_h = options === null || options === void 0 ? void 0 : options.testName) !== null && _h !== void 0 ? _h : session.options.testName,
|
|
64
|
+
properties: [{ value: 'Execution Cloud', name: 'Yes' }],
|
|
65
|
+
batch: { ...session.options.batch, ...options === null || options === void 0 ? void 0 : options.batch },
|
|
66
|
+
environment: {
|
|
67
|
+
hostingApp: `${(_j = environment.browserName) !== null && _j !== void 0 ? _j : ''} ${(_k = environment.browserVersion) !== null && _k !== void 0 ? _k : ''}`.trim(),
|
|
68
|
+
os: `${(_l = environment.platformName) !== null && _l !== void 0 ? _l : ''} ${(_m = environment.platformVersion) !== null && _m !== void 0 ? _m : ''}`.trim(),
|
|
69
|
+
deviceName: environment.deviceName,
|
|
70
|
+
viewportSize: await driver.getViewportSize(),
|
|
71
|
+
ecSessionId: session.sessionId,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
logger,
|
|
75
|
+
});
|
|
76
|
+
response.writeHead(200).end(JSON.stringify({ value: null }));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
else if (requestBody.script === 'applitools:endTest') {
|
|
80
|
+
if ((_o = session.tests) === null || _o === void 0 ? void 0 : _o.current) {
|
|
81
|
+
await session.tests.current.close({
|
|
82
|
+
settings: { status: options === null || options === void 0 ? void 0 : options.status, testMetadata: session.metadata },
|
|
83
|
+
logger,
|
|
84
|
+
});
|
|
85
|
+
(_p = (_t = session.tests).ended) !== null && _p !== void 0 ? _p : (_t.ended = []);
|
|
86
|
+
session.tests.ended.push(session.tests.current);
|
|
87
|
+
session.tests.current = undefined;
|
|
88
|
+
}
|
|
89
|
+
response.writeHead(200).end(JSON.stringify({ value: null }));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
else if (requestBody.script === 'applitools:getResults') {
|
|
93
|
+
if ((_q = session.tests) === null || _q === void 0 ? void 0 : _q.ended) {
|
|
94
|
+
const results = await Promise.all(session.tests.ended.map(test => test.getResults({ logger })));
|
|
95
|
+
response.writeHead(200).end(JSON.stringify({ value: results.flat() }));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
response.writeHead(200).end(JSON.stringify({ value: [] }));
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
else if (requestBody.script === 'applitools:metadata') {
|
|
103
|
+
logger.log('Session metadata requested, returning', session.metadata);
|
|
104
|
+
response.writeHead(200).end(JSON.stringify({ value: (_r = session.metadata) !== null && _r !== void 0 ? _r : [] }));
|
|
105
|
+
session.metadata = [];
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
await req(request.url, { body: requestBody, io: { request, response }, logger });
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
exports.makeExecuteScript = makeExecuteScript;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeFindElement = void 0;
|
|
4
|
+
function makeFindElement({ req }) {
|
|
5
|
+
return async function findElement({ session, request, response, logger, }) {
|
|
6
|
+
var _a, _b, _c;
|
|
7
|
+
logger.log('Inspecting element lookup request to collect self-healing metadata');
|
|
8
|
+
const proxyResponse = await req(request.url, { io: { request, response, handle: false }, logger });
|
|
9
|
+
const responseBody = await proxyResponse.json();
|
|
10
|
+
if ((_b = (_a = responseBody === null || responseBody === void 0 ? void 0 : responseBody.appliCustomData) === null || _a === void 0 ? void 0 : _a.selfHealing) === null || _b === void 0 ? void 0 : _b.successfulSelector) {
|
|
11
|
+
logger.log('Self-healed locators detected', responseBody.appliCustomData.selfHealing);
|
|
12
|
+
(_c = session.metadata) !== null && _c !== void 0 ? _c : (session.metadata = []);
|
|
13
|
+
session.metadata.push(responseBody.appliCustomData.selfHealing);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
logger.log('No self-healing metadata found');
|
|
17
|
+
}
|
|
18
|
+
response
|
|
19
|
+
.writeHead(proxyResponse.status, Object.fromEntries(proxyResponse.headers.entries()))
|
|
20
|
+
.end(JSON.stringify(responseBody));
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
exports.makeFindElement = makeFindElement;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.makeStartSession = void 0;
|
|
27
|
+
const queue_1 = require("../utils/queue");
|
|
28
|
+
const utils = __importStar(require("@applitools/utils"));
|
|
29
|
+
const RETRY_BACKOFF = [
|
|
30
|
+
...Array(5).fill(2000),
|
|
31
|
+
...Array(4).fill(5000),
|
|
32
|
+
10000, // all next tries with delay 10s
|
|
33
|
+
];
|
|
34
|
+
function makeStartSession({ settings, req, tunnels }) {
|
|
35
|
+
const queues = new Map();
|
|
36
|
+
return async function createSession({ request, response, logger, }) {
|
|
37
|
+
var _a, _b, _c;
|
|
38
|
+
const requestBody = await utils.streams.toJSON(request);
|
|
39
|
+
logger.log(`Request was intercepted with body:`, requestBody);
|
|
40
|
+
const capabilities = (_b = (_a = requestBody.capabilities) === null || _a === void 0 ? void 0 : _a.alwaysMatch) !== null && _b !== void 0 ? _b : requestBody.desiredCapabilities;
|
|
41
|
+
const options = {
|
|
42
|
+
...settings.options,
|
|
43
|
+
...capabilities === null || capabilities === void 0 ? void 0 : capabilities['applitools:options'],
|
|
44
|
+
...(capabilities &&
|
|
45
|
+
Object.fromEntries(Object.entries(capabilities).map(([key, value]) => [key.replace(/^applitools:/, ''), value]))),
|
|
46
|
+
};
|
|
47
|
+
const session = {
|
|
48
|
+
credentials: { eyesServerUrl: options.eyesServerUrl, apiKey: options.apiKey },
|
|
49
|
+
options,
|
|
50
|
+
};
|
|
51
|
+
if (options.tunnel && tunnels) {
|
|
52
|
+
session.tunnels = await tunnels.acquire(session.credentials);
|
|
53
|
+
session.tunnels.forEach((tunnel, index) => {
|
|
54
|
+
options[`x-tunnel-id-${index}`] = tunnel.tunnelId;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const applitoolsCapabilities = Object.fromEntries(Object.entries(options).map(([key, value]) => [`applitools:${key}`, value]));
|
|
58
|
+
if (requestBody.capabilities) {
|
|
59
|
+
requestBody.capabilities.alwaysMatch = { ...(_c = requestBody.capabilities) === null || _c === void 0 ? void 0 : _c.alwaysMatch, ...applitoolsCapabilities };
|
|
60
|
+
}
|
|
61
|
+
if (requestBody.desiredCapabilities) {
|
|
62
|
+
requestBody.desiredCapabilities = { ...requestBody.desiredCapabilities, ...applitoolsCapabilities };
|
|
63
|
+
}
|
|
64
|
+
logger.log('Request body has modified:', requestBody);
|
|
65
|
+
const queueKey = JSON.stringify(session.credentials);
|
|
66
|
+
let queue = queues.get(queueKey);
|
|
67
|
+
if (!queue) {
|
|
68
|
+
queue = (0, queue_1.makeQueue)({ logger: logger.extend({ tags: { queue: queueKey } }) });
|
|
69
|
+
queues.set(queueKey, queue);
|
|
70
|
+
}
|
|
71
|
+
request.socket.on('close', () => queue.cancel(task));
|
|
72
|
+
return queue.run(task);
|
|
73
|
+
async function task(signal, attempt = 1) {
|
|
74
|
+
var _a, _b, _c;
|
|
75
|
+
var _d;
|
|
76
|
+
// do not start the task if it is already aborted
|
|
77
|
+
if (signal.aborted)
|
|
78
|
+
return queue.pause;
|
|
79
|
+
const proxyResponse = await req(request.url, {
|
|
80
|
+
body: requestBody,
|
|
81
|
+
io: { request, response, handle: false },
|
|
82
|
+
// TODO uncomment when we can throw different abort reasons for task cancelation and timeout abortion
|
|
83
|
+
// signal,
|
|
84
|
+
logger,
|
|
85
|
+
});
|
|
86
|
+
const responseBody = await proxyResponse.json();
|
|
87
|
+
logger.log(`Response was intercepted with body:`, responseBody);
|
|
88
|
+
if (['CONCURRENCY_LIMIT_REACHED', 'NO_AVAILABLE_DRIVER_POD'].includes((_b = (_a = responseBody.value) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.appliErrorCode)) {
|
|
89
|
+
queue.cork();
|
|
90
|
+
// after query is corked the task might be aborted
|
|
91
|
+
if (signal.aborted)
|
|
92
|
+
return queue.pause;
|
|
93
|
+
await utils.general.sleep(RETRY_BACKOFF[Math.min(attempt, RETRY_BACKOFF.length - 1)]);
|
|
94
|
+
logger.log(`Attempt (${attempt}) to create session was failed with applitools status code:`, responseBody.value.data.appliErrorCode);
|
|
95
|
+
return task(signal, attempt + 1);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
queue.uncork();
|
|
99
|
+
if (responseBody.value) {
|
|
100
|
+
(_c = (_d = responseBody.value).capabilities) !== null && _c !== void 0 ? _c : (_d.capabilities = {});
|
|
101
|
+
responseBody.value.capabilities['applitools:isECClient'] = true;
|
|
102
|
+
if (proxyResponse.headers.has('content-length')) {
|
|
103
|
+
proxyResponse.headers.set('content-length', Buffer.byteLength(JSON.stringify(responseBody)).toString());
|
|
104
|
+
}
|
|
105
|
+
session.serverUrl = settings.serverUrl;
|
|
106
|
+
session.sessionId = responseBody.value.sessionId;
|
|
107
|
+
session.proxy = settings.proxy;
|
|
108
|
+
session.capabilities = responseBody.value.capabilities;
|
|
109
|
+
}
|
|
110
|
+
response
|
|
111
|
+
.writeHead(proxyResponse.status, Object.fromEntries(proxyResponse.headers.entries()))
|
|
112
|
+
.end(JSON.stringify(responseBody));
|
|
113
|
+
return session;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
exports.makeStartSession = makeStartSession;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.makeServer = void 0;
|
|
27
|
+
const http_1 = require("http");
|
|
28
|
+
const logger_1 = require("@applitools/logger");
|
|
29
|
+
const core_base_1 = require("@applitools/core-base");
|
|
30
|
+
const router_1 = require("./utils/router");
|
|
31
|
+
const manager_1 = require("./tunnels/manager");
|
|
32
|
+
const manager_client_1 = require("./tunnels/manager-client");
|
|
33
|
+
const req_proxy_1 = require("./req-proxy");
|
|
34
|
+
const start_session_1 = require("./commands/start-session");
|
|
35
|
+
const execute_script_1 = require("./commands/execute-script");
|
|
36
|
+
const end_session_1 = require("./commands/end-session");
|
|
37
|
+
const find_element_1 = require("./commands/find-element");
|
|
38
|
+
const utils = __importStar(require("@applitools/utils"));
|
|
39
|
+
async function makeServer({ settings, logger }) {
|
|
40
|
+
var _a, _b;
|
|
41
|
+
const serverLogger = logger ? logger.extend({ label: 'ec-client' }) : (0, logger_1.makeLogger)({ label: 'ec-client', colors: true });
|
|
42
|
+
const req = (0, req_proxy_1.makeReqProxy)({
|
|
43
|
+
targetUrl: settings.serverUrl,
|
|
44
|
+
proxy: settings.proxy,
|
|
45
|
+
retry: {
|
|
46
|
+
validate: async ({ response, error }) => {
|
|
47
|
+
if (error)
|
|
48
|
+
return !utils.types.instanceOf(error, 'AbortError');
|
|
49
|
+
if (response)
|
|
50
|
+
return response.status >= 500 && !utils.types.has(await response.clone().json(), 'value');
|
|
51
|
+
return false;
|
|
52
|
+
},
|
|
53
|
+
limit: 10,
|
|
54
|
+
timeout: 5000,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const core = (0, core_base_1.makeCore)({ agentId: `js/ec-client/${require('../package.json').version}` });
|
|
58
|
+
const tunnels = ((_a = settings.tunnel) === null || _a === void 0 ? void 0 : _a.serverUrl)
|
|
59
|
+
? await (0, manager_1.makeTunnelManager)({ settings: settings.tunnel, logger: serverLogger })
|
|
60
|
+
: await (0, manager_client_1.makeTunnelManagerClient)({ settings: settings.tunnel });
|
|
61
|
+
const sessions = new Map();
|
|
62
|
+
const commands = {
|
|
63
|
+
startSession: (0, start_session_1.makeStartSession)({ settings, req, tunnels }),
|
|
64
|
+
endSession: (0, end_session_1.makeEndSession)({ req, tunnels }),
|
|
65
|
+
executeScript: (0, execute_script_1.makeExecuteScript)({ req, core }),
|
|
66
|
+
findElement: (0, find_element_1.makeFindElement)({ req }),
|
|
67
|
+
};
|
|
68
|
+
const server = (0, http_1.createServer)((0, router_1.makeCallback)(({ router, request, response }) => {
|
|
69
|
+
const requestLogger = serverLogger.extend({
|
|
70
|
+
tags: { request: `[${request.method}] ${request.url}`, requestId: utils.general.guid() },
|
|
71
|
+
});
|
|
72
|
+
router.post('/session', async () => {
|
|
73
|
+
const session = await commands.startSession({ request, response, logger: requestLogger });
|
|
74
|
+
sessions.set(session.sessionId, session);
|
|
75
|
+
});
|
|
76
|
+
router.post('/session/:sessionId/execute/sync', async ({ match }) => {
|
|
77
|
+
const session = sessions.get(match.groups.sessionId);
|
|
78
|
+
await commands.executeScript({ session, request, response, logger: requestLogger });
|
|
79
|
+
});
|
|
80
|
+
router.post('/session/:sessionId/element', async ({ match }) => {
|
|
81
|
+
const session = sessions.get(match.groups.sessionId);
|
|
82
|
+
await commands.findElement({ session, request, response, logger: requestLogger });
|
|
83
|
+
});
|
|
84
|
+
router.delete('/session/:sessionId', async ({ match }) => {
|
|
85
|
+
const session = sessions.get(match.groups.sessionId);
|
|
86
|
+
await commands.endSession({ session, request, response, logger: requestLogger });
|
|
87
|
+
sessions.delete(session.sessionId);
|
|
88
|
+
});
|
|
89
|
+
router.any(async () => {
|
|
90
|
+
requestLogger.log('Passthrough request');
|
|
91
|
+
await req(request.url, { io: { request, response }, logger: requestLogger });
|
|
92
|
+
});
|
|
93
|
+
router.catch(async ({ error }) => {
|
|
94
|
+
requestLogger.error(`Error during processing request`, error);
|
|
95
|
+
if (response.writableEnded)
|
|
96
|
+
return;
|
|
97
|
+
response
|
|
98
|
+
.writeHead(500)
|
|
99
|
+
.end(JSON.stringify({ value: { error: 'internal proxy server error', message: error.message, stacktrace: '' } }));
|
|
100
|
+
});
|
|
101
|
+
router.finally(async () => {
|
|
102
|
+
requestLogger.log(`Request was responded with status ${response.statusCode}`);
|
|
103
|
+
});
|
|
104
|
+
}));
|
|
105
|
+
server.listen({ port: (_b = settings.port) !== null && _b !== void 0 ? _b : 0, hostname: 'localhost' });
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
server.on('listening', () => {
|
|
108
|
+
const address = server.address();
|
|
109
|
+
serverLogger.log(`Proxy server has started on port ${address.port}`);
|
|
110
|
+
resolve({
|
|
111
|
+
url: `http://localhost:${address.port}`,
|
|
112
|
+
port: address.port,
|
|
113
|
+
unref: () => server.unref(),
|
|
114
|
+
close: () => server.close(),
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
server.on('error', async (err) => {
|
|
118
|
+
serverLogger.fatal('Error starting proxy server', err);
|
|
119
|
+
reject(err);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
exports.makeServer = makeServer;
|
|
@@ -88,7 +88,7 @@ async function makeTunnelManagerServer({ settings, path, idleTimeout = 600000, /
|
|
|
88
88
|
exports.makeTunnelManagerServer = makeTunnelManagerServer;
|
|
89
89
|
async function makeTunnelManagerServerProcess(options) {
|
|
90
90
|
return new Promise((resolve, reject) => {
|
|
91
|
-
const server = (0, child_process_1.fork)(path.resolve(__dirname, '../../dist/cli.js'), [`tunnel-manager`, `--config=${JSON.stringify(options)}`], {
|
|
91
|
+
const server = (0, child_process_1.fork)(path.resolve(__dirname, '../../dist/cli/cli.js'), [`tunnel-manager`, `--config=${JSON.stringify(options)}`], {
|
|
92
92
|
stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
|
|
93
93
|
detached: true,
|
|
94
94
|
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeCallback = void 0;
|
|
4
|
+
function makeCallback(handler) {
|
|
5
|
+
return async function callback(request, response) {
|
|
6
|
+
const routes = [];
|
|
7
|
+
const fallbacks = [];
|
|
8
|
+
const catches = [];
|
|
9
|
+
const finals = [];
|
|
10
|
+
const router = {
|
|
11
|
+
get: (pattern, callback) => routes.push({ method: 'GET', pattern, callback }),
|
|
12
|
+
post: (pattern, callback) => routes.push({ method: 'POST', pattern, callback }),
|
|
13
|
+
put: (pattern, callback) => routes.push({ method: 'PUT', pattern, callback }),
|
|
14
|
+
delete: (pattern, callback) => routes.push({ method: 'DELETE', pattern, callback }),
|
|
15
|
+
any: callback => fallbacks.push({ callback }),
|
|
16
|
+
catch: callback => catches.push({ callback }),
|
|
17
|
+
finally: callback => finals.push({ callback }),
|
|
18
|
+
};
|
|
19
|
+
handler({ router, request, response });
|
|
20
|
+
try {
|
|
21
|
+
for (const { method, pattern, callback } of routes) {
|
|
22
|
+
if (request.method === method && request.url) {
|
|
23
|
+
const regexp = pattern instanceof RegExp
|
|
24
|
+
? pattern
|
|
25
|
+
: new RegExp(`^${pattern.replace(/:([^\/]+)/g, (_, name) => `(?<${name}>[^/]+)`)}/?$`);
|
|
26
|
+
const match = request.url.match(regexp);
|
|
27
|
+
if (match) {
|
|
28
|
+
await callback({ match });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const { callback } of fallbacks) {
|
|
34
|
+
await callback();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
for (const { callback } of catches) {
|
|
39
|
+
await callback({ error });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
for (const { callback } of finals) {
|
|
44
|
+
await callback();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
exports.makeCallback = makeCallback;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@applitools/ec-client",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.21",
|
|
4
4
|
"homepage": "https://applitools.com",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/applitools/eyes.sdk.javascript1/issues"
|
|
@@ -63,17 +63,22 @@
|
|
|
63
63
|
"postversion": "bongo postversion"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@applitools/
|
|
66
|
+
"@applitools/core-base": "1.1.49",
|
|
67
|
+
"@applitools/driver": "1.11.42",
|
|
68
|
+
"@applitools/execution-grid-tunnel": "1.0.24",
|
|
67
69
|
"@applitools/logger": "1.1.48",
|
|
68
|
-
"@applitools/req": "1.1.
|
|
70
|
+
"@applitools/req": "1.1.35",
|
|
69
71
|
"@applitools/socket": "1.0.10",
|
|
72
|
+
"@applitools/spec-driver-webdriver": "1.0.23",
|
|
70
73
|
"@applitools/utils": "1.3.32",
|
|
71
74
|
"abort-controller": "3.0.0",
|
|
75
|
+
"webdriver": "7",
|
|
72
76
|
"yargs": "17.6.2"
|
|
73
77
|
},
|
|
74
78
|
"devDependencies": {
|
|
75
79
|
"@applitools/bongo": "^3.0.3",
|
|
76
80
|
"@applitools/test-server": "^1.1.28",
|
|
81
|
+
"@applitools/test-utils": "^1.5.16",
|
|
77
82
|
"@types/node": "^12.20.55",
|
|
78
83
|
"@types/node-fetch": "^2.6.1",
|
|
79
84
|
"@types/selenium-webdriver": "^4.0.19",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ECSession } from '../types';
|
|
2
|
+
import { type IncomingMessage, type ServerResponse } from 'http';
|
|
3
|
+
import { type Logger } from '@applitools/logger';
|
|
4
|
+
import { type ReqProxy } from '../req-proxy';
|
|
5
|
+
import { type TunnelManager } from '../tunnels/manager';
|
|
6
|
+
type Options = {
|
|
7
|
+
req: ReqProxy;
|
|
8
|
+
tunnels?: TunnelManager;
|
|
9
|
+
};
|
|
10
|
+
export declare function makeEndSession({ req, tunnels }: Options): ({ session, request, response, logger, }: {
|
|
11
|
+
session: ECSession;
|
|
12
|
+
request: IncomingMessage;
|
|
13
|
+
response: ServerResponse;
|
|
14
|
+
logger: Logger;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ECSession } from '../types';
|
|
2
|
+
import { type IncomingMessage, type ServerResponse } from 'http';
|
|
3
|
+
import { type Core } from '@applitools/core-base';
|
|
4
|
+
import { type Logger } from '@applitools/logger';
|
|
5
|
+
import { type ReqProxy } from '../req-proxy';
|
|
6
|
+
type Options = {
|
|
7
|
+
core: Core;
|
|
8
|
+
req: ReqProxy;
|
|
9
|
+
};
|
|
10
|
+
export declare function makeExecuteScript({ req, core }: Options): ({ session, request, response, logger, }: {
|
|
11
|
+
session: ECSession;
|
|
12
|
+
request: IncomingMessage;
|
|
13
|
+
response: ServerResponse;
|
|
14
|
+
logger: Logger;
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ECSession } from '../types';
|
|
2
|
+
import { type IncomingMessage, type ServerResponse } from 'http';
|
|
3
|
+
import { type Logger } from '@applitools/logger';
|
|
4
|
+
import { type ReqProxy } from '../req-proxy';
|
|
5
|
+
type Options = {
|
|
6
|
+
req: ReqProxy;
|
|
7
|
+
};
|
|
8
|
+
export declare function makeFindElement({ req }: Options): ({ session, request, response, logger, }: {
|
|
9
|
+
session: ECSession;
|
|
10
|
+
request: IncomingMessage;
|
|
11
|
+
response: ServerResponse;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
}) => Promise<void>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ECSession, ECClientSettings } from '../types';
|
|
2
|
+
import { type IncomingMessage, type ServerResponse } from 'http';
|
|
3
|
+
import { type Logger } from '@applitools/logger';
|
|
4
|
+
import { type ReqProxy } from '../req-proxy';
|
|
5
|
+
import { type TunnelManager } from '../tunnels/manager';
|
|
6
|
+
type Options = {
|
|
7
|
+
settings: ECClientSettings;
|
|
8
|
+
req: ReqProxy;
|
|
9
|
+
tunnels?: TunnelManager;
|
|
10
|
+
};
|
|
11
|
+
export declare function makeStartSession({ settings, req, tunnels }: Options): ({ request, response, logger, }: {
|
|
12
|
+
request: IncomingMessage;
|
|
13
|
+
response: ServerResponse;
|
|
14
|
+
logger: Logger;
|
|
15
|
+
}) => Promise<ECSession>;
|
|
16
|
+
export {};
|
package/types/req-proxy.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import { type IncomingMessage, type ServerResponse } from 'http';
|
|
3
3
|
import { type Logger } from '@applitools/logger';
|
|
4
|
-
import { type Proxy, type Retry, type Options } from '@applitools/req';
|
|
4
|
+
import { type Req, type Proxy, type Retry, type Options } from '@applitools/req';
|
|
5
5
|
export type ReqProxyConfig = {
|
|
6
6
|
targetUrl: string;
|
|
7
7
|
proxy?: Proxy;
|
|
@@ -16,4 +16,5 @@ export type ReqProxyOptions = Options & {
|
|
|
16
16
|
};
|
|
17
17
|
logger?: Logger;
|
|
18
18
|
};
|
|
19
|
-
export
|
|
19
|
+
export type ReqProxy = Req<ReqProxyOptions>;
|
|
20
|
+
export declare function makeReqProxy(config: ReqProxyConfig): Req<ReqProxyOptions>;
|
package/types/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type Proxy } from '@applitools/req';
|
|
2
|
+
import { type Batch, type FunctionalSession } from '@applitools/core-base';
|
|
3
|
+
import { type Tunnel } from './tunnels/manager';
|
|
2
4
|
export interface ECClient {
|
|
3
5
|
readonly url: string;
|
|
4
6
|
readonly port: number;
|
|
@@ -8,7 +10,7 @@ export interface ECClient {
|
|
|
8
10
|
export interface ECClientSettings {
|
|
9
11
|
serverUrl: string;
|
|
10
12
|
proxy?: Proxy;
|
|
11
|
-
|
|
13
|
+
options?: ECCapabilitiesOptions;
|
|
12
14
|
port?: number;
|
|
13
15
|
/** @internal */
|
|
14
16
|
tunnel?: {
|
|
@@ -23,10 +25,13 @@ export interface ECClientSettings {
|
|
|
23
25
|
};
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
|
-
export interface
|
|
28
|
+
export interface ECCapabilitiesOptions {
|
|
27
29
|
eyesServerUrl?: string;
|
|
28
30
|
apiKey?: string;
|
|
29
31
|
sessionName?: string;
|
|
32
|
+
appName?: string;
|
|
33
|
+
testName?: string;
|
|
34
|
+
batch?: Batch;
|
|
30
35
|
useSelfHealing?: boolean;
|
|
31
36
|
tunnel?: boolean;
|
|
32
37
|
timeout?: number;
|
|
@@ -34,3 +39,20 @@ export interface ECCapabilities {
|
|
|
34
39
|
requestDriverTimeout?: number;
|
|
35
40
|
selfHealingMaxRetryTime?: number;
|
|
36
41
|
}
|
|
42
|
+
export interface ECSession {
|
|
43
|
+
serverUrl: string;
|
|
44
|
+
sessionId: string;
|
|
45
|
+
proxy?: Proxy;
|
|
46
|
+
credentials: {
|
|
47
|
+
eyesServerUrl: string;
|
|
48
|
+
apiKey: string;
|
|
49
|
+
};
|
|
50
|
+
capabilities: Record<string, any>;
|
|
51
|
+
options: ECCapabilitiesOptions;
|
|
52
|
+
tunnels?: Tunnel[];
|
|
53
|
+
metadata?: any[];
|
|
54
|
+
tests?: {
|
|
55
|
+
current?: FunctionalSession;
|
|
56
|
+
ended?: FunctionalSession[];
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -2,6 +2,7 @@ import { type Logger } from '@applitools/logger';
|
|
|
2
2
|
import { type AbortSignal } from 'abort-controller';
|
|
3
3
|
export type Queue = {
|
|
4
4
|
readonly corked: boolean;
|
|
5
|
+
readonly pause: never;
|
|
5
6
|
run<TResult>(task: Task<TResult>): Promise<TResult>;
|
|
6
7
|
cancel(task: (signal: AbortSignal) => Promise<any>): void;
|
|
7
8
|
cork(): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { type IncomingMessage, type ServerResponse } from 'http';
|
|
3
|
+
type Router = {
|
|
4
|
+
get: (pattern: string | RegExp, callback: (options: {
|
|
5
|
+
match: RegExpMatchArray;
|
|
6
|
+
}) => Promise<void> | void) => void;
|
|
7
|
+
post: (pattern: string | RegExp, callback: (options: {
|
|
8
|
+
match: RegExpMatchArray;
|
|
9
|
+
}) => Promise<void> | void) => void;
|
|
10
|
+
put: (pattern: string | RegExp, callback: (options: {
|
|
11
|
+
match: RegExpMatchArray;
|
|
12
|
+
}) => Promise<void> | void) => void;
|
|
13
|
+
delete: (pattern: string | RegExp, callback: (options: {
|
|
14
|
+
match: RegExpMatchArray;
|
|
15
|
+
}) => Promise<void> | void) => void;
|
|
16
|
+
any: (callback: () => Promise<void> | void) => void;
|
|
17
|
+
catch: (callback: (options: {
|
|
18
|
+
error: any;
|
|
19
|
+
}) => Promise<void> | void) => void;
|
|
20
|
+
finally: (callback: () => Promise<void> | void) => void;
|
|
21
|
+
};
|
|
22
|
+
export declare function makeCallback(handler: (options: {
|
|
23
|
+
router: Router;
|
|
24
|
+
request: IncomingMessage;
|
|
25
|
+
response: ServerResponse;
|
|
26
|
+
}) => void): (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
|
27
|
+
export {};
|
package/dist/proxy-server.js
DELETED
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
-
if (mod && mod.__esModule) return mod;
|
|
20
|
-
var result = {};
|
|
21
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
-
__setModuleDefault(result, mod);
|
|
23
|
-
return result;
|
|
24
|
-
};
|
|
25
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.makeServer = void 0;
|
|
27
|
-
const http_1 = require("http");
|
|
28
|
-
const logger_1 = require("@applitools/logger");
|
|
29
|
-
const queue_1 = require("./queue");
|
|
30
|
-
const manager_1 = require("./tunnels/manager");
|
|
31
|
-
const manager_client_1 = require("./tunnels/manager-client");
|
|
32
|
-
const req_proxy_1 = require("./req-proxy");
|
|
33
|
-
const utils = __importStar(require("@applitools/utils"));
|
|
34
|
-
const RETRY_BACKOFF = [
|
|
35
|
-
...Array(5).fill(2000),
|
|
36
|
-
...Array(4).fill(5000),
|
|
37
|
-
10000, // all next tries with delay 10s
|
|
38
|
-
];
|
|
39
|
-
const RETRY_ERROR_CODES = ['CONCURRENCY_LIMIT_REACHED', 'NO_AVAILABLE_DRIVER_POD'];
|
|
40
|
-
async function makeServer({ settings, logger }) {
|
|
41
|
-
var _a, _b;
|
|
42
|
-
const serverLogger = logger ? logger.extend({ label: 'ec-client' }) : (0, logger_1.makeLogger)({ label: 'ec-client', colors: true });
|
|
43
|
-
const req = (0, req_proxy_1.makeReqProxy)({
|
|
44
|
-
targetUrl: settings.serverUrl,
|
|
45
|
-
proxy: settings.proxy,
|
|
46
|
-
retry: {
|
|
47
|
-
validate: async ({ response, error }) => {
|
|
48
|
-
if (error)
|
|
49
|
-
return !utils.types.instanceOf(error, 'AbortError');
|
|
50
|
-
if (response)
|
|
51
|
-
return response.status >= 500 && !utils.types.has(await response.clone().json(), 'value');
|
|
52
|
-
return false;
|
|
53
|
-
},
|
|
54
|
-
limit: 10,
|
|
55
|
-
timeout: 5000,
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
const tunnelManager = ((_a = settings.tunnel) === null || _a === void 0 ? void 0 : _a.serverUrl)
|
|
59
|
-
? await (0, manager_1.makeTunnelManager)({ settings: settings.tunnel, logger: serverLogger })
|
|
60
|
-
: await (0, manager_client_1.makeTunnelManagerClient)({ settings: settings.tunnel });
|
|
61
|
-
const sessions = new Map();
|
|
62
|
-
const queues = new Map();
|
|
63
|
-
const server = (0, http_1.createServer)(async (request, response) => {
|
|
64
|
-
var _a, _b, _c, _d;
|
|
65
|
-
const url = request.url;
|
|
66
|
-
const requestLogger = serverLogger.extend({
|
|
67
|
-
tags: { request: `[${request.method}] ${request.url}`, requestId: utils.general.guid() },
|
|
68
|
-
});
|
|
69
|
-
try {
|
|
70
|
-
if (request.method === 'POST' && /^\/session\/?$/.test(url)) {
|
|
71
|
-
return await createSession({ request, response, logger: requestLogger });
|
|
72
|
-
}
|
|
73
|
-
else if (request.method === 'DELETE' && /^\/session\/[^\/]+\/?$/.test(url)) {
|
|
74
|
-
return await deleteSession({ request, response, logger: requestLogger });
|
|
75
|
-
}
|
|
76
|
-
else if (request.method === 'POST' && /^\/session\/[^\/]+\/element\/?$/.test(url)) {
|
|
77
|
-
requestLogger.log('Inspecting element lookup request to collect self-healing metadata');
|
|
78
|
-
const proxyResponse = await req(url, { io: { request, response, handle: false }, logger: requestLogger });
|
|
79
|
-
const responseBody = await proxyResponse.json();
|
|
80
|
-
if ((_b = (_a = responseBody === null || responseBody === void 0 ? void 0 : responseBody.appliCustomData) === null || _a === void 0 ? void 0 : _a.selfHealing) === null || _b === void 0 ? void 0 : _b.successfulSelector) {
|
|
81
|
-
requestLogger.log('Self-healed locators detected', responseBody.appliCustomData.selfHealing);
|
|
82
|
-
const session = sessions.get(getSessionId(url));
|
|
83
|
-
(_c = session.metadata) !== null && _c !== void 0 ? _c : (session.metadata = []);
|
|
84
|
-
session.metadata.push(responseBody.appliCustomData.selfHealing);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
requestLogger.log('No self-healing metadata found');
|
|
88
|
-
}
|
|
89
|
-
response
|
|
90
|
-
.writeHead(proxyResponse.status, Object.fromEntries(proxyResponse.headers.entries()))
|
|
91
|
-
.end(JSON.stringify(responseBody));
|
|
92
|
-
}
|
|
93
|
-
else if (request.method === 'GET' && /^\/session\/[^\/]+\/applitools\/metadata?$/.test(url)) {
|
|
94
|
-
const session = sessions.get(getSessionId(url));
|
|
95
|
-
requestLogger.log('Session metadata requested, returning', session.metadata);
|
|
96
|
-
response.writeHead(200).end(JSON.stringify({ value: (_d = session.metadata) !== null && _d !== void 0 ? _d : [] }));
|
|
97
|
-
session.metadata = [];
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
requestLogger.log('Passthrough request');
|
|
101
|
-
return await req(url, { io: { request, response }, logger: requestLogger });
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
105
|
-
requestLogger.error(`Error during processing request:`, err);
|
|
106
|
-
if (!response.writableEnded) {
|
|
107
|
-
response
|
|
108
|
-
.writeHead(500)
|
|
109
|
-
.end(JSON.stringify({ value: { error: 'internal proxy server error', message: err.message, stacktrace: '' } }));
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
requestLogger.log(`Request was responded with status ${response.statusCode}`);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
server.listen({ port: (_b = settings.port) !== null && _b !== void 0 ? _b : 0, hostname: 'localhost' });
|
|
117
|
-
return new Promise((resolve, reject) => {
|
|
118
|
-
server.on('listening', () => {
|
|
119
|
-
const address = server.address();
|
|
120
|
-
serverLogger.log(`Proxy server has started on port ${address.port}`);
|
|
121
|
-
resolve({
|
|
122
|
-
url: `http://localhost:${address.port}`,
|
|
123
|
-
port: address.port,
|
|
124
|
-
unref: () => server.unref(),
|
|
125
|
-
close: () => server.close(),
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
server.on('error', async (err) => {
|
|
129
|
-
serverLogger.fatal('Error starting proxy server', err);
|
|
130
|
-
reject(err);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
async function createSession({ request, response, logger, }) {
|
|
134
|
-
var _a, _b, _c;
|
|
135
|
-
const requestBody = await utils.streams.toJSON(request);
|
|
136
|
-
logger.log(`Request was intercepted with body:`, requestBody);
|
|
137
|
-
const capabilities = (_b = (_a = requestBody.capabilities) === null || _a === void 0 ? void 0 : _a.alwaysMatch) !== null && _b !== void 0 ? _b : requestBody.desiredCapabilities;
|
|
138
|
-
const options = {
|
|
139
|
-
...settings.capabilities,
|
|
140
|
-
...capabilities === null || capabilities === void 0 ? void 0 : capabilities['applitools:options'],
|
|
141
|
-
...(capabilities &&
|
|
142
|
-
Object.entries(capabilities).reduce((capabilities, [key, value]) => {
|
|
143
|
-
if (key.startsWith('applitools:')) {
|
|
144
|
-
capabilities[key.replace(/^applitools:/, '')] = value;
|
|
145
|
-
}
|
|
146
|
-
return capabilities;
|
|
147
|
-
}, {})),
|
|
148
|
-
};
|
|
149
|
-
const session = {
|
|
150
|
-
credentials: { eyesServerUrl: options.eyesServerUrl, apiKey: options.apiKey },
|
|
151
|
-
};
|
|
152
|
-
if (options.tunnel) {
|
|
153
|
-
session.tunnels = await tunnelManager.acquire(session.credentials);
|
|
154
|
-
session.tunnels.forEach((tunnel, index) => {
|
|
155
|
-
options[`x-tunnel-id-${index}`] = tunnel.tunnelId;
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
const applitoolsCapabilities = Object.fromEntries(Object.entries(options).map(([key, value]) => [`applitools:${key}`, value]));
|
|
159
|
-
if (requestBody.capabilities) {
|
|
160
|
-
requestBody.capabilities.alwaysMatch = { ...(_c = requestBody.capabilities) === null || _c === void 0 ? void 0 : _c.alwaysMatch, ...applitoolsCapabilities };
|
|
161
|
-
}
|
|
162
|
-
if (requestBody.desiredCapabilities) {
|
|
163
|
-
requestBody.desiredCapabilities = { ...requestBody.desiredCapabilities, ...applitoolsCapabilities };
|
|
164
|
-
}
|
|
165
|
-
logger.log('Request body has modified:', requestBody);
|
|
166
|
-
const queueKey = JSON.stringify(session.credentials);
|
|
167
|
-
let queue = queues.get(queueKey);
|
|
168
|
-
if (!queue) {
|
|
169
|
-
queue = (0, queue_1.makeQueue)({ logger: logger.extend({ tags: { queue: queueKey } }) });
|
|
170
|
-
queues.set(queueKey, queue);
|
|
171
|
-
}
|
|
172
|
-
request.socket.on('close', () => queue.cancel(task));
|
|
173
|
-
await queue.run(task);
|
|
174
|
-
async function task(signal, attempt = 1) {
|
|
175
|
-
var _a, _b, _c;
|
|
176
|
-
var _d;
|
|
177
|
-
// do not start the task if it is already aborted
|
|
178
|
-
if (signal.aborted)
|
|
179
|
-
return;
|
|
180
|
-
const proxyResponse = await req(request.url, {
|
|
181
|
-
body: requestBody,
|
|
182
|
-
io: { request, response, handle: false },
|
|
183
|
-
// TODO uncomment when we can throw different abort reasons for task cancelation and timeout abortion
|
|
184
|
-
// signal,
|
|
185
|
-
logger,
|
|
186
|
-
});
|
|
187
|
-
const responseBody = await proxyResponse.json();
|
|
188
|
-
logger.log(`Response was intercepted with body:`, responseBody);
|
|
189
|
-
if (RETRY_ERROR_CODES.includes((_b = (_a = responseBody.value) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.appliErrorCode)) {
|
|
190
|
-
queue.cork();
|
|
191
|
-
// after query is corked the task might be aborted
|
|
192
|
-
if (signal.aborted)
|
|
193
|
-
return;
|
|
194
|
-
await utils.general.sleep(RETRY_BACKOFF[Math.min(attempt, RETRY_BACKOFF.length - 1)]);
|
|
195
|
-
logger.log(`Attempt (${attempt}) to create session was failed with applitools status code:`, responseBody.value.data.appliErrorCode);
|
|
196
|
-
return task(signal, attempt + 1);
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
queue.uncork();
|
|
200
|
-
if (responseBody.value) {
|
|
201
|
-
sessions.set(responseBody.value.sessionId, session);
|
|
202
|
-
(_c = (_d = responseBody.value).capabilities) !== null && _c !== void 0 ? _c : (_d.capabilities = {});
|
|
203
|
-
responseBody.value.capabilities['applitools:isECClient'] = true;
|
|
204
|
-
if (proxyResponse.headers.has('content-length')) {
|
|
205
|
-
proxyResponse.headers.set('content-length', Buffer.byteLength(JSON.stringify(responseBody)).toString());
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
response
|
|
209
|
-
.writeHead(proxyResponse.status, Object.fromEntries(proxyResponse.headers.entries()))
|
|
210
|
-
.end(JSON.stringify(responseBody));
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async function deleteSession({ request, response, logger, }) {
|
|
216
|
-
const url = request.url;
|
|
217
|
-
const sessionId = getSessionId(url);
|
|
218
|
-
logger.log(`Request was intercepted with sessionId:`, sessionId);
|
|
219
|
-
await req(url, { io: { request, response }, logger });
|
|
220
|
-
const session = sessions.get(sessionId);
|
|
221
|
-
if (session.tunnels) {
|
|
222
|
-
await tunnelManager.release(session.tunnels);
|
|
223
|
-
logger.log(`Tunnels with id ${session.tunnels.map(tunnel => tunnel.tunnelId)} was released for session with id ${sessionId}`);
|
|
224
|
-
}
|
|
225
|
-
sessions.delete(sessionId);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
exports.makeServer = makeServer;
|
|
229
|
-
function getSessionId(requestUrl) {
|
|
230
|
-
var _a;
|
|
231
|
-
return (_a = requestUrl === null || requestUrl === void 0 ? void 0 : requestUrl.split('/')[2]) !== null && _a !== void 0 ? _a : '';
|
|
232
|
-
}
|
|
File without changes
|
|
File without changes
|