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.
Files changed (130) hide show
  1. package/README.md +20 -29
  2. package/dist/Command.js +52 -0
  3. package/dist/Npm.js +154 -74
  4. package/dist/browsers/RecoverableBrowserError.js +6 -0
  5. package/dist/browsers/chromeArguments.js +5 -0
  6. package/dist/browsers/chromeOptions.js +29 -0
  7. package/dist/browsers/consoleMessage.js +23 -0
  8. package/dist/browsers/factory.js +168 -3
  9. package/dist/browsers/networkResponse.js +15 -0
  10. package/dist/browsers/playwright.js +104 -0
  11. package/dist/browsers/puppeteer.js +54 -88
  12. package/dist/browsers/seleniumWebdriver.js +124 -0
  13. package/dist/browsers/webdriverio.js +85 -0
  14. package/dist/cli.js +18 -12
  15. package/dist/configuration/CommandLine.js +46 -26
  16. package/dist/configuration/ConfigurationValidator.js +132 -18
  17. package/dist/configuration/OptionValidationError.js +50 -2
  18. package/dist/configuration/derivations.js +8 -0
  19. package/dist/configuration/options.js +380 -23
  20. package/dist/configuration/validations.js +135 -0
  21. package/dist/configuration/validators/boolean.js +1 -1
  22. package/dist/configuration/validators/enumeration.js +7 -0
  23. package/dist/configuration/validators/fsEntry.js +12 -12
  24. package/dist/configuration/validators/index.js +8 -2
  25. package/dist/configuration/validators/integer.js +7 -1
  26. package/dist/configuration/validators/json.js +23 -0
  27. package/dist/configuration/validators/libraryMapping.js +28 -0
  28. package/dist/configuration/validators/percent.js +2 -2
  29. package/dist/configuration/validators/regexp.js +2 -2
  30. package/dist/configuration/validators/secret.js +8 -0
  31. package/dist/configuration/validators/string.js +1 -1
  32. package/dist/configuration/validators/timeout.js +2 -2
  33. package/dist/configuration/validators/url.js +3 -3
  34. package/dist/end.js +58 -0
  35. package/dist/if.js +22 -0
  36. package/dist/modes/Modes.js +2 -1
  37. package/dist/modes/batch/BatchItem.js +1 -0
  38. package/dist/modes/batch/batchTask.js +159 -0
  39. package/dist/modes/batch/index.js +98 -0
  40. package/dist/modes/batch/output.js +22 -0
  41. package/dist/modes/batch/report.js +45 -0
  42. package/dist/modes/batch/resolve.js +105 -0
  43. package/dist/modes/dumpConfig.js +6 -0
  44. package/dist/modes/execute.js +6 -5
  45. package/dist/modes/help.js +87 -2
  46. package/dist/modes/log/LogReader.js +3 -2
  47. package/dist/modes/log/LogStorage.js +31 -27
  48. package/dist/modes/log/index.js +38 -6
  49. package/dist/modes/log/{REserve.js → reserve.js} +25 -2
  50. package/dist/modes/mcp/IDocument.js +2 -0
  51. package/dist/modes/mcp/frontmatter.js +142 -0
  52. package/dist/modes/mcp/index.js +49 -0
  53. package/dist/modes/mcp/knowledgeBase.js +104 -0
  54. package/dist/modes/mcp/remoteKnowledgeBase.js +79 -0
  55. package/dist/modes/mcp/reserve.js +175 -0
  56. package/dist/modes/mcp/tools/getTopic.js +46 -0
  57. package/dist/modes/mcp/tools/index.js +6 -0
  58. package/dist/modes/mcp/tools/listTopics.js +14 -0
  59. package/dist/modes/test/PageContext.js +1 -0
  60. package/dist/modes/test/Server.js +99 -0
  61. package/dist/modes/test/browser.js +31 -17
  62. package/dist/modes/test/browserConfig.js +17 -0
  63. package/dist/modes/test/coverage/collect.js +38 -0
  64. package/dist/modes/test/coverage/index.js +3 -0
  65. package/dist/modes/test/coverage/instrument.js +70 -0
  66. package/dist/modes/test/coverage/nyc.js +15 -0
  67. package/dist/modes/test/coverage/report.js +71 -0
  68. package/dist/modes/test/coverage/settings.js +53 -0
  69. package/dist/modes/test/index.js +94 -34
  70. package/dist/modes/test/pageTask.js +293 -98
  71. package/dist/modes/test/report.js +20 -3
  72. package/dist/modes/test/reserve.js +76 -0
  73. package/dist/modes/test/screenshot.js +49 -0
  74. package/dist/platform/Crypto.js +9 -0
  75. package/dist/platform/Exit.js +33 -24
  76. package/dist/platform/FileSystem.js +6 -2
  77. package/dist/platform/Host.js +9 -1
  78. package/dist/platform/Http.js +54 -29
  79. package/dist/platform/Module.js +2 -1
  80. package/dist/platform/Path.js +5 -1
  81. package/dist/platform/Process.js +67 -13
  82. package/dist/platform/Terminal.js +12 -8
  83. package/dist/platform/Thread.js +1 -1
  84. package/dist/platform/assert.js +13 -12
  85. package/dist/platform/environment.js +9 -18
  86. package/dist/platform/index.js +1 -0
  87. package/dist/platform/logger/CompressionContext.js +117 -0
  88. package/dist/platform/logger/allCompressed.js +8 -7
  89. package/dist/platform/logger/compress.js +29 -129
  90. package/dist/platform/logger/output/BaseLoggerOutput.js +49 -31
  91. package/dist/platform/logger/output/InteractiveLoggerOutput.js +1 -1
  92. package/dist/platform/logger/output/LlmLoggerOutput.js +29 -0
  93. package/dist/platform/logger/output/factory.js +5 -1
  94. package/dist/platform/logger/output.js +24 -6
  95. package/dist/platform/logger/toInternalLogAttributes.js +10 -11
  96. package/dist/platform/logger/traceFileName.js +1 -0
  97. package/dist/platform/logger.js +52 -21
  98. package/dist/platform/mock.js +110 -19
  99. package/dist/platform/version.js +1 -2
  100. package/dist/platform/workerBootstrap.js +1 -1
  101. package/dist/reports/initReportBuilder.js +18 -0
  102. package/dist/reports/junit.js +64 -0
  103. package/dist/reports/saveReport.js +10 -0
  104. package/dist/reports/textReport.js +92 -0
  105. package/dist/reserveLogger.js +22 -0
  106. package/dist/sendToParentProcess.js +6 -0
  107. package/dist/start.js +79 -0
  108. package/dist/types/ISecretValue.js +1 -0
  109. package/dist/types/SecretValue.js +15 -0
  110. package/dist/ui/agent.js +4 -2
  111. package/dist/ui/html-report.js +2 -2
  112. package/dist/ui/lib.js +1 -1
  113. package/dist/ui/log-viewer.js +1 -1
  114. package/dist/utils/node/Folder.js +2 -4
  115. package/dist/utils/node/FramedStreamReader.js +4 -5
  116. package/dist/utils/node/FramedStreamWriter.js +6 -5
  117. package/dist/utils/node/anonymize.js +11 -0
  118. package/dist/utils/node/timeout.js +11 -0
  119. package/dist/utils/shared/ProgressBar.js +1 -2
  120. package/dist/utils/shared/TestReportBuilder.js +11 -7
  121. package/dist/utils/shared/host.js +11 -0
  122. package/dist/utils/shared/object.js +12 -3
  123. package/dist/utils/shared/schema.js +20 -0
  124. package/dist/utils/shared/string.js +1 -0
  125. package/dist/utils/shared/toIError.js +3 -7
  126. package/dist/utils/shared/version.js +52 -0
  127. package/package.json +3 -79
  128. package/dist/configuration/validators/browser.js +0 -11
  129. package/dist/modes/test/REserve.js +0 -31
  130. 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 { createCompressionContext, compress } from './compress.js';
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 = createCompressionContext();
9
- export const workerMain = ({ configuration }) => {
10
- const LOG_FILE_NAME = `traces-${new Date().toISOString().slice(0, 19).replaceAll(/[-:]/g, '').replace('T', '-')}.logz`;
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: 'Logger terminating' });
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: 'Logger ready' });
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
- const ASCII_RECORD_SEPARATOR = '\u001E';
6
- export const DIGITS = Array.from({ length: 127 - 32 })
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: Context.compressNumber(timestamp, MAX_TIMESTAMP_DIGITS) };
30
+ return { compressed: CompressionContext.compressNumber(timestamp, MAX_TIMESTAMP_DIGITS) };
142
31
  },
