@flash-ai-team/flash-test-framework 0.0.12 → 0.0.13
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/dist/cli/index.js +187 -0
- package/dist/core/Keyword.d.ts +0 -5
- package/dist/core/Keyword.js +26 -4
- package/dist/keywords/WebUI.d.ts +37 -12
- package/dist/keywords/WebUI.js +25 -24
- package/dist/reporting/HtmlReporter.d.ts +1 -0
- package/dist/reporting/HtmlReporter.js +48 -0
- package/dist/reporting/ReporterUtils.js +13 -2
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
9
10
|
const args = process.argv.slice(2);
|
|
10
11
|
const command = args[0];
|
|
11
12
|
if (command === 'init') {
|
|
@@ -15,8 +16,13 @@ if (command === 'init') {
|
|
|
15
16
|
else if (command === '--version' || command === '-v') {
|
|
16
17
|
printVersion();
|
|
17
18
|
}
|
|
19
|
+
else if (command === 'history') {
|
|
20
|
+
const suiteName = args[1];
|
|
21
|
+
showHistory(suiteName);
|
|
22
|
+
}
|
|
18
23
|
else {
|
|
19
24
|
console.log('Usage: flash-test init [project-name]');
|
|
25
|
+
console.log(' flash-test history [suite-name]');
|
|
20
26
|
console.log(' flash-test -v / --version');
|
|
21
27
|
process.exit(1);
|
|
22
28
|
}
|
|
@@ -149,3 +155,184 @@ function createFile(filePath, content) {
|
|
|
149
155
|
console.log(`Created: ${path_1.default.basename(filePath)}`);
|
|
150
156
|
}
|
|
151
157
|
}
|
|
158
|
+
function showHistory(suiteFilter) {
|
|
159
|
+
const reportsDir = path_1.default.join(process.cwd(), 'reports');
|
|
160
|
+
if (!fs_1.default.existsSync(reportsDir)) {
|
|
161
|
+
console.log('No reports found.');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
let runs = [];
|
|
165
|
+
const suites = suiteFilter ? [suiteFilter] : fs_1.default.readdirSync(reportsDir).filter(f => fs_1.default.statSync(path_1.default.join(reportsDir, f)).isDirectory());
|
|
166
|
+
for (const suite of suites) {
|
|
167
|
+
const suitePath = path_1.default.join(reportsDir, suite);
|
|
168
|
+
if (!fs_1.default.existsSync(suitePath))
|
|
169
|
+
continue;
|
|
170
|
+
const testRuns = fs_1.default.readdirSync(suitePath).filter(f => f.startsWith('test_') && fs_1.default.statSync(path_1.default.join(suitePath, f)).isDirectory());
|
|
171
|
+
for (const run of testRuns) {
|
|
172
|
+
const runPath = path_1.default.join(suitePath, run);
|
|
173
|
+
let status = 'Unknown';
|
|
174
|
+
let duration = 0;
|
|
175
|
+
let total = 0;
|
|
176
|
+
let passed = 0;
|
|
177
|
+
let failed = 0;
|
|
178
|
+
// Try to read custom-report.json for details
|
|
179
|
+
const jsonPath = path_1.default.join(runPath, 'custom-report.json');
|
|
180
|
+
if (fs_1.default.existsSync(jsonPath)) {
|
|
181
|
+
try {
|
|
182
|
+
const data = JSON.parse(fs_1.default.readFileSync(jsonPath, 'utf-8'));
|
|
183
|
+
if (Array.isArray(data)) {
|
|
184
|
+
total = data.length;
|
|
185
|
+
passed = data.filter((t) => t.status === 'passed').length;
|
|
186
|
+
failed = data.filter((t) => t.status === 'failed' || t.status === 'timedOut').length;
|
|
187
|
+
// Sum duration or max? Let's use sum for now as a simple metric
|
|
188
|
+
duration = data.reduce((acc, curr) => acc + (curr.duration || 0), 0);
|
|
189
|
+
status = failed > 0 ? 'Failed' : (passed === total && total > 0 ? 'Passed' : 'Mixed');
|
|
190
|
+
}
|
|
191
|
+
else if (data.stats) {
|
|
192
|
+
// Legacy support if needed
|
|
193
|
+
duration = data.stats.duration;
|
|
194
|
+
total = data.stats.total;
|
|
195
|
+
passed = data.stats.passed;
|
|
196
|
+
failed = data.stats.failed;
|
|
197
|
+
status = failed > 0 ? 'Failed' : (passed === total && total > 0 ? 'Passed' : 'Mixed');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (e) { /* ignore */ }
|
|
201
|
+
}
|
|
202
|
+
// Extract timestamp from folder name test_YYYY-MM-DD_HHmmss
|
|
203
|
+
const timePart = run.replace('test_', '');
|
|
204
|
+
// Format for display: YYYY-MM-DD HH:mm:ss
|
|
205
|
+
const formattedTime = timePart.replace('_', ' ').replace(/(\d{4}-\d{2}-\d{2}) (\d{2})(\d{2})(\d{2})/, '$1 $2:$3:$4');
|
|
206
|
+
runs.push({
|
|
207
|
+
suite,
|
|
208
|
+
runFolder: run,
|
|
209
|
+
timestamp: formattedTime,
|
|
210
|
+
rawTimestamp: timePart,
|
|
211
|
+
path: runPath,
|
|
212
|
+
status,
|
|
213
|
+
duration,
|
|
214
|
+
passed,
|
|
215
|
+
failed,
|
|
216
|
+
total
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Sort by newest first
|
|
221
|
+
runs.sort((a, b) => b.rawTimestamp.localeCompare(a.rawTimestamp));
|
|
222
|
+
// Console Output
|
|
223
|
+
console.table(runs.map(r => ({
|
|
224
|
+
Suite: r.suite,
|
|
225
|
+
Timestamp: r.timestamp,
|
|
226
|
+
Status: r.status,
|
|
227
|
+
Passed: r.passed,
|
|
228
|
+
Failed: r.failed,
|
|
229
|
+
Duration: `${(r.duration / 1000).toFixed(2)}s`
|
|
230
|
+
})));
|
|
231
|
+
// Generate HTML Dashboard
|
|
232
|
+
generateHistoryHtml(runs, reportsDir);
|
|
233
|
+
}
|
|
234
|
+
function generateHistoryHtml(runs, reportsDir) {
|
|
235
|
+
const htmlPath = path_1.default.join(reportsDir, 'history.html');
|
|
236
|
+
// Group by Stats
|
|
237
|
+
const totalRuns = runs.length;
|
|
238
|
+
const passedRuns = runs.filter(r => r.status === 'Passed').length;
|
|
239
|
+
const failedRuns = runs.filter(r => r.status === 'Failed').length;
|
|
240
|
+
const rows = runs.map(r => {
|
|
241
|
+
const statusClass = r.status === 'Passed' ? 'passed' : (r.status === 'Failed' ? 'failed' : 'mixed');
|
|
242
|
+
// Relative path from reports/history.html to reports/suite/run/report.html
|
|
243
|
+
const reportLink = `${r.suite}/${r.runFolder}/report.html`;
|
|
244
|
+
return `
|
|
245
|
+
<tr>
|
|
246
|
+
<td>${r.timestamp}</td>
|
|
247
|
+
<td><span class="suite-badge">${r.suite}</span></td>
|
|
248
|
+
<td><span class="status-badge status-${statusClass}">${r.status}</span></td>
|
|
249
|
+
<td>${r.passed} / ${r.total}</td>
|
|
250
|
+
<td>${(r.duration / 1000).toFixed(2)}s</td>
|
|
251
|
+
<td><a href="${reportLink}" target="_blank" class="view-btn">View Report</a></td>
|
|
252
|
+
</tr>
|
|
253
|
+
`;
|
|
254
|
+
}).join('');
|
|
255
|
+
const html = `
|
|
256
|
+
<!DOCTYPE html>
|
|
257
|
+
<html lang="en">
|
|
258
|
+
<head>
|
|
259
|
+
<meta charset="UTF-8">
|
|
260
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
261
|
+
<title>Test Run History</title>
|
|
262
|
+
<style>
|
|
263
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif; background: #f4f4f4; padding: 20px; color: #333; }
|
|
264
|
+
.container { max-width: 1000px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
|
|
265
|
+
h1 { margin-top: 0; border-bottom: 2px solid #eee; padding-bottom: 15px; }
|
|
266
|
+
|
|
267
|
+
.stats-grid { display: flex; gap: 20px; margin-bottom: 30px; }
|
|
268
|
+
.stat-card { flex: 1; background: #fafafa; padding: 15px; border-radius: 6px; text-align: center; border: 1px solid #eee; }
|
|
269
|
+
.stat-value { font-size: 2em; font-weight: bold; display: block; }
|
|
270
|
+
.stat-label { color: #666; font-size: 0.9em; text-transform: uppercase; letter-spacing: 1px; }
|
|
271
|
+
.stat-passed .stat-value { color: #4caf50; }
|
|
272
|
+
.stat-failed .stat-value { color: #f44336; }
|
|
273
|
+
|
|
274
|
+
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
|
|
275
|
+
th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #eee; }
|
|
276
|
+
th { background: #f8f8f8; font-weight: 600; color: #555; }
|
|
277
|
+
tr:hover { background: #fcfcfc; }
|
|
278
|
+
|
|
279
|
+
.status-badge { padding: 4px 8px; border-radius: 4px; font-size: 0.85em; font-weight: 600; }
|
|
280
|
+
.status-passed { background: #e8f5e9; color: #2e7d32; }
|
|
281
|
+
.status-failed { background: #ffebee; color: #c62828; }
|
|
282
|
+
.status-mixed { background: #fff3e0; color: #ef6c00; }
|
|
283
|
+
|
|
284
|
+
.suite-badge { background: #e3f2fd; color: #1565c0; padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }
|
|
285
|
+
|
|
286
|
+
.view-btn { text-decoration: none; background: #333; color: white; padding: 6px 12px; border-radius: 4px; font-size: 0.9em; transition: background 0.2s; }
|
|
287
|
+
.view-btn:hover { background: #555; }
|
|
288
|
+
</style>
|
|
289
|
+
</head>
|
|
290
|
+
<body>
|
|
291
|
+
<div class="container">
|
|
292
|
+
<h1>📜 Test Run History</h1>
|
|
293
|
+
|
|
294
|
+
<div class="stats-grid">
|
|
295
|
+
<div class="stat-card">
|
|
296
|
+
<span class="stat-value">${totalRuns}</span>
|
|
297
|
+
<span class="stat-label">Total Runs</span>
|
|
298
|
+
</div>
|
|
299
|
+
<div class="stat-card stat-passed">
|
|
300
|
+
<span class="stat-value">${passedRuns}</span>
|
|
301
|
+
<span class="stat-label">Passed</span>
|
|
302
|
+
</div>
|
|
303
|
+
<div class="stat-card stat-failed">
|
|
304
|
+
<span class="stat-value">${failedRuns}</span>
|
|
305
|
+
<span class="stat-label">Failed</span>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
|
|
309
|
+
<table>
|
|
310
|
+
<thead>
|
|
311
|
+
<tr>
|
|
312
|
+
<th>Timestamp</th>
|
|
313
|
+
<th>Suite</th>
|
|
314
|
+
<th>Status</th>
|
|
315
|
+
<th>Pass / Total</th>
|
|
316
|
+
<th>Duration</th>
|
|
317
|
+
<th>Action</th>
|
|
318
|
+
</tr>
|
|
319
|
+
</thead>
|
|
320
|
+
<tbody>
|
|
321
|
+
${rows}
|
|
322
|
+
</tbody>
|
|
323
|
+
</table>
|
|
324
|
+
</div>
|
|
325
|
+
</body>
|
|
326
|
+
</html>
|
|
327
|
+
`;
|
|
328
|
+
fs_1.default.writeFileSync(htmlPath, html);
|
|
329
|
+
console.log(`\nHistory Dashboard generated: ${htmlPath}`);
|
|
330
|
+
// Open in browser
|
|
331
|
+
try {
|
|
332
|
+
const startCmd = process.platform === 'win32' ? 'start' : (process.platform === 'darwin' ? 'open' : 'xdg-open');
|
|
333
|
+
(0, child_process_1.execSync)(`${startCmd} "${htmlPath}"`);
|
|
334
|
+
}
|
|
335
|
+
catch (e) {
|
|
336
|
+
console.log('Could not auto-open browser. Please open the file manually.');
|
|
337
|
+
}
|
|
338
|
+
}
|
package/dist/core/Keyword.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { Page, TestInfo } from '@playwright/test';
|
|
2
2
|
export declare const KeywordRegistry: Record<string, Function>;
|
|
3
|
-
/**
|
|
4
|
-
* Decorator to register a function as a Keyword and wrap it in a Playwright Step
|
|
5
|
-
* Supports both Stage 3 (Standard) and Stage 2 (Legacy/Experimental) decorators.
|
|
6
|
-
* @param name Custom name for the keyword (optional)
|
|
7
|
-
*/
|
|
8
3
|
export declare function Keyword(name?: string): (targetOrValue: any, contextOrKey: any, descriptor?: PropertyDescriptor) => any;
|
|
9
4
|
/**
|
|
10
5
|
* Context holder to share Page and TestInfo across keywords
|
package/dist/core/Keyword.js
CHANGED
|
@@ -33,6 +33,16 @@ function formatArg(arg) {
|
|
|
33
33
|
* Supports both Stage 3 (Standard) and Stage 2 (Legacy/Experimental) decorators.
|
|
34
34
|
* @param name Custom name for the keyword (optional)
|
|
35
35
|
*/
|
|
36
|
+
// Helper to check for options object
|
|
37
|
+
function extractDescription(args) {
|
|
38
|
+
if (args.length > 0) {
|
|
39
|
+
const lastArg = args[args.length - 1];
|
|
40
|
+
if (lastArg && typeof lastArg === 'object' && 'description' in lastArg && typeof lastArg.description === 'string') {
|
|
41
|
+
return lastArg.description;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
36
46
|
function Keyword(name) {
|
|
37
47
|
return function (targetOrValue, contextOrKey, descriptor) {
|
|
38
48
|
// Stage 3: (value, context) where context is an object
|
|
@@ -43,8 +53,14 @@ function Keyword(name) {
|
|
|
43
53
|
// Registry
|
|
44
54
|
exports.KeywordRegistry[keywordName] = originalMethod;
|
|
45
55
|
return async function (...args) {
|
|
46
|
-
const
|
|
47
|
-
|
|
56
|
+
const customDescription = extractDescription(args);
|
|
57
|
+
// Filter out the options object from display args if it was used for description
|
|
58
|
+
const displayArgs = customDescription ? args.slice(0, -1) : args;
|
|
59
|
+
const argString = displayArgs.map(formatArg).join(', ');
|
|
60
|
+
let stepName = argString ? `${keywordName} (${argString})` : keywordName;
|
|
61
|
+
if (customDescription) {
|
|
62
|
+
stepName = customDescription;
|
|
63
|
+
}
|
|
48
64
|
return await test_1.test.step(stepName, async () => {
|
|
49
65
|
return await originalMethod.apply(this, args);
|
|
50
66
|
});
|
|
@@ -60,8 +76,14 @@ function Keyword(name) {
|
|
|
60
76
|
const originalMethod = legacyDescriptor.value;
|
|
61
77
|
const keywordName = name || contextOrKey;
|
|
62
78
|
legacyDescriptor.value = async function (...args) {
|
|
63
|
-
const
|
|
64
|
-
|
|
79
|
+
const customDescription = extractDescription(args);
|
|
80
|
+
// Filter out the options object from display args if it was used for description
|
|
81
|
+
const displayArgs = customDescription ? args.slice(0, -1) : args;
|
|
82
|
+
const argString = displayArgs.map(formatArg).join(', ');
|
|
83
|
+
let stepName = argString ? `${keywordName} (${argString})` : keywordName;
|
|
84
|
+
if (customDescription) {
|
|
85
|
+
stepName = customDescription;
|
|
86
|
+
}
|
|
65
87
|
return await test_1.test.step(stepName, async () => {
|
|
66
88
|
return await originalMethod.apply(this, args);
|
|
67
89
|
});
|
package/dist/keywords/WebUI.d.ts
CHANGED
|
@@ -6,66 +6,91 @@ export declare class Web {
|
|
|
6
6
|
* Navigates to the specified URL.
|
|
7
7
|
* @param url - The URL to navigate to.
|
|
8
8
|
*/
|
|
9
|
-
static navigateToUrl(url: string
|
|
9
|
+
static navigateToUrl(url: string, options?: {
|
|
10
|
+
description?: string;
|
|
11
|
+
}): Promise<void>;
|
|
10
12
|
/**
|
|
11
13
|
* Verifies that the current page URL matches the expected URL.
|
|
12
14
|
* @param url - The expected URL.
|
|
13
15
|
* @param timeout - The maximum time to wait in milliseconds (default: 5000).
|
|
14
16
|
*/
|
|
15
|
-
static verifyUrl(url: string, timeout?: number
|
|
17
|
+
static verifyUrl(url: string, timeout?: number, options?: {
|
|
18
|
+
description?: string;
|
|
19
|
+
}): Promise<void>;
|
|
16
20
|
/**
|
|
17
21
|
* Clicks on the specified element.
|
|
18
22
|
* @param to - The TestObject representing the element.
|
|
23
|
+
* @param options - Optional parameters (description).
|
|
19
24
|
*/
|
|
20
|
-
static click(to: TestObject
|
|
25
|
+
static click(to: TestObject, options?: {
|
|
26
|
+
description?: string;
|
|
27
|
+
}): Promise<void>;
|
|
21
28
|
/**
|
|
22
29
|
* Sets the text of an input element.
|
|
23
30
|
* @param to - The TestObject representing the input element.
|
|
24
31
|
* @param text - The text to set.
|
|
25
32
|
*/
|
|
26
|
-
static setText(to: TestObject, text: string
|
|
33
|
+
static setText(to: TestObject, text: string, options?: {
|
|
34
|
+
description?: string;
|
|
35
|
+
}): Promise<void>;
|
|
27
36
|
/**
|
|
28
37
|
* Searches for the specified text using heuristic selectors.
|
|
29
38
|
* @param text - The text to search for.
|
|
30
39
|
*/
|
|
31
|
-
static search(text: string
|
|
40
|
+
static search(text: string, options?: {
|
|
41
|
+
description?: string;
|
|
42
|
+
}): Promise<void>;
|
|
32
43
|
/**
|
|
33
44
|
* Verifies that the specified element is present (visible) on the page.
|
|
34
45
|
* @param to - The TestObject representing the element.
|
|
35
46
|
* @param timeout - The maximum time to wait in milliseconds (default: 5000).
|
|
36
47
|
*/
|
|
37
|
-
static verifyElementPresent(to: TestObject, timeout?: number
|
|
48
|
+
static verifyElementPresent(to: TestObject, timeout?: number, options?: {
|
|
49
|
+
description?: string;
|
|
50
|
+
}): Promise<void>;
|
|
38
51
|
/**
|
|
39
52
|
* Gets the visible text of the specified element.
|
|
40
53
|
* @param to - The TestObject representing the element.
|
|
41
54
|
* @returns The text content of the element.
|
|
42
55
|
*/
|
|
43
|
-
static getText(to: TestObject
|
|
56
|
+
static getText(to: TestObject, options?: {
|
|
57
|
+
description?: string;
|
|
58
|
+
}): Promise<string>;
|
|
44
59
|
/**
|
|
45
60
|
* Closes the current browser context/page.
|
|
46
61
|
*/
|
|
47
|
-
static closeBrowser(
|
|
62
|
+
static closeBrowser(options?: {
|
|
63
|
+
description?: string;
|
|
64
|
+
}): Promise<void>;
|
|
48
65
|
/**
|
|
49
66
|
* Double clicks on the specified element.
|
|
50
67
|
* @param to - The TestObject representing the element.
|
|
51
68
|
*/
|
|
52
|
-
static doubleClick(to: TestObject
|
|
69
|
+
static doubleClick(to: TestObject, options?: {
|
|
70
|
+
description?: string;
|
|
71
|
+
}): Promise<void>;
|
|
53
72
|
/**
|
|
54
73
|
* Right clicks (context click) on the specified element.
|
|
55
74
|
* @param to - The TestObject representing the element.
|
|
56
75
|
*/
|
|
57
|
-
static rightClick(to: TestObject
|
|
76
|
+
static rightClick(to: TestObject, options?: {
|
|
77
|
+
description?: string;
|
|
78
|
+
}): Promise<void>;
|
|
58
79
|
/**
|
|
59
80
|
* Hovers over the specified element.
|
|
60
81
|
* @param to - The TestObject representing the element.
|
|
61
82
|
*/
|
|
62
|
-
static mouseOver(to: TestObject
|
|
83
|
+
static mouseOver(to: TestObject, options?: {
|
|
84
|
+
description?: string;
|
|
85
|
+
}): Promise<void>;
|
|
63
86
|
/**
|
|
64
87
|
* Drags the source element and drops it onto the target element.
|
|
65
88
|
* @param source - The TestObject representing the source element.
|
|
66
89
|
* @param target - The TestObject representing the target element.
|
|
67
90
|
*/
|
|
68
|
-
static dragAndDrop(source: TestObject, target: TestObject
|
|
91
|
+
static dragAndDrop(source: TestObject, target: TestObject, options?: {
|
|
92
|
+
description?: string;
|
|
93
|
+
}): Promise<void>;
|
|
69
94
|
/**
|
|
70
95
|
* Checks (selects) the specified checkbox or radio button.
|
|
71
96
|
* @param to - The TestObject representing the element.
|
package/dist/keywords/WebUI.js
CHANGED
|
@@ -28,7 +28,7 @@ class Web {
|
|
|
28
28
|
* Navigates to the specified URL.
|
|
29
29
|
* @param url - The URL to navigate to.
|
|
30
30
|
*/
|
|
31
|
-
static async navigateToUrl(url) {
|
|
31
|
+
static async navigateToUrl(url, options) {
|
|
32
32
|
await this.page.goto(url);
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
@@ -36,14 +36,15 @@ class Web {
|
|
|
36
36
|
* @param url - The expected URL.
|
|
37
37
|
* @param timeout - The maximum time to wait in milliseconds (default: 5000).
|
|
38
38
|
*/
|
|
39
|
-
static async verifyUrl(url, timeout = 5000) {
|
|
39
|
+
static async verifyUrl(url, timeout = 5000, options) {
|
|
40
40
|
await (0, test_1.expect)(this.page).toHaveURL(url, { timeout });
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
43
|
* Clicks on the specified element.
|
|
44
44
|
* @param to - The TestObject representing the element.
|
|
45
|
+
* @param options - Optional parameters (description).
|
|
45
46
|
*/
|
|
46
|
-
static async click(to) {
|
|
47
|
+
static async click(to, options) {
|
|
47
48
|
await this.getLocator(to).click();
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
@@ -51,14 +52,14 @@ class Web {
|
|
|
51
52
|
* @param to - The TestObject representing the input element.
|
|
52
53
|
* @param text - The text to set.
|
|
53
54
|
*/
|
|
54
|
-
static async setText(to, text) {
|
|
55
|
+
static async setText(to, text, options) {
|
|
55
56
|
await this.getLocator(to).fill(text);
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
58
59
|
* Searches for the specified text using heuristic selectors.
|
|
59
60
|
* @param text - The text to search for.
|
|
60
61
|
*/
|
|
61
|
-
static async search(text) {
|
|
62
|
+
static async search(text, options) {
|
|
62
63
|
// Common search input selectors
|
|
63
64
|
const selectors = [
|
|
64
65
|
'input[name="q"]',
|
|
@@ -78,7 +79,7 @@ class Web {
|
|
|
78
79
|
* @param to - The TestObject representing the element.
|
|
79
80
|
* @param timeout - The maximum time to wait in milliseconds (default: 5000).
|
|
80
81
|
*/
|
|
81
|
-
static async verifyElementPresent(to, timeout = 5000) {
|
|
82
|
+
static async verifyElementPresent(to, timeout = 5000, options) {
|
|
82
83
|
await (0, test_1.expect)(this.getLocator(to)).toBeVisible({ timeout });
|
|
83
84
|
}
|
|
84
85
|
/**
|
|
@@ -86,13 +87,13 @@ class Web {
|
|
|
86
87
|
* @param to - The TestObject representing the element.
|
|
87
88
|
* @returns The text content of the element.
|
|
88
89
|
*/
|
|
89
|
-
static async getText(to) {
|
|
90
|
+
static async getText(to, options) {
|
|
90
91
|
return await this.getLocator(to).innerText();
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
93
94
|
* Closes the current browser context/page.
|
|
94
95
|
*/
|
|
95
|
-
static async closeBrowser() {
|
|
96
|
+
static async closeBrowser(options) {
|
|
96
97
|
await this.page.close();
|
|
97
98
|
}
|
|
98
99
|
// --- Interaction Keywords ---
|
|
@@ -100,21 +101,21 @@ class Web {
|
|
|
100
101
|
* Double clicks on the specified element.
|
|
101
102
|
* @param to - The TestObject representing the element.
|
|
102
103
|
*/
|
|
103
|
-
static async doubleClick(to) {
|
|
104
|
+
static async doubleClick(to, options) {
|
|
104
105
|
await this.getLocator(to).dblclick();
|
|
105
106
|
}
|
|
106
107
|
/**
|
|
107
108
|
* Right clicks (context click) on the specified element.
|
|
108
109
|
* @param to - The TestObject representing the element.
|
|
109
110
|
*/
|
|
110
|
-
static async rightClick(to) {
|
|
111
|
+
static async rightClick(to, options) {
|
|
111
112
|
await this.getLocator(to).click({ button: 'right' });
|
|
112
113
|
}
|
|
113
114
|
/**
|
|
114
115
|
* Hovers over the specified element.
|
|
115
116
|
* @param to - The TestObject representing the element.
|
|
116
117
|
*/
|
|
117
|
-
static async mouseOver(to) {
|
|
118
|
+
static async mouseOver(to, options) {
|
|
118
119
|
await this.getLocator(to).hover();
|
|
119
120
|
}
|
|
120
121
|
/**
|
|
@@ -122,7 +123,7 @@ class Web {
|
|
|
122
123
|
* @param source - The TestObject representing the source element.
|
|
123
124
|
* @param target - The TestObject representing the target element.
|
|
124
125
|
*/
|
|
125
|
-
static async dragAndDrop(source, target) {
|
|
126
|
+
static async dragAndDrop(source, target, options) {
|
|
126
127
|
await this.getLocator(source).dragTo(this.getLocator(target));
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
@@ -391,73 +392,73 @@ exports.Web = Web;
|
|
|
391
392
|
__decorate([
|
|
392
393
|
(0, Keyword_1.Keyword)("Navigate To Url"),
|
|
393
394
|
__metadata("design:type", Function),
|
|
394
|
-
__metadata("design:paramtypes", [String]),
|
|
395
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
395
396
|
__metadata("design:returntype", Promise)
|
|
396
397
|
], Web, "navigateToUrl", null);
|
|
397
398
|
__decorate([
|
|
398
399
|
(0, Keyword_1.Keyword)("Verify Url"),
|
|
399
400
|
__metadata("design:type", Function),
|
|
400
|
-
__metadata("design:paramtypes", [String, Number]),
|
|
401
|
+
__metadata("design:paramtypes", [String, Number, Object]),
|
|
401
402
|
__metadata("design:returntype", Promise)
|
|
402
403
|
], Web, "verifyUrl", null);
|
|
403
404
|
__decorate([
|
|
404
405
|
(0, Keyword_1.Keyword)("Click"),
|
|
405
406
|
__metadata("design:type", Function),
|
|
406
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject]),
|
|
407
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, Object]),
|
|
407
408
|
__metadata("design:returntype", Promise)
|
|
408
409
|
], Web, "click", null);
|
|
409
410
|
__decorate([
|
|
410
411
|
(0, Keyword_1.Keyword)("Set Text"),
|
|
411
412
|
__metadata("design:type", Function),
|
|
412
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, String]),
|
|
413
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, String, Object]),
|
|
413
414
|
__metadata("design:returntype", Promise)
|
|
414
415
|
], Web, "setText", null);
|
|
415
416
|
__decorate([
|
|
416
417
|
(0, Keyword_1.Keyword)("Search"),
|
|
417
418
|
__metadata("design:type", Function),
|
|
418
|
-
__metadata("design:paramtypes", [String]),
|
|
419
|
+
__metadata("design:paramtypes", [String, Object]),
|
|
419
420
|
__metadata("design:returntype", Promise)
|
|
420
421
|
], Web, "search", null);
|
|
421
422
|
__decorate([
|
|
422
423
|
(0, Keyword_1.Keyword)("Verify Element Present"),
|
|
423
424
|
__metadata("design:type", Function),
|
|
424
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, Number]),
|
|
425
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, Number, Object]),
|
|
425
426
|
__metadata("design:returntype", Promise)
|
|
426
427
|
], Web, "verifyElementPresent", null);
|
|
427
428
|
__decorate([
|
|
428
429
|
(0, Keyword_1.Keyword)("Get Text"),
|
|
429
430
|
__metadata("design:type", Function),
|
|
430
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject]),
|
|
431
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, Object]),
|
|
431
432
|
__metadata("design:returntype", Promise)
|
|
432
433
|
], Web, "getText", null);
|
|
433
434
|
__decorate([
|
|
434
435
|
(0, Keyword_1.Keyword)("Close Browser"),
|
|
435
436
|
__metadata("design:type", Function),
|
|
436
|
-
__metadata("design:paramtypes", []),
|
|
437
|
+
__metadata("design:paramtypes", [Object]),
|
|
437
438
|
__metadata("design:returntype", Promise)
|
|
438
439
|
], Web, "closeBrowser", null);
|
|
439
440
|
__decorate([
|
|
440
441
|
(0, Keyword_1.Keyword)("Double Click"),
|
|
441
442
|
__metadata("design:type", Function),
|
|
442
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject]),
|
|
443
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, Object]),
|
|
443
444
|
__metadata("design:returntype", Promise)
|
|
444
445
|
], Web, "doubleClick", null);
|
|
445
446
|
__decorate([
|
|
446
447
|
(0, Keyword_1.Keyword)("Right Click"),
|
|
447
448
|
__metadata("design:type", Function),
|
|
448
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject]),
|
|
449
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, Object]),
|
|
449
450
|
__metadata("design:returntype", Promise)
|
|
450
451
|
], Web, "rightClick", null);
|
|
451
452
|
__decorate([
|
|
452
453
|
(0, Keyword_1.Keyword)("Mouse Over"),
|
|
453
454
|
__metadata("design:type", Function),
|
|
454
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject]),
|
|
455
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, Object]),
|
|
455
456
|
__metadata("design:returntype", Promise)
|
|
456
457
|
], Web, "mouseOver", null);
|
|
457
458
|
__decorate([
|
|
458
459
|
(0, Keyword_1.Keyword)("Drag And Drop"),
|
|
459
460
|
__metadata("design:type", Function),
|
|
460
|
-
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, ObjectRepository_1.TestObject]),
|
|
461
|
+
__metadata("design:paramtypes", [ObjectRepository_1.TestObject, ObjectRepository_1.TestObject, Object]),
|
|
461
462
|
__metadata("design:returntype", Promise)
|
|
462
463
|
], Web, "dragAndDrop", null);
|
|
463
464
|
__decorate([
|
|
@@ -2,6 +2,7 @@ import { Reporter, FullConfig, Suite, TestCase, TestResult, FullResult } from '@
|
|
|
2
2
|
declare class HtmlReporter implements Reporter {
|
|
3
3
|
private suiteStartTime;
|
|
4
4
|
private reportPath;
|
|
5
|
+
private envInfo;
|
|
5
6
|
private fileCache;
|
|
6
7
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
7
8
|
onEnd(result: FullResult): Promise<void>;
|
|
@@ -35,11 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
const fs = __importStar(require("fs"));
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
+
const os = __importStar(require("os"));
|
|
38
39
|
const ReporterUtils_1 = require("./ReporterUtils");
|
|
39
40
|
class HtmlReporter {
|
|
40
41
|
constructor() {
|
|
41
42
|
this.suiteStartTime = 0;
|
|
42
43
|
this.reportPath = '';
|
|
44
|
+
this.envInfo = { user: 'Unknown', os: 'Unknown', ip: 'Unknown' };
|
|
43
45
|
// Cache for file contents to avoid repeated reads
|
|
44
46
|
this.fileCache = new Map();
|
|
45
47
|
// onExit removed as generation is now in onEnd
|
|
@@ -49,6 +51,31 @@ class HtmlReporter {
|
|
|
49
51
|
this.suiteStartTime = Date.now();
|
|
50
52
|
const folder = (0, ReporterUtils_1.getReportFolder)(suite);
|
|
51
53
|
this.reportPath = path.join(folder, 'report.html');
|
|
54
|
+
// Collect Env Info
|
|
55
|
+
try {
|
|
56
|
+
const userInfo = os.userInfo();
|
|
57
|
+
this.envInfo.user = userInfo.username;
|
|
58
|
+
}
|
|
59
|
+
catch (e) { /* ignore */ }
|
|
60
|
+
try {
|
|
61
|
+
this.envInfo.os = `${os.type()} ${os.release()} (${os.platform()})`;
|
|
62
|
+
}
|
|
63
|
+
catch (e) { /* ignore */ }
|
|
64
|
+
try {
|
|
65
|
+
const nets = os.networkInterfaces();
|
|
66
|
+
for (const name of Object.keys(nets)) {
|
|
67
|
+
for (const net of nets[name]) {
|
|
68
|
+
// Skip internal and non-IPv4 addresses
|
|
69
|
+
if (net.family === 'IPv4' && !net.internal) {
|
|
70
|
+
this.envInfo.ip = net.address;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (this.envInfo.ip !== 'Unknown')
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (e) { /* ignore */ }
|
|
52
79
|
}
|
|
53
80
|
async onEnd(result) {
|
|
54
81
|
if (!this.reportPath)
|
|
@@ -542,6 +569,15 @@ class HtmlReporter {
|
|
|
542
569
|
</div>
|
|
543
570
|
</div>
|
|
544
571
|
|
|
572
|
+
<div style="margin-top:15px; font-size:0.8em; color:#666; text-align:center" id="reportTimestamp"></div>
|
|
573
|
+
|
|
574
|
+
<!-- Environment Info -->
|
|
575
|
+
<div style="margin-top:15px; padding:15px; background-color:#2a2d2e; border-top:1px solid var(--border-color); font-size:0.85em; color:#bbb">
|
|
576
|
+
<div style="margin-bottom:5px"><strong style="color:#888">User:</strong> <span id="envUser">...</span></div>
|
|
577
|
+
<div style="margin-bottom:5px"><strong style="color:#888">OS:</strong> <span id="envOs">...</span></div>
|
|
578
|
+
<div><strong style="color:#888">IP:</strong> <span id="envIp">...</span></div>
|
|
579
|
+
</div>
|
|
580
|
+
|
|
545
581
|
<div class="filter-group">
|
|
546
582
|
<button class="filter-btn active" onclick="setFilter('all')">All</button>
|
|
547
583
|
<button class="filter-btn" onclick="setFilter('failed')">Failures</button>
|
|
@@ -563,9 +599,21 @@ class HtmlReporter {
|
|
|
563
599
|
|
|
564
600
|
<script>
|
|
565
601
|
const data = ${resultsData};
|
|
602
|
+
const startTime = ${this.suiteStartTime};
|
|
603
|
+
const envInfo = ${JSON.stringify(this.envInfo)};
|
|
566
604
|
let currentId = null;
|
|
567
605
|
let currentFilter = 'all';
|
|
568
606
|
|
|
607
|
+
// Set local timestamp
|
|
608
|
+
document.getElementById('reportTimestamp').textContent = new Date(startTime).toLocaleString();
|
|
609
|
+
|
|
610
|
+
// Set Env Info
|
|
611
|
+
if (envInfo) {
|
|
612
|
+
document.getElementById('envUser').textContent = envInfo.user;
|
|
613
|
+
document.getElementById('envOs').textContent = envInfo.os;
|
|
614
|
+
document.getElementById('envIp').textContent = envInfo.ip;
|
|
615
|
+
}
|
|
616
|
+
|
|
569
617
|
function setFilter(filter) {
|
|
570
618
|
currentFilter = filter;
|
|
571
619
|
// Update buttons
|
|
@@ -39,15 +39,26 @@ exports.resetReportFolderCache = resetReportFolderCache;
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
let cachedReportFolder = null;
|
|
42
|
+
// Helper to get local YYYY-MM-DD_HHmmss timestamp
|
|
43
|
+
function getLocalTimestamp() {
|
|
44
|
+
const now = new Date();
|
|
45
|
+
const year = now.getFullYear();
|
|
46
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
47
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
48
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
49
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
50
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
51
|
+
return `${year}-${month}-${day}_${hours}${minutes}${seconds}`;
|
|
52
|
+
}
|
|
42
53
|
function generateReportPath(baseDir = 'reports', prefix = 'test') {
|
|
43
|
-
const timestamp =
|
|
54
|
+
const timestamp = getLocalTimestamp();
|
|
44
55
|
return path.join(process.cwd(), baseDir, `${prefix}_${timestamp}`);
|
|
45
56
|
}
|
|
46
57
|
function getReportFolder(suite) {
|
|
47
58
|
if (cachedReportFolder) {
|
|
48
59
|
return cachedReportFolder;
|
|
49
60
|
}
|
|
50
|
-
const timestamp =
|
|
61
|
+
const timestamp = getLocalTimestamp();
|
|
51
62
|
// Find the first test file suite
|
|
52
63
|
let suiteName = 'TestRun';
|
|
53
64
|
if (suite.suites.length > 0) {
|