ui5-test-runner 6.0.0-beta.5 → 6.0.1
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 +87 -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 +3 -79
- package/dist/configuration/validators/browser.js +0 -11
- package/dist/modes/test/REserve.js +0 -31
- package/dist/modes/test/server.js +0 -109
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
import { split } from '../../utils/shared/string.js';
|
|
3
|
+
export const DIGITS = Array.from({ length: 127 - 32 }, () => 0)
|
|
4
|
+
.map((_, index) => String.fromCodePoint(32 + index))
|
|
5
|
+
.join('');
|
|
6
|
+
export const MAX_TIMESTAMP_DIGITS = 7;
|
|
7
|
+
export const MAX_DWORD_DIGITS = 5;
|
|
8
|
+
const MAX_INDEX_DIGITS = 2;
|
|
9
|
+
const CONTEXT_PROCESS_ID = 'p';
|
|
10
|
+
const CONTEXT_SOURCE_ID = 's';
|
|
11
|
+
export class CompressionContext {
|
|
12
|
+
constructor() { }
|
|
13
|
+
static create() {
|
|
14
|
+
return new CompressionContext();
|
|
15
|
+
}
|
|
16
|
+
static compressNumber(value, maxLength) {
|
|
17
|
+
const digits = [];
|
|
18
|
+
while (value > 0) {
|
|
19
|
+
const digit = value % DIGITS.length;
|
|
20
|
+
digits.push(DIGITS[digit]);
|
|
21
|
+
value = (value - digit) / DIGITS.length;
|
|
22
|
+
}
|
|
23
|
+
return digits.join('').padEnd(maxLength, DIGITS[0]);
|
|
24
|
+
}
|
|
25
|
+
static uncompressNumber(value) {
|
|
26
|
+
let result = 0;
|
|
27
|
+
let factor = 1;
|
|
28
|
+
for (const digit of value) {
|
|
29
|
+
const index = DIGITS.indexOf(digit);
|
|
30
|
+
result += index * factor;
|
|
31
|
+
factor *= DIGITS.length;
|
|
32
|
+
}
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
fail(message) {
|
|
36
|
+
throw new Error(message);
|
|
37
|
+
}
|
|
38
|
+
_compressWithList({ array, value, compress }) {
|
|
39
|
+
const index = array.findIndex((candidate) => isDeepStrictEqual(candidate, value));
|
|
40
|
+
if (index !== -1) {
|
|
41
|
+
const compressed = CompressionContext.compressNumber(index, MAX_INDEX_DIGITS);
|
|
42
|
+
return {
|
|
43
|
+
context: '',
|
|
44
|
+
compressed
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const lastIndex = array.length;
|
|
48
|
+
array.push(value);
|
|
49
|
+
return {
|
|
50
|
+
context: compress(value).join(''),
|
|
51
|
+
compressed: CompressionContext.compressNumber(lastIndex, MAX_INDEX_DIGITS)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
_uncompressFromList({ type, array, compressed }) {
|
|
55
|
+
const index = CompressionContext.uncompressNumber(compressed);
|
|
56
|
+
return array[index] ?? this.fail(`Invalid ${type} index ${index} (length: ${array.length})`);
|
|
57
|
+
}
|
|
58
|
+
_processes = [];
|
|
59
|
+
compressProcess(value) {
|
|
60
|
+
return this._compressWithList({
|
|
61
|
+
array: this._processes,
|
|
62
|
+
value,
|
|
63
|
+
compress: ({ processId, threadId, isMainThread }) => {
|
|
64
|
+
return threadId === -1
|
|
65
|
+
? [CONTEXT_PROCESS_ID, CompressionContext.compressNumber(processId, MAX_DWORD_DIGITS)]
|
|
66
|
+
: [
|
|
67
|
+
CONTEXT_PROCESS_ID,
|
|
68
|
+
CompressionContext.compressNumber(processId, MAX_DWORD_DIGITS),
|
|
69
|
+
CompressionContext.compressNumber(threadId, MAX_DWORD_DIGITS),
|
|
70
|
+
isMainThread ? '!' : ''
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
addProcess(compressed) {
|
|
76
|
+
const [, cProcessId, cThreadId, cIsMainThread] = split(compressed, 1, MAX_DWORD_DIGITS, MAX_DWORD_DIGITS, 1);
|
|
77
|
+
if (cThreadId) {
|
|
78
|
+
this._processes.push({
|
|
79
|
+
processId: CompressionContext.uncompressNumber(cProcessId),
|
|
80
|
+
threadId: CompressionContext.uncompressNumber(cThreadId),
|
|
81
|
+
isMainThread: cIsMainThread === '!'
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this._processes.push({
|
|
86
|
+
processId: CompressionContext.uncompressNumber(cProcessId),
|
|
87
|
+
threadId: -1,
|
|
88
|
+
isMainThread: false
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
uncompressProcess(compressed) {
|
|
93
|
+
return this._uncompressFromList({
|
|
94
|
+
type: 'process',
|
|
95
|
+
array: this._processes,
|
|
96
|
+
compressed
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
_sources = [];
|
|
100
|
+
compressSource(value) {
|
|
101
|
+
return this._compressWithList({
|
|
102
|
+
array: this._sources,
|
|
103
|
+
value,
|
|
104
|
+
compress: (value) => [CONTEXT_SOURCE_ID, value]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
addSource(compressed) {
|
|
108
|
+
this._sources.push(compressed.slice(1));
|
|
109
|
+
}
|
|
110
|
+
uncompressSource(compressed) {
|
|
111
|
+
return this._uncompressFromList({
|
|
112
|
+
type: 'source',
|
|
113
|
+
array: this._sources,
|
|
114
|
+
compressed
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Path, Thread, ZLib } from '../index.js';
|
|
2
2
|
import { LogLevel } from './types.js';
|
|
3
3
|
import { toInternalLogAttributes } from './toInternalLogAttributes.js';
|
|
4
|
-
import {
|
|
4
|
+
import { CompressionContext } from './CompressionContext.js';
|
|
5
|
+
import { compress } from './compress.js';
|
|
5
6
|
import { FramedStreamWriter } from '../../utils/node/FramedStreamWriter.js';
|
|
7
|
+
import { traceFileName } from './traceFileName.js';
|
|
6
8
|
export const MAX_BUFFER_COUNT = 50;
|
|
7
9
|
const FLUSH_INTERVAL_MS = 200;
|
|
8
|
-
const compressionContext =
|
|
9
|
-
export const workerMain = ({ configuration }) => {
|
|
10
|
-
const
|
|
11
|
-
const stream = FramedStreamWriter.create(Path.join(configuration.reportDir, LOG_FILE_NAME));
|
|
10
|
+
const compressionContext = CompressionContext.create();
|
|
11
|
+
export const workerMain = ({ configuration, startedAt }) => {
|
|
12
|
+
const stream = FramedStreamWriter.create(Path.join(configuration.reportDir, traceFileName(startedAt)));
|
|
12
13
|
const buffer = [];
|
|
13
14
|
let flushTimeout;
|
|
14
15
|
let writePromise = Promise.resolve();
|
|
@@ -37,7 +38,7 @@ export const workerMain = ({ configuration }) => {
|
|
|
37
38
|
channel.onmessage = (event) => {
|
|
38
39
|
const { data: message } = event;
|
|
39
40
|
if (message.command === 'terminate') {
|
|
40
|
-
_log({ source: 'logger', message: '
|
|
41
|
+
_log({ source: 'logger', message: 'logger terminating' });
|
|
41
42
|
channel.close();
|
|
42
43
|
flushBuffer();
|
|
43
44
|
void writePromise.then(() => stream.end());
|
|
@@ -46,7 +47,7 @@ export const workerMain = ({ configuration }) => {
|
|
|
46
47
|
log(message);
|
|
47
48
|
}
|
|
48
49
|
};
|
|
49
|
-
_log({ source: 'logger', message: '
|
|
50
|
+
_log({ source: 'logger', message: 'logger ready' });
|
|
50
51
|
channel.postMessage({
|
|
51
52
|
command: 'ready',
|
|
52
53
|
source: 'allCompressed'
|
|
@@ -1,123 +1,12 @@
|
|
|
1
1
|
import { LogLevel } from './types.js';
|
|
2
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
3
2
|
import assert from 'node:assert/strict';
|
|
4
3
|
import { split } from '../../utils/shared/string.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.fill(0)
|
|
8
|
-
.map((_, index) => String.fromCodePoint(32 + index))
|
|
9
|
-
.join('');
|
|
4
|
+
import { CompressionContext, MAX_TIMESTAMP_DIGITS } from './CompressionContext.js';
|
|
5
|
+
const ASCII_RECORD_SEPARATOR = '\u{1E}';
|
|
10
6
|
const JSON_VALUE_SEP = ASCII_RECORD_SEPARATOR;
|
|
11
7
|
const MAX_INDEX_DIGITS = 2;
|
|
12
8
|
const CONTEXT_PROCESS_ID = 'p';
|
|
13
9
|
const CONTEXT_SOURCE_ID = 's';
|
|
14
|
-
export const MAX_TIMESTAMP_DIGITS = 7;
|
|
15
|
-
export const MAX_DWORD_DIGITS = 5;
|
|
16
|
-
class Context {
|
|
17
|
-
static compressNumber(value, maxLength) {
|
|
18
|
-
const digits = [];
|
|
19
|
-
while (value > 0) {
|
|
20
|
-
const digit = value % DIGITS.length;
|
|
21
|
-
digits.push(DIGITS[digit]);
|
|
22
|
-
value = (value - digit) / DIGITS.length;
|
|
23
|
-
}
|
|
24
|
-
return digits.join('').padEnd(maxLength, DIGITS[0]);
|
|
25
|
-
}
|
|
26
|
-
static uncompressNumber(value) {
|
|
27
|
-
let result = 0;
|
|
28
|
-
let factor = 1;
|
|
29
|
-
for (const digit of value) {
|
|
30
|
-
const index = DIGITS.indexOf(digit);
|
|
31
|
-
result += index * factor;
|
|
32
|
-
factor *= DIGITS.length;
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
fail(message) {
|
|
37
|
-
throw new Error(message);
|
|
38
|
-
}
|
|
39
|
-
_compressWithList({ array, value, compress }) {
|
|
40
|
-
const index = array.findIndex((candidate) => isDeepStrictEqual(candidate, value));
|
|
41
|
-
if (index !== -1) {
|
|
42
|
-
const compressed = Context.compressNumber(index, MAX_INDEX_DIGITS);
|
|
43
|
-
return {
|
|
44
|
-
context: '',
|
|
45
|
-
compressed
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
const lastIndex = array.length;
|
|
49
|
-
array.push(value);
|
|
50
|
-
return {
|
|
51
|
-
context: compress(value).join(''),
|
|
52
|
-
compressed: Context.compressNumber(lastIndex, MAX_INDEX_DIGITS)
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
_uncompressFromList({ type, array, compressed }) {
|
|
56
|
-
const index = Context.uncompressNumber(compressed);
|
|
57
|
-
return array[index] ?? this.fail(`Invalid ${type} index ${index} (length: ${array.length})`);
|
|
58
|
-
}
|
|
59
|
-
_processes = [];
|
|
60
|
-
compressProcess(value) {
|
|
61
|
-
return this._compressWithList({
|
|
62
|
-
array: this._processes,
|
|
63
|
-
value,
|
|
64
|
-
compress: ({ processId, threadId, isMainThread }) => {
|
|
65
|
-
if (threadId === -1) {
|
|
66
|
-
return [CONTEXT_PROCESS_ID, Context.compressNumber(processId, MAX_DWORD_DIGITS)];
|
|
67
|
-
}
|
|
68
|
-
return [
|
|
69
|
-
CONTEXT_PROCESS_ID,
|
|
70
|
-
Context.compressNumber(processId, MAX_DWORD_DIGITS),
|
|
71
|
-
Context.compressNumber(threadId, MAX_DWORD_DIGITS),
|
|
72
|
-
isMainThread ? '!' : ''
|
|
73
|
-
];
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
addProcess(compressed) {
|
|
78
|
-
const [, cProcessId, cThreadId, cIsMainThread] = split(compressed, 1, MAX_DWORD_DIGITS, MAX_DWORD_DIGITS, 1);
|
|
79
|
-
if (cThreadId) {
|
|
80
|
-
this._processes.push({
|
|
81
|
-
processId: Context.uncompressNumber(cProcessId),
|
|
82
|
-
threadId: Context.uncompressNumber(cThreadId),
|
|
83
|
-
isMainThread: cIsMainThread === '!'
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
this._processes.push({
|
|
88
|
-
processId: Context.uncompressNumber(cProcessId),
|
|
89
|
-
threadId: -1,
|
|
90
|
-
isMainThread: false
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
uncompressProcess(compressed) {
|
|
95
|
-
return this._uncompressFromList({
|
|
96
|
-
type: 'process',
|
|
97
|
-
array: this._processes,
|
|
98
|
-
compressed
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
_sources = [];
|
|
102
|
-
compressSource(value) {
|
|
103
|
-
return this._compressWithList({
|
|
104
|
-
array: this._sources,
|
|
105
|
-
value,
|
|
106
|
-
compress: (value) => [CONTEXT_SOURCE_ID, value]
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
addSource(compressed) {
|
|
110
|
-
this._sources.push(compressed.slice(1));
|
|
111
|
-
}
|
|
112
|
-
uncompressSource(compressed) {
|
|
113
|
-
return this._uncompressFromList({
|
|
114
|
-
type: 'source',
|
|
115
|
-
array: this._sources,
|
|
116
|
-
compressed
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
export const createCompressionContext = () => new Context();
|
|
121
10
|
const LEVEL_MAPPING = {
|
|
122
11
|
[LogLevel.debug]: 'D',
|
|
123
12
|
[LogLevel.info]: 'I',
|
|
@@ -138,10 +27,10 @@ const levelSlot = {
|
|
|
138
27
|
const timestampSlot = {
|
|
139
28
|
width: MAX_TIMESTAMP_DIGITS,
|
|
140
29
|
compress(_, { timestamp }) {
|
|
141
|
-
return { compressed:
|
|
30
|
+
return { compressed: CompressionContext.compressNumber(timestamp, MAX_TIMESTAMP_DIGITS) };
|
|
142
31
|
},
|
|
143
32
|
uncompress(_, compressed) {
|
|
144
|
-
return { timestamp:
|
|
33
|
+
return { timestamp: CompressionContext.uncompressNumber(compressed) };
|
|
145
34
|
}
|
|
146
35
|
};
|
|
147
36
|
const processSlot = {
|
|
@@ -167,23 +56,33 @@ const sourceSlot = {
|
|
|
167
56
|
const pageIdSlot = {
|
|
168
57
|
width: MAX_INDEX_DIGITS,
|
|
169
58
|
compress(_, { pageId }) {
|
|
170
|
-
return { compressed:
|
|
59
|
+
return { compressed: CompressionContext.compressNumber(pageId === undefined ? 0 : pageId + 1, MAX_INDEX_DIGITS) };
|
|
171
60
|
},
|
|
172
61
|
uncompress(_, compressed) {
|
|
173
|
-
const value =
|
|
62
|
+
const value = CompressionContext.uncompressNumber(compressed);
|
|
174
63
|
return value > 0 ? { pageId: value - 1 } : {};
|
|
175
64
|
}
|
|
176
65
|
};
|
|
177
66
|
const messageAndExtraSlot = {
|
|
178
67
|
width: 0,
|
|
179
68
|
compress(_, { message, data, error }) {
|
|
180
|
-
const parts = [
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
69
|
+
const parts = [
|
|
70
|
+
message.replaceAll(/\r?\n/g, '\r'),
|
|
71
|
+
...(data || error
|
|
72
|
+
? [
|
|
73
|
+
JSON_VALUE_SEP,
|
|
74
|
+
JSON.stringify([data ?? 0, error ?? 0]).replaceAll(/"(\w+)":/g, (_, name) => `${name}${JSON_VALUE_SEP}`)
|
|
75
|
+
]
|
|
76
|
+
: [])
|
|
77
|
+
];
|
|
184
78
|
return { compressed: parts.join('') };
|
|
185
79
|
},
|
|
186
80
|
uncompress(_, compressed) {
|
|
81
|
+
if (!compressed) {
|
|
82
|
+
return {
|
|
83
|
+
message: ''
|
|
84
|
+
};
|
|
85
|
+
}
|
|
187
86
|
const startOfJson = compressed.indexOf(JSON_VALUE_SEP);
|
|
188
87
|
if (startOfJson === -1) {
|
|
189
88
|
return { message: compressed.replaceAll('\r', '\n') };
|
|
@@ -195,12 +94,12 @@ const messageAndExtraSlot = {
|
|
|
195
94
|
const [data, error] = JSON.parse(json);
|
|
196
95
|
return {
|
|
197
96
|
message,
|
|
198
|
-
...(data
|
|
199
|
-
...(error
|
|
97
|
+
...(data && { data }),
|
|
98
|
+
...(error && { error })
|
|
200
99
|
};
|
|
201
100
|
}
|
|
202
101
|
};
|
|
203
|
-
const DATA_LINE_SLOTS = [
|
|
102
|
+
export const DATA_LINE_SLOTS = [
|
|
204
103
|
levelSlot,
|
|
205
104
|
timestampSlot,
|
|
206
105
|
processSlot,
|
|
@@ -208,9 +107,9 @@ const DATA_LINE_SLOTS = [
|
|
|
208
107
|
pageIdSlot,
|
|
209
108
|
messageAndExtraSlot
|
|
210
109
|
];
|
|
211
|
-
const FIXED_SLOTS = DATA_LINE_SLOTS.filter((
|
|
212
|
-
const FIXED_SLOT_WIDTHS = FIXED_SLOTS.map((
|
|
213
|
-
const VARIABLE_SLOTS = DATA_LINE_SLOTS.filter((
|
|
110
|
+
const FIXED_SLOTS = DATA_LINE_SLOTS.filter((dataSlot) => dataSlot.width > 0);
|
|
111
|
+
const FIXED_SLOT_WIDTHS = FIXED_SLOTS.map((dataSlot) => dataSlot.width);
|
|
112
|
+
const VARIABLE_SLOTS = DATA_LINE_SLOTS.filter((dataSlot) => dataSlot.width === 0);
|
|
214
113
|
assert.ok(VARIABLE_SLOTS.length === 1, 'DATA_LINE_SLOTS must contain exactly one variable-width slot');
|
|
215
114
|
const VARIABLE_SLOT = VARIABLE_SLOTS[0];
|
|
216
115
|
export const _ALL_LOG_ATTRIBUTES_ARE_HANDLED = {
|
|
@@ -221,12 +120,13 @@ export const _ALL_LOG_ATTRIBUTES_ARE_HANDLED = {
|
|
|
221
120
|
isMainThread: processSlot,
|
|
222
121
|
source: sourceSlot,
|
|
223
122
|
pageId: pageIdSlot,
|
|
123
|
+
forceRender: null,
|
|
224
124
|
message: messageAndExtraSlot,
|
|
225
125
|
data: messageAndExtraSlot,
|
|
226
126
|
error: messageAndExtraSlot
|
|
227
127
|
};
|
|
228
128
|
export const compress = (context, attributes) => {
|
|
229
|
-
assert.ok(context instanceof
|
|
129
|
+
assert.ok(context instanceof CompressionContext);
|
|
230
130
|
const contextLines = [];
|
|
231
131
|
const parts = [];
|
|
232
132
|
for (const slot of DATA_LINE_SLOTS) {
|
|
@@ -262,7 +162,7 @@ const uncompressLine = (context, line) => {
|
|
|
262
162
|
return attributes;
|
|
263
163
|
};
|
|
264
164
|
export const uncompress = (context, compressed) => {
|
|
265
|
-
assert.ok(context instanceof
|
|
165
|
+
assert.ok(context instanceof CompressionContext);
|
|
266
166
|
const result = [];
|
|
267
167
|
for (const line of compressed.split('\n')) {
|
|
268
168
|
if (!line) {
|
|
@@ -1,47 +1,61 @@
|
|
|
1
1
|
import { FileSystem, Path, Terminal } from '../../index.js';
|
|
2
2
|
import { LogLevel } from '../types.js';
|
|
3
3
|
import { ProgressBar } from '../../../utils/shared/ProgressBar.js';
|
|
4
|
+
import { objectValues } from '../../../utils/shared/object.js';
|
|
4
5
|
import { formatDuration } from '../../../utils/shared/string.js';
|
|
5
6
|
import { assert } from '../../assert.js';
|
|
6
7
|
const icons = {
|
|
7
8
|
[LogLevel.debug]: Terminal.BLUE + '<o>',
|
|
8
|
-
[LogLevel.info]: '
|
|
9
|
+
[LogLevel.info]: ' '.repeat(3),
|
|
9
10
|
[LogLevel.warn]: Terminal.YELLOW + '/!\\',
|
|
10
11
|
[LogLevel.error]: Terminal.RED + '(X)',
|
|
11
12
|
[LogLevel.fatal]: Terminal.RED + 'o*!'
|
|
12
13
|
};
|
|
13
14
|
const DO_NOT_RENDER_SOURCE = [
|
|
14
15
|
'browser',
|
|
16
|
+
'browser/agent',
|
|
15
17
|
'browser/console',
|
|
16
18
|
'browser/network',
|
|
17
19
|
'metric',
|
|
18
|
-
'
|
|
20
|
+
'process',
|
|
21
|
+
'process/stdout',
|
|
22
|
+
'process/stderr',
|
|
23
|
+
'progress',
|
|
24
|
+
'server/unhandled'
|
|
19
25
|
];
|
|
20
26
|
export class BaseLoggerOutput {
|
|
21
27
|
_configuration;
|
|
22
28
|
_startedAt;
|
|
29
|
+
_debugLog;
|
|
23
30
|
constructor(configuration, startedAt) {
|
|
24
31
|
this._configuration = configuration;
|
|
25
32
|
this._startedAt = startedAt;
|
|
33
|
+
this._debugLog = configuration.debugLog;
|
|
26
34
|
}
|
|
27
35
|
formatTimestamp(timestamp) {
|
|
28
36
|
return formatDuration(timestamp - this._startedAt);
|
|
29
37
|
}
|
|
38
|
+
_shouldRender({ source, forceRender, level }) {
|
|
39
|
+
return (forceRender ||
|
|
40
|
+
this._debugLog?.includes(source) ||
|
|
41
|
+
(!DO_NOT_RENDER_SOURCE.includes(source) && level !== LogLevel.debug));
|
|
42
|
+
}
|
|
30
43
|
render(attributes) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return [
|
|
34
|
-
icons[level],
|
|
35
|
-
Terminal.MAGENTA,
|
|
36
|
-
this.formatTimestamp(timestamp),
|
|
37
|
-
Terminal.WHITE,
|
|
38
|
-
' ',
|
|
39
|
-
message,
|
|
40
|
-
data ? ` ${JSON.stringify(data)}` : '',
|
|
41
|
-
error ? ` ${Terminal.RED}${error.name} ${error.message}` : '',
|
|
42
|
-
'\n'
|
|
43
|
-
].join('');
|
|
44
|
+
if (!this._shouldRender(attributes)) {
|
|
45
|
+
return;
|
|
44
46
|
}
|
|
47
|
+
const { level, timestamp, message, data, error } = attributes;
|
|
48
|
+
return [
|
|
49
|
+
icons[level],
|
|
50
|
+
Terminal.MAGENTA,
|
|
51
|
+
this.formatTimestamp(timestamp),
|
|
52
|
+
Terminal.WHITE,
|
|
53
|
+
' ',
|
|
54
|
+
message,
|
|
55
|
+
data ? ` ${JSON.stringify(data)}` : '',
|
|
56
|
+
error ? ` ${Terminal.RED}${error.name} ${error.message}` : '',
|
|
57
|
+
'\n'
|
|
58
|
+
].join('');
|
|
45
59
|
}
|
|
46
60
|
addToReport(rawText) {
|
|
47
61
|
FileSystem.writeFileSync(Path.join(this._configuration.reportDir, 'output.txt'), rawText, {
|
|
@@ -68,11 +82,12 @@ export class BaseLoggerOutput {
|
|
|
68
82
|
totalNumberOfTests: 0
|
|
69
83
|
};
|
|
70
84
|
_updateOverallProgressSnapshot(data) {
|
|
71
|
-
if (data.type
|
|
72
|
-
|
|
73
|
-
this._overallprogressSnapshot.totalNumberOfErrors += data.errors;
|
|
74
|
-
this._overallprogressSnapshot.totalNumberOfTests += data.max;
|
|
85
|
+
if (data.type === 'unknown') {
|
|
86
|
+
return;
|
|
75
87
|
}
|
|
88
|
+
this._overallprogressSnapshot.totalNumberOfExecutedTests += data.value;
|
|
89
|
+
this._overallprogressSnapshot.totalNumberOfErrors += data.errors;
|
|
90
|
+
this._overallprogressSnapshot.totalNumberOfTests += data.max;
|
|
76
91
|
}
|
|
77
92
|
_overallprogress = {
|
|
78
93
|
totalNumberOfExecutedTests: 0,
|
|
@@ -81,12 +96,13 @@ export class BaseLoggerOutput {
|
|
|
81
96
|
};
|
|
82
97
|
_updateOverallProgress() {
|
|
83
98
|
const overallProgress = { ...this._overallprogressSnapshot };
|
|
84
|
-
for (const pageProgress of
|
|
85
|
-
if (pageProgress.type
|
|
86
|
-
|
|
87
|
-
overallProgress.totalNumberOfErrors += pageProgress.errors;
|
|
88
|
-
overallProgress.totalNumberOfTests += pageProgress.bar.max;
|
|
99
|
+
for (const pageProgress of objectValues(this._pageProgressMap)) {
|
|
100
|
+
if (pageProgress.type === 'unknown') {
|
|
101
|
+
continue;
|
|
89
102
|
}
|
|
103
|
+
overallProgress.totalNumberOfExecutedTests += pageProgress.bar.value;
|
|
104
|
+
overallProgress.totalNumberOfErrors += pageProgress.errors;
|
|
105
|
+
overallProgress.totalNumberOfTests += pageProgress.bar.max;
|
|
90
106
|
}
|
|
91
107
|
this._overallprogress = overallProgress;
|
|
92
108
|
}
|
|
@@ -146,13 +162,15 @@ export class BaseLoggerOutput {
|
|
|
146
162
|
return true;
|
|
147
163
|
}
|
|
148
164
|
addAttributesToLoggerOutput(attributes) {
|
|
149
|
-
if (this.renderAttributes(attributes)) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
165
|
+
if (!this.renderAttributes(attributes)) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const rendered = this.render(attributes);
|
|
169
|
+
if (!rendered) {
|
|
170
|
+
return;
|
|
156
171
|
}
|
|
172
|
+
const raw = Terminal.stripVTControlCharacters(rendered);
|
|
173
|
+
this.addTextToLoggerOutput(rendered, raw);
|
|
174
|
+
this.addToReport(raw);
|
|
157
175
|
}
|
|
158
176
|
}
|
|
@@ -44,7 +44,7 @@ export class InteractiveLoggerOutput extends BaseLoggerOutput {
|
|
|
44
44
|
for (const pageId of this.pageIds) {
|
|
45
45
|
const pageProgress = this.pageProgressMap[pageId];
|
|
46
46
|
const rendered = pageProgress.bar.render(this._terminalWidth - 4);
|
|
47
|
-
let status = '
|
|
47
|
+
let status = ' '.repeat(3);
|
|
48
48
|
if (pageProgress.errors) {
|
|
49
49
|
status = `${Terminal.RED}${Math.min(pageProgress.errors, 999).toString().padEnd(3, ' ')}${Terminal.WHITE}`;
|
|
50
50
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Terminal } from '../../index.js';
|
|
2
|
+
import { LogLevel } from '../types.js';
|
|
3
|
+
import { BaseLoggerOutput } from './BaseLoggerOutput.js';
|
|
4
|
+
const SEVERITY = {
|
|
5
|
+
[LogLevel.warn]: 'WARN ',
|
|
6
|
+
[LogLevel.error]: 'ERROR ',
|
|
7
|
+
[LogLevel.fatal]: 'FATAL '
|
|
8
|
+
};
|
|
9
|
+
export class LlmLoggerOutput extends BaseLoggerOutput {
|
|
10
|
+
_current;
|
|
11
|
+
terminalResized() { }
|
|
12
|
+
closeLoggerOutput() { }
|
|
13
|
+
addAttributesToLoggerOutput(attributes) {
|
|
14
|
+
this._current = attributes;
|
|
15
|
+
super.addAttributesToLoggerOutput(attributes);
|
|
16
|
+
this._current = undefined;
|
|
17
|
+
}
|
|
18
|
+
addTextToLoggerOutput() {
|
|
19
|
+
const attributes = this._current;
|
|
20
|
+
if (!attributes) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { source, pageId, level, message, error } = attributes;
|
|
24
|
+
const context = pageId === undefined ? source : `${source}#${pageId}`;
|
|
25
|
+
const severity = SEVERITY[level] ?? '';
|
|
26
|
+
const details = error ? ` ${error.name}: ${error.message}` : '';
|
|
27
|
+
Terminal.write(`[${context}] ${severity}${message}${details}\n`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { InteractiveLoggerOutput } from './InteractiveLoggerOutput.js';
|
|
2
|
+
import { LlmLoggerOutput } from './LlmLoggerOutput.js';
|
|
2
3
|
import { StaticLoggerOutput } from './StaticLoggerOutput.js';
|
|
3
4
|
export const LoggerOutputFactory = {
|
|
4
5
|
build(configuration, startedAt) {
|
|
5
|
-
const { ci } = configuration;
|
|
6
|
+
const { ci, llm } = configuration;
|
|
7
|
+
if (llm) {
|
|
8
|
+
return new LlmLoggerOutput(configuration, startedAt);
|
|
9
|
+
}
|
|
6
10
|
return ci
|
|
7
11
|
? new StaticLoggerOutput(configuration, startedAt)
|
|
8
12
|
: new InteractiveLoggerOutput(configuration, startedAt);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Thread } from '../Thread.js';
|
|
2
2
|
import '../logger.js';
|
|
3
3
|
import { LoggerOutputFactory } from './output/factory.js';
|
|
4
|
+
const SORT_WINDOW_MS = 20;
|
|
4
5
|
const fromHexaCode = (value) => ({
|
|
5
6
|
r: Number.parseInt(value.slice(1, 3), 16),
|
|
6
7
|
g: Number.parseInt(value.slice(3, 5), 16),
|
|
@@ -8,15 +9,30 @@ const fromHexaCode = (value) => ({
|
|
|
8
9
|
});
|
|
9
10
|
export const workerMain = ({ configuration, startedAt }) => {
|
|
10
11
|
const loggerOutput = LoggerOutputFactory.build(configuration, startedAt);
|
|
12
|
+
const queue = [];
|
|
13
|
+
let flushTimer;
|
|
14
|
+
const flush = () => {
|
|
15
|
+
flushTimer = undefined;
|
|
16
|
+
queue.sort((a, b) => a.timestamp - b.timestamp);
|
|
17
|
+
for (const message of queue) {
|
|
18
|
+
loggerOutput.addAttributesToLoggerOutput(message);
|
|
19
|
+
}
|
|
20
|
+
queue.length = 0;
|
|
21
|
+
};
|
|
11
22
|
const channel = Thread.createBroadcastChannel('logger');
|
|
12
23
|
channel.onmessage = (event) => {
|
|
13
24
|
const { data: message } = event;
|
|
14
25
|
if (message.command === 'terminate') {
|
|
26
|
+
if (flushTimer !== undefined) {
|
|
27
|
+
clearTimeout(flushTimer);
|
|
28
|
+
flush();
|
|
29
|
+
}
|
|
15
30
|
channel.close();
|
|
16
31
|
loggerOutput.closeLoggerOutput();
|
|
17
32
|
}
|
|
18
33
|
else if (message.command === 'log') {
|
|
19
|
-
|
|
34
|
+
queue.push(message);
|
|
35
|
+
flushTimer ??= setTimeout(flush, SORT_WINDOW_MS);
|
|
20
36
|
}
|
|
21
37
|
else if (message.command === 'terminal-resized') {
|
|
22
38
|
loggerOutput.terminalResized(message.width);
|
|
@@ -24,7 +40,7 @@ export const workerMain = ({ configuration, startedAt }) => {
|
|
|
24
40
|
};
|
|
25
41
|
const from = fromHexaCode('#FF5A37');
|
|
26
42
|
const to = fromHexaCode('#FFA42C');
|
|
27
|
-
const background = '\
|
|
43
|
+
const background = '\u{1B}[40m';
|
|
28
44
|
const diff = {
|
|
29
45
|
r: to.r - from.r,
|
|
30
46
|
g: to.g - from.g,
|
|
@@ -44,13 +60,15 @@ export const workerMain = ({ configuration, startedAt }) => {
|
|
|
44
60
|
const r = Math.floor(from.r + (diff.r * index) / width);
|
|
45
61
|
const g = Math.floor(from.g + (diff.g * index) / width);
|
|
46
62
|
const b = Math.floor(from.b + (diff.b * index) / width);
|
|
47
|
-
return `\
|
|
63
|
+
return `\u{1B}[38;2;${r};${g};${b}m` + c;
|
|
48
64
|
})
|
|
49
65
|
.join('') +
|
|
50
|
-
'\
|
|
66
|
+
'\u{1B}[0m')
|
|
51
67
|
.join('\n');
|
|
52
|
-
|
|
53
|
-
|
|
68
|
+
if (!configuration.noBanner) {
|
|
69
|
+
loggerOutput.addToReport(rawLogo + '\n');
|
|
70
|
+
loggerOutput.addTextToLoggerOutput(logo + '\u{1B}[0m\n', rawLogo + '\n');
|
|
71
|
+
}
|
|
54
72
|
channel.postMessage({
|
|
55
73
|
command: 'ready',
|
|
56
74
|
source: 'output'
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { Host } from '../Host.js';
|
|
2
2
|
import { Thread } from '../Thread.js';
|
|
3
3
|
export const toInternalLogAttributes = (attributes, level) => {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
return attributes.processId === undefined
|
|
5
|
+
? {
|
|
6
|
+
timestamp: Date.now(),
|
|
7
|
+
level,
|
|
8
|
+
processId: Host.pid,
|
|
9
|
+
threadId: Thread.threadId,
|
|
10
|
+
isMainThread: Thread.isMainThread,
|
|
11
|
+
...attributes
|
|
12
|
+
}
|
|
13
|
+
: {
|
|
6
14
|
timestamp: Date.now(),
|
|
7
15
|
level,
|
|
8
16
|
threadId: 0,
|
|
@@ -10,13 +18,4 @@ export const toInternalLogAttributes = (attributes, level) => {
|
|
|
10
18
|
...attributes,
|
|
11
19
|
processId: attributes.processId
|
|
12
20
|
};
|
|
13
|
-
}
|
|
14
|
-
return {
|
|
15
|
-
timestamp: Date.now(),
|
|
16
|
-
level,
|
|
17
|
-
processId: Host.pid,
|
|
18
|
-
threadId: Thread.threadId,
|
|
19
|
-
isMainThread: Thread.isMainThread,
|
|
20
|
-
...attributes
|
|
21
|
-
};
|
|
22
21
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const traceFileName = (startedAt) => `traces-${new Date(startedAt).toISOString().slice(0, 19).replaceAll(/[-:]/g, '').replace('T', '-')}.logz`;
|