@alejandrojca/elmulo-reporter 2.0.0-beta.5
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 +412 -0
- package/VERSION.json +9 -0
- package/assets/app.css +4457 -0
- package/assets/app.js +3014 -0
- package/assets/donkey-favicon.png +0 -0
- package/assets/donkey-silhouette-white.png +0 -0
- package/assets/donkey-silhouette.png +0 -0
- package/assets/fonts/NotoSans-Variable.ttf +0 -0
- package/assets/fonts/OFL.txt +93 -0
- package/cli.cjs +272 -0
- package/core.cjs +274 -0
- package/executive-pdf.cjs +713 -0
- package/finalize.cjs +982 -0
- package/package.json +65 -0
- package/plugin.cjs +249 -0
- package/rerun.cjs +298 -0
- package/security.cjs +56 -0
- package/server.cjs +569 -0
- package/support.ts +210 -0
package/support.ts
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
type ElmuloLogEntry = {
|
|
2
|
+
name: string;
|
|
3
|
+
message: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type ElmuloHttpExchange = {
|
|
8
|
+
startedAt: string;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
request: Record<string, unknown>;
|
|
11
|
+
response: Record<string, unknown>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const elmuloLogs: ElmuloLogEntry[] = [];
|
|
15
|
+
const elmuloHttp: ElmuloHttpExchange[] = [];
|
|
16
|
+
const elmuloEnabled = Cypress.env('elmulo') === true;
|
|
17
|
+
const elmuloCaptureHttp = Cypress.env('elmuloCaptureHttp') !== false;
|
|
18
|
+
|
|
19
|
+
function serializable(value: unknown): unknown {
|
|
20
|
+
const seen = new WeakSet<object>();
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(JSON.stringify(value, (_key, current) => {
|
|
23
|
+
if (typeof current === 'bigint') {
|
|
24
|
+
return current.toString();
|
|
25
|
+
}
|
|
26
|
+
if (current instanceof Error) {
|
|
27
|
+
return {
|
|
28
|
+
name: current.name,
|
|
29
|
+
message: current.message,
|
|
30
|
+
stack: current.stack,
|
|
31
|
+
...current,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (current && typeof current === 'object') {
|
|
35
|
+
if (seen.has(current)) return '[Circular]';
|
|
36
|
+
seen.add(current);
|
|
37
|
+
}
|
|
38
|
+
return current;
|
|
39
|
+
}));
|
|
40
|
+
} catch {
|
|
41
|
+
return String(value);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeRequestArguments(args: unknown[]) {
|
|
46
|
+
const [first, second, third] = args;
|
|
47
|
+
if (first && typeof first === 'object' && !Array.isArray(first)) {
|
|
48
|
+
return { ...(first as Record<string, unknown>) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const methods = new Set([
|
|
52
|
+
'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'TRACE', 'CONNECT',
|
|
53
|
+
]);
|
|
54
|
+
if (typeof first === 'string' && typeof second === 'string' && methods.has(first.toUpperCase())) {
|
|
55
|
+
return { method: first.toUpperCase(), url: second, body: third };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { method: 'GET', url: first, body: second };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function recordHttpExchange(
|
|
62
|
+
options: Record<string, unknown>,
|
|
63
|
+
response: any,
|
|
64
|
+
startedAt: string,
|
|
65
|
+
startedTime: number,
|
|
66
|
+
error?: unknown,
|
|
67
|
+
existing?: ElmuloHttpExchange,
|
|
68
|
+
) {
|
|
69
|
+
const request = {
|
|
70
|
+
method: String(options.method || 'GET').toUpperCase(),
|
|
71
|
+
url: options.url || '',
|
|
72
|
+
headers: response?.requestHeaders ?? options.headers ?? {},
|
|
73
|
+
body: response?.requestBody !== undefined ? response.requestBody : options.body,
|
|
74
|
+
qs: options.qs,
|
|
75
|
+
auth: options.auth,
|
|
76
|
+
form: options.form,
|
|
77
|
+
encoding: options.encoding,
|
|
78
|
+
};
|
|
79
|
+
const received = {
|
|
80
|
+
status: response?.status ?? null,
|
|
81
|
+
statusText: response?.statusText ?? '',
|
|
82
|
+
headers: response?.headers ?? {},
|
|
83
|
+
body: response?.body ?? null,
|
|
84
|
+
durationMs: response?.duration ?? Date.now() - startedTime,
|
|
85
|
+
redirectedToUrl: response?.redirectedToUrl ?? null,
|
|
86
|
+
isOkStatusCode: response?.isOkStatusCode ?? null,
|
|
87
|
+
error: error ? serializable(error) : null,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const exchange = {
|
|
91
|
+
startedAt,
|
|
92
|
+
durationMs: Number(received.durationMs || Date.now() - startedTime),
|
|
93
|
+
request: serializable(request) as Record<string, unknown>,
|
|
94
|
+
response: serializable(received) as Record<string, unknown>,
|
|
95
|
+
};
|
|
96
|
+
if (existing) Object.assign(existing, exchange);
|
|
97
|
+
else elmuloHttp.push(exchange);
|
|
98
|
+
return exchange;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (elmuloEnabled) {
|
|
102
|
+
Cypress.on('log:added', (attributes: any) => {
|
|
103
|
+
const message = Array.isArray(attributes.message)
|
|
104
|
+
? attributes.message.join(' ')
|
|
105
|
+
: String(attributes.message || '');
|
|
106
|
+
|
|
107
|
+
elmuloLogs.push({
|
|
108
|
+
name: String(attributes.displayName || attributes.name || 'command'),
|
|
109
|
+
message,
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (elmuloCaptureHttp) {
|
|
115
|
+
Cypress.Commands.overwrite('request', (originalFn, ...args: unknown[]) => {
|
|
116
|
+
const options = normalizeRequestArguments(args);
|
|
117
|
+
const startedAt = new Date().toISOString();
|
|
118
|
+
const startedTime = Date.now();
|
|
119
|
+
const exchange = recordHttpExchange(
|
|
120
|
+
options,
|
|
121
|
+
null,
|
|
122
|
+
startedAt,
|
|
123
|
+
startedTime,
|
|
124
|
+
);
|
|
125
|
+
const invokeRequest = originalFn as (...requestArgs: any[]) => Cypress.Chainable<any>;
|
|
126
|
+
return invokeRequest(...args).then((response: unknown) => {
|
|
127
|
+
recordHttpExchange(
|
|
128
|
+
options,
|
|
129
|
+
response,
|
|
130
|
+
startedAt,
|
|
131
|
+
startedTime,
|
|
132
|
+
undefined,
|
|
133
|
+
exchange,
|
|
134
|
+
);
|
|
135
|
+
return response;
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
Cypress.on('fail', (error: any) => {
|
|
140
|
+
const pendingExchange = [...elmuloHttp].reverse().find(
|
|
141
|
+
(exchange) => exchange.response.status === null,
|
|
142
|
+
);
|
|
143
|
+
if (pendingExchange) {
|
|
144
|
+
recordHttpExchange(
|
|
145
|
+
pendingExchange.request,
|
|
146
|
+
error?.response,
|
|
147
|
+
pendingExchange.startedAt,
|
|
148
|
+
Date.parse(pendingExchange.startedAt),
|
|
149
|
+
error,
|
|
150
|
+
pendingExchange,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
beforeEach(() => {
|
|
159
|
+
if (elmuloEnabled) {
|
|
160
|
+
elmuloLogs.length = 0;
|
|
161
|
+
elmuloHttp.length = 0;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
afterEach(function () {
|
|
166
|
+
if (!elmuloEnabled) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const testKey = this.currentTest?.titlePath().join(' › ') || this.currentTest?.title || '';
|
|
171
|
+
cy.task('elmulo:recordLogs', {
|
|
172
|
+
testKey,
|
|
173
|
+
logs: elmuloLogs.slice(-500),
|
|
174
|
+
}, { log: false });
|
|
175
|
+
cy.task('elmulo:recordHttp', {
|
|
176
|
+
testKey,
|
|
177
|
+
exchanges: this.currentTest?.state === 'failed' ? elmuloHttp : [],
|
|
178
|
+
}, { log: false });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
Cypress.Commands.add(
|
|
182
|
+
'elmuloAttach',
|
|
183
|
+
(name: string, content: unknown, mimeType = 'application/json') => {
|
|
184
|
+
if (!elmuloEnabled) {
|
|
185
|
+
return cy.wrap(null, { log: false });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const testKey = Cypress.currentTest.titlePath.join(' › ');
|
|
189
|
+
return cy.task('elmulo:attach', {
|
|
190
|
+
testKey,
|
|
191
|
+
name,
|
|
192
|
+
mimeType,
|
|
193
|
+
content,
|
|
194
|
+
}, { log: false });
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
declare global {
|
|
199
|
+
namespace Cypress {
|
|
200
|
+
interface Chainable {
|
|
201
|
+
elmuloAttach(
|
|
202
|
+
name: string,
|
|
203
|
+
content: unknown,
|
|
204
|
+
mimeType?: string,
|
|
205
|
+
): Chainable<unknown>;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export {};
|