143
32
  uncompress(_, compressed) {
144
- return { timestamp: Context.uncompressNumber(compressed) };
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: Context.compressNumber(pageId === undefined ? 0 : pageId + 1, MAX_INDEX_DIGITS) };
59
+ return { compressed: CompressionContext.compressNumber(pageId === undefined ? 0 : pageId + 1, MAX_INDEX_DIGITS) };
171
60
  },
172
61
  uncompress(_, compressed) {
173
- const value = Context.uncompressNumber(compressed);
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 = [message.replaceAll(/\r?\n/g, '\r')];
181
- if (data || error) {
182
- parts.push(JSON_VALUE_SEP, JSON.stringify([data ?? 0, error ?? 0]).replaceAll(/"(\w+)":/g, (_, name) => `${name}${JSON_VALUE_SEP}`));
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 ? { data } : {}),
199
- ...(error ? { 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((s) => s.width > 0);
212
- const FIXED_SLOT_WIDTHS = FIXED_SLOTS.map((s) => s.width);
213
- const VARIABLE_SLOTS = DATA_LINE_SLOTS.filter((s) => s.width === 0);
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 Context);
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 Context);
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
- 'progress'
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
- const { level, timestamp, source, message, data, error } = attributes;
32
- if (!DO_NOT_RENDER_SOURCE.includes(source) && level !== LogLevel.debug) {
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 !== 'unknown') {
72
- this._overallprogressSnapshot.totalNumberOfExecutedTests += data.value;
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 Object.values(this._pageProgressMap)) {
85
- if (pageProgress.type !== 'unknown') {
86
- overallProgress.totalNumberOfExecutedTests += pageProgress.bar.value;
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
- const rendered = this.render(attributes);
151
- if (rendered) {
152
- const raw = Terminal.stripVTControlCharacters(rendered);
153
- this.addTextToLoggerOutput(rendered, raw);
154
- this.addToReport(raw);
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
- loggerOutput.addAttributesToLoggerOutput(message);
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 = '\u001B[40m';
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 `\u001B[38;2;${r};${g};${b}m` + c;
63
+ return `\u{1B}[38;2;${r};${g};${b}m` + c;
48
64
  })
49
65
  .join('') +
50
- '\u001B[0m')
66
+ '\u{1B}[0m')
51
67
  .join('\n');
52
- loggerOutput.addToReport(rawLogo + '\n');
53
- loggerOutput.addTextToLoggerOutput(logo + '\u001B[0m\n', rawLogo + '\n');
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
- if (attributes.processId !== undefined) {
5
- return {
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`;