ui5-test-runner 6.0.0-beta.5 → 6.0.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/README.md +20 -29
- package/dist/Command.js +52 -0
- package/dist/Npm.js +154 -74
- package/dist/browsers/RecoverableBrowserError.js +6 -0
- package/dist/browsers/chromeArguments.js +5 -0
- package/dist/browsers/chromeOptions.js +29 -0
- package/dist/browsers/consoleMessage.js +23 -0
- package/dist/browsers/factory.js +168 -3
- package/dist/browsers/networkResponse.js +15 -0
- package/dist/browsers/playwright.js +104 -0
- package/dist/browsers/puppeteer.js +54 -88
- package/dist/browsers/seleniumWebdriver.js +124 -0
- package/dist/browsers/webdriverio.js +85 -0
- package/dist/cli.js +18 -12
- package/dist/configuration/CommandLine.js +46 -26
- package/dist/configuration/ConfigurationValidator.js +132 -18
- package/dist/configuration/OptionValidationError.js +50 -2
- package/dist/configuration/derivations.js +8 -0
- package/dist/configuration/options.js +380 -23
- package/dist/configuration/validations.js +135 -0
- package/dist/configuration/validators/boolean.js +1 -1
- package/dist/configuration/validators/enumeration.js +7 -0
- package/dist/configuration/validators/fsEntry.js +12 -12
- package/dist/configuration/validators/index.js +8 -2
- package/dist/configuration/validators/integer.js +7 -1
- package/dist/configuration/validators/json.js +23 -0
- package/dist/configuration/validators/libraryMapping.js +28 -0
- package/dist/configuration/validators/percent.js +2 -2
- package/dist/configuration/validators/regexp.js +2 -2
- package/dist/configuration/validators/secret.js +8 -0
- package/dist/configuration/validators/string.js +1 -1
- package/dist/configuration/validators/timeout.js +2 -2
- package/dist/configuration/validators/url.js +3 -3
- package/dist/end.js +58 -0
- package/dist/if.js +22 -0
- package/dist/modes/Modes.js +2 -1
- package/dist/modes/batch/BatchItem.js +1 -0
- package/dist/modes/batch/batchTask.js +159 -0
- package/dist/modes/batch/index.js +98 -0
- package/dist/modes/batch/output.js +22 -0
- package/dist/modes/batch/report.js +45 -0
- package/dist/modes/batch/resolve.js +105 -0
- package/dist/modes/dumpConfig.js +6 -0
- package/dist/modes/execute.js +6 -5
- package/dist/modes/help.js +86 -2
- package/dist/modes/log/LogReader.js +3 -2
- package/dist/modes/log/LogStorage.js +31 -27
- package/dist/modes/log/index.js +38 -6
- package/dist/modes/log/{REserve.js → reserve.js} +25 -2
- package/dist/modes/mcp/IDocument.js +2 -0
- package/dist/modes/mcp/frontmatter.js +142 -0
- package/dist/modes/mcp/index.js +49 -0
- package/dist/modes/mcp/knowledgeBase.js +104 -0
- package/dist/modes/mcp/remoteKnowledgeBase.js +79 -0
- package/dist/modes/mcp/reserve.js +175 -0
- package/dist/modes/mcp/tools/getTopic.js +46 -0
- package/dist/modes/mcp/tools/index.js +6 -0
- package/dist/modes/mcp/tools/listTopics.js +14 -0
- package/dist/modes/test/PageContext.js +1 -0
- package/dist/modes/test/Server.js +99 -0
- package/dist/modes/test/browser.js +31 -17
- package/dist/modes/test/browserConfig.js +17 -0
- package/dist/modes/test/coverage/collect.js +38 -0
- package/dist/modes/test/coverage/index.js +3 -0
- package/dist/modes/test/coverage/instrument.js +70 -0
- package/dist/modes/test/coverage/nyc.js +15 -0
- package/dist/modes/test/coverage/report.js +71 -0
- package/dist/modes/test/coverage/settings.js +53 -0
- package/dist/modes/test/index.js +94 -34
- package/dist/modes/test/pageTask.js +293 -98
- package/dist/modes/test/report.js +20 -3
- package/dist/modes/test/reserve.js +76 -0
- package/dist/modes/test/screenshot.js +49 -0
- package/dist/platform/Crypto.js +9 -0
- package/dist/platform/Exit.js +33 -24
- package/dist/platform/FileSystem.js +6 -2
- package/dist/platform/Host.js +9 -1
- package/dist/platform/Http.js +54 -29
- package/dist/platform/Module.js +2 -1
- package/dist/platform/Path.js +5 -1
- package/dist/platform/Process.js +67 -13
- package/dist/platform/Terminal.js +12 -8
- package/dist/platform/Thread.js +1 -1
- package/dist/platform/assert.js +13 -12
- package/dist/platform/environment.js +9 -18
- package/dist/platform/index.js +1 -0
- package/dist/platform/logger/CompressionContext.js +117 -0
- package/dist/platform/logger/allCompressed.js +8 -7
- package/dist/platform/logger/compress.js +29 -129
- package/dist/platform/logger/output/BaseLoggerOutput.js +49 -31
- package/dist/platform/logger/output/InteractiveLoggerOutput.js +1 -1
- package/dist/platform/logger/output/LlmLoggerOutput.js +29 -0
- package/dist/platform/logger/output/factory.js +5 -1
- package/dist/platform/logger/output.js +24 -6
- package/dist/platform/logger/toInternalLogAttributes.js +10 -11
- package/dist/platform/logger/traceFileName.js +1 -0
- package/dist/platform/logger.js +52 -21
- package/dist/platform/mock.js +110 -19
- package/dist/platform/version.js +1 -2
- package/dist/platform/workerBootstrap.js +1 -1
- package/dist/reports/initReportBuilder.js +18 -0
- package/dist/reports/junit.js +64 -0
- package/dist/reports/saveReport.js +10 -0
- package/dist/reports/textReport.js +92 -0
- package/dist/reserveLogger.js +22 -0
- package/dist/sendToParentProcess.js +6 -0
- package/dist/start.js +79 -0
- package/dist/types/ISecretValue.js +1 -0
- package/dist/types/SecretValue.js +15 -0
- package/dist/ui/agent.js +4 -2
- package/dist/ui/html-report.js +2 -2
- package/dist/ui/lib.js +1 -1
- package/dist/ui/log-viewer.js +1 -1
- package/dist/utils/node/Folder.js +2 -4
- package/dist/utils/node/FramedStreamReader.js +4 -5
- package/dist/utils/node/FramedStreamWriter.js +6 -5
- package/dist/utils/node/anonymize.js +11 -0
- package/dist/utils/node/timeout.js +11 -0
- package/dist/utils/shared/ProgressBar.js +1 -2
- package/dist/utils/shared/TestReportBuilder.js +11 -7
- package/dist/utils/shared/host.js +11 -0
- package/dist/utils/shared/object.js +12 -3
- package/dist/utils/shared/schema.js +20 -0
- package/dist/utils/shared/string.js +1 -0
- package/dist/utils/shared/toIError.js +3 -7
- package/dist/utils/shared/version.js +52 -0
- package/package.json +2 -78
- package/dist/configuration/validators/browser.js +0 -11
- package/dist/modes/test/REserve.js +0 -31
- package/dist/modes/test/server.js +0 -109
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# UI5 Test runner
|
|
1
|
+
# UI5 Test runner
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ArnaudBuchholz/ui5-test-runner/actions/workflows/node.js.yml)
|
|
4
4
|
[](https://packagequality.com/#?package=ui5-test-runner)
|
|
@@ -6,48 +6,39 @@
|
|
|
6
6
|
[](https://www.npmjs.org/package/ui5-test-runner)
|
|
7
7
|
[](https://packagephobia.com/result?p=ui5-test-runner)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
|
-
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner?ref=badge_shield)
|
|
10
|
-
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner?ref=badge_shield&issueType=license)
|
|
10
|
+
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner?ref=badge_shield&issueType=security)
|
|
11
|
+
[](https://github.com/ArnaudBuchholz/ui5-test-runner/tree/main/docs#-documentation)
|
|
11
12
|
|
|
12
|
-
A self-sufficient test runner for UI5 applications enabling parallel execution of tests.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
A self-sufficient test runner for [UI5 applications](https://ui5.sap.com/) enabling parallel execution of tests.
|
|
15
15
|
|
|
16
|
-
>
|
|
16
|
+
> To put it in a nutshell, some [UI5 applications](https://ui5.sap.com/) have so many tests that when you run them in a browser, it ends up **crashing**. The main reason is **memory consumption** : the browser process goes up to 2 GB and it blows up. JavaScript is based on garbage collecting but it needs time to operate and the stress caused by executing the tests as well as the use of iframes do not let enough bandwidth for the browser to free up the memory.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
> This tool is designed and built as a **substitute** of the [UI5 karma runner](https://github.com/SAP/karma-ui5). It executes all the tests in **parallel** thanks to several browser tabs *(which also **reduces the total execution time**)*.
|
|
19
19
|
|
|
20
20
|
## 💿 How to install
|
|
21
21
|
|
|
22
|
-
* Works with [Node.js](https://nodejs.org/en/download/) >= 24
|
|
22
|
+
* Works with [Node.js](https://nodejs.org/en/download/) >= 24.11.1
|
|
23
23
|
* Local installation
|
|
24
|
-
* `npm install --save-dev ui5-test-runner`
|
|
24
|
+
* `npm install --save-dev ui5-test-runner@6`
|
|
25
25
|
* Trigger either with `npx ui5-test-runner` or through an npm script invoking `ui5-test-runner`
|
|
26
26
|
* Global installation
|
|
27
|
-
* `npm install --global ui5-test-runner`
|
|
27
|
+
* `npm install --global ui5-test-runner@6`
|
|
28
28
|
* Trigger with `ui5-test-runner`
|
|
29
29
|
|
|
30
|
-
**NOTE** : additional packages might be needed during the execution (`puppeteer`, `selenium-webdriver`, `nyc`...) . If they are found installed **locally** in the tested project, they are used. Otherwise, they are installed **
|
|
30
|
+
**NOTE** : additional packages might be needed during the execution (`puppeteer`, `selenium-webdriver`, `nyc`...) . If they are found installed **locally** in the tested project, they are used. Otherwise, they are installed **on demand**.
|
|
31
31
|
|
|
32
|
-
##
|
|
33
|
-
[](https://app.fossa.com/projects/git%2Bgithub.com%2FArnaudBuchholz%2Fui5-test-runner?ref=badge_large)
|
|
32
|
+
## 🤖 AI-powered documentation
|
|
34
33
|
|
|
35
|
-
|
|
34
|
+
Use `npx ui5-test-runner@6 --mcp` to start an MCP server exposing the latest indexed documentation to your AI client.
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|| • Dependencies are installed **on demand** |
|
|
45
|
-
|| • Browser instantiation command evolved in an **incompatible way** (see [documentation](https://arnaudbuchholz.github.io/ui5-test-runner/browser.html)) |
|
|
46
|
-
|| • Output is different (report, traces) |
|
|
36
|
+
## 🧭 Links
|
|
37
|
+
|
|
38
|
+
* 📚 [Documentation](https://github.com/ArnaudBuchholz/ui5-test-runner/tree/main/docs#-documentation)
|
|
39
|
+
|
|
40
|
+
* ⚠️ [Breaking changes](https://github.com/ArnaudBuchholz/ui5-test-runner/blob/main/docs/v6_breaking_changes.md)
|
|
41
|
+
|
|
42
|
+
* ✒ [Contributors](https://github.com/ArnaudBuchholz/ui5-test-runner/graphs/contributors?all=1)
|
|
47
43
|
|
|
48
|
-
## ✒ Contributors
|
|
49
44
|
|
|
50
|
-
* [Marian Zeis](https://github.com/marianfoo): Documentation page revamp [PR #54](https://github.com/ArnaudBuchholz/ui5-test-runner/pull/54)
|
|
51
|
-
* [Raj Singh](https://github.com/rajxsingh): Basic HTTP Authentication in Puppeteer [PR #71](https://github.com/ArnaudBuchholz/ui5-test-runner/pull/71)
|
|
52
|
-
* [Andreas Kunz](https://github.com/akudev): Improved documentation for TypeScript testing and reviewed `nyc` settings handling [PR #110](https://github.com/ArnaudBuchholz/ui5-test-runner/pull/110)
|
|
53
|
-
* [Juri Zeisler](https://github.com/juriz889): Added screenshots to the JUnit XML Report [PR #158](https://github.com/ArnaudBuchholz/ui5-test-runner/pull/158)
|
package/dist/Command.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { assert } from './platform/index.js';
|
|
2
|
+
import { Npm } from './Npm.js';
|
|
3
|
+
const QUOTED_AND_UNQUOTED_TOKENS = /"[^"]*"|'[^']*'|[^ ]+/g;
|
|
4
|
+
export const Command = {
|
|
5
|
+
split(command) {
|
|
6
|
+
return command
|
|
7
|
+
.matchAll(QUOTED_AND_UNQUOTED_TOKENS)
|
|
8
|
+
.map(([value]) => {
|
|
9
|
+
return (value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"'))
|
|
10
|
+
? value.slice(1, -1)
|
|
11
|
+
: value;
|
|
12
|
+
})
|
|
13
|
+
.toArray();
|
|
14
|
+
},
|
|
15
|
+
substitute(configuration, value, extras = {}) {
|
|
16
|
+
return value.replaceAll(/\{\{(\w+)\}\}/g, (_, optionName) => {
|
|
17
|
+
if (optionName in extras) {
|
|
18
|
+
return extras[optionName];
|
|
19
|
+
}
|
|
20
|
+
assert(optionName in configuration, `Invalid command line substitution parameter: ${optionName}`);
|
|
21
|
+
return configuration[optionName]?.toString() ?? '';
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
async parse(configuration, command, extras = {}) {
|
|
25
|
+
let executable = 'node';
|
|
26
|
+
const tokens = Command.split(command);
|
|
27
|
+
const environment = {};
|
|
28
|
+
while (tokens.length > 0 && /^\w+=/.test(tokens[0])) {
|
|
29
|
+
const [key, ...rest] = tokens.shift().split('=');
|
|
30
|
+
environment[key] = Command.substitute(configuration, rest.join('='), extras);
|
|
31
|
+
}
|
|
32
|
+
const [commandSpecifier, ...parameters] = tokens;
|
|
33
|
+
assert(!!commandSpecifier);
|
|
34
|
+
if (commandSpecifier === 'npm') {
|
|
35
|
+
parameters.unshift(await Npm.getCliPath());
|
|
36
|
+
}
|
|
37
|
+
else if (commandSpecifier !== 'node') {
|
|
38
|
+
const scripts = await Npm.listPackageScriptNames(configuration.cwd);
|
|
39
|
+
if (scripts.includes(commandSpecifier)) {
|
|
40
|
+
parameters.unshift(await Npm.getCliPath(), 'run', commandSpecifier);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
executable = commandSpecifier;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return [
|
|
47
|
+
executable,
|
|
48
|
+
parameters.map((parameter) => Command.substitute(configuration, parameter, extras)),
|
|
49
|
+
environment
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
};
|
package/dist/Npm.js
CHANGED
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import { logger, FileSystem, Http, Module, Path, Process, Url } from './platform/index.js';
|
|
1
|
+
import { assert, logger, FileSystem, Http, Module, Path, Process, Url } from './platform/index.js';
|
|
2
2
|
import { memoize } from './utils/shared/memoize.js';
|
|
3
3
|
export const getNpmCliPath = async () => {
|
|
4
4
|
const npmChildProcess = Process.spawn('npm', [], {
|
|
5
5
|
shell: true
|
|
6
6
|
});
|
|
7
7
|
await npmChildProcess.closed;
|
|
8
|
-
const error = new Error('Unable to initialize NPM');
|
|
9
8
|
const match = /^npm@([^ ]+) (.*)$/gm.exec(npmChildProcess.stdout);
|
|
10
|
-
|
|
11
|
-
logger.fatal({ source: 'npm', message: 'Unable to match NPM output', error });
|
|
12
|
-
throw error;
|
|
13
|
-
}
|
|
9
|
+
assert(match !== null, 'unable to match NPM output');
|
|
14
10
|
const [, semver, path] = match;
|
|
15
|
-
|
|
16
|
-
logger.fatal({ source: 'npm', message: 'Failed to parse NPM output', error, data: { semver, path } });
|
|
17
|
-
throw error;
|
|
18
|
-
}
|
|
11
|
+
assert(!!semver && !!path, 'failed to parse NPM output');
|
|
19
12
|
logger.debug({ source: 'npm', message: `npm@${semver} ${path}` });
|
|
20
13
|
return Path.join(path, 'bin/npm-cli.js');
|
|
21
14
|
};
|
|
@@ -26,76 +19,87 @@ const npm = async (...arguments_) => {
|
|
|
26
19
|
detached: true
|
|
27
20
|
});
|
|
28
21
|
};
|
|
22
|
+
const validateRoot = async (proc, label) => {
|
|
23
|
+
if (proc.code !== 0) {
|
|
24
|
+
logger.fatal({ source: 'npm', message: `npm root ${label} failed with code ${proc.code}` });
|
|
25
|
+
}
|
|
26
|
+
const value = proc.stdout.trim();
|
|
27
|
+
if (value === '' || !Path.isAbsolute(value)) {
|
|
28
|
+
logger.fatal({ source: 'npm', message: `npm root ${label} returned an invalid path: "${value}"` });
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
await FileSystem.access(value, FileSystem.constants.R_OK);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
logger.fatal({ source: 'npm', message: `npm root ${label} directory is not accessible: "${value}"` });
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
29
38
|
const getRoots = memoize(async () => {
|
|
30
39
|
const localRootProcess = await npm('root');
|
|
31
40
|
const globalRootProcess = await npm('root', '--global');
|
|
32
41
|
await Promise.all([localRootProcess.closed, globalRootProcess.closed]);
|
|
33
|
-
const local = localRootProcess
|
|
34
|
-
const global = globalRootProcess
|
|
35
|
-
logger.debug({ source: 'npm', message: '
|
|
36
|
-
return {
|
|
37
|
-
local: localRootProcess.stdout.trim(),
|
|
38
|
-
global: globalRootProcess.stdout.trim()
|
|
39
|
-
};
|
|
42
|
+
const local = await validateRoot(localRootProcess, '(local)');
|
|
43
|
+
const global = await validateRoot(globalRootProcess, '(global)');
|
|
44
|
+
logger.debug({ source: 'npm', message: 'roots', data: { local, global } });
|
|
45
|
+
return { local, global };
|
|
40
46
|
});
|
|
41
|
-
const buildInstallPlan = (strategy, moduleName,
|
|
47
|
+
const buildInstallPlan = (strategy, moduleName, options) => {
|
|
48
|
+
const { globalRoot, prefix, allowScripts, minReleaseAge } = options;
|
|
49
|
+
const noScripts = allowScripts ? [] : ['--ignore-scripts'];
|
|
50
|
+
const releaseAge = minReleaseAge > 0 ? [`--min-release-age=${minReleaseAge}`] : [];
|
|
51
|
+
const extraFlags = [...noScripts, ...releaseAge];
|
|
42
52
|
if (strategy === 'global') {
|
|
43
|
-
return { installArguments: ['install', '-g', moduleName], reimportPath: globalRoot };
|
|
53
|
+
return { installArguments: ['install', '-g', ...extraFlags, moduleName], reimportPath: globalRoot };
|
|
44
54
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
installArguments: ['install', '--prefix', prefix, '--no-save', moduleName],
|
|
55
|
+
return strategy === 'prefix'
|
|
56
|
+
? {
|
|
57
|
+
installArguments: ['install', '--prefix', prefix, '--no-save', ...extraFlags, moduleName],
|
|
48
58
|
reimportPath: Path.join(prefix, 'node_modules')
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return { installArguments: ['install', '--no-save', moduleName], reimportPath: undefined };
|
|
59
|
+
}
|
|
60
|
+
: { installArguments: ['install', '--no-save', ...extraFlags, moduleName], reimportPath: undefined };
|
|
52
61
|
};
|
|
53
62
|
export class Npm {
|
|
54
63
|
static dynamicImport(specifier) {
|
|
55
64
|
return import(specifier);
|
|
56
65
|
}
|
|
57
|
-
static
|
|
66
|
+
static tryResolvePackageDir(moduleName, nodeModulesPath) {
|
|
58
67
|
try {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
return await this.dynamicImport(Url.pathToFileURL(resolved).href);
|
|
68
|
+
const packageJsonPath = Module.findPackageJSON(moduleName, `${nodeModulesPath}/`);
|
|
69
|
+
return packageJsonPath === undefined ? undefined : Path.dirname(packageJsonPath);
|
|
62
70
|
}
|
|
63
71
|
catch {
|
|
64
72
|
return undefined;
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
|
-
static async
|
|
75
|
+
static async tryImportFromPath(configuration, moduleName, nodeModulesPath) {
|
|
68
76
|
try {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
return
|
|
77
|
+
const require = Module.createRequire(`${Url.pathToFileURL(configuration.cwd).href}/`);
|
|
78
|
+
const resolved = require.resolve(moduleName, { paths: [nodeModulesPath] });
|
|
79
|
+
return await this.dynamicImport(Url.pathToFileURL(resolved).href);
|
|
72
80
|
}
|
|
73
|
-
catch
|
|
74
|
-
|
|
75
|
-
cause: error
|
|
76
|
-
});
|
|
81
|
+
catch {
|
|
82
|
+
return undefined;
|
|
77
83
|
}
|
|
78
84
|
}
|
|
79
|
-
static async
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
static async tryImportFromRoots(configuration, moduleName, roots) {
|
|
86
|
+
for (const { path, label, checkVersion } of roots) {
|
|
87
|
+
const result = await this.tryImportFromPath(configuration, moduleName, path);
|
|
88
|
+
if (result !== undefined) {
|
|
89
|
+
logger.debug({ source: 'npm', message: `Module ${moduleName} ${label}` });
|
|
90
|
+
if (checkVersion)
|
|
91
|
+
void this.checkIfLatestVersion(configuration, moduleName);
|
|
92
|
+
return result;
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
|
-
|
|
90
|
-
logger.error({ source: 'npm', message: 'Failed in checkIfLatestVersion', error });
|
|
91
|
-
}
|
|
95
|
+
return undefined;
|
|
92
96
|
}
|
|
93
97
|
static async import(configuration, moduleName) {
|
|
94
|
-
logger.debug({ source: 'npm', message: `
|
|
98
|
+
logger.debug({ source: 'npm', message: `Importing module: ${moduleName}` });
|
|
95
99
|
try {
|
|
96
100
|
const module = await this.dynamicImport(moduleName);
|
|
97
101
|
logger.debug({ source: 'npm', message: `Module ${moduleName} found locally` });
|
|
98
|
-
void
|
|
102
|
+
void this.checkIfLatestVersion(configuration, moduleName);
|
|
99
103
|
return module;
|
|
100
104
|
}
|
|
101
105
|
catch (error) {
|
|
@@ -103,37 +107,38 @@ export class Npm {
|
|
|
103
107
|
if (code !== 'ERR_MODULE_NOT_FOUND' && code !== 'MODULE_NOT_FOUND') {
|
|
104
108
|
throw error;
|
|
105
109
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const { global: globalRoot } = await getRoots();
|
|
109
|
-
const fromGlobal = await this.tryImportFromPath(configuration, moduleName, globalRoot);
|
|
110
|
-
if (fromGlobal !== undefined) {
|
|
111
|
-
logger.debug({ source: 'npm', message: `Module ${moduleName} found globally` });
|
|
112
|
-
void Npm.checkIfLatestVersion(moduleName, false);
|
|
113
|
-
return fromGlobal;
|
|
114
|
-
}
|
|
115
|
-
if (configuration.alternateNpmPath) {
|
|
116
|
-
const fromAlternate = await this.tryImportFromPath(configuration, moduleName, configuration.alternateNpmPath);
|
|
117
|
-
if (fromAlternate !== undefined) {
|
|
118
|
-
logger.debug({ source: 'npm', message: `Module ${moduleName} found in alternateNpmPath` });
|
|
119
|
-
return fromAlternate;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (configuration.npmInstallPrefix) {
|
|
123
|
-
const fromPrefix = await this.tryImportFromPath(configuration, moduleName, Path.join(configuration.npmInstallPrefix, 'node_modules'));
|
|
124
|
-
if (fromPrefix !== undefined) {
|
|
125
|
-
logger.debug({ source: 'npm', message: `Module ${moduleName} found in npmInstallPrefix` });
|
|
126
|
-
return fromPrefix;
|
|
110
|
+
if (moduleName === this.packageNameOf(moduleName)) {
|
|
111
|
+
logger.warn({ source: 'npm', message: `Module ${moduleName} not found locally` });
|
|
127
112
|
}
|
|
128
113
|
}
|
|
114
|
+
const { local: localRoot, global: globalRoot } = await getRoots();
|
|
115
|
+
const extraRoots = [
|
|
116
|
+
...(configuration.alternateNpmPath
|
|
117
|
+
? [{ path: configuration.alternateNpmPath, label: 'found in alternateNpmPath' }]
|
|
118
|
+
: []),
|
|
119
|
+
...(configuration.npmInstallPrefix
|
|
120
|
+
? [{ path: Path.join(configuration.npmInstallPrefix, 'node_modules'), label: 'found in npmInstallPrefix' }]
|
|
121
|
+
: [])
|
|
122
|
+
];
|
|
123
|
+
const found = await this.tryImportFromRoots(configuration, moduleName, [
|
|
124
|
+
{ path: localRoot, label: 'found in local node_modules', checkVersion: true },
|
|
125
|
+
{ path: globalRoot, label: 'found globally', checkVersion: true },
|
|
126
|
+
...extraRoots
|
|
127
|
+
]);
|
|
128
|
+
if (found !== undefined)
|
|
129
|
+
return found;
|
|
129
130
|
if (configuration.noNpmInstall) {
|
|
130
131
|
const message = `Module ${moduleName} not found and noNpmInstall is set`;
|
|
131
132
|
logger.fatal({ source: 'npm', message });
|
|
132
|
-
throw new Error(message);
|
|
133
133
|
}
|
|
134
134
|
const strategy = configuration.npmInstall;
|
|
135
135
|
logger.info({ source: 'npm', message: `Installing ${moduleName} (strategy: ${strategy})` });
|
|
136
|
-
const { installArguments, reimportPath } = buildInstallPlan(strategy, moduleName,
|
|
136
|
+
const { installArguments, reimportPath } = buildInstallPlan(strategy, moduleName, {
|
|
137
|
+
globalRoot,
|
|
138
|
+
prefix: configuration.npmInstallPrefix ?? '',
|
|
139
|
+
allowScripts: !!configuration.npmAllowInstallScripts,
|
|
140
|
+
minReleaseAge: configuration.npmInstallMinReleaseAge
|
|
141
|
+
});
|
|
137
142
|
const installProcess = await npm(...installArguments);
|
|
138
143
|
await installProcess.closed;
|
|
139
144
|
if (reimportPath !== undefined) {
|
|
@@ -141,10 +146,85 @@ export class Npm {
|
|
|
141
146
|
if (result === undefined) {
|
|
142
147
|
const message = `Module ${moduleName} could not be loaded after install`;
|
|
143
148
|
logger.fatal({ source: 'npm', message });
|
|
144
|
-
throw new Error(message);
|
|
145
149
|
}
|
|
146
150
|
return result;
|
|
147
151
|
}
|
|
148
152
|
return await this.dynamicImport(moduleName);
|
|
149
153
|
}
|
|
154
|
+
static async resolvePackageDir(configuration, moduleName) {
|
|
155
|
+
const { local, global: globalRoot } = await getRoots();
|
|
156
|
+
const fromLocal = this.tryResolvePackageDir(moduleName, local);
|
|
157
|
+
if (fromLocal)
|
|
158
|
+
return fromLocal;
|
|
159
|
+
const fromGlobal = this.tryResolvePackageDir(moduleName, globalRoot);
|
|
160
|
+
if (fromGlobal)
|
|
161
|
+
return fromGlobal;
|
|
162
|
+
if (configuration.alternateNpmPath) {
|
|
163
|
+
const fromAlternate = this.tryResolvePackageDir(moduleName, configuration.alternateNpmPath);
|
|
164
|
+
if (fromAlternate)
|
|
165
|
+
return fromAlternate;
|
|
166
|
+
}
|
|
167
|
+
if (configuration.npmInstallPrefix) {
|
|
168
|
+
const fromPrefix = this.tryResolvePackageDir(moduleName, Path.join(configuration.npmInstallPrefix, 'node_modules'));
|
|
169
|
+
if (fromPrefix)
|
|
170
|
+
return fromPrefix;
|
|
171
|
+
}
|
|
172
|
+
throw new Error(`Cannot locate package directory for ${moduleName}`);
|
|
173
|
+
}
|
|
174
|
+
static packageNameOf(moduleName) {
|
|
175
|
+
if (moduleName.startsWith('@')) {
|
|
176
|
+
const [scope, package_] = moduleName.split('/', 2);
|
|
177
|
+
return `${scope}/${package_}`;
|
|
178
|
+
}
|
|
179
|
+
return moduleName.split('/', 1)[0];
|
|
180
|
+
}
|
|
181
|
+
static async getLatestVersion(packageName) {
|
|
182
|
+
try {
|
|
183
|
+
const response = await Http.getAsText(`https://registry.npmjs.org/${packageName}/latest`);
|
|
184
|
+
const { version } = JSON.parse(response);
|
|
185
|
+
return version;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
throw new Error(`Unable to fetch latest version of ${packageName} from NPM registry`, {
|
|
189
|
+
cause: error
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
static _checkedPackages = new Set();
|
|
194
|
+
static async checkIfLatestVersion(configuration, moduleName) {
|
|
195
|
+
const packageName = this.packageNameOf(moduleName);
|
|
196
|
+
if (this._checkedPackages.has(packageName)) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this._checkedPackages.add(packageName);
|
|
200
|
+
try {
|
|
201
|
+
const packageDirectory = await this.resolvePackageDir(configuration, packageName);
|
|
202
|
+
const { version: installedVersion } = JSON.parse(await FileSystem.readFile(Path.join(packageDirectory, 'package.json'), 'utf8'));
|
|
203
|
+
logger.info({ source: 'npm', message: `Installed version of ${packageName} is ${installedVersion}` });
|
|
204
|
+
const latestVersion = await this.getLatestVersion(packageName);
|
|
205
|
+
if (latestVersion !== installedVersion) {
|
|
206
|
+
logger.warn({ source: 'npm', message: `Latest version of ${packageName} is ${latestVersion}` });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
logger.error({ source: 'npm', message: 'failed in checkIfLatestVersion', error });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
static async getCliPath() {
|
|
214
|
+
return await memoizedNpmCliPath();
|
|
215
|
+
}
|
|
216
|
+
static async listPackageScriptNames(cwd) {
|
|
217
|
+
try {
|
|
218
|
+
const packageJsonPath = Module.findPackageJSON(`${Url.pathToFileURL(cwd).href}/`);
|
|
219
|
+
if (!packageJsonPath) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
const content = JSON.parse(await FileSystem.readFile(packageJsonPath, 'utf8'));
|
|
223
|
+
return Object.keys(content?.scripts ?? {});
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
logger.error({ source: 'npm', message: 'failed to list package script names', error, data: { cwd } });
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
150
230
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getExtraChromeArguments } from './chromeArguments.js';
|
|
2
|
+
export const DEFAULT_CHROME_ARGS = [
|
|
3
|
+
'--disable-background-timer-throttling',
|
|
4
|
+
'--disable-backgrounding-occluded-windows',
|
|
5
|
+
'--disable-renderer-backgrounding'
|
|
6
|
+
];
|
|
7
|
+
export const buildChromeLaunchOptions = (options, extraArguments = [], shouldIncludeEnvironmentArguments = true) => {
|
|
8
|
+
const rawArguments = options?.['args'];
|
|
9
|
+
const userArguments = Array.isArray(rawArguments)
|
|
10
|
+
? rawArguments.filter((a) => typeof a === 'string')
|
|
11
|
+
: [];
|
|
12
|
+
const rawIgnoreDefaultArguments = options?.['ignoreDefaultArgs'];
|
|
13
|
+
let ignoreDefaultArguments;
|
|
14
|
+
if (rawIgnoreDefaultArguments === true) {
|
|
15
|
+
ignoreDefaultArguments = true;
|
|
16
|
+
}
|
|
17
|
+
else if (Array.isArray(rawIgnoreDefaultArguments)) {
|
|
18
|
+
ignoreDefaultArguments = rawIgnoreDefaultArguments.filter((argument) => typeof argument === 'string');
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
args: [
|
|
22
|
+
...DEFAULT_CHROME_ARGS,
|
|
23
|
+
...extraArguments,
|
|
24
|
+
...userArguments,
|
|
25
|
+
...(shouldIncludeEnvironmentArguments ? getExtraChromeArguments() : [])
|
|
26
|
+
],
|
|
27
|
+
...(ignoreDefaultArguments !== undefined && { ignoreDefaultArgs: ignoreDefaultArguments })
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { logger } from '../platform/index.js';
|
|
2
|
+
import { agentLogPrefix } from '../types/AgentState.js';
|
|
3
|
+
const LOG_TYPES = {
|
|
4
|
+
error: 'error',
|
|
5
|
+
warn: 'warn',
|
|
6
|
+
assert: 'warn',
|
|
7
|
+
debug: 'debug'
|
|
8
|
+
};
|
|
9
|
+
export const handleConsoleMessage = (type, text, pageId) => {
|
|
10
|
+
const normalizedType = type === 'warning' ? 'warn' : type;
|
|
11
|
+
const logType = LOG_TYPES[normalizedType] ?? 'info';
|
|
12
|
+
let source;
|
|
13
|
+
let message;
|
|
14
|
+
if (text.startsWith(agentLogPrefix)) {
|
|
15
|
+
source = 'browser/agent';
|
|
16
|
+
message = text.slice(agentLogPrefix.length);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
source = 'browser/console';
|
|
20
|
+
message = text;
|
|
21
|
+
}
|
|
22
|
+
logger[logType]({ source, message, pageId, data: { type: normalizedType } });
|
|
23
|
+
};
|