@alternative-path/testlens-playwright-reporter 0.4.3 → 0.4.4
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 +3 -3
- package/index.d.ts +193 -192
- package/index.js +113 -65
- package/index.ts +118 -70
- package/package.json +73 -82
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ A Playwright reporter for [TestLens](https://testlens.qa-path.com) - real-time t
|
|
|
12
12
|
## Installation
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
npm
|
|
15
|
+
npm i @alternative-path/testlens-playwright-reporter
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
## Configuration
|
|
@@ -72,7 +72,7 @@ export default defineConfig({
|
|
|
72
72
|
trace: 'on',
|
|
73
73
|
},
|
|
74
74
|
reporter: [
|
|
75
|
-
['testlens-playwright-reporter', {
|
|
75
|
+
['@alternative-path/testlens-playwright-reporter', {
|
|
76
76
|
apiKey: process.env.TESTLENS_API_KEY || 'your-api-key-here',
|
|
77
77
|
|
|
78
78
|
// Optional: explicitly forward build metadata from env vars
|
|
@@ -96,7 +96,7 @@ module.exports = defineConfig({
|
|
|
96
96
|
trace: 'on',
|
|
97
97
|
},
|
|
98
98
|
reporter: [
|
|
99
|
-
['testlens-playwright-reporter', {
|
|
99
|
+
['@alternative-path/testlens-playwright-reporter', {
|
|
100
100
|
apiKey: process.env.TESTLENS_API_KEY || 'your-api-key-here',
|
|
101
101
|
|
|
102
102
|
// Optional: explicitly forward build metadata from env vars
|
package/index.d.ts
CHANGED
|
@@ -1,192 +1,193 @@
|
|
|
1
|
-
import type { Reporter, TestCase, TestResult, FullConfig, Suite } from '@playwright/test/reporter';
|
|
2
|
-
export interface TestLensReporterConfig {
|
|
3
|
-
/** TestLens API endpoint URL */
|
|
4
|
-
apiEndpoint?: string;
|
|
5
|
-
/** API key for authentication - can be provided in config or via TESTLENS_API_KEY environment variable */
|
|
6
|
-
apiKey?: string;
|
|
7
|
-
/** Enable real-time streaming of test events */
|
|
8
|
-
enableRealTimeStream?: boolean;
|
|
9
|
-
/** Enable Git information collection */
|
|
10
|
-
enableGitInfo?: boolean;
|
|
11
|
-
/** Enable artifact processing */
|
|
12
|
-
enableArtifacts?: boolean;
|
|
13
|
-
/** Enable video capture - defaults to true */
|
|
14
|
-
enableVideo?: boolean;
|
|
15
|
-
/** Enable screenshot capture - defaults to true */
|
|
16
|
-
enableScreenshot?: boolean;
|
|
17
|
-
/** Batch size for API requests */
|
|
18
|
-
batchSize?: number;
|
|
19
|
-
/** Flush interval in milliseconds */
|
|
20
|
-
flushInterval?: number;
|
|
21
|
-
/** Number of retry attempts for failed API calls */
|
|
22
|
-
retryAttempts?: number;
|
|
23
|
-
/** Request timeout in milliseconds */
|
|
24
|
-
timeout?: number;
|
|
25
|
-
/** SSL certificate validation - set to false to disable SSL verification */
|
|
26
|
-
rejectUnauthorized?: boolean;
|
|
27
|
-
/** Alternative SSL option - set to true to ignore SSL certificate errors */
|
|
28
|
-
ignoreSslErrors?: boolean;
|
|
29
|
-
/** Custom metadata from CLI arguments (automatically parsed from --key=value arguments) */
|
|
30
|
-
customMetadata?: Record<string, string | string[]>;
|
|
31
|
-
}
|
|
32
|
-
export interface TestLensReporterOptions {
|
|
33
|
-
/** TestLens API endpoint URL */
|
|
34
|
-
apiEndpoint?: string;
|
|
35
|
-
/** API key for authentication - can be provided in config or via TESTLENS_API_KEY environment variable */
|
|
36
|
-
apiKey?: string;
|
|
37
|
-
/** Enable real-time streaming of test events */
|
|
38
|
-
enableRealTimeStream?: boolean;
|
|
39
|
-
/** Enable Git information collection */
|
|
40
|
-
enableGitInfo?: boolean;
|
|
41
|
-
/** Enable artifact processing */
|
|
42
|
-
enableArtifacts?: boolean;
|
|
43
|
-
/** Enable video capture - defaults to true */
|
|
44
|
-
enableVideo?: boolean;
|
|
45
|
-
/** Enable screenshot capture - defaults to true */
|
|
46
|
-
enableScreenshot?: boolean;
|
|
47
|
-
/** Batch size for API requests */
|
|
48
|
-
batchSize?: number;
|
|
49
|
-
/** Flush interval in milliseconds */
|
|
50
|
-
flushInterval?: number;
|
|
51
|
-
/** Number of retry attempts for failed API calls */
|
|
52
|
-
retryAttempts?: number;
|
|
53
|
-
/** Request timeout in milliseconds */
|
|
54
|
-
timeout?: number;
|
|
55
|
-
/** SSL certificate validation - set to false to disable SSL verification */
|
|
56
|
-
rejectUnauthorized?: boolean;
|
|
57
|
-
/** Alternative SSL option - set to true to ignore SSL certificate errors */
|
|
58
|
-
ignoreSslErrors?: boolean;
|
|
59
|
-
/** Custom metadata from CLI arguments (automatically parsed from --key=value arguments) */
|
|
60
|
-
customMetadata?: Record<string, string | string[]>;
|
|
61
|
-
}
|
|
62
|
-
export interface GitInfo {
|
|
63
|
-
branch: string;
|
|
64
|
-
commit: string;
|
|
65
|
-
shortCommit: string;
|
|
66
|
-
author: string;
|
|
67
|
-
message: string;
|
|
68
|
-
timestamp: string;
|
|
69
|
-
isDirty: boolean;
|
|
70
|
-
remoteName: string;
|
|
71
|
-
remoteUrl: string;
|
|
72
|
-
}
|
|
73
|
-
export interface CodeBlock {
|
|
74
|
-
type: 'test' | 'describe';
|
|
75
|
-
name: string;
|
|
76
|
-
content: string;
|
|
77
|
-
summary?: string;
|
|
78
|
-
describe?: string;
|
|
79
|
-
startLine?: number;
|
|
80
|
-
endLine?: number;
|
|
81
|
-
}
|
|
82
|
-
export interface RunMetadata {
|
|
83
|
-
id: string;
|
|
84
|
-
startTime: string;
|
|
85
|
-
endTime?: string;
|
|
86
|
-
duration?: number;
|
|
87
|
-
environment: string;
|
|
88
|
-
browser: string;
|
|
89
|
-
os: string;
|
|
90
|
-
playwrightVersion: string;
|
|
91
|
-
nodeVersion: string;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
private
|
|
156
|
-
private
|
|
157
|
-
private
|
|
158
|
-
private
|
|
159
|
-
private
|
|
160
|
-
private
|
|
161
|
-
private
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
private
|
|
169
|
-
|
|
170
|
-
private
|
|
171
|
-
private
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
private
|
|
182
|
-
private
|
|
183
|
-
private
|
|
184
|
-
private
|
|
185
|
-
private
|
|
186
|
-
private
|
|
187
|
-
private
|
|
188
|
-
private
|
|
189
|
-
private
|
|
190
|
-
private
|
|
191
|
-
|
|
192
|
-
|
|
1
|
+
import type { Reporter, TestCase, TestResult, FullConfig, Suite } from '@playwright/test/reporter';
|
|
2
|
+
export interface TestLensReporterConfig {
|
|
3
|
+
/** TestLens API endpoint URL */
|
|
4
|
+
apiEndpoint?: string;
|
|
5
|
+
/** API key for authentication - can be provided in config or via TESTLENS_API_KEY environment variable */
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
/** Enable real-time streaming of test events */
|
|
8
|
+
enableRealTimeStream?: boolean;
|
|
9
|
+
/** Enable Git information collection */
|
|
10
|
+
enableGitInfo?: boolean;
|
|
11
|
+
/** Enable artifact processing */
|
|
12
|
+
enableArtifacts?: boolean;
|
|
13
|
+
/** Enable video capture - defaults to true */
|
|
14
|
+
enableVideo?: boolean;
|
|
15
|
+
/** Enable screenshot capture - defaults to true */
|
|
16
|
+
enableScreenshot?: boolean;
|
|
17
|
+
/** Batch size for API requests */
|
|
18
|
+
batchSize?: number;
|
|
19
|
+
/** Flush interval in milliseconds */
|
|
20
|
+
flushInterval?: number;
|
|
21
|
+
/** Number of retry attempts for failed API calls */
|
|
22
|
+
retryAttempts?: number;
|
|
23
|
+
/** Request timeout in milliseconds */
|
|
24
|
+
timeout?: number;
|
|
25
|
+
/** SSL certificate validation - set to false to disable SSL verification */
|
|
26
|
+
rejectUnauthorized?: boolean;
|
|
27
|
+
/** Alternative SSL option - set to true to ignore SSL certificate errors */
|
|
28
|
+
ignoreSslErrors?: boolean;
|
|
29
|
+
/** Custom metadata from CLI arguments (automatically parsed from --key=value arguments) */
|
|
30
|
+
customMetadata?: Record<string, string | string[]>;
|
|
31
|
+
}
|
|
32
|
+
export interface TestLensReporterOptions {
|
|
33
|
+
/** TestLens API endpoint URL */
|
|
34
|
+
apiEndpoint?: string;
|
|
35
|
+
/** API key for authentication - can be provided in config or via TESTLENS_API_KEY environment variable */
|
|
36
|
+
apiKey?: string;
|
|
37
|
+
/** Enable real-time streaming of test events */
|
|
38
|
+
enableRealTimeStream?: boolean;
|
|
39
|
+
/** Enable Git information collection */
|
|
40
|
+
enableGitInfo?: boolean;
|
|
41
|
+
/** Enable artifact processing */
|
|
42
|
+
enableArtifacts?: boolean;
|
|
43
|
+
/** Enable video capture - defaults to true */
|
|
44
|
+
enableVideo?: boolean;
|
|
45
|
+
/** Enable screenshot capture - defaults to true */
|
|
46
|
+
enableScreenshot?: boolean;
|
|
47
|
+
/** Batch size for API requests */
|
|
48
|
+
batchSize?: number;
|
|
49
|
+
/** Flush interval in milliseconds */
|
|
50
|
+
flushInterval?: number;
|
|
51
|
+
/** Number of retry attempts for failed API calls */
|
|
52
|
+
retryAttempts?: number;
|
|
53
|
+
/** Request timeout in milliseconds */
|
|
54
|
+
timeout?: number;
|
|
55
|
+
/** SSL certificate validation - set to false to disable SSL verification */
|
|
56
|
+
rejectUnauthorized?: boolean;
|
|
57
|
+
/** Alternative SSL option - set to true to ignore SSL certificate errors */
|
|
58
|
+
ignoreSslErrors?: boolean;
|
|
59
|
+
/** Custom metadata from CLI arguments (automatically parsed from --key=value arguments) */
|
|
60
|
+
customMetadata?: Record<string, string | string[]>;
|
|
61
|
+
}
|
|
62
|
+
export interface GitInfo {
|
|
63
|
+
branch: string;
|
|
64
|
+
commit: string;
|
|
65
|
+
shortCommit: string;
|
|
66
|
+
author: string;
|
|
67
|
+
message: string;
|
|
68
|
+
timestamp: string;
|
|
69
|
+
isDirty: boolean;
|
|
70
|
+
remoteName: string;
|
|
71
|
+
remoteUrl: string;
|
|
72
|
+
}
|
|
73
|
+
export interface CodeBlock {
|
|
74
|
+
type: 'test' | 'describe';
|
|
75
|
+
name: string;
|
|
76
|
+
content: string;
|
|
77
|
+
summary?: string;
|
|
78
|
+
describe?: string;
|
|
79
|
+
startLine?: number;
|
|
80
|
+
endLine?: number;
|
|
81
|
+
}
|
|
82
|
+
export interface RunMetadata {
|
|
83
|
+
id: string;
|
|
84
|
+
startTime: string;
|
|
85
|
+
endTime?: string;
|
|
86
|
+
duration?: number;
|
|
87
|
+
environment: string;
|
|
88
|
+
browser: string;
|
|
89
|
+
os: string;
|
|
90
|
+
playwrightVersion: string;
|
|
91
|
+
nodeVersion: string;
|
|
92
|
+
testlensVersion?: string;
|
|
93
|
+
gitInfo?: GitInfo | null;
|
|
94
|
+
shardInfo?: {
|
|
95
|
+
current: number;
|
|
96
|
+
total: number;
|
|
97
|
+
};
|
|
98
|
+
totalTests?: number;
|
|
99
|
+
passedTests?: number;
|
|
100
|
+
failedTests?: number;
|
|
101
|
+
skippedTests?: number;
|
|
102
|
+
status?: string;
|
|
103
|
+
testlensBuildName?: string;
|
|
104
|
+
customMetadata?: Record<string, string | string[]>;
|
|
105
|
+
}
|
|
106
|
+
export interface TestError {
|
|
107
|
+
message: string;
|
|
108
|
+
stack?: string;
|
|
109
|
+
location?: {
|
|
110
|
+
file: string;
|
|
111
|
+
line: number;
|
|
112
|
+
column: number;
|
|
113
|
+
};
|
|
114
|
+
snippet?: string;
|
|
115
|
+
expected?: string;
|
|
116
|
+
actual?: string;
|
|
117
|
+
diff?: string;
|
|
118
|
+
matcherName?: string;
|
|
119
|
+
timeout?: number;
|
|
120
|
+
}
|
|
121
|
+
export interface TestData {
|
|
122
|
+
id: string;
|
|
123
|
+
name: string;
|
|
124
|
+
status: string;
|
|
125
|
+
originalStatus?: string;
|
|
126
|
+
duration: number;
|
|
127
|
+
startTime: string;
|
|
128
|
+
endTime: string;
|
|
129
|
+
errorMessages: string[];
|
|
130
|
+
errors?: TestError[];
|
|
131
|
+
retryAttempts: number;
|
|
132
|
+
currentRetry: number;
|
|
133
|
+
annotations: Array<{
|
|
134
|
+
type: string;
|
|
135
|
+
description?: string;
|
|
136
|
+
}>;
|
|
137
|
+
projectName: string;
|
|
138
|
+
workerIndex?: number;
|
|
139
|
+
parallelIndex?: number;
|
|
140
|
+
location?: {
|
|
141
|
+
file: string;
|
|
142
|
+
line: number;
|
|
143
|
+
column: number;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
export interface SpecData {
|
|
147
|
+
filePath: string;
|
|
148
|
+
testSuiteName: string;
|
|
149
|
+
tags?: string[];
|
|
150
|
+
startTime: string;
|
|
151
|
+
endTime?: string;
|
|
152
|
+
status: string;
|
|
153
|
+
}
|
|
154
|
+
export declare class TestLensReporter implements Reporter {
|
|
155
|
+
private config;
|
|
156
|
+
private axiosInstance;
|
|
157
|
+
private runId;
|
|
158
|
+
private runMetadata;
|
|
159
|
+
private specMap;
|
|
160
|
+
private testMap;
|
|
161
|
+
private runCreationFailed;
|
|
162
|
+
private cliArgs;
|
|
163
|
+
private pendingUploads;
|
|
164
|
+
/**
|
|
165
|
+
* Parse custom metadata from environment variables
|
|
166
|
+
* Checks for common metadata environment variables
|
|
167
|
+
*/
|
|
168
|
+
private static parseCustomArgs;
|
|
169
|
+
constructor(options: TestLensReporterOptions);
|
|
170
|
+
private initializeRunMetadata;
|
|
171
|
+
private getPlaywrightVersion;
|
|
172
|
+
private getTestLensVersion;
|
|
173
|
+
private normalizeTestStatus;
|
|
174
|
+
private normalizeRunStatus;
|
|
175
|
+
onBegin(config: FullConfig, suite: Suite): Promise<void>;
|
|
176
|
+
onTestBegin(test: TestCase, result: TestResult): Promise<void>;
|
|
177
|
+
onTestEnd(test: TestCase, result: TestResult): Promise<void>;
|
|
178
|
+
onEnd(result: {
|
|
179
|
+
status: string;
|
|
180
|
+
}): Promise<void>;
|
|
181
|
+
private sendToApi;
|
|
182
|
+
private processArtifacts;
|
|
183
|
+
private sendSpecCodeBlocks;
|
|
184
|
+
private extractTestBlocks;
|
|
185
|
+
private collectGitInfo;
|
|
186
|
+
private getArtifactType;
|
|
187
|
+
private extractTags;
|
|
188
|
+
private getTestId;
|
|
189
|
+
private uploadArtifactToS3;
|
|
190
|
+
private getContentType;
|
|
191
|
+
private getFileSize;
|
|
192
|
+
}
|
|
193
|
+
export default TestLensReporter;
|
package/index.js
CHANGED
|
@@ -60,6 +60,7 @@ class TestLensReporter {
|
|
|
60
60
|
constructor(options) {
|
|
61
61
|
this.runCreationFailed = false; // Track if run creation failed due to limits
|
|
62
62
|
this.cliArgs = {}; // Store CLI args separately
|
|
63
|
+
this.pendingUploads = new Set(); // Track pending artifact uploads
|
|
63
64
|
// Parse custom CLI arguments
|
|
64
65
|
const customArgs = TestLensReporter.parseCustomArgs();
|
|
65
66
|
this.cliArgs = customArgs; // Store CLI args separately for later use
|
|
@@ -166,7 +167,8 @@ class TestLensReporter {
|
|
|
166
167
|
browser: 'multiple',
|
|
167
168
|
os: `${os.type()} ${os.release()}`,
|
|
168
169
|
playwrightVersion: this.getPlaywrightVersion(),
|
|
169
|
-
nodeVersion: process.version
|
|
170
|
+
nodeVersion: process.version,
|
|
171
|
+
testlensVersion: this.getTestLensVersion()
|
|
170
172
|
};
|
|
171
173
|
// Add custom metadata if provided
|
|
172
174
|
if (this.config.customMetadata && Object.keys(this.config.customMetadata).length > 0) {
|
|
@@ -189,6 +191,15 @@ class TestLensReporter {
|
|
|
189
191
|
return 'unknown';
|
|
190
192
|
}
|
|
191
193
|
}
|
|
194
|
+
getTestLensVersion() {
|
|
195
|
+
try {
|
|
196
|
+
const testlensPackage = require('./package.json');
|
|
197
|
+
return testlensPackage.version;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
return 'unknown';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
192
203
|
normalizeTestStatus(status) {
|
|
193
204
|
// Treat timeout as failed for consistency with analytics
|
|
194
205
|
if (status === 'timedOut') {
|
|
@@ -465,23 +476,20 @@ class TestLensReporter {
|
|
|
465
476
|
}
|
|
466
477
|
return testError;
|
|
467
478
|
});
|
|
468
|
-
//
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
// Pass test case DB ID if available for faster lookups
|
|
483
|
-
await this.processArtifacts(testId, result, testEndResponse?.testCaseId);
|
|
484
|
-
}
|
|
479
|
+
// Send testEnd event for all tests, regardless of status
|
|
480
|
+
// This ensures tests that are interrupted or have unexpected statuses are properly recorded
|
|
481
|
+
console.log(`[TestLens] Sending testEnd - testId: ${testData.id}, status: ${testData.status}, originalStatus: ${testData.originalStatus}`);
|
|
482
|
+
// Send test end event to API and get response
|
|
483
|
+
const testEndResponse = await this.sendToApi({
|
|
484
|
+
type: 'testEnd',
|
|
485
|
+
runId: this.runId,
|
|
486
|
+
timestamp: new Date().toISOString(),
|
|
487
|
+
test: testData
|
|
488
|
+
});
|
|
489
|
+
// Handle artifacts (test case is now guaranteed to be in database)
|
|
490
|
+
if (this.config.enableArtifacts) {
|
|
491
|
+
// Pass test case DB ID if available for faster lookups
|
|
492
|
+
await this.processArtifacts(testId, result, testEndResponse?.testCaseId);
|
|
485
493
|
}
|
|
486
494
|
}
|
|
487
495
|
// Update spec status
|
|
@@ -497,12 +505,33 @@ class TestLensReporter {
|
|
|
497
505
|
specData.status = 'skipped';
|
|
498
506
|
}
|
|
499
507
|
// Check if all tests in spec are complete
|
|
508
|
+
// Only consider tests that were actually executed (have testData)
|
|
500
509
|
const remainingTests = test.parent.tests.filter((t) => {
|
|
501
510
|
const tId = this.getTestId(t);
|
|
502
511
|
const tData = this.testMap.get(tId);
|
|
503
|
-
|
|
512
|
+
// If testData exists but no endTime, it's still running
|
|
513
|
+
return tData && !tData.endTime;
|
|
504
514
|
});
|
|
505
515
|
if (remainingTests.length === 0) {
|
|
516
|
+
// Determine final spec status based on all executed tests
|
|
517
|
+
const executedTests = test.parent.tests
|
|
518
|
+
.map((t) => {
|
|
519
|
+
const tId = this.getTestId(t);
|
|
520
|
+
return this.testMap.get(tId);
|
|
521
|
+
})
|
|
522
|
+
.filter((tData) => !!tData);
|
|
523
|
+
if (executedTests.length > 0) {
|
|
524
|
+
const allTestStatuses = executedTests.map(tData => tData.status);
|
|
525
|
+
if (allTestStatuses.every(status => status === 'passed')) {
|
|
526
|
+
specData.status = 'passed';
|
|
527
|
+
}
|
|
528
|
+
else if (allTestStatuses.some(status => status === 'failed')) {
|
|
529
|
+
specData.status = 'failed';
|
|
530
|
+
}
|
|
531
|
+
else if (allTestStatuses.every(status => status === 'skipped')) {
|
|
532
|
+
specData.status = 'skipped';
|
|
533
|
+
}
|
|
534
|
+
}
|
|
506
535
|
// Aggregate tags from all tests in this spec
|
|
507
536
|
const allTags = new Set();
|
|
508
537
|
test.parent.tests.forEach((t) => {
|
|
@@ -530,6 +559,17 @@ class TestLensReporter {
|
|
|
530
559
|
async onEnd(result) {
|
|
531
560
|
this.runMetadata.endTime = new Date().toISOString();
|
|
532
561
|
this.runMetadata.duration = Date.now() - new Date(this.runMetadata.startTime).getTime();
|
|
562
|
+
// Wait for all pending artifact uploads to complete before sending runEnd
|
|
563
|
+
if (this.pendingUploads.size > 0) {
|
|
564
|
+
console.log(`⏳ Waiting for ${this.pendingUploads.size} artifact upload(s) to complete...`);
|
|
565
|
+
try {
|
|
566
|
+
await Promise.all(Array.from(this.pendingUploads));
|
|
567
|
+
console.log(`✅ All artifact uploads completed`);
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
console.error(`⚠️ Some artifact uploads failed, continuing with runEnd`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
533
573
|
// Calculate final stats
|
|
534
574
|
const totalTests = Array.from(this.testMap.values()).length;
|
|
535
575
|
const passedTests = Array.from(this.testMap.values()).filter(t => t.status === 'passed').length;
|
|
@@ -715,34 +755,53 @@ class TestLensReporter {
|
|
|
715
755
|
}
|
|
716
756
|
}
|
|
717
757
|
// Upload to S3 first (pass DB ID if available for faster lookup)
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
758
|
+
// Create upload promise that we can track
|
|
759
|
+
const uploadPromise = Promise.resolve().then(async () => {
|
|
760
|
+
try {
|
|
761
|
+
if (!attachment.path) {
|
|
762
|
+
console.log(`⏭️ [Test: ${testId.substring(0, 8)}...] Skipping artifact ${attachment.name} - no file path`);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName, testCaseDbId);
|
|
766
|
+
// Skip if upload failed or file was too large
|
|
767
|
+
if (!s3Data) {
|
|
768
|
+
console.log(`⏭️ [Test: ${testId.substring(0, 8)}...] Skipping artifact ${attachment.name} - upload failed or file too large`);
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const artifactData = {
|
|
772
|
+
testId,
|
|
773
|
+
type: this.getArtifactType(attachment.name),
|
|
774
|
+
path: attachment.path,
|
|
775
|
+
name: fileName,
|
|
776
|
+
contentType: attachment.contentType,
|
|
777
|
+
fileSize: this.getFileSize(attachment.path),
|
|
778
|
+
storageType: 's3',
|
|
779
|
+
s3Key: s3Data.key,
|
|
780
|
+
s3Url: s3Data.url
|
|
781
|
+
};
|
|
782
|
+
// Send artifact data to API
|
|
783
|
+
await this.sendToApi({
|
|
784
|
+
type: 'artifact',
|
|
785
|
+
runId: this.runId,
|
|
786
|
+
timestamp: new Date().toISOString(),
|
|
787
|
+
artifact: artifactData
|
|
788
|
+
});
|
|
789
|
+
console.log(`📎 [Test: ${testId.substring(0, 8)}...] Processed artifact: ${fileName}`);
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to process artifact ${attachment.name}:`, error.message);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
// Track this upload and ensure cleanup on completion
|
|
796
|
+
this.pendingUploads.add(uploadPromise);
|
|
797
|
+
uploadPromise.finally(() => {
|
|
798
|
+
this.pendingUploads.delete(uploadPromise);
|
|
741
799
|
});
|
|
742
|
-
|
|
800
|
+
// Don't await here - let uploads happen in parallel
|
|
801
|
+
// They will be awaited in onEnd
|
|
743
802
|
}
|
|
744
803
|
catch (error) {
|
|
745
|
-
console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to
|
|
804
|
+
console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to setup artifact upload ${attachment.name}:`, error.message);
|
|
746
805
|
}
|
|
747
806
|
}
|
|
748
807
|
}
|
|
@@ -760,8 +819,13 @@ class TestLensReporter {
|
|
|
760
819
|
describe: block.describe // parent describe block name
|
|
761
820
|
}));
|
|
762
821
|
// Send to dedicated spec code blocks API endpoint
|
|
763
|
-
|
|
764
|
-
|
|
822
|
+
// Extract base URL - handle both full and partial endpoint patterns
|
|
823
|
+
let baseUrl = this.config.apiEndpoint.replace('/api/v1/webhook/playwright', '');
|
|
824
|
+
if (baseUrl === this.config.apiEndpoint) {
|
|
825
|
+
// Fallback: try alternative pattern if main pattern didn't match
|
|
826
|
+
baseUrl = this.config.apiEndpoint.replace('/webhook/playwright', '');
|
|
827
|
+
}
|
|
828
|
+
const specEndpoint = `${baseUrl}/api/v1/webhook/playwright/spec-code-blocks`;
|
|
765
829
|
await this.axiosInstance.post(specEndpoint, {
|
|
766
830
|
filePath: path.relative(process.cwd(), specPath),
|
|
767
831
|
codeBlocks,
|
|
@@ -1014,7 +1078,7 @@ class TestLensReporter {
|
|
|
1014
1078
|
const errorData = error?.response?.data;
|
|
1015
1079
|
if (errorData?.error === 'trial_expired' || errorData?.error === 'subscription_inactive' ||
|
|
1016
1080
|
errorData?.error === 'test_cases_limit_reached' || errorData?.error === 'test_runs_limit_reached') {
|
|
1017
|
-
console.error('
|
|
1081
|
+
console.error('\n' + '='.repeat(80));
|
|
1018
1082
|
if (errorData?.error === 'test_cases_limit_reached') {
|
|
1019
1083
|
console.error('❌ TESTLENS ERROR: Test Cases Limit Reached');
|
|
1020
1084
|
}
|
|
@@ -1093,24 +1157,8 @@ class TestLensReporter {
|
|
|
1093
1157
|
};
|
|
1094
1158
|
return contentTypes[ext] || 'application/octet-stream';
|
|
1095
1159
|
}
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const safeTestId = this.sanitizeForS3(testId);
|
|
1099
|
-
const safeFileName = this.sanitizeForS3(fileName);
|
|
1100
|
-
const ext = path.extname(fileName);
|
|
1101
|
-
const baseName = path.basename(fileName, ext);
|
|
1102
|
-
return `test-artifacts/${date}/${runId}/${safeTestId}/${safeFileName}${ext}`;
|
|
1103
|
-
}
|
|
1104
|
-
sanitizeForS3(value) {
|
|
1105
|
-
return value
|
|
1106
|
-
.replace(/[\/:*?"<>|]/g, '-')
|
|
1107
|
-
.replace(/[-\u001f\u007f]/g, '-')
|
|
1108
|
-
.replace(/[^-~]/g, '-')
|
|
1109
|
-
.replace(/\s+/g, '-')
|
|
1110
|
-
.replace(/[_]/g, '-')
|
|
1111
|
-
.replace(/-+/g, '-')
|
|
1112
|
-
.replace(/^-|-$/g, '');
|
|
1113
|
-
}
|
|
1160
|
+
// Note: S3 key generation and sanitization are handled server-side
|
|
1161
|
+
// generateS3Key() and sanitizeForS3() methods removed as they were not used
|
|
1114
1162
|
getFileSize(filePath) {
|
|
1115
1163
|
try {
|
|
1116
1164
|
const stats = fs.statSync(filePath);
|
package/index.ts
CHANGED
|
@@ -113,6 +113,7 @@ export interface RunMetadata {
|
|
|
113
113
|
os: string;
|
|
114
114
|
playwrightVersion: string;
|
|
115
115
|
nodeVersion: string;
|
|
116
|
+
testlensVersion?: string;
|
|
116
117
|
gitInfo?: GitInfo | null;
|
|
117
118
|
shardInfo?: {
|
|
118
119
|
current: number;
|
|
@@ -184,6 +185,7 @@ export class TestLensReporter implements Reporter {
|
|
|
184
185
|
private testMap: Map<string, TestData>;
|
|
185
186
|
private runCreationFailed: boolean = false; // Track if run creation failed due to limits
|
|
186
187
|
private cliArgs: Record<string, any> = {}; // Store CLI args separately
|
|
188
|
+
private pendingUploads: Set<Promise<any>> = new Set(); // Track pending artifact uploads
|
|
187
189
|
|
|
188
190
|
/**
|
|
189
191
|
* Parse custom metadata from environment variables
|
|
@@ -347,7 +349,8 @@ export class TestLensReporter implements Reporter {
|
|
|
347
349
|
browser: 'multiple',
|
|
348
350
|
os: `${os.type()} ${os.release()}`,
|
|
349
351
|
playwrightVersion: this.getPlaywrightVersion(),
|
|
350
|
-
nodeVersion: process.version
|
|
352
|
+
nodeVersion: process.version,
|
|
353
|
+
testlensVersion: this.getTestLensVersion()
|
|
351
354
|
};
|
|
352
355
|
|
|
353
356
|
// Add custom metadata if provided
|
|
@@ -374,6 +377,15 @@ export class TestLensReporter implements Reporter {
|
|
|
374
377
|
}
|
|
375
378
|
}
|
|
376
379
|
|
|
380
|
+
private getTestLensVersion(): string {
|
|
381
|
+
try {
|
|
382
|
+
const testlensPackage = require('./package.json');
|
|
383
|
+
return testlensPackage.version;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return 'unknown';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
377
389
|
private normalizeTestStatus(status: string): string {
|
|
378
390
|
// Treat timeout as failed for consistency with analytics
|
|
379
391
|
if (status === 'timedOut') {
|
|
@@ -683,25 +695,21 @@ export class TestLensReporter implements Reporter {
|
|
|
683
695
|
return testError;
|
|
684
696
|
});
|
|
685
697
|
|
|
686
|
-
//
|
|
687
|
-
//
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
timestamp: new Date().toISOString(),
|
|
697
|
-
test: testData
|
|
698
|
-
});
|
|
698
|
+
// Send testEnd event for all tests, regardless of status
|
|
699
|
+
// This ensures tests that are interrupted or have unexpected statuses are properly recorded
|
|
700
|
+
console.log(`[TestLens] Sending testEnd - testId: ${testData.id}, status: ${testData.status}, originalStatus: ${testData.originalStatus}`);
|
|
701
|
+
// Send test end event to API and get response
|
|
702
|
+
const testEndResponse = await this.sendToApi({
|
|
703
|
+
type: 'testEnd',
|
|
704
|
+
runId: this.runId,
|
|
705
|
+
timestamp: new Date().toISOString(),
|
|
706
|
+
test: testData
|
|
707
|
+
});
|
|
699
708
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
}
|
|
709
|
+
// Handle artifacts (test case is now guaranteed to be in database)
|
|
710
|
+
if (this.config.enableArtifacts) {
|
|
711
|
+
// Pass test case DB ID if available for faster lookups
|
|
712
|
+
await this.processArtifacts(testId, result, testEndResponse?.testCaseId);
|
|
705
713
|
}
|
|
706
714
|
}
|
|
707
715
|
|
|
@@ -718,13 +726,33 @@ export class TestLensReporter implements Reporter {
|
|
|
718
726
|
}
|
|
719
727
|
|
|
720
728
|
// Check if all tests in spec are complete
|
|
729
|
+
// Only consider tests that were actually executed (have testData)
|
|
721
730
|
const remainingTests = test.parent.tests.filter((t: any) => {
|
|
722
731
|
const tId = this.getTestId(t);
|
|
723
732
|
const tData = this.testMap.get(tId);
|
|
724
|
-
|
|
733
|
+
// If testData exists but no endTime, it's still running
|
|
734
|
+
return tData && !tData.endTime;
|
|
725
735
|
});
|
|
726
736
|
|
|
727
737
|
if (remainingTests.length === 0) {
|
|
738
|
+
// Determine final spec status based on all executed tests
|
|
739
|
+
const executedTests = test.parent.tests
|
|
740
|
+
.map((t: any) => {
|
|
741
|
+
const tId = this.getTestId(t);
|
|
742
|
+
return this.testMap.get(tId);
|
|
743
|
+
})
|
|
744
|
+
.filter((tData: TestData | undefined): tData is TestData => !!tData);
|
|
745
|
+
|
|
746
|
+
if (executedTests.length > 0) {
|
|
747
|
+
const allTestStatuses = executedTests.map(tData => tData.status);
|
|
748
|
+
if (allTestStatuses.every(status => status === 'passed')) {
|
|
749
|
+
specData.status = 'passed';
|
|
750
|
+
} else if (allTestStatuses.some(status => status === 'failed')) {
|
|
751
|
+
specData.status = 'failed';
|
|
752
|
+
} else if (allTestStatuses.every(status => status === 'skipped')) {
|
|
753
|
+
specData.status = 'skipped';
|
|
754
|
+
}
|
|
755
|
+
}
|
|
728
756
|
// Aggregate tags from all tests in this spec
|
|
729
757
|
const allTags = new Set<string>();
|
|
730
758
|
test.parent.tests.forEach((t: any) => {
|
|
@@ -757,6 +785,17 @@ export class TestLensReporter implements Reporter {
|
|
|
757
785
|
this.runMetadata.endTime = new Date().toISOString();
|
|
758
786
|
this.runMetadata.duration = Date.now() - new Date(this.runMetadata.startTime).getTime();
|
|
759
787
|
|
|
788
|
+
// Wait for all pending artifact uploads to complete before sending runEnd
|
|
789
|
+
if (this.pendingUploads.size > 0) {
|
|
790
|
+
console.log(`⏳ Waiting for ${this.pendingUploads.size} artifact upload(s) to complete...`);
|
|
791
|
+
try {
|
|
792
|
+
await Promise.all(Array.from(this.pendingUploads));
|
|
793
|
+
console.log(`✅ All artifact uploads completed`);
|
|
794
|
+
} catch (error) {
|
|
795
|
+
console.error(`⚠️ Some artifact uploads failed, continuing with runEnd`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
760
799
|
// Calculate final stats
|
|
761
800
|
const totalTests = Array.from(this.testMap.values()).length;
|
|
762
801
|
const passedTests = Array.from(this.testMap.values()).filter(t => t.status === 'passed').length;
|
|
@@ -952,37 +991,58 @@ export class TestLensReporter implements Reporter {
|
|
|
952
991
|
}
|
|
953
992
|
|
|
954
993
|
// Upload to S3 first (pass DB ID if available for faster lookup)
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
994
|
+
// Create upload promise that we can track
|
|
995
|
+
const uploadPromise = Promise.resolve().then(async () => {
|
|
996
|
+
try {
|
|
997
|
+
if (!attachment.path) {
|
|
998
|
+
console.log(`⏭️ [Test: ${testId.substring(0, 8)}...] Skipping artifact ${attachment.name} - no file path`);
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const s3Data = await this.uploadArtifactToS3(attachment.path, testId, fileName, testCaseDbId);
|
|
1003
|
+
|
|
1004
|
+
// Skip if upload failed or file was too large
|
|
1005
|
+
if (!s3Data) {
|
|
1006
|
+
console.log(`⏭️ [Test: ${testId.substring(0, 8)}...] Skipping artifact ${attachment.name} - upload failed or file too large`);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
const artifactData = {
|
|
1011
|
+
testId,
|
|
1012
|
+
type: this.getArtifactType(attachment.name),
|
|
1013
|
+
path: attachment.path,
|
|
1014
|
+
name: fileName,
|
|
1015
|
+
contentType: attachment.contentType,
|
|
1016
|
+
fileSize: this.getFileSize(attachment.path),
|
|
1017
|
+
storageType: 's3',
|
|
1018
|
+
s3Key: s3Data.key,
|
|
1019
|
+
s3Url: s3Data.url
|
|
1020
|
+
};
|
|
974
1021
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1022
|
+
// Send artifact data to API
|
|
1023
|
+
await this.sendToApi({
|
|
1024
|
+
type: 'artifact',
|
|
1025
|
+
runId: this.runId,
|
|
1026
|
+
timestamp: new Date().toISOString(),
|
|
1027
|
+
artifact: artifactData
|
|
1028
|
+
});
|
|
1029
|
+
|
|
1030
|
+
console.log(`📎 [Test: ${testId.substring(0, 8)}...] Processed artifact: ${fileName}`);
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to process artifact ${attachment.name}:`, (error as Error).message);
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
// Track this upload and ensure cleanup on completion
|
|
1037
|
+
this.pendingUploads.add(uploadPromise);
|
|
1038
|
+
uploadPromise.finally(() => {
|
|
1039
|
+
this.pendingUploads.delete(uploadPromise);
|
|
981
1040
|
});
|
|
982
1041
|
|
|
983
|
-
|
|
1042
|
+
// Don't await here - let uploads happen in parallel
|
|
1043
|
+
// They will be awaited in onEnd
|
|
984
1044
|
} catch (error) {
|
|
985
|
-
console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to
|
|
1045
|
+
console.error(`❌ [Test: ${testId.substring(0, 8)}...] Failed to setup artifact upload ${attachment.name}:`, (error as Error).message);
|
|
986
1046
|
}
|
|
987
1047
|
}
|
|
988
1048
|
}
|
|
@@ -1003,8 +1063,13 @@ export class TestLensReporter implements Reporter {
|
|
|
1003
1063
|
}));
|
|
1004
1064
|
|
|
1005
1065
|
// Send to dedicated spec code blocks API endpoint
|
|
1006
|
-
|
|
1007
|
-
|
|
1066
|
+
// Extract base URL - handle both full and partial endpoint patterns
|
|
1067
|
+
let baseUrl = this.config.apiEndpoint.replace('/api/v1/webhook/playwright', '');
|
|
1068
|
+
if (baseUrl === this.config.apiEndpoint) {
|
|
1069
|
+
// Fallback: try alternative pattern if main pattern didn't match
|
|
1070
|
+
baseUrl = this.config.apiEndpoint.replace('/webhook/playwright', '');
|
|
1071
|
+
}
|
|
1072
|
+
const specEndpoint = `${baseUrl}/api/v1/webhook/playwright/spec-code-blocks`;
|
|
1008
1073
|
|
|
1009
1074
|
await this.axiosInstance.post(specEndpoint, {
|
|
1010
1075
|
filePath: path.relative(process.cwd(), specPath),
|
|
@@ -1291,7 +1356,7 @@ export class TestLensReporter implements Reporter {
|
|
|
1291
1356
|
|
|
1292
1357
|
if (errorData?.error === 'trial_expired' || errorData?.error === 'subscription_inactive' ||
|
|
1293
1358
|
errorData?.error === 'test_cases_limit_reached' || errorData?.error === 'test_runs_limit_reached') {
|
|
1294
|
-
console.error('
|
|
1359
|
+
console.error('\n' + '='.repeat(80));
|
|
1295
1360
|
|
|
1296
1361
|
if (errorData?.error === 'test_cases_limit_reached') {
|
|
1297
1362
|
console.error('❌ TESTLENS ERROR: Test Cases Limit Reached');
|
|
@@ -1372,26 +1437,8 @@ export class TestLensReporter implements Reporter {
|
|
|
1372
1437
|
return contentTypes[ext] || 'application/octet-stream';
|
|
1373
1438
|
}
|
|
1374
1439
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
const safeTestId = this.sanitizeForS3(testId);
|
|
1378
|
-
const safeFileName = this.sanitizeForS3(fileName);
|
|
1379
|
-
const ext = path.extname(fileName);
|
|
1380
|
-
const baseName = path.basename(fileName, ext);
|
|
1381
|
-
|
|
1382
|
-
return `test-artifacts/${date}/${runId}/${safeTestId}/${safeFileName}${ext}`;
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
private sanitizeForS3(value: string): string {
|
|
1386
|
-
return value
|
|
1387
|
-
.replace(/[\/:*?"<>|]/g, '-')
|
|
1388
|
-
.replace(/[-\u001f\u007f]/g, '-')
|
|
1389
|
-
.replace(/[^-~]/g, '-')
|
|
1390
|
-
.replace(/\s+/g, '-')
|
|
1391
|
-
.replace(/[_]/g, '-')
|
|
1392
|
-
.replace(/-+/g, '-')
|
|
1393
|
-
.replace(/^-|-$/g, '');
|
|
1394
|
-
}
|
|
1440
|
+
// Note: S3 key generation and sanitization are handled server-side
|
|
1441
|
+
// generateS3Key() and sanitizeForS3() methods removed as they were not used
|
|
1395
1442
|
|
|
1396
1443
|
private getFileSize(filePath: string): number {
|
|
1397
1444
|
try {
|
|
@@ -1405,3 +1452,4 @@ export class TestLensReporter implements Reporter {
|
|
|
1405
1452
|
}
|
|
1406
1453
|
|
|
1407
1454
|
export default TestLensReporter;
|
|
1455
|
+
|
package/package.json
CHANGED
|
@@ -1,82 +1,73 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@alternative-path/testlens-playwright-reporter",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Universal Playwright reporter for TestLens - works with both TypeScript and JavaScript projects",
|
|
5
|
-
"main": "index.js",
|
|
6
|
-
"types": "index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"testlens-cross-env": "cross-env.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"index.js",
|
|
12
|
-
"index.d.ts",
|
|
13
|
-
"index.ts",
|
|
14
|
-
"lib/",
|
|
15
|
-
"postinstall.js",
|
|
16
|
-
"cross-env.js",
|
|
17
|
-
"README.md",
|
|
18
|
-
"CHANGELOG.md"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"postinstall": "node postinstall.js",
|
|
22
|
-
"prepack": "node build-embed-env.js",
|
|
23
|
-
"prepublishOnly": "npm run lint && npm run test",
|
|
24
|
-
"test": "echo 'Tests will be added in future versions'",
|
|
25
|
-
"lint": "echo 'Linting passed - no linter configured yet'",
|
|
26
|
-
"build": "tsc",
|
|
27
|
-
"dev": "node index.js"
|
|
28
|
-
},
|
|
29
|
-
"keywords": [
|
|
30
|
-
"playwright",
|
|
31
|
-
"reporter",
|
|
32
|
-
"testing",
|
|
33
|
-
"testlens",
|
|
34
|
-
"typescript",
|
|
35
|
-
"javascript",
|
|
36
|
-
"e2e",
|
|
37
|
-
"automation",
|
|
38
|
-
"ci-cd",
|
|
39
|
-
"dashboard",
|
|
40
|
-
"test-reporting"
|
|
41
|
-
],
|
|
42
|
-
"author": {
|
|
43
|
-
"name": "TestLens Team",
|
|
44
|
-
"email": "support@alternative-path.com",
|
|
45
|
-
"url": "https://testlens.qa-path.com"
|
|
46
|
-
},
|
|
47
|
-
"license": "MIT",
|
|
48
|
-
"peerDependencies": {
|
|
49
|
-
"@playwright/test": ">=1.40.0"
|
|
50
|
-
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"@aws-sdk/client-s3": "^3.624.0",
|
|
53
|
-
"@aws-sdk/s3-request-presigner": "^3.624.0",
|
|
54
|
-
"axios": "^1.11.0",
|
|
55
|
-
"cross-env": "^7.0.3",
|
|
56
|
-
"dotenv": "^16.4.5",
|
|
57
|
-
"form-data": "^4.0.1",
|
|
58
|
-
"mime": "^4.0.4",
|
|
59
|
-
"tslib": "^2.8.1"
|
|
60
|
-
},
|
|
61
|
-
"engines": {
|
|
62
|
-
"node": ">=16.0.0",
|
|
63
|
-
"npm": ">=8.0.0"
|
|
64
|
-
},
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
"publishConfig": {
|
|
75
|
-
"access": "public",
|
|
76
|
-
"registry": "https://registry.npmjs.org/"
|
|
77
|
-
},
|
|
78
|
-
"devDependencies": {
|
|
79
|
-
"@types/node": "^24.3.1",
|
|
80
|
-
"typescript": "^5.9.2"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@alternative-path/testlens-playwright-reporter",
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "Universal Playwright reporter for TestLens - works with both TypeScript and JavaScript projects",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"testlens-cross-env": "cross-env.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"index.d.ts",
|
|
13
|
+
"index.ts",
|
|
14
|
+
"lib/",
|
|
15
|
+
"postinstall.js",
|
|
16
|
+
"cross-env.js",
|
|
17
|
+
"README.md",
|
|
18
|
+
"CHANGELOG.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"postinstall": "node postinstall.js",
|
|
22
|
+
"prepack": "node build-embed-env.js",
|
|
23
|
+
"prepublishOnly": "npm run lint && npm run test",
|
|
24
|
+
"test": "echo 'Tests will be added in future versions'",
|
|
25
|
+
"lint": "echo 'Linting passed - no linter configured yet'",
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"dev": "node index.js"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"playwright",
|
|
31
|
+
"reporter",
|
|
32
|
+
"testing",
|
|
33
|
+
"testlens",
|
|
34
|
+
"typescript",
|
|
35
|
+
"javascript",
|
|
36
|
+
"e2e",
|
|
37
|
+
"automation",
|
|
38
|
+
"ci-cd",
|
|
39
|
+
"dashboard",
|
|
40
|
+
"test-reporting"
|
|
41
|
+
],
|
|
42
|
+
"author": {
|
|
43
|
+
"name": "TestLens Team",
|
|
44
|
+
"email": "support@alternative-path.com",
|
|
45
|
+
"url": "https://testlens.qa-path.com"
|
|
46
|
+
},
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"@playwright/test": ">=1.40.0"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@aws-sdk/client-s3": "^3.624.0",
|
|
53
|
+
"@aws-sdk/s3-request-presigner": "^3.624.0",
|
|
54
|
+
"axios": "^1.11.0",
|
|
55
|
+
"cross-env": "^7.0.3",
|
|
56
|
+
"dotenv": "^16.4.5",
|
|
57
|
+
"form-data": "^4.0.1",
|
|
58
|
+
"mime": "^4.0.4",
|
|
59
|
+
"tslib": "^2.8.1"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=16.0.0",
|
|
63
|
+
"npm": ">=8.0.0"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public",
|
|
67
|
+
"registry": "https://registry.npmjs.org/"
|
|
68
|
+
},
|
|
69
|
+
"devDependencies": {
|
|
70
|
+
"@types/node": "^24.3.1",
|
|
71
|
+
"typescript": "^5.9.2"
|
|
72
|
+
}
|
|
73
|
+
}
|