@testim/testim-cli 3.254.0 → 3.256.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/cliJsCode/service.js +11 -8
- 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 +6 -6
- package/cliAgentMode.js +11 -10
- package/codim/codim-cli.js +14 -9
- package/commons/featureFlags.js +29 -7
- package/commons/httpRequest.js +5 -1
- package/commons/httpRequestCounters.js +21 -10
- package/commons/initializeUserWithAuth.js +7 -4
- package/commons/lazyRequire.js +4 -3
- package/commons/preloadTests.js +6 -3
- package/commons/prepareRunner.js +7 -5
- package/commons/prepareRunnerAndTestimStartUtils.js +51 -45
- package/commons/runnerFileCache.js +10 -2
- package/commons/testimServicesApi.js +36 -5
- 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 +214 -471
- package/package.json +4 -3
- package/player/services/tabService.js +15 -1
- package/player/stepActions/apiStepAction.js +49 -43
- package/player/stepActions/baseCliJsStepAction.js +19 -14
- package/player/stepActions/baseJsStepAction.js +9 -8
- package/player/stepActions/dropFileStepAction.js +1 -3
- package/player/stepActions/inputFileStepAction.js +10 -8
- package/player/stepActions/locateStepAction.js +2 -0
- package/player/stepActions/mouseStepAction.js +21 -22
- package/player/stepActions/nodePackageStepAction.js +34 -35
- package/player/stepActions/stepAction.js +1 -0
- package/player/utils/imageCaptureUtils.js +133 -172
- package/player/utils/screenshotUtils.js +16 -13
- package/player/utils/windowUtils.js +20 -8
- package/player/webdriver.js +25 -22
- package/processHandler.js +4 -0
- package/reports/junitReporter.js +6 -7
- package/reports/reporter.js +34 -39
- package/runOptions.d.ts +286 -0
- package/runOptions.js +60 -45
- package/runner.js +64 -24
- package/runners/ParallelWorkerManager.js +12 -12
- package/runners/TestPlanRunner.js +14 -15
- package/runners/buildCodeTests.js +1 -0
- package/runners/runnerUtils.js +11 -2
- package/services/branchService.js +11 -5
- package/services/gridService.js +36 -40
- package/services/localRCASaver.js +4 -0
- 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
|
@@ -40,10 +40,6 @@ function runCode(transactionId, incomingParams, context, code, packageLocalLocat
|
|
|
40
40
|
return all;
|
|
41
41
|
}, '');
|
|
42
42
|
|
|
43
|
-
if (fileDataUrl === 'data:') { // fix chrome/safari bug that creates malformed datauri for empty files
|
|
44
|
-
fileDataUrl = 'data:,';
|
|
45
|
-
}
|
|
46
|
-
|
|
47
43
|
const fileDataUrlToFileBuffer = !fileDataUrl ? 'var fileBuffer = null;' :
|
|
48
44
|
`
|
|
49
45
|
${dataUriToBuffer.toString()}
|
|
@@ -297,10 +293,6 @@ function runCodeWithWorkerThread(transactionId, incomingParams, context, code, p
|
|
|
297
293
|
return all;
|
|
298
294
|
}, '');
|
|
299
295
|
|
|
300
|
-
if (fileDataUrl === 'data:') { // fix chrome/safari bug that creates malformed datauri for empty files
|
|
301
|
-
fileDataUrl = 'data:,';
|
|
302
|
-
}
|
|
303
|
-
|
|
304
296
|
const fileDataUrlToFileBuffer = !fileDataUrl ? 'var fileBuffer = null;' :
|
|
305
297
|
`
|
|
306
298
|
${dataUriToBuffer.toString()}
|
|
@@ -581,6 +573,17 @@ function runCodeWithPackages(code, stepId, incomingParams, context, testResultId
|
|
|
581
573
|
}
|
|
582
574
|
}
|
|
583
575
|
|
|
576
|
+
const emptyFileDataUrl = 'data:,';
|
|
577
|
+
if (fileDataUrl === 'data:') { // Fix chrome/safari bug that creates malformed data-uri for empty files
|
|
578
|
+
logger.debug('runCodeWithPackages, fileDataUrl was an empty string ', { fileDataUrl });
|
|
579
|
+
fileDataUrl = emptyFileDataUrl;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (Buffer.isBuffer(fileDataUrl)) { // Fix, S3 is returning a buffer and not a string on empty files
|
|
583
|
+
logger.debug('runCodeWithPackages, fileDataUrl was an empty Buffer ', { fileDataUrl });
|
|
584
|
+
fileDataUrl = emptyFileDataUrl;
|
|
585
|
+
}
|
|
586
|
+
|
|
584
587
|
if (workerThreads && featureFlags.flags.enableWorkerThreadsCliCodeExecution.isEnabled()) {
|
|
585
588
|
return runCodeWithWorkerThread(transactionId, incomingParams, context, code, packageLocalLocations, timeout, fileDataUrl);
|
|
586
589
|
}
|
|
@@ -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);
|
|
@@ -85,14 +85,14 @@ async function main() {
|
|
|
85
85
|
}
|
|
86
86
|
const res = await runnerFileCache.waitForSave();
|
|
87
87
|
if (res.success) {
|
|
88
|
-
console.log(`created
|
|
88
|
+
console.log(`created prefetched data at ${runnerFileCache.getCacheFileLocation()}`);
|
|
89
89
|
} else {
|
|
90
|
-
console.error('failed to create
|
|
90
|
+
console.error('failed to create prefetch data', res.error);
|
|
91
91
|
}
|
|
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,16 +32,14 @@ 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
|
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
* @param {*} options
|
|
44
|
-
*/
|
|
42
|
+
/** @param {import('./runOptions').AgentModeOptions} options */
|
|
45
43
|
async function runAgentMode(options) {
|
|
46
44
|
let testimStandaloneBrowser;
|
|
47
45
|
|
|
@@ -53,7 +51,7 @@ async function runAgentMode(options) {
|
|
|
53
51
|
// Consider moving that into the agent server and add endpoint to start browser?
|
|
54
52
|
testimStandaloneBrowser = await startTestimStandaloneBrowser(options);
|
|
55
53
|
} catch (e) {
|
|
56
|
-
if (e
|
|
54
|
+
if (e?.message?.includes('user data directory is already in use')) {
|
|
57
55
|
throw new ArgError('Please close all chrome browsers that were opened with "testim start" and try again');
|
|
58
56
|
}
|
|
59
57
|
throw e;
|
|
@@ -62,7 +60,7 @@ async function runAgentMode(options) {
|
|
|
62
60
|
|
|
63
61
|
const agentServer = require('./agent/server');
|
|
64
62
|
|
|
65
|
-
if (testimStandaloneBrowser
|
|
63
|
+
if (testimStandaloneBrowser?.webdriverApi) {
|
|
66
64
|
// if we're starting the agent here, pre-load the sessionPlayer so it loads faster
|
|
67
65
|
// on first play
|
|
68
66
|
const LOAD_PLAYER_DELAY = 6000;
|
|
@@ -168,6 +166,7 @@ async function startFixedVersionChromium(options, extensionBase64, downloadedExt
|
|
|
168
166
|
};
|
|
169
167
|
}
|
|
170
168
|
|
|
169
|
+
/** @param {import('./runOptions').AgentModeOptions} options */
|
|
171
170
|
async function startTestimStandaloneBrowser(options) {
|
|
172
171
|
// After next clickim release we will have also testim-full.zip
|
|
173
172
|
// const fullExtensionUrl = "https://testimstatic.blob.core.windows.net/extension/testim-full-master.zip";
|
|
@@ -221,9 +220,11 @@ async function startTestimStandaloneBrowser(options) {
|
|
|
221
220
|
}
|
|
222
221
|
await prepareUtils.prepareChromeDriver(
|
|
223
222
|
{ projectId: options.project },
|
|
223
|
+
// @ts-expect-error not clear where chromeBinaryLocations comes from
|
|
224
224
|
{ chromeBinaryLocation: options.chromeBinaryLocations },
|
|
225
225
|
);
|
|
226
226
|
|
|
227
|
+
// @ts-expect-error not clear where chromeBinaryLocations comes from
|
|
227
228
|
const seleniumOptions = buildSeleniumOptions(USER_DATA_DIR, extensionBase64, options.extensionPath, options.chromeBinaryLocations);
|
|
228
229
|
|
|
229
230
|
const WebDriver = require('./player/webdriver');
|
|
@@ -263,7 +264,7 @@ async function startTestimStandaloneBrowser(options) {
|
|
|
263
264
|
|
|
264
265
|
/**
|
|
265
266
|
* @param {string} userDataDir
|
|
266
|
-
* @param {string} fullExtensionPath
|
|
267
|
+
* @param {string | null} fullExtensionPath
|
|
267
268
|
*/
|
|
268
269
|
function buildSeleniumOptions(userDataDir, fullExtensionPath, unpackedExtensionPath, chromeBinaryPath) {
|
|
269
270
|
const extensions = fullExtensionPath ? [fullExtensionPath] : [];
|
|
@@ -355,7 +356,7 @@ async function readAndValidateChromedriverDevToolsActivePortFile() {
|
|
|
355
356
|
|
|
356
357
|
/**
|
|
357
358
|
* @param {string | import("url").URL} webSocketDebuggerUrl
|
|
358
|
-
* @param {number
|
|
359
|
+
* @param {number=} timeout
|
|
359
360
|
*/
|
|
360
361
|
async function tryToCloseBrowserWithCDPUrl(webSocketDebuggerUrl, timeout = 100) {
|
|
361
362
|
const websocketConnection = await wsConnectAndOpen(webSocketDebuggerUrl, timeout);
|
|
@@ -370,7 +371,7 @@ async function tryToCloseBrowserWithCDPUrl(webSocketDebuggerUrl, timeout = 100)
|
|
|
370
371
|
|
|
371
372
|
/**
|
|
372
373
|
* @param {string | import("url").URL} webSocketDebuggerUrl
|
|
373
|
-
* @param {number
|
|
374
|
+
* @param {number=} timeout
|
|
374
375
|
*/
|
|
375
376
|
async function wsConnectAndOpen(webSocketDebuggerUrl, timeout = 100) {
|
|
376
377
|
const websocket = new WebSocket(webSocketDebuggerUrl, { timeout });
|
package/codim/codim-cli.js
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
/* eslint-disable no-console */
|
|
2
4
|
|
|
3
5
|
'use strict';
|
|
4
6
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const exec = promisifyAll(require('child_process')).execAsync;
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const childProcess = require('child_process');
|
|
8
9
|
const path = require('path');
|
|
9
10
|
const validateNpmPackageName = require('validate-npm-package-name');
|
|
10
|
-
const
|
|
11
|
+
const { promisify } = require('util');
|
|
12
|
+
const { ArgError } = require('../errors.js');
|
|
13
|
+
|
|
14
|
+
const exec = promisify(childProcess.exec);
|
|
11
15
|
|
|
16
|
+
/** @param {string} name */
|
|
12
17
|
module.exports.init = async function init(name) {
|
|
13
18
|
const ora = require('ora');
|
|
14
19
|
const prompts = require('prompts');
|
|
@@ -25,7 +30,7 @@ module.exports.init = async function init(name) {
|
|
|
25
30
|
|
|
26
31
|
const fullPath = path.resolve(name);
|
|
27
32
|
|
|
28
|
-
if (
|
|
33
|
+
if (fs.existsSync(fullPath) && fs.readdirSync(fullPath).length !== 0) {
|
|
29
34
|
console.log(`${fullPath} is not empty. Quitting...`);
|
|
30
35
|
process.exit(1);
|
|
31
36
|
}
|
|
@@ -57,20 +62,20 @@ module.exports.init = async function init(name) {
|
|
|
57
62
|
|
|
58
63
|
let spinner = ora(`Creating new test project in ${dest}`).start();
|
|
59
64
|
|
|
60
|
-
await
|
|
65
|
+
await fs.promises.copyFile(source, dest);
|
|
61
66
|
|
|
62
67
|
const sourcePackageJson = path.join(__dirname, sourceFolder, 'package.json');
|
|
63
68
|
const destPackageJson = path.join(process.cwd(), name, 'package.json');
|
|
64
69
|
|
|
65
|
-
const packageContents = await
|
|
70
|
+
const packageContents = await fs.promises.readFile(sourcePackageJson);
|
|
66
71
|
|
|
67
72
|
const newPackageJson = packageContents.toString().replace('~testim-codeful-test-project~', packageName);
|
|
68
73
|
|
|
69
|
-
await
|
|
74
|
+
await fs.promises.writeFile(destPackageJson, newPackageJson);
|
|
70
75
|
|
|
71
76
|
const gitIgnore = 'node_modules';
|
|
72
77
|
const gitIgnoreFilePath = path.join(process.cwd(), name, '.gitignore');
|
|
73
|
-
await
|
|
78
|
+
await fs.promises.writeFile(gitIgnoreFilePath, gitIgnore);
|
|
74
79
|
|
|
75
80
|
spinner.succeed();
|
|
76
81
|
spinner = ora('Installing dependencies').start();
|
package/commons/featureFlags.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { ROLLOUT_KEY, IS_ON_PREM, GATEWAY_URL } = require('./config');
|
|
4
|
-
const logger = require('./logger').getLogger('FeatureFlagsService');
|
|
5
|
-
const Promise = require('bluebird');
|
|
6
3
|
const Rox = require('rox-node');
|
|
4
|
+
const { getLogger } = require('./logger');
|
|
5
|
+
const { promiseTimeout } = require('../utils');
|
|
6
|
+
const { ROLLOUT_KEY, IS_ON_PREM, GATEWAY_URL } = require('./config');
|
|
7
|
+
|
|
8
|
+
const logger = getLogger('FeatureFlagsService');
|
|
7
9
|
|
|
8
10
|
// IS_UNIT_TEST = disable rollout if code run in unit test mode to ignore mocha process stuck on running
|
|
9
11
|
const USE_FEATURE_FLAGS = !IS_ON_PREM && !process.env.IS_UNIT_TEST && !GATEWAY_URL;
|
|
@@ -11,6 +13,7 @@ const USE_FEATURE_FLAGS = !IS_ON_PREM && !process.env.IS_UNIT_TEST && !GATEWAY_U
|
|
|
11
13
|
const FORCE_FETCH_TIMEOUT_MS = 20000; // rollout sometimes takes up to 15 seconds to load
|
|
12
14
|
const SEC_IN_DAY = 60 * 60 * 24;
|
|
13
15
|
|
|
16
|
+
/** @type {['labs', 'disabled', 'enabled'] as const} */
|
|
14
17
|
const LAB_FEATURE_FLAG_VALUES = ['labs', 'disabled', 'enabled'];
|
|
15
18
|
|
|
16
19
|
class LabFeatureFlag extends Rox.Variant {
|
|
@@ -18,6 +21,7 @@ class LabFeatureFlag extends Rox.Variant {
|
|
|
18
21
|
super(initialValue, LAB_FEATURE_FLAG_VALUES);
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
/** @returns {(typeof LAB_FEATURE_FLAG_VALUES)[number]} */
|
|
21
25
|
getValue() {
|
|
22
26
|
const value = super.getValue();
|
|
23
27
|
if (!LAB_FEATURE_FLAG_VALUES.includes(value)) {
|
|
@@ -63,33 +67,51 @@ class FeatureFlagsService {
|
|
|
63
67
|
Rox.register('default', this.flags);
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
/** @param {string} projectId */
|
|
66
71
|
setProjectId(projectId) {
|
|
67
72
|
Rox.setCustomStringProperty('projectId', projectId);
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
/** @param {string} projectType */
|
|
76
|
+
setProjectType(projectType) {
|
|
77
|
+
Rox.setCustomStringProperty('projectType', projectType);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @param {string} productType */
|
|
81
|
+
setCompanyProductType(productType) {
|
|
82
|
+
Rox.setCustomStringProperty('productType', productType);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @param {string} companyId */
|
|
70
86
|
setCompanyId(companyId) {
|
|
71
87
|
Rox.setCustomStringProperty('companyId', companyId);
|
|
72
88
|
}
|
|
73
89
|
|
|
90
|
+
/** @param {string} planType */
|
|
74
91
|
setPlanType(planType) {
|
|
75
92
|
Rox.setCustomStringProperty('planType', planType);
|
|
76
93
|
}
|
|
77
94
|
|
|
95
|
+
/** @param {boolean} isPOC */
|
|
78
96
|
setIsPOC(isPOC) {
|
|
79
97
|
Rox.setCustomBooleanProperty('isPOC', isPOC);
|
|
80
98
|
}
|
|
99
|
+
|
|
100
|
+
/** @param {boolean} isStartUp */
|
|
81
101
|
setIsStartUp(isStartUp) {
|
|
82
102
|
Rox.setCustomBooleanProperty('isStartUp', isStartUp);
|
|
83
103
|
}
|
|
84
104
|
|
|
105
|
+
/** @param {string} mode */
|
|
85
106
|
setRunnerMode(mode) {
|
|
86
107
|
Rox.setCustomStringProperty('runnerMode', mode);
|
|
87
108
|
}
|
|
88
109
|
|
|
89
|
-
fetch() {
|
|
110
|
+
async fetch() {
|
|
90
111
|
if (!USE_FEATURE_FLAGS) {
|
|
91
|
-
return
|
|
112
|
+
return undefined;
|
|
92
113
|
}
|
|
114
|
+
/** @type {Rox.RoxSetupOptions} */
|
|
93
115
|
const opts = {
|
|
94
116
|
fetchIntervalInSec: SEC_IN_DAY, // we don't actually want to refresh feature flags in the CLI,
|
|
95
117
|
disableNetworkFetch: false,
|
|
@@ -102,8 +124,8 @@ class FeatureFlagsService {
|
|
|
102
124
|
opts.httpAgent = agent;
|
|
103
125
|
}
|
|
104
126
|
|
|
105
|
-
return
|
|
106
|
-
.
|
|
127
|
+
return promiseTimeout(Rox.setup(ROLLOUT_KEY, opts), FORCE_FETCH_TIMEOUT_MS)
|
|
128
|
+
.catch(err => logger.error('failed to get feature flag status', err));
|
|
107
129
|
}
|
|
108
130
|
}
|
|
109
131
|
|
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<Omit<superagent.Response, 'body'> & { body: Buffer }>}
|
|
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);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
const perf = require('./performance-logger');
|
|
3
3
|
const localRunnerCache = require('./runnerFileCache');
|
|
4
|
-
const servicesApi = require('./testimServicesApi
|
|
4
|
+
const servicesApi = require('./testimServicesApi');
|
|
5
5
|
const testimCustomToken = require('./testimCustomToken');
|
|
6
6
|
const { CLI_MODE } = require('./constants');
|
|
7
7
|
|
|
@@ -24,12 +24,15 @@ function preloadSlowRequires(mode) {
|
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* @param {import('../runOptions').RunnerOptions} options
|
|
29
|
+
*/
|
|
27
30
|
async function initializeUserWithAuth(options) {
|
|
28
31
|
const { project, token, lightweightMode, useLocalChromeDriver, useChromeLauncher, mode } = options;
|
|
29
32
|
|
|
30
|
-
const
|
|
33
|
+
const lightweightModeGeneral = Boolean(lightweightMode?.general);
|
|
31
34
|
const localGrid = Boolean(useLocalChromeDriver || useChromeLauncher);
|
|
32
|
-
const memoizationTTL =
|
|
35
|
+
const memoizationTTL = lightweightModeGeneral ? TEN_HOURS_MS : FIVE_MINUTES_MS;
|
|
33
36
|
|
|
34
37
|
perf.log('before initializeUserWithAuth');
|
|
35
38
|
const result = await localRunnerCache.memoize(async () => {
|
|
@@ -41,7 +44,7 @@ async function initializeUserWithAuth(options) {
|
|
|
41
44
|
lightweightMode,
|
|
42
45
|
localGrid,
|
|
43
46
|
});
|
|
44
|
-
}, 'initializeUserWithAuth', memoizationTTL, { project, token, branchName: options.branch,
|
|
47
|
+
}, 'initializeUserWithAuth', memoizationTTL, { project, token, branchName: options.branch, lightweightModeGeneral, localGrid })();
|
|
45
48
|
perf.log('after initializeUserWithAuth');
|
|
46
49
|
|
|
47
50
|
testimCustomToken.initFromData(result.authData, options.project, options.token);
|
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) {
|