@testim/testim-cli 3.254.0 → 3.255.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/agent/routers/codim/router.test.js +9 -12
- package/agent/routers/codim/service.js +16 -16
- package/agent/routers/playground/service.js +5 -7
- package/cli.js +4 -4
- package/cliAgentMode.js +4 -3
- package/codim/codim-cli.js +10 -8
- package/commons/featureFlags.js +8 -0
- package/commons/httpRequest.js +5 -1
- package/commons/httpRequestCounters.js +21 -10
- package/commons/lazyRequire.js +4 -3
- package/commons/preloadTests.js +2 -2
- package/commons/prepareRunner.js +4 -2
- package/commons/prepareRunnerAndTestimStartUtils.js +40 -41
- package/commons/runnerFileCache.js +1 -1
- package/commons/testimTunnel.test.js +2 -1
- package/coverage/SummaryToObjectReport.js +0 -1
- package/coverage/jsCoverage.js +12 -10
- package/inputFileUtils.js +11 -9
- package/npm-shrinkwrap.json +187 -444
- package/package.json +4 -3
- package/player/services/tabService.js +15 -1
- package/player/stepActions/locateStepAction.js +2 -0
- package/player/utils/imageCaptureUtils.js +81 -120
- package/player/webdriver.js +25 -22
- package/reports/junitReporter.js +6 -7
- package/reports/reporter.js +34 -39
- package/runOptions.d.ts +260 -0
- package/runOptions.js +53 -38
- package/runner.js +2 -1
- package/runners/ParallelWorkerManager.js +9 -10
- package/runners/TestPlanRunner.js +5 -9
- package/services/gridService.js +36 -40
- package/testRunStatus.js +8 -5
- package/utils/argsUtils.js +86 -0
- package/utils/argsUtils.test.js +32 -0
- package/utils/fsUtils.js +154 -0
- package/utils/index.js +10 -161
- package/utils/promiseUtils.js +13 -2
- package/utils/stringUtils.js +4 -2
- package/utils/stringUtils.test.js +22 -0
- package/utils/timeUtils.js +25 -0
- package/utils/utils.test.js +0 -41
- package/workers/WorkerExtension.js +6 -7
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const router = require('./router');
|
|
4
|
-
|
|
5
4
|
const path = require('path');
|
|
6
5
|
const os = require('os');
|
|
7
6
|
const compression = require('compression');
|
|
8
7
|
const express = require('express');
|
|
9
|
-
|
|
10
|
-
const app = express();
|
|
11
8
|
const bodyParser = require('body-parser');
|
|
12
|
-
const Promise = require('bluebird');
|
|
13
|
-
const { promiseFromCallback } = require('../../../utils');
|
|
14
9
|
const superagent = require('superagent');
|
|
10
|
+
const fs = require('fs');
|
|
15
11
|
const { expect } = require('chai'); // eslint-disable-line import/no-extraneous-dependencies
|
|
16
12
|
|
|
17
|
-
const
|
|
13
|
+
const app = express();
|
|
18
14
|
|
|
19
15
|
app.use(bodyParser.urlencoded({ extended: false, limit: '50mb' }));
|
|
20
16
|
app.use(compression());
|
|
@@ -25,25 +21,26 @@ app.use('/files', router.router);
|
|
|
25
21
|
describe('codim router', () => {
|
|
26
22
|
let listener;
|
|
27
23
|
async function saveLocators(locatorsObject) {
|
|
28
|
-
const request = superagent.post(`http://localhost:${listener.address().port}/files/locators`)
|
|
29
|
-
|
|
30
|
-
await promiseFromCallback((callback) => request.end(callback));
|
|
24
|
+
const request = superagent.post(`http://localhost:${listener.address().port}/files/locators`).send(locatorsObject);
|
|
25
|
+
await request;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
async function loadLocators() {
|
|
34
29
|
const request = superagent.get(`http://localhost:${listener.address().port}/files/locators`);
|
|
35
|
-
|
|
30
|
+
const response = await request;
|
|
31
|
+
return JSON.parse(response.text);
|
|
36
32
|
}
|
|
37
33
|
|
|
38
34
|
let tmpDir;
|
|
35
|
+
|
|
39
36
|
before((done) => {
|
|
40
37
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tdk-test-'));
|
|
41
38
|
process.chdir(tmpDir);
|
|
42
|
-
listener = app.listen(0, done);
|
|
39
|
+
listener = app.listen(0, () => done());
|
|
43
40
|
});
|
|
44
41
|
|
|
45
42
|
after(async () => {
|
|
46
|
-
await fs.
|
|
43
|
+
await fs.promises.unlink(tmpDir).catch(() => {});
|
|
47
44
|
});
|
|
48
45
|
|
|
49
46
|
it('saves locators', async () => {
|
|
@@ -2,17 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const path = require('path');
|
|
5
|
-
const
|
|
6
|
-
const fs = Promise.promisifyAll(require('fs'));
|
|
5
|
+
const fsPromises = require('fs/promises');
|
|
7
6
|
const { fromPairs } = require('lodash');
|
|
8
7
|
const { buildCodeTests } = require('../../../runners/buildCodeTests');
|
|
9
8
|
const { AbortError } = require('../../../commons/AbortError');
|
|
10
9
|
|
|
11
10
|
const findTestFolder = _.memoize(async (fromFolder) => {
|
|
12
|
-
const files = await
|
|
11
|
+
const files = await fsPromises.readdir(fromFolder);
|
|
13
12
|
// this is either invoked by running the Testim CLI from inside the tests folder or from inside the `init` folder
|
|
14
13
|
// so deal with the case we're inside tests.
|
|
15
|
-
const isInProjectFolder = files.includes('tests') && (await
|
|
14
|
+
const isInProjectFolder = files.includes('tests') && (await fsPromises.stat(path.join(fromFolder, 'tests'))).isDirectory();
|
|
16
15
|
if (isInProjectFolder) {
|
|
17
16
|
return path.join(fromFolder, 'tests');
|
|
18
17
|
}
|
|
@@ -27,7 +26,7 @@ async function getLocalLocators() {
|
|
|
27
26
|
// eslint-disable-next-line no-eval
|
|
28
27
|
return eval(buffer.toString().replace(/require/g, '(x => /locator\.(.*)\.json/.exec(x)[1])'));
|
|
29
28
|
}
|
|
30
|
-
const locators = await
|
|
29
|
+
const locators = await fsPromises.readFile(locatorsFilePath).then(parseLocators, () => ({}));
|
|
31
30
|
return _(Object.keys(locators)).map((id) => {
|
|
32
31
|
const escapedId = id.replace(/"/g, '\\"');
|
|
33
32
|
return [escapedId, locators[id]];
|
|
@@ -36,7 +35,7 @@ async function getLocalLocators() {
|
|
|
36
35
|
|
|
37
36
|
async function findTests(folder = process.cwd()) {
|
|
38
37
|
const testFolder = await findTestFolder(folder);
|
|
39
|
-
const filesWithStat = await
|
|
38
|
+
const filesWithStat = await fsPromises.readdir(testFolder, { withFileTypes: true });
|
|
40
39
|
|
|
41
40
|
// things we know are not tests but end in js
|
|
42
41
|
const excluded = ['webpack.config.js', 'tsconfig.js', '.DS_Store', 'functions.js'];
|
|
@@ -70,13 +69,14 @@ async function getLocalLocatorContents(locators, full = false, originFolder = pr
|
|
|
70
69
|
if (full) {
|
|
71
70
|
const folder = await findTestFolder(originFolder);
|
|
72
71
|
for (const key of Object.values(locators)) {
|
|
73
|
-
props[key] =
|
|
72
|
+
props[key] = fsPromises.readFile(path.join(folder, 'locators', `locator.${key}.json`)).then(JSON.parse);
|
|
74
73
|
}
|
|
75
74
|
}
|
|
76
75
|
try {
|
|
77
76
|
const contents = await promiseFromProps(props);
|
|
78
77
|
return contents;
|
|
79
78
|
} catch (e) {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
80
|
console.error(e);
|
|
81
81
|
return {};
|
|
82
82
|
}
|
|
@@ -104,10 +104,10 @@ async function saveTest({
|
|
|
104
104
|
if (filename.endsWith('locators/locators.js')) {
|
|
105
105
|
throw new Error('Cannot override locators file from the internet as it is evaluated by the runner');
|
|
106
106
|
}
|
|
107
|
-
await
|
|
108
|
-
await
|
|
107
|
+
await fsPromises.writeFile(filename, body);
|
|
108
|
+
await fsPromises.mkdir(path.join(folder, 'locators')).catch(() => {});
|
|
109
109
|
for (const { id, body } of locators) {
|
|
110
|
-
await
|
|
110
|
+
await fsPromises.writeFile(path.join(folder, 'locators', `locator.${id}.json`), JSON.stringify(body));
|
|
111
111
|
}
|
|
112
112
|
const locatorMap = fromPairs(locators.map(({ name, id }) => [name, id]));
|
|
113
113
|
const localLocatorMap = await getLocalLocators();
|
|
@@ -120,15 +120,15 @@ async function writeLocators(locatorsFilePath, locatorMap) {
|
|
|
120
120
|
content += ` "${key}": require('./locator.${value}.json'),\n`;
|
|
121
121
|
}
|
|
122
122
|
content += '};';
|
|
123
|
-
await
|
|
123
|
+
await fsPromises.writeFile(locatorsFilePath, content);
|
|
124
124
|
}
|
|
125
125
|
async function saveLocators(locators, { mergeIntoExisting } = { mergeIntoExisting: false }) {
|
|
126
126
|
const folder = await findTestFolder(process.cwd());
|
|
127
127
|
const locatorsFilePath = path.join(folder, 'locators', 'locators.js');
|
|
128
|
-
await
|
|
128
|
+
await fsPromises.mkdir(path.join(folder, 'locators')).catch(() => {});
|
|
129
129
|
|
|
130
130
|
for (const { name, id, elementLocator } of locators) {
|
|
131
|
-
await
|
|
131
|
+
await fsPromises.writeFile(path.join(folder, 'locators', `locator.${id}.json`), JSON.stringify({ name, id, elementLocator }));
|
|
132
132
|
}
|
|
133
133
|
const locatorMap = fromPairs(locators.map(({ name, id }) => [name, id]));
|
|
134
134
|
if (mergeIntoExisting) {
|
|
@@ -139,14 +139,14 @@ async function saveLocators(locators, { mergeIntoExisting } = { mergeIntoExistin
|
|
|
139
139
|
await writeLocators(locatorsFilePath, locatorMap);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
async function compileFunctionsLibrary({ fileSystem, bypassWebpack } = {}, optionalAbortSignal) {
|
|
142
|
+
async function compileFunctionsLibrary({ fileSystem, bypassWebpack } = {}, optionalAbortSignal = undefined) {
|
|
143
143
|
const folder = await findTestFolder(process.cwd());
|
|
144
|
-
if (optionalAbortSignal
|
|
144
|
+
if (optionalAbortSignal?.aborted) {
|
|
145
145
|
throw new AbortError();
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
const functionsFile = path.join(folder, 'functions.js');
|
|
149
|
-
if (bypassWebpack
|
|
149
|
+
if (bypassWebpack?.testim) {
|
|
150
150
|
const Module = require('module');
|
|
151
151
|
// attempt to require without webpack compile - useful for puppeteer/selenium hybrid
|
|
152
152
|
const originalRequire = Module.prototype.require;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const util = require('util');
|
|
5
|
-
const writeFileAsync = util.promisify(require('fs').writeFile);
|
|
3
|
+
const fs = require('fs');
|
|
6
4
|
const path = require('path');
|
|
7
|
-
const { fork } = require('child_process');
|
|
8
5
|
const os = require('os');
|
|
6
|
+
const { fork } = require('child_process');
|
|
9
7
|
const { ClientError, PlaygroundCodeError } = require('../../../errors');
|
|
10
8
|
|
|
11
9
|
const CODE_TYPES = ['playwright', 'selenium', 'puppeteer'];
|
|
@@ -14,13 +12,13 @@ const runForks = {};
|
|
|
14
12
|
|
|
15
13
|
async function createTempFile(fileName, data, encoding = 'utf8') {
|
|
16
14
|
const fullPath = path.join(os.tmpdir(), fileName);
|
|
17
|
-
await
|
|
15
|
+
await fs.promises.writeFile(fullPath, data, encoding);
|
|
18
16
|
return fullPath;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
const forkAsync = (fileFullPath) => {
|
|
22
20
|
let gotResolved;
|
|
23
|
-
const promise = new Promise(resolve => gotResolved = resolve);
|
|
21
|
+
const promise = new Promise(resolve => { gotResolved = resolve; });
|
|
24
22
|
|
|
25
23
|
const child = fork(fileFullPath, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'] });
|
|
26
24
|
promise.child = child;
|
|
@@ -30,7 +28,7 @@ const forkAsync = (fileFullPath) => {
|
|
|
30
28
|
}
|
|
31
29
|
const { type, error } = message;
|
|
32
30
|
if (error && ['uncaughtException', 'unhandledRejection'].includes(type)) {
|
|
33
|
-
|
|
31
|
+
gotResolved({ error: Object.assign(new PlaygroundCodeError(), { innerStack: message.error.stack }) });
|
|
34
32
|
}
|
|
35
33
|
});
|
|
36
34
|
child.on('error', (error) => {
|
package/cli.js
CHANGED
|
@@ -68,14 +68,14 @@ async function main() {
|
|
|
68
68
|
require('./commons/logger').setProjectId(processedOptions.project);
|
|
69
69
|
require('./commons/runnerFileCache').setEncryptKey(typeof processedOptions.token === 'string' ? processedOptions.token : 'anonymous_encrypt_key');
|
|
70
70
|
|
|
71
|
-
if (processedOptions
|
|
71
|
+
if (utils.isInitCodimMode(processedOptions)) {
|
|
72
72
|
const codimCli = require('./codim/codim-cli');
|
|
73
73
|
return codimCli.init(processedOptions.initTestProject);
|
|
74
74
|
}
|
|
75
|
-
if (processedOptions
|
|
75
|
+
if (utils.isLoginMode(processedOptions)) {
|
|
76
76
|
return undefined;
|
|
77
77
|
}
|
|
78
|
-
if (processedOptions
|
|
78
|
+
if (utils.isCreatePrefetchedDataMode(processedOptions)) {
|
|
79
79
|
const runnerFileCache = require('./commons/runnerFileCache');
|
|
80
80
|
await runnerFileCache.clear();
|
|
81
81
|
await prepareRunner.initializeUserWithAuth(processedOptions);
|
|
@@ -92,7 +92,7 @@ async function main() {
|
|
|
92
92
|
return undefined;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
if (processedOptions
|
|
95
|
+
if (utils.isTunnelOnlyMode(processedOptions)) {
|
|
96
96
|
await testRunner.init(processedOptions);
|
|
97
97
|
await require('./commons/testimTunnel').serveTunneling(processedOptions);
|
|
98
98
|
return undefined;
|
package/cliAgentMode.js
CHANGED
|
@@ -32,9 +32,10 @@ module.exports = {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* @
|
|
35
|
+
* @type {(options: import('./runOptions').Options) => options is import('./runOptions').AgentModeOptions}
|
|
36
36
|
*/
|
|
37
37
|
function shouldStartAgentMode(options) {
|
|
38
|
+
// @ts-ignore should be `as any`
|
|
38
39
|
return options.agentMode;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -53,7 +54,7 @@ async function runAgentMode(options) {
|
|
|
53
54
|
// Consider moving that into the agent server and add endpoint to start browser?
|
|
54
55
|
testimStandaloneBrowser = await startTestimStandaloneBrowser(options);
|
|
55
56
|
} catch (e) {
|
|
56
|
-
if (e
|
|
57
|
+
if (e?.message?.includes('user data directory is already in use')) {
|
|
57
58
|
throw new ArgError('Please close all chrome browsers that were opened with "testim start" and try again');
|
|
58
59
|
}
|
|
59
60
|
throw e;
|
|
@@ -62,7 +63,7 @@ async function runAgentMode(options) {
|
|
|
62
63
|
|
|
63
64
|
const agentServer = require('./agent/server');
|
|
64
65
|
|
|
65
|
-
if (testimStandaloneBrowser
|
|
66
|
+
if (testimStandaloneBrowser?.webdriverApi) {
|
|
66
67
|
// if we're starting the agent here, pre-load the sessionPlayer so it loads faster
|
|
67
68
|
// on first play
|
|
68
69
|
const LOAD_PLAYER_DELAY = 6000;
|
package/codim/codim-cli.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const exec = promisifyAll(require('child_process')).execAsync;
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const childProcess = require('child_process');
|
|
8
7
|
const path = require('path');
|
|
9
8
|
const validateNpmPackageName = require('validate-npm-package-name');
|
|
10
9
|
const ArgError = require('../errors.js').ArgError;
|
|
10
|
+
const { promisify } = require('util');
|
|
11
|
+
|
|
12
|
+
const exec = promisify(childProcess.exec);
|
|
11
13
|
|
|
12
14
|
module.exports.init = async function init(name) {
|
|
13
15
|
const ora = require('ora');
|
|
@@ -25,7 +27,7 @@ module.exports.init = async function init(name) {
|
|
|
25
27
|
|
|
26
28
|
const fullPath = path.resolve(name);
|
|
27
29
|
|
|
28
|
-
if (
|
|
30
|
+
if (fs.existsSync(fullPath) && fs.readdirSync(fullPath).length !== 0) {
|
|
29
31
|
console.log(`${fullPath} is not empty. Quitting...`);
|
|
30
32
|
process.exit(1);
|
|
31
33
|
}
|
|
@@ -57,20 +59,20 @@ module.exports.init = async function init(name) {
|
|
|
57
59
|
|
|
58
60
|
let spinner = ora(`Creating new test project in ${dest}`).start();
|
|
59
61
|
|
|
60
|
-
await
|
|
62
|
+
await fs.promises.copyFile(source, dest);
|
|
61
63
|
|
|
62
64
|
const sourcePackageJson = path.join(__dirname, sourceFolder, 'package.json');
|
|
63
65
|
const destPackageJson = path.join(process.cwd(), name, 'package.json');
|
|
64
66
|
|
|
65
|
-
const packageContents = await
|
|
67
|
+
const packageContents = await fs.promises.readFile(sourcePackageJson);
|
|
66
68
|
|
|
67
69
|
const newPackageJson = packageContents.toString().replace('~testim-codeful-test-project~', packageName);
|
|
68
70
|
|
|
69
|
-
await
|
|
71
|
+
await fs.promises.writeFile(destPackageJson, newPackageJson);
|
|
70
72
|
|
|
71
73
|
const gitIgnore = 'node_modules';
|
|
72
74
|
const gitIgnoreFilePath = path.join(process.cwd(), name, '.gitignore');
|
|
73
|
-
await
|
|
75
|
+
await fs.promises.writeFile(gitIgnoreFilePath, gitIgnore);
|
|
74
76
|
|
|
75
77
|
spinner.succeed();
|
|
76
78
|
spinner = ora('Installing dependencies').start();
|
package/commons/featureFlags.js
CHANGED
|
@@ -67,6 +67,14 @@ class FeatureFlagsService {
|
|
|
67
67
|
Rox.setCustomStringProperty('projectId', projectId);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
setProjectType(projectType) {
|
|
71
|
+
Rox.setCustomStringProperty('projectType', projectType);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
setCompanyProductType(productType) {
|
|
75
|
+
Rox.setCustomStringProperty('productType', productType);
|
|
76
|
+
}
|
|
77
|
+
|
|
70
78
|
setCompanyId(companyId) {
|
|
71
79
|
Rox.setCustomStringProperty('companyId', companyId);
|
|
72
80
|
}
|
package/commons/httpRequest.js
CHANGED
|
@@ -79,7 +79,7 @@ function post({
|
|
|
79
79
|
.catch(logErrorAndRethrow('failed to post request', { url }));
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function postFullRes(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT, retry) {
|
|
82
|
+
function postFullRes(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT, retry = 0) {
|
|
83
83
|
const request = superagent
|
|
84
84
|
.post(url)
|
|
85
85
|
.send(body)
|
|
@@ -215,6 +215,10 @@ function put(url, body, headers = {}, timeout = DEFAULT_REQUEST_TIMEOUT) {
|
|
|
215
215
|
.catch(logErrorAndRethrow('failed to put request', { url }));
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* @param {string} url
|
|
220
|
+
* @returns {Promise<superagent.Response>}
|
|
221
|
+
*/
|
|
218
222
|
function download(url) {
|
|
219
223
|
logger.info('start to download', { url });
|
|
220
224
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
'use
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
|
-
const { sum } = require('lodash');
|
|
4
|
-
const Bluebird = require('bluebird');
|
|
5
|
-
const dns = require('dns');
|
|
6
3
|
const _ = require('lodash');
|
|
7
4
|
const config = require('./config');
|
|
5
|
+
const dns = require('dns').promises;
|
|
6
|
+
const Bluebird = require('bluebird');
|
|
7
|
+
const { sum } = require('lodash');
|
|
8
|
+
const { promiseMap } = require('../utils/promiseUtils');
|
|
8
9
|
|
|
9
10
|
const logger = require('./logger').getLogger('http-request-counters');
|
|
10
11
|
|
|
@@ -18,7 +19,7 @@ const testNetworkConnectivity = async () => {
|
|
|
18
19
|
const hostnames = ['www.google.com', 'www.facebook.com', 'www.microsoft.com', 'testim.io'];
|
|
19
20
|
try {
|
|
20
21
|
// If any of these domains resolve we consider the connectivity to be ok
|
|
21
|
-
const result = Boolean(await
|
|
22
|
+
const result = Boolean(await promiseMap(hostnames, host => dns.lookup(host)));
|
|
22
23
|
if (!result) {
|
|
23
24
|
networkConnectivityTestFailed = true;
|
|
24
25
|
}
|
|
@@ -40,18 +41,28 @@ const ttl = 60 * 1000 * 15;
|
|
|
40
41
|
|
|
41
42
|
module.exports.makeCounters = () => {
|
|
42
43
|
const counters = {
|
|
43
|
-
call: new Map(),
|
|
44
|
-
success: new Map(),
|
|
45
|
-
fail: new Map(),
|
|
44
|
+
/** @type {Map<string, number>} */ call: new Map(),
|
|
45
|
+
/** @type {Map<string, number>} */ success: new Map(),
|
|
46
|
+
/** @type {Map<string, number>} */ fail: new Map(),
|
|
46
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* @param {counters[keyof counters]} counter
|
|
50
|
+
* @param {string} key
|
|
51
|
+
*/
|
|
47
52
|
function update(counter, key) {
|
|
48
53
|
const result = counter.get(key) || 0;
|
|
49
54
|
counter.set(key, result + 1);
|
|
50
55
|
setTimeout(() => {
|
|
51
|
-
const
|
|
52
|
-
counter.set(key,
|
|
56
|
+
const _result = counter.get(key) || 1;
|
|
57
|
+
counter.set(key, _result - 1);
|
|
53
58
|
}, ttl);
|
|
54
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* @template T, TArgs
|
|
62
|
+
* @param {(...args: TArgs) => T} fn
|
|
63
|
+
* @param {string=} name
|
|
64
|
+
* @return {(...args: TArgs) => Bluebird<Awaited<T>>}
|
|
65
|
+
*/
|
|
55
66
|
function wrapWithMonitoring(fn, name = fn.name) {
|
|
56
67
|
return Bluebird.method(async function (...args) {
|
|
57
68
|
update(counters.call, name);
|
package/commons/lazyRequire.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const Bluebird = require('bluebird');
|
|
4
3
|
const npmWrapper = require('./npmWrapper');
|
|
5
4
|
const ora = require('ora');
|
|
6
5
|
const path = require('path');
|
|
@@ -76,10 +75,12 @@ async function lazyRequireImpl(dependency) {
|
|
|
76
75
|
return requireWithFallback(dependency);
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
function installAllLazyDependencies() {
|
|
78
|
+
async function installAllLazyDependencies() {
|
|
80
79
|
const allLazyDependencies = Object.keys(packageJson.lazyDependencies);
|
|
81
80
|
|
|
82
|
-
|
|
81
|
+
for (const dep of allLazyDependencies) {
|
|
82
|
+
await lazyRequireImpl(dep);
|
|
83
|
+
}
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
if (require.main === module) {
|
package/commons/preloadTests.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const Promise = require('bluebird');
|
|
2
1
|
const _ = require('lodash');
|
|
2
|
+
const { promiseMap } = require('../utils');
|
|
3
3
|
const localRunnerCache = require('./runnerFileCache');
|
|
4
4
|
const servicesApi = require('./testimServicesApi.js');
|
|
5
5
|
|
|
@@ -15,7 +15,7 @@ async function preloadTests(options) {
|
|
|
15
15
|
projectId: options.project,
|
|
16
16
|
};
|
|
17
17
|
return await localRunnerCache.memoize(async () => {
|
|
18
|
-
const results = await
|
|
18
|
+
const results = await promiseMap(options.testId, testId => servicesApi.loadTest({ ...opts, testId }), { concurrency: 2 });
|
|
19
19
|
return _.keyBy(results, 'testData.id');
|
|
20
20
|
}, 'loadTests', TEN_HOURS, [opts, options.testId])();
|
|
21
21
|
}
|
package/commons/prepareRunner.js
CHANGED
|
@@ -24,7 +24,7 @@ Promise.resolve().then(() => {
|
|
|
24
24
|
|
|
25
25
|
async function prepare(options) {
|
|
26
26
|
/**
|
|
27
|
-
* @type {Promise}
|
|
27
|
+
* @type {globalThis.Promise<void>}
|
|
28
28
|
*/
|
|
29
29
|
let chromedriverPromise = Promise.resolve();
|
|
30
30
|
|
|
@@ -35,7 +35,7 @@ async function prepare(options) {
|
|
|
35
35
|
chromedriverPromise = prepareRunnerAndTestimStartUtils.prepareChromeDriver(
|
|
36
36
|
{ projectId: options.project, userId: options.user },
|
|
37
37
|
{ chromeBinaryLocation: options.chromeBinaryLocation },
|
|
38
|
-
Boolean(options.lightweightMode
|
|
38
|
+
Boolean(options.lightweightMode?.general)
|
|
39
39
|
);
|
|
40
40
|
options.useLocalChromeDriver = true;
|
|
41
41
|
}
|
|
@@ -61,6 +61,8 @@ async function prepare(options) {
|
|
|
61
61
|
|
|
62
62
|
async function prepareMockNetwork(location) {
|
|
63
63
|
logger.info('prepare MockNetwork', { location });
|
|
64
|
+
/** @type {Buffer} */
|
|
65
|
+
// @ts-expect-error There seems to be an actual bug in case the location is a URL.
|
|
64
66
|
const rulesJsonBuf = await utils.getSourceAsBuffer(location);
|
|
65
67
|
if (rulesJsonBuf.byteLength > MAX_RULE_FILE_SIZE_IN_MB * 1000000) {
|
|
66
68
|
throw new Error(`${PREPARE_MOCK_NETWORK_ERROR_PREFIX} exceeded ${MAX_RULE_FILE_SIZE_IN_MB}MB`);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
// @ts-ignore
|
|
4
|
-
const Promise = require('bluebird');
|
|
5
3
|
const path = require('path');
|
|
6
|
-
const fs =
|
|
4
|
+
const fs = require('fs');
|
|
7
5
|
const ms = require('ms');
|
|
8
6
|
const { serializeError } = require('serialize-error');
|
|
9
7
|
const { additionalLogDetails } = require('./logUtils');
|
|
@@ -11,7 +9,15 @@ const { additionalLogDetails } = require('./logUtils');
|
|
|
11
9
|
const config = require('./config');
|
|
12
10
|
const { ArgError, NpmPermissionsError } = require('../errors');
|
|
13
11
|
const {
|
|
14
|
-
getCliLocation,
|
|
12
|
+
getCliLocation,
|
|
13
|
+
isURL,
|
|
14
|
+
downloadAndSave,
|
|
15
|
+
getSource,
|
|
16
|
+
getLocalFileSizeInMB,
|
|
17
|
+
download,
|
|
18
|
+
unzipFile,
|
|
19
|
+
getSourcePath,
|
|
20
|
+
promiseMap,
|
|
15
21
|
} = require('../utils');
|
|
16
22
|
const localRunnerCache = require('./runnerFileCache');
|
|
17
23
|
const logger = require('./logger').getLogger('prepare runner and testim start');
|
|
@@ -31,63 +37,58 @@ module.exports = {
|
|
|
31
37
|
/**
|
|
32
38
|
* @param {string} location
|
|
33
39
|
*/
|
|
34
|
-
function prepareCustomExtension(location, unlimitedSize = false) {
|
|
40
|
+
async function prepareCustomExtension(location, unlimitedSize = false) {
|
|
35
41
|
if (!location) {
|
|
36
|
-
return
|
|
42
|
+
return undefined;
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
if (isURL(location)) {
|
|
40
46
|
const destFile = path.join(process.cwd(), location.replace(/^.*[\\\/]/, ''));
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
})
|
|
48
|
-
.then(() => destFile);
|
|
47
|
+
const contentLength = await getRemoteFileSizeInMB(location);
|
|
48
|
+
if (contentLength > MAX_CUSTOM_EXT_SIZE_MB && !unlimitedSize) {
|
|
49
|
+
throw new ArgError(MAX_CUSTOM_SIZE_ERROR_MSG);
|
|
50
|
+
}
|
|
51
|
+
await downloadAndSave(location, destFile);
|
|
52
|
+
return destFile;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
const destFile = path.resolve(location);
|
|
52
56
|
if (!fs.existsSync(destFile)) {
|
|
53
|
-
|
|
57
|
+
throw new ArgError(`Failed to find custom extension in location: ${destFile}`);
|
|
54
58
|
}
|
|
55
59
|
const fileSize = getLocalFileSizeInMB(destFile);
|
|
56
60
|
if (fileSize > MAX_CUSTOM_EXT_SIZE_MB && !unlimitedSize) {
|
|
57
|
-
|
|
61
|
+
throw new ArgError(MAX_CUSTOM_SIZE_ERROR_MSG);
|
|
58
62
|
}
|
|
59
|
-
return
|
|
63
|
+
return destFile;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
|
|
63
67
|
/**
|
|
64
68
|
* @param {string} url
|
|
65
69
|
*/
|
|
66
|
-
function getRemoteFileSizeInMB(url) {
|
|
70
|
+
async function getRemoteFileSizeInMB(url) {
|
|
67
71
|
const httpRequest = require('./httpRequest');
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
});
|
|
72
|
+
try {
|
|
73
|
+
const res = await httpRequest.head(url);
|
|
74
|
+
const contentLengthHeader = res.headers['content-length'];
|
|
75
|
+
const contentLengthBytes = contentLengthHeader ? parseInt(contentLengthHeader, 10) : 0;
|
|
76
|
+
return contentLengthBytes / 1000000;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
logger.warn('failed to download custom extension', { err });
|
|
79
|
+
throw new ArgError(`Failed to download custom extension from location: ${url}`);
|
|
80
|
+
}
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
|
-
*
|
|
82
84
|
* @param {string[]} locations
|
|
83
|
-
*
|
|
84
85
|
*/
|
|
85
86
|
function prepareExtension(locations) {
|
|
86
87
|
logger.info('prepare extension', { locations });
|
|
87
88
|
|
|
88
89
|
const fullLocations = locations.map(location => ({ location, path: getSourcePath(location) }));
|
|
89
90
|
return localRunnerCache.memoize(
|
|
90
|
-
() =>
|
|
91
|
+
() => promiseMap(fullLocations, ({ location, path }) => getSource(location, path)),
|
|
91
92
|
'prepareExtension',
|
|
92
93
|
MSEC_IN_HALF_DAY,
|
|
93
94
|
fullLocations
|
|
@@ -131,25 +132,23 @@ async function prepareChromeDriver(userDetails = {}, driverOptions = {}, skipIsR
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
function getPlayerVersion() {
|
|
135
|
+
async function getPlayerVersion() {
|
|
135
136
|
const url = `${config.BLOB_URL}/extension/sessionPlayer_LATEST_RELEASE`;
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
const res = await download(url);
|
|
138
|
+
return res.body.toString('utf8');
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
/**
|
|
141
142
|
* @param {string} location
|
|
142
143
|
* @param {string | undefined} canary
|
|
143
|
-
*
|
|
144
|
-
* @returns {Promise<string>}
|
|
145
144
|
*/
|
|
146
|
-
function getPlayerLocation(location, canary) {
|
|
145
|
+
async function getPlayerLocation(location, canary) {
|
|
147
146
|
if (!isURL(location) || (isURL(location) && canary) || config.IS_ON_PREM) {
|
|
148
|
-
return
|
|
147
|
+
return location;
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
const ver = await getPlayerVersion();
|
|
151
|
+
return `${location}-${ver}`;
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
function getSessionPlayerFolder() {
|
|
@@ -178,7 +177,7 @@ async function downloadAndUnzip(loc, playerFileName, isRetry = false) {
|
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
179
|
|
|
181
|
-
function preparePlayer(location, canary) {
|
|
180
|
+
async function preparePlayer(location, canary) {
|
|
182
181
|
logger.info('prepare player', { location, canary });
|
|
183
182
|
const playerFileName = getPlayerDestination();
|
|
184
183
|
return localRunnerCache.memoize(
|
|
@@ -96,7 +96,7 @@ function memoize(fn, fnName, duration = THREE_HOURS, parameters = undefined) {
|
|
|
96
96
|
}
|
|
97
97
|
logger.debug('cache miss:', { fnName });
|
|
98
98
|
if (!cacheMissAllowed) {
|
|
99
|
-
throw new Error(`
|
|
99
|
+
throw new Error(`Attempted to rebuild cache for ${originalFnName}. cache miss is not allowed with current run configuration`);
|
|
100
100
|
}
|
|
101
101
|
const value = await fn();
|
|
102
102
|
if (value) {
|
|
@@ -66,7 +66,8 @@ describe('testimTunnel', () => {
|
|
|
66
66
|
|
|
67
67
|
it('should handle connect errors', async () => {
|
|
68
68
|
ltConnectStub.rejects('error');
|
|
69
|
-
|
|
69
|
+
const connectPromise = testimTunnel.connect({ tunnel: true, gridData: { type: 'testimLambdaTest', tunnel: 'lambdatest' } });
|
|
70
|
+
await expect(connectPromise).to.be.rejectedWith('Failed to start tunnel. Please contact support@testim.io');
|
|
70
71
|
});
|
|
71
72
|
});
|
|
72
73
|
|