@zohodesk/testinglibrary 0.0.85-n20-experimental → 0.0.87-n20-experimental
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/build/core/playwright/builtInFixtures/actorContext.js +13 -10
- package/build/core/playwright/builtInFixtures/page.js +0 -1
- package/build/core/playwright/constants/failureTagConstants.js +3 -5
- package/build/core/playwright/helpers/failureTagClassifier.js +67 -137
- package/build/core/playwright/helpers/logCollector.js +4 -1
- package/build/core/playwright/helpers/runtimeLogAnnotator.js +140 -0
- package/build/core/playwright/patterns/codePatterns.js +1 -1
- package/build/core/playwright/patterns/environmentPatterns.js +1 -1
- package/build/core/playwright/patterns/toolPatterns.js +1 -9
- package/build/core/playwright/setup/qc-custom-reporter.js +1 -2
- package/build/test/core/playwright/helpers/__tests__/failureTagClassifier.test.js +249 -67
- package/build/test/core/playwright/helpers/__tests__/logCollector.test.js +25 -1
- package/npm-shrinkwrap.json +39 -39
- package/package.json +1 -1
- package/unit_reports/unit-report.html +1 -1
|
@@ -25,20 +25,20 @@ class ActorContext {
|
|
|
25
25
|
};
|
|
26
26
|
const additionalActors = (0, _additionalProfiles.additionProfiles)($tags);
|
|
27
27
|
await Promise.all(Object.entries(additionalActors).map(async ([role, actorInfo]) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
page,
|
|
28
|
+
const additionalContext = await browser.newContext();
|
|
29
|
+
const additionalPage = await additionalContext.newPage();
|
|
30
|
+
const ctxTestDetails = {
|
|
31
|
+
page: additionalPage,
|
|
32
32
|
$tags,
|
|
33
|
-
context,
|
|
33
|
+
context: additionalContext,
|
|
34
34
|
...actorInfo
|
|
35
35
|
};
|
|
36
|
-
await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(
|
|
36
|
+
await (0, _loginDefaultStepsHelper.executeDefaultLoginSteps)(additionalContext, testInfo, ctxTestDetails, actorInfo);
|
|
37
37
|
this.actorsObj[role] = {
|
|
38
38
|
role,
|
|
39
39
|
browser,
|
|
40
|
-
context,
|
|
41
|
-
page,
|
|
40
|
+
context: additionalContext,
|
|
41
|
+
page: additionalPage,
|
|
42
42
|
executionContext: {
|
|
43
43
|
actorInfo
|
|
44
44
|
}
|
|
@@ -69,7 +69,10 @@ var _default = exports.default = {
|
|
|
69
69
|
}, use, testInfo) => {
|
|
70
70
|
const ctxObject = new ActorContext();
|
|
71
71
|
await ctxObject.setup(browser, $tags, testInfo, context, page, executionContext);
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
try {
|
|
73
|
+
await use(ctxObject);
|
|
74
|
+
} finally {
|
|
75
|
+
await ctxObject.cleanup();
|
|
76
|
+
}
|
|
74
77
|
}
|
|
75
78
|
};
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.FAILURE_TAG_SUFFIX = exports.
|
|
6
|
+
exports.FAILURE_TAG_SUFFIX = exports.FAILURE_TAG_METADATA_ATTACHMENT = exports.FAILURE_TAGS = void 0;
|
|
7
7
|
const FAILURE_TAGS = exports.FAILURE_TAGS = {
|
|
8
8
|
CASE: 'CASE',
|
|
9
9
|
CODE: 'CODE',
|
|
10
10
|
ENVIRONMENT: 'ENVIRONMENT',
|
|
11
|
-
FLAKY: 'FLAKY',
|
|
12
11
|
TOOL: 'TOOL'
|
|
13
12
|
};
|
|
14
13
|
|
|
15
14
|
// Suffix appended to a category when merging into the existing spec.tags array,
|
|
16
|
-
// e.g. "CODE" -> "CODE_failure".
|
|
15
|
+
// e.g. "CODE" -> "CODE_failure".
|
|
17
16
|
const FAILURE_TAG_SUFFIX = exports.FAILURE_TAG_SUFFIX = '_failure';
|
|
18
|
-
const FAILURE_TAG_METADATA_ATTACHMENT = exports.FAILURE_TAG_METADATA_ATTACHMENT = 'failure-tag-metadata';
|
|
19
|
-
const FAILURE_TAG_METADATA_SCHEMA_VERSION = exports.FAILURE_TAG_METADATA_SCHEMA_VERSION = 1;
|
|
17
|
+
const FAILURE_TAG_METADATA_ATTACHMENT = exports.FAILURE_TAG_METADATA_ATTACHMENT = 'failure-tag-metadata';
|
|
@@ -9,6 +9,7 @@ exports.classifyFailureDetails = classifyFailureDetails;
|
|
|
9
9
|
exports.classifyFailureDetailsFromEvents = classifyFailureDetailsFromEvents;
|
|
10
10
|
exports.classifyFailureDetailsFromResult = classifyFailureDetailsFromResult;
|
|
11
11
|
exports.classifyFailureTags = classifyFailureTags;
|
|
12
|
+
exports.isFailingResultStatus = isFailingResultStatus;
|
|
12
13
|
exports.mergeFailureTags = mergeFailureTags;
|
|
13
14
|
exports.upsertFailureTagMetadataAttachment = upsertFailureTagMetadataAttachment;
|
|
14
15
|
var _path = _interopRequireDefault(require("path"));
|
|
@@ -16,89 +17,35 @@ var _failureTagConstants = require("../constants/failureTagConstants");
|
|
|
16
17
|
var _patterns = require("../patterns");
|
|
17
18
|
var _readConfigFile = require("../readConfigFile");
|
|
18
19
|
var _runtimeLogReader = require("./runtimeLogReader");
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
// `
|
|
24
|
-
//
|
|
20
|
+
var _runtimeLogAnnotator = require("./runtimeLogAnnotator");
|
|
21
|
+
// A failing test resolves to a SINGLE category tag by strict priority:
|
|
22
|
+
// CODE > CASE > TOOL > ENVIRONMENT. If several categories match, only the
|
|
23
|
+
// highest-priority one is emitted (e.g. CODE + ENVIRONMENT -> CODE only).
|
|
24
|
+
// Metadata `matches` keeps only that winning category's unique hits (deduped by
|
|
25
|
+
// tag + pattern + text). CODE/ENVIRONMENT patterns are matched against the
|
|
26
|
+
// runtime log; TOOL/CASE against stdout/stderr. If a failure matches no
|
|
27
|
+
// category at all it defaults to CODE. Retry-passed (flaky) outcomes are not tagged.
|
|
25
28
|
|
|
26
|
-
const FAILING_RESULT_STATUSES = new Set(['failed', 'timedOut', 'interrupted']);
|
|
27
29
|
const UNEXPECTED_OUTCOME = 'unexpected';
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
patterns: _patterns.ENVIRONMENT_PATTERNS
|
|
35
|
-
}, {
|
|
36
|
-
tag: _failureTagConstants.FAILURE_TAGS.CODE,
|
|
37
|
-
patterns: _patterns.CODE_PATTERNS
|
|
38
|
-
}];
|
|
30
|
+
function isFailingResultStatus(status) {
|
|
31
|
+
return !['passed', 'skipped'].includes(status);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Single-tag priority: the highest-ranked matched category wins.
|
|
35
|
+
const CATEGORY_PRIORITY = [_failureTagConstants.FAILURE_TAGS.CODE, _failureTagConstants.FAILURE_TAGS.CASE, _failureTagConstants.FAILURE_TAGS.TOOL, _failureTagConstants.FAILURE_TAGS.ENVIRONMENT];
|
|
39
36
|
const STDIO_RULES = [{
|
|
40
37
|
tag: _failureTagConstants.FAILURE_TAGS.TOOL,
|
|
41
38
|
patterns: _patterns.TOOL_PATTERNS
|
|
42
39
|
}, {
|
|
43
40
|
tag: _failureTagConstants.FAILURE_TAGS.CASE,
|
|
44
|
-
patterns: _patterns.CASE_PATTERNS
|
|
45
|
-
impliesCode: true
|
|
41
|
+
patterns: _patterns.CASE_PATTERNS
|
|
46
42
|
}];
|
|
47
|
-
function resolveLogsDir(
|
|
48
|
-
if (logsDir) {
|
|
49
|
-
return logsDir;
|
|
50
|
-
}
|
|
43
|
+
function resolveLogsDir() {
|
|
51
44
|
const {
|
|
52
45
|
uatDirectory
|
|
53
46
|
} = (0, _readConfigFile.generateConfigFromFile)();
|
|
54
47
|
return _path.default.join(uatDirectory, 'runtime-logs');
|
|
55
48
|
}
|
|
56
|
-
function patternLabel(pattern) {
|
|
57
|
-
return pattern.source;
|
|
58
|
-
}
|
|
59
|
-
function testPattern(pattern, text) {
|
|
60
|
-
if (pattern.global || pattern.sticky) {
|
|
61
|
-
pattern.lastIndex = 0;
|
|
62
|
-
}
|
|
63
|
-
return pattern.test(text);
|
|
64
|
-
}
|
|
65
|
-
function truncateText(text) {
|
|
66
|
-
if (!text || text.length <= MAX_MATCH_TEXT_LENGTH) {
|
|
67
|
-
return text || '';
|
|
68
|
-
}
|
|
69
|
-
return `${text.slice(0, MAX_MATCH_TEXT_LENGTH)}...`;
|
|
70
|
-
}
|
|
71
|
-
function eventToText(event) {
|
|
72
|
-
var _event$location;
|
|
73
|
-
switch (event.kind) {
|
|
74
|
-
case 'pageerror':
|
|
75
|
-
return `pageerror ${event.name || ''} ${event.message || ''}`;
|
|
76
|
-
case 'console':
|
|
77
|
-
return `console ${event.text || ''} ${((_event$location = event.location) === null || _event$location === void 0 ? void 0 : _event$location.url) || ''}`;
|
|
78
|
-
case 'requestfailed':
|
|
79
|
-
return `requestfailed ${event.resourceType || ''} ${event.method || ''} ${event.url || ''} ${event.failure || ''}`;
|
|
80
|
-
case 'response':
|
|
81
|
-
return `response ${event.status || ''} ${event.method || ''} ${event.url || ''}`;
|
|
82
|
-
default:
|
|
83
|
-
return JSON.stringify(event);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function findEventMatch(events, patterns, tag, source) {
|
|
87
|
-
for (const event of events) {
|
|
88
|
-
const text = eventToText(event);
|
|
89
|
-
for (const pattern of patterns) {
|
|
90
|
-
if (testPattern(pattern, text)) {
|
|
91
|
-
return {
|
|
92
|
-
tag,
|
|
93
|
-
pattern: patternLabel(pattern),
|
|
94
|
-
source,
|
|
95
|
-
text: truncateText(text)
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
49
|
function stdioToText(entries = []) {
|
|
103
50
|
return entries.map(entry => {
|
|
104
51
|
if (typeof entry === 'string') {
|
|
@@ -125,82 +72,75 @@ function buildStdioText(result = {}) {
|
|
|
125
72
|
}
|
|
126
73
|
function findStdioMatch(stdio, patterns, tag) {
|
|
127
74
|
for (const pattern of patterns) {
|
|
128
|
-
if (testPattern(pattern, stdio)) {
|
|
75
|
+
if ((0, _runtimeLogAnnotator.testPattern)(pattern, stdio)) {
|
|
129
76
|
return {
|
|
130
77
|
tag,
|
|
131
|
-
pattern: patternLabel(pattern),
|
|
78
|
+
pattern: (0, _runtimeLogAnnotator.patternLabel)(pattern),
|
|
132
79
|
source: 'stdio',
|
|
133
|
-
text: truncateText(stdio)
|
|
80
|
+
text: (0, _runtimeLogAnnotator.truncateText)(stdio)
|
|
134
81
|
};
|
|
135
82
|
}
|
|
136
83
|
}
|
|
137
84
|
return null;
|
|
138
85
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
86
|
+
function collectStdioMatches(stdio) {
|
|
87
|
+
const matches = [];
|
|
88
|
+
for (const rule of STDIO_RULES) {
|
|
89
|
+
const match = findStdioMatch(stdio, rule.patterns, rule.tag);
|
|
142
90
|
if (match) {
|
|
143
91
|
matches.push(match);
|
|
144
92
|
}
|
|
145
93
|
}
|
|
94
|
+
return matches;
|
|
146
95
|
}
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
96
|
+
function matchKey(match) {
|
|
97
|
+
return `${match.tag}\0${match.pattern}\0${match.text}`;
|
|
98
|
+
}
|
|
99
|
+
function uniqueMatchesByEvidence(matches = []) {
|
|
100
|
+
const uniqueMatches = [];
|
|
101
|
+
const seen = new Set();
|
|
102
|
+
for (const match of matches) {
|
|
103
|
+
const key = matchKey(match);
|
|
104
|
+
if (seen.has(key)) {
|
|
151
105
|
continue;
|
|
152
106
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
implyCodeFromCase(matches, match);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
function implyCodeFromCase(matches, caseMatch) {
|
|
160
|
-
if (matches.some(match => match.tag === _failureTagConstants.FAILURE_TAGS.CODE)) {
|
|
161
|
-
return;
|
|
107
|
+
seen.add(key);
|
|
108
|
+
uniqueMatches.push(match);
|
|
162
109
|
}
|
|
163
|
-
|
|
164
|
-
tag: _failureTagConstants.FAILURE_TAGS.CODE,
|
|
165
|
-
pattern: null,
|
|
166
|
-
source: 'case-implied',
|
|
167
|
-
text: caseMatch.text
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
function classifyFailureDetailsFromEvents(events = []) {
|
|
171
|
-
const matches = [];
|
|
172
|
-
collectRuntimeLogMatches(events, matches);
|
|
173
|
-
return buildFailureTagMetadata(matches);
|
|
110
|
+
return uniqueMatches;
|
|
174
111
|
}
|
|
175
|
-
function
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const matches = [];
|
|
179
|
-
collectRuntimeLogMatches(events, matches);
|
|
180
|
-
collectStdioMatches(buildStdioText(result), matches);
|
|
181
|
-
return buildFailureTagMetadata(matches);
|
|
112
|
+
function collectMatchesFromResult(result, testTitle) {
|
|
113
|
+
const events = (0, _runtimeLogAnnotator.normalizeRuntimeLogEvents)((0, _runtimeLogReader.readRuntimeLogEventsForResult)(result, testTitle, resolveLogsDir()));
|
|
114
|
+
return [...(0, _runtimeLogAnnotator.collectRuntimeLogMatches)(events), ...collectStdioMatches(buildStdioText(result))];
|
|
182
115
|
}
|
|
183
116
|
function buildFailureTagMetadata(matches = []) {
|
|
184
|
-
|
|
117
|
+
// Collapse the matched categories to the single highest-priority winner.
|
|
118
|
+
// When a failure matched no category, fall back to CODE.
|
|
119
|
+
const winningCategory = CATEGORY_PRIORITY.find(tag => matches.some(match => match.tag === tag)) || _failureTagConstants.FAILURE_TAGS.CODE;
|
|
120
|
+
|
|
121
|
+
// Only the winning category's evidence is surfaced; drop lower-priority
|
|
122
|
+
// categories (e.g. ENVIRONMENT when CODE wins) and identical duplicates.
|
|
123
|
+
const winningMatches = uniqueMatchesByEvidence(matches.filter(match => match.tag === winningCategory));
|
|
185
124
|
return {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
matches
|
|
125
|
+
tags: [winningCategory],
|
|
126
|
+
matches: winningMatches
|
|
189
127
|
};
|
|
190
128
|
}
|
|
191
|
-
function
|
|
192
|
-
|
|
129
|
+
function classifyFailureDetailsFromEvents(events = []) {
|
|
130
|
+
return buildFailureTagMetadata((0, _runtimeLogAnnotator.collectRuntimeLogMatches)((0, _runtimeLogAnnotator.normalizeRuntimeLogEvents)(events)));
|
|
131
|
+
}
|
|
132
|
+
function classifyFailureDetailsFromResult(result, testTitle) {
|
|
133
|
+
return buildFailureTagMetadata(collectMatchesFromResult(result, testTitle));
|
|
134
|
+
}
|
|
135
|
+
function classifyFailureDetails(testDetails = [], testTitle = 'test') {
|
|
136
|
+
if (!testDetails.some(detail => detail.status === UNEXPECTED_OUTCOME)) {
|
|
137
|
+
return {
|
|
138
|
+
tags: [],
|
|
139
|
+
matches: []
|
|
140
|
+
};
|
|
141
|
+
}
|
|
193
142
|
const matches = [];
|
|
194
143
|
for (const detail of testDetails) {
|
|
195
|
-
if (detail.status === FLAKY_OUTCOME) {
|
|
196
|
-
matches.push({
|
|
197
|
-
tag: _failureTagConstants.FAILURE_TAGS.FLAKY,
|
|
198
|
-
pattern: null,
|
|
199
|
-
source: 'retry-outcome',
|
|
200
|
-
text: null
|
|
201
|
-
});
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
144
|
if (detail.status !== UNEXPECTED_OUTCOME) {
|
|
205
145
|
continue;
|
|
206
146
|
}
|
|
@@ -208,31 +148,21 @@ function classifyFailureDetails(testDetails = [], testTitle = 'test', logsDir) {
|
|
|
208
148
|
if (!failingResult) {
|
|
209
149
|
continue;
|
|
210
150
|
}
|
|
211
|
-
|
|
212
|
-
matches.push(...resultDetails.matches);
|
|
151
|
+
matches.push(...collectMatchesFromResult(failingResult, testTitle));
|
|
213
152
|
}
|
|
214
|
-
|
|
215
|
-
for (const match of matches) {
|
|
216
|
-
if (!uniqueMatches.some(existing => existing.tag === match.tag)) {
|
|
217
|
-
uniqueMatches.push(match);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return buildFailureTagMetadata(uniqueMatches);
|
|
153
|
+
return buildFailureTagMetadata(matches);
|
|
221
154
|
}
|
|
222
|
-
function classifyFailureTags(testDetails = [], testTitle = 'test'
|
|
223
|
-
return classifyFailureDetails(testDetails, testTitle
|
|
155
|
+
function classifyFailureTags(testDetails = [], testTitle = 'test') {
|
|
156
|
+
return classifyFailureDetails(testDetails, testTitle).tags;
|
|
224
157
|
}
|
|
225
158
|
function pickFailingResult(results = []) {
|
|
226
|
-
const failing = results.filter(result =>
|
|
159
|
+
const failing = results.filter(result => isFailingResultStatus(result.status));
|
|
227
160
|
if (failing.length === 0) {
|
|
228
161
|
return null;
|
|
229
162
|
}
|
|
230
163
|
return failing[failing.length - 1];
|
|
231
164
|
}
|
|
232
165
|
function formatFailureTag(tag) {
|
|
233
|
-
if (tag === _failureTagConstants.FAILURE_TAGS.FLAKY) {
|
|
234
|
-
return tag;
|
|
235
|
-
}
|
|
236
166
|
return `${tag}${_failureTagConstants.FAILURE_TAG_SUFFIX}`;
|
|
237
167
|
}
|
|
238
168
|
function mergeFailureTags(existingTags = [], failureTags = []) {
|
|
@@ -8,6 +8,7 @@ exports.LogCollector = void 0;
|
|
|
8
8
|
var _path = _interopRequireDefault(require("path"));
|
|
9
9
|
var _fileUtils = require("../../../utils/fileUtils");
|
|
10
10
|
var _logCollectorConstants = require("../constants/logCollectorConstants");
|
|
11
|
+
var _runtimeLogAnnotator = require("./runtimeLogAnnotator");
|
|
11
12
|
// Per-context Playwright runtime event collector — network failures,
|
|
12
13
|
// console errors, page errors and crashes. Buffered in-memory and flushed
|
|
13
14
|
// to the project runtime-logs folder per test.
|
|
@@ -136,7 +137,9 @@ class LogCollector {
|
|
|
136
137
|
return null;
|
|
137
138
|
}
|
|
138
139
|
const file = _path.default.join(dir, `${baseName}.jsonl`);
|
|
139
|
-
|
|
140
|
+
// Keep in-memory events raw; annotate category matches only on disk.
|
|
141
|
+
const lines = (0, _runtimeLogAnnotator.annotateRuntimeLogEvents)(this.events);
|
|
142
|
+
const body = lines.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
140
143
|
(0, _fileUtils.writeFileContents)(file, body);
|
|
141
144
|
return file;
|
|
142
145
|
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.annotateRuntimeLogEvents = annotateRuntimeLogEvents;
|
|
7
|
+
exports.collectRuntimeLogMatches = collectRuntimeLogMatches;
|
|
8
|
+
exports.eventToText = eventToText;
|
|
9
|
+
exports.normalizeRuntimeLogEvents = normalizeRuntimeLogEvents;
|
|
10
|
+
exports.patternLabel = patternLabel;
|
|
11
|
+
exports.testPattern = testPattern;
|
|
12
|
+
exports.truncateText = truncateText;
|
|
13
|
+
var _failureTagConstants = require("../constants/failureTagConstants");
|
|
14
|
+
var _patterns = require("../patterns");
|
|
15
|
+
// Shared runtime-log event text + CODE/ENVIRONMENT tagging.
|
|
16
|
+
// Used by LogCollector (JSONL flush) and failureTagClassifier (metadata).
|
|
17
|
+
|
|
18
|
+
const RUNTIME_LOG_SOURCE = 'runtime-log';
|
|
19
|
+
const MAX_MATCH_TEXT_LENGTH = 500;
|
|
20
|
+
const RUNTIME_LOG_RULES = [{
|
|
21
|
+
tag: _failureTagConstants.FAILURE_TAGS.ENVIRONMENT,
|
|
22
|
+
patterns: _patterns.ENVIRONMENT_PATTERNS
|
|
23
|
+
}, {
|
|
24
|
+
tag: _failureTagConstants.FAILURE_TAGS.CODE,
|
|
25
|
+
patterns: _patterns.CODE_PATTERNS
|
|
26
|
+
}];
|
|
27
|
+
function testPattern(pattern, text) {
|
|
28
|
+
if (pattern.global || pattern.sticky) {
|
|
29
|
+
pattern.lastIndex = 0;
|
|
30
|
+
}
|
|
31
|
+
return pattern.test(text);
|
|
32
|
+
}
|
|
33
|
+
function truncateText(text) {
|
|
34
|
+
if (!text || text.length <= MAX_MATCH_TEXT_LENGTH) {
|
|
35
|
+
return text || '';
|
|
36
|
+
}
|
|
37
|
+
return `${text.slice(0, MAX_MATCH_TEXT_LENGTH)}...`;
|
|
38
|
+
}
|
|
39
|
+
function patternLabel(pattern) {
|
|
40
|
+
return pattern.source;
|
|
41
|
+
}
|
|
42
|
+
function eventToText(event) {
|
|
43
|
+
var _event$location;
|
|
44
|
+
switch (event.kind) {
|
|
45
|
+
case 'pageerror':
|
|
46
|
+
return `pageerror ${event.name || ''} ${event.message || ''}`;
|
|
47
|
+
case 'console':
|
|
48
|
+
return `console ${event.text || ''} ${((_event$location = event.location) === null || _event$location === void 0 ? void 0 : _event$location.url) || ''}`;
|
|
49
|
+
case 'requestfailed':
|
|
50
|
+
return `requestfailed ${event.resourceType || ''} ${event.method || ''} ${event.url || ''} ${event.failure || ''}`;
|
|
51
|
+
case 'response':
|
|
52
|
+
return `response ${event.status || ''} ${event.method || ''} ${event.url || ''}`;
|
|
53
|
+
default:
|
|
54
|
+
return JSON.stringify(event);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function matchingRuntimeLogTags(event) {
|
|
58
|
+
const text = eventToText(event);
|
|
59
|
+
const tags = [];
|
|
60
|
+
for (const rule of RUNTIME_LOG_RULES) {
|
|
61
|
+
if (rule.patterns.some(pattern => testPattern(pattern, text))) {
|
|
62
|
+
tags.push(rule.tag);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return tags;
|
|
66
|
+
}
|
|
67
|
+
function findAllEventMatches(events, patterns, tag, source) {
|
|
68
|
+
const matches = [];
|
|
69
|
+
for (const event of events) {
|
|
70
|
+
const text = eventToText(event);
|
|
71
|
+
for (const pattern of patterns) {
|
|
72
|
+
if (testPattern(pattern, text)) {
|
|
73
|
+
matches.push({
|
|
74
|
+
tag,
|
|
75
|
+
pattern: patternLabel(pattern),
|
|
76
|
+
source,
|
|
77
|
+
text: truncateText(text)
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return matches;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Matching events get a category `pattern` field (CODE / ENVIRONMENT).
|
|
86
|
+
// Dual matches emit two lines with the same payload.
|
|
87
|
+
function annotateRuntimeLogEvents(events = []) {
|
|
88
|
+
const lines = [];
|
|
89
|
+
for (const event of events) {
|
|
90
|
+
if (!event) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const {
|
|
94
|
+
pattern: _ignored,
|
|
95
|
+
...raw
|
|
96
|
+
} = event;
|
|
97
|
+
const matchedTags = matchingRuntimeLogTags(raw);
|
|
98
|
+
if (matchedTags.length === 0) {
|
|
99
|
+
lines.push(raw);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const tag of matchedTags) {
|
|
103
|
+
lines.push({
|
|
104
|
+
pattern: tag,
|
|
105
|
+
...raw
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return lines;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Strip flush-time `pattern` annotations and drop duplicate event payloads so
|
|
113
|
+
// CODE+ENVIRONMENT dual lines on disk do not inflate classification counts.
|
|
114
|
+
function normalizeRuntimeLogEvents(events = []) {
|
|
115
|
+
const seen = new Set();
|
|
116
|
+
const normalized = [];
|
|
117
|
+
for (const event of events) {
|
|
118
|
+
if (!event) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const {
|
|
122
|
+
pattern: _ignored,
|
|
123
|
+
...raw
|
|
124
|
+
} = event;
|
|
125
|
+
const key = JSON.stringify(raw);
|
|
126
|
+
if (seen.has(key)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
seen.add(key);
|
|
130
|
+
normalized.push(raw);
|
|
131
|
+
}
|
|
132
|
+
return normalized;
|
|
133
|
+
}
|
|
134
|
+
function collectRuntimeLogMatches(events = []) {
|
|
135
|
+
const matches = [];
|
|
136
|
+
for (const rule of RUNTIME_LOG_RULES) {
|
|
137
|
+
matches.push(...findAllEventMatches(events, rule.patterns, rule.tag, RUNTIME_LOG_SOURCE));
|
|
138
|
+
}
|
|
139
|
+
return matches;
|
|
140
|
+
}
|
|
@@ -6,4 +6,4 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.CODE_PATTERNS = void 0;
|
|
7
7
|
// Code patterns: real application defects surfacing as JS exceptions in the
|
|
8
8
|
// browser. Matched against a flattened text form of each runtime-log event.
|
|
9
|
-
const CODE_PATTERNS = exports.CODE_PATTERNS = [/Cannot read properties of (undefined|null)/i, /\bis not a function\b/i, /\bis not defined\b/i, /\bis not iterable\b/i];
|
|
9
|
+
const CODE_PATTERNS = exports.CODE_PATTERNS = [/Cannot read properties of (undefined|null)/i, /\bis not a function\b/i, /\bis not defined\b/i, /\bis not iterable\b/i, /\bis not valid JSON\b/i, /\bis not a valid URL\b/i, /\bFailed to load resource: the server responded with a status of 422\b/i];
|
|
@@ -5,7 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.ENVIRONMENT_PATTERNS = void 0;
|
|
7
7
|
// Environment patterns: infra / network / server breakage. Matched against a
|
|
8
|
-
// flattened text form of each runtime-log event (see eventToText).
|
|
8
|
+
// flattened text form of each runtime-log event (see runtimeLogAnnotator.eventToText).
|
|
9
9
|
const ENVIRONMENT_PATTERNS = exports.ENVIRONMENT_PATTERNS = [
|
|
10
10
|
// Main document navigation was aborted (page never loaded).
|
|
11
11
|
/^requestfailed document\b[\s\S]*ERR_ABORTED/i,
|
|
@@ -20,15 +20,7 @@ const TOOL_PATTERNS = exports.TOOL_PATTERNS = [/Error during login/i, /fetch fai
|
|
|
20
20
|
/Error while parsing cookies/i,
|
|
21
21
|
// FileMutex
|
|
22
22
|
/Watch timeout exceeded/i,
|
|
23
|
-
// Runner
|
|
24
|
-
/Method 'run\(\)' must be implemented\./i, /Invalid runner type/i,
|
|
25
|
-
// Process
|
|
26
|
-
/Child Process Exited with Code/i, /Terminating Playwright Process\.\.\./i, /Cleaning up\.\.\./i,
|
|
27
|
-
// TestData
|
|
28
|
-
/Error appending or creating the test data file:/i, /Error\(err\)/i, /Error while deleting the test data file:/i,
|
|
29
|
-
// FeatureFile
|
|
30
|
-
/Error while parsing feature files\. Please fix the above issues before running test cases/i,
|
|
31
23
|
// RPC
|
|
32
|
-
/
|
|
24
|
+
/Invalid payload\. It must be a non-null object\./i, /Invalid payload\.arguments\.entityId\./i,
|
|
33
25
|
// Search
|
|
34
26
|
/Invalid or missing data in dataTable/i, /JSONPath query/i];
|
|
@@ -44,9 +44,8 @@ class CustomJsonReporter {
|
|
|
44
44
|
onTestEnd(test, result) {
|
|
45
45
|
var _result$errors, _result$steps;
|
|
46
46
|
const key = `${test.location.file}:${test.location.line}:${test.title}`;
|
|
47
|
-
const isFailure = !['passed', 'skipped'].includes(result.status);
|
|
48
47
|
let attachments = result.attachments ?? [];
|
|
49
|
-
if (
|
|
48
|
+
if ((0, _failureTagClassifier.isFailingResultStatus)(result.status)) {
|
|
50
49
|
const metadata = (0, _failureTagClassifier.classifyFailureDetailsFromResult)({
|
|
51
50
|
status: result.status,
|
|
52
51
|
workerIndex: result.workerIndex,
|