testlens-playwright-reporter 0.3.2 → 0.3.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/index.ts CHANGED
@@ -22,8 +22,8 @@ async function getMime() {
22
22
  export interface TestLensReporterConfig {
23
23
  /** TestLens API endpoint URL */
24
24
  apiEndpoint?: string;
25
- /** API key for authentication - required, must be provided in config */
26
- apiKey: string;
25
+ /** API key for authentication - can be provided in config or via TESTLENS_API_KEY environment variable */
26
+ apiKey?: string;
27
27
  /** Enable real-time streaming of test events */
28
28
  enableRealTimeStream?: boolean;
29
29
  /** Enable Git information collection */
@@ -46,13 +46,15 @@ export interface TestLensReporterConfig {
46
46
  rejectUnauthorized?: boolean;
47
47
  /** Alternative SSL option - set to true to ignore SSL certificate errors */
48
48
  ignoreSslErrors?: boolean;
49
+ /** Custom metadata from CLI arguments (automatically parsed from --key=value arguments) */
50
+ customMetadata?: Record<string, string>;
49
51
  }
50
52
 
51
53
  export interface TestLensReporterOptions {
52
54
  /** TestLens API endpoint URL */
53
55
  apiEndpoint?: string;
54
- /** API key for authentication - required, must be provided in config */
55
- apiKey: string;
56
+ /** API key for authentication - can be provided in config or via TESTLENS_API_KEY environment variable */
57
+ apiKey?: string;
56
58
  /** Enable real-time streaming of test events */
57
59
  enableRealTimeStream?: boolean;
58
60
  /** Enable Git information collection */
@@ -75,6 +77,8 @@ export interface TestLensReporterOptions {
75
77
  rejectUnauthorized?: boolean;
76
78
  /** Alternative SSL option - set to true to ignore SSL certificate errors */
77
79
  ignoreSslErrors?: boolean;
80
+ /** Custom metadata from CLI arguments (automatically parsed from --key=value arguments) */
81
+ customMetadata?: Record<string, string>;
78
82
  }
79
83
 
80
84
  export interface GitInfo {
@@ -119,6 +123,8 @@ export interface RunMetadata {
119
123
  failedTests?: number;
120
124
  skippedTests?: number;
121
125
  status?: string;
126
+ testlensBuildName?: string;
127
+ customMetadata?: Record<string, string>;
122
128
  }
123
129
 
124
130
  export interface TestError {
@@ -163,7 +169,7 @@ export interface TestData {
163
169
  export interface SpecData {
164
170
  filePath: string;
165
171
  testSuiteName: string;
166
- tags: string[];
172
+ tags?: string[];
167
173
  startTime: string;
168
174
  endTime?: string;
169
175
  status: string;
@@ -176,11 +182,61 @@ export class TestLensReporter implements Reporter {
176
182
  private runMetadata: RunMetadata;
177
183
  private specMap: Map<string, SpecData>;
178
184
  private testMap: Map<string, TestData>;
185
+ private runCreationFailed: boolean = false; // Track if run creation failed due to limits
186
+
187
+ /**
188
+ * Parse custom metadata from environment variables
189
+ * Checks for common metadata environment variables
190
+ */
191
+ private static parseCustomArgs(): Record<string, string> {
192
+ const customArgs: Record<string, string> = {};
193
+
194
+ // Common environment variable names for build metadata
195
+ const envVarMappings: Record<string, string[]> = {
196
+ 'testlensBuildTag': ['BUILDTAG', 'BUILD_TAG','TestlensBuildTag'],
197
+ 'testlensBuildName': ['BUILDNAME', 'BUILD_NAME', 'TestlensBuildName'],
198
+ 'environment': ['ENVIRONMENT', 'ENV', 'NODE_ENV', 'DEPLOYMENT_ENV'],
199
+ 'branch': ['BRANCH', 'GIT_BRANCH', 'CI_COMMIT_BRANCH', 'GITHUB_REF_NAME'],
200
+ 'team': ['TEAM', 'TEAM_NAME'],
201
+ 'project': ['PROJECT', 'PROJECT_NAME'],
202
+ 'customvalue': ['CUSTOMVALUE', 'CUSTOM_VALUE']
203
+ };
204
+
205
+ // Check for each metadata key
206
+ Object.entries(envVarMappings).forEach(([key, envVars]) => {
207
+ for (const envVar of envVars) {
208
+ const value = process.env[envVar];
209
+ if (value) {
210
+ customArgs[key] = value;
211
+ console.log(`✓ Found ${envVar}=${value} (mapped to '${key}')`);
212
+ break; // Use first match
213
+ }
214
+ }
215
+ });
216
+
217
+ return customArgs;
218
+ }
179
219
 
180
220
  constructor(options: TestLensReporterOptions) {
221
+ // Parse custom CLI arguments
222
+ const customArgs = TestLensReporter.parseCustomArgs();
223
+
224
+ // Allow API key from environment variable if not provided in config
225
+ // Check multiple environment variable names in priority order (uppercase and lowercase)
226
+ const apiKey = options.apiKey
227
+ || process.env.TESTLENS_API_KEY
228
+ || process.env.testlens_api_key
229
+ || process.env.TESTLENS_KEY
230
+ || process.env.testlens_key
231
+ || process.env.testlensApiKey
232
+ || process.env.PLAYWRIGHT_API_KEY
233
+ || process.env.playwright_api_key
234
+ || process.env.PW_API_KEY
235
+ || process.env.pw_api_key;
236
+
181
237
  this.config = {
182
238
  apiEndpoint: options.apiEndpoint || 'https://testlens.qa-path.com/api/v1/webhook/playwright',
183
- apiKey: options.apiKey, // API key must come from config file
239
+ apiKey: apiKey, // API key from config or environment variable
184
240
  enableRealTimeStream: options.enableRealTimeStream !== undefined ? options.enableRealTimeStream : true,
185
241
  enableGitInfo: options.enableGitInfo !== undefined ? options.enableGitInfo : true,
186
242
  enableArtifacts: options.enableArtifacts !== undefined ? options.enableArtifacts : true,
@@ -189,11 +245,16 @@ export class TestLensReporter implements Reporter {
189
245
  batchSize: options.batchSize || 10,
190
246
  flushInterval: options.flushInterval || 5000,
191
247
  retryAttempts: options.retryAttempts !== undefined ? options.retryAttempts : 0,
192
- timeout: options.timeout || 60000
248
+ timeout: options.timeout || 60000,
249
+ customMetadata: { ...customArgs, ...options.customMetadata } // CLI args + config metadata
193
250
  } as Required<TestLensReporterConfig>;
194
251
 
195
252
  if (!this.config.apiKey) {
196
- throw new Error('API_KEY is required for TestLensReporter. Pass it as apiKey option in your playwright config.');
253
+ throw new Error('API_KEY is required for TestLensReporter. Pass it as apiKey option in your playwright config or set one of these environment variables: TESTLENS_API_KEY, TESTLENS_KEY, PLAYWRIGHT_API_KEY, PW_API_KEY, API_KEY, or APIKEY.');
254
+ }
255
+
256
+ if (apiKey !== options.apiKey) {
257
+ console.log('✓ Using API key from environment variable');
197
258
  }
198
259
 
199
260
  // Determine SSL validation behavior
@@ -257,10 +318,20 @@ export class TestLensReporter implements Reporter {
257
318
  this.runMetadata = this.initializeRunMetadata();
258
319
  this.specMap = new Map<string, SpecData>();
259
320
  this.testMap = new Map<string, TestData>();
321
+ this.runCreationFailed = false;
322
+
323
+ // Log custom metadata if any
324
+ if (this.config.customMetadata && Object.keys(this.config.customMetadata).length > 0) {
325
+ console.log('\n📋 Custom Metadata Detected:');
326
+ Object.entries(this.config.customMetadata).forEach(([key, value]) => {
327
+ console.log(` ${key}: ${value}`);
328
+ });
329
+ console.log('');
330
+ }
260
331
  }
261
332
 
262
333
  private initializeRunMetadata(): RunMetadata {
263
- return {
334
+ const metadata: RunMetadata = {
264
335
  id: this.runId,
265
336
  startTime: new Date().toISOString(),
266
337
  environment: 'production',
@@ -269,6 +340,18 @@ export class TestLensReporter implements Reporter {
269
340
  playwrightVersion: this.getPlaywrightVersion(),
270
341
  nodeVersion: process.version
271
342
  };
343
+
344
+ // Add custom metadata if provided
345
+ if (this.config.customMetadata && Object.keys(this.config.customMetadata).length > 0) {
346
+ metadata.customMetadata = this.config.customMetadata;
347
+
348
+ // Extract testlensBuildName as a dedicated field for dashboard display
349
+ if (this.config.customMetadata.testlensBuildName) {
350
+ metadata.testlensBuildName = this.config.customMetadata.testlensBuildName;
351
+ }
352
+ }
353
+
354
+ return metadata;
272
355
  }
273
356
 
274
357
  private getPlaywrightVersion(): string {
@@ -301,7 +384,13 @@ export class TestLensReporter implements Reporter {
301
384
  }
302
385
 
303
386
  async onBegin(config: FullConfig, suite: Suite): Promise<void> {
304
- console.log(`🚀 TestLens Reporter starting - Run ID: ${this.runId}`);
387
+ // Show Build Name if provided, otherwise show Run ID
388
+ if (this.runMetadata.testlensBuildName) {
389
+ console.log(`🚀 TestLens Reporter starting - Build: ${this.runMetadata.testlensBuildName}`);
390
+ console.log(` Run ID: ${this.runId}`);
391
+ } else {
392
+ console.log(`🚀 TestLens Reporter starting - Run ID: ${this.runId}`);
393
+ }
305
394
 
306
395
  // Collect Git information if enabled
307
396
  if (this.config.enableGitInfo) {
@@ -333,18 +422,24 @@ export class TestLensReporter implements Reporter {
333
422
  }
334
423
 
335
424
  async onTestBegin(test: TestCase, result: TestResult): Promise<void> {
425
+ // Log which test is starting
426
+ console.log(`\n▶️ Running test: ${test.title}`);
427
+
336
428
  const specPath = test.location.file;
337
429
  const specKey = `${specPath}-${test.parent.title}`;
338
430
 
339
431
  // Create or update spec data
340
432
  if (!this.specMap.has(specKey)) {
433
+ const extractedTags = this.extractTags(test);
341
434
  const specData: SpecData = {
342
435
  filePath: path.relative(process.cwd(), specPath),
343
436
  testSuiteName: test.parent.title,
344
- tags: this.extractTags(test),
345
437
  startTime: new Date().toISOString(),
346
438
  status: 'running'
347
439
  };
440
+ if (extractedTags.length > 0) {
441
+ specData.tags = extractedTags;
442
+ }
348
443
  this.specMap.set(specKey, specData);
349
444
 
350
445
  // Send spec start event to API
@@ -419,13 +514,16 @@ export class TestLensReporter implements Reporter {
419
514
  const specKey = `${specPath}-${test.parent.title}`;
420
515
 
421
516
  if (!this.specMap.has(specKey)) {
517
+ const extractedTags = this.extractTags(test);
422
518
  const specData: SpecData = {
423
519
  filePath: path.relative(process.cwd(), specPath),
424
520
  testSuiteName: test.parent.title,
425
- tags: this.extractTags(test),
426
521
  startTime: new Date().toISOString(),
427
522
  status: 'skipped'
428
523
  };
524
+ if (extractedTags.length > 0) {
525
+ specData.tags = extractedTags;
526
+ }
429
527
  this.specMap.set(specKey, specData);
430
528
 
431
529
  // Send spec start event to API
@@ -615,6 +713,18 @@ export class TestLensReporter implements Reporter {
615
713
  });
616
714
 
617
715
  if (remainingTests.length === 0) {
716
+ // Aggregate tags from all tests in this spec
717
+ const allTags = new Set<string>();
718
+ test.parent.tests.forEach((t: any) => {
719
+ const tags = this.extractTags(t);
720
+ tags.forEach(tag => allTags.add(tag));
721
+ });
722
+ const aggregatedTags = Array.from(allTags);
723
+ // Only update tags if we have any
724
+ if (aggregatedTags.length > 0) {
725
+ specData.tags = aggregatedTags;
726
+ }
727
+
618
728
  specData.endTime = new Date().toISOString();
619
729
 
620
730
  // Send spec end event to API
@@ -664,13 +774,23 @@ export class TestLensReporter implements Reporter {
664
774
  }
665
775
  });
666
776
 
667
- console.log(`📊 TestLens Report completed - Run ID: ${this.runId}`);
777
+ // Show Build Name if provided, otherwise show Run ID
778
+ if (this.runMetadata.testlensBuildName) {
779
+ console.log(`📊 TestLens Report completed - Build: ${this.runMetadata.testlensBuildName}`);
780
+ console.log(` Run ID: ${this.runId}`);
781
+ } else {
782
+ console.log(`📊 TestLens Report completed - Run ID: ${this.runId}`);
783
+ }
668
784
  console.log(`🎯 Results: ${passedTests} passed, ${failedTests} failed (${timedOutTests} timeouts), ${skippedTests} skipped`);
669
785
  }
670
786
 
671
787
  private async sendToApi(payload: any): Promise<void> {
788
+ // Skip sending if run creation already failed
789
+ if (this.runCreationFailed && payload.type !== 'runStart') {
790
+ return;
791
+ }
792
+
672
793
  try {
673
- console.log(`📤 Sending ${payload.type} event to ${this.config.apiEndpoint}`);
674
794
  const response = await this.axiosInstance.post('', payload, {
675
795
  headers: {
676
796
  'X-API-Key': this.config.apiKey
@@ -680,21 +800,91 @@ export class TestLensReporter implements Reporter {
680
800
  console.log(`✅ Sent ${payload.type} event to TestLens (HTTP ${response.status})`);
681
801
  }
682
802
  } catch (error: any) {
683
- console.error(`❌ Failed to send ${payload.type} event to TestLens:`, {
684
- message: error?.message || 'Unknown error',
685
- status: error?.response?.status,
686
- statusText: error?.response?.statusText,
687
- data: error?.response?.data,
688
- code: error?.code,
689
- url: error?.config?.url,
690
- method: error?.config?.method
691
- });
803
+ const errorData = error?.response?.data;
804
+ const status = error?.response?.status;
805
+
806
+ // Check for limit exceeded (403)
807
+ if (status === 403 && errorData?.error === 'limit_exceeded') {
808
+ // Set flag to skip subsequent events
809
+ if (payload.type === 'runStart' && errorData?.limit_type === 'test_runs') {
810
+ this.runCreationFailed = true;
811
+ }
812
+
813
+ console.error('\n' + '='.repeat(80));
814
+ if (errorData?.limit_type === 'test_cases') {
815
+ console.error('❌ TESTLENS ERROR: Test Cases Limit Reached');
816
+ } else if (errorData?.limit_type === 'test_runs') {
817
+ console.error('❌ TESTLENS ERROR: Test Runs Limit Reached');
818
+ } else {
819
+ console.error('❌ TESTLENS ERROR: Plan Limit Reached');
820
+ }
821
+ console.error('='.repeat(80));
822
+ console.error('');
823
+ console.error(errorData?.message || 'You have reached your plan limit.');
824
+ console.error('');
825
+ console.error(`Current usage: ${errorData?.current_usage || 'N/A'} / ${errorData?.limit || 'N/A'}`);
826
+ console.error('');
827
+ console.error('To continue, please upgrade your plan.');
828
+ console.error('Contact: support@alternative-path.com');
829
+ console.error('');
830
+ console.error('='.repeat(80));
831
+ console.error('');
832
+ return; // Don't log the full error object for limit errors
833
+ }
834
+
835
+ // Check for trial expiration, subscription errors, or limit errors (401)
836
+ if (status === 401) {
837
+ if (errorData?.error === 'trial_expired' || errorData?.error === 'subscription_inactive' ||
838
+ errorData?.error === 'test_cases_limit_reached' || errorData?.error === 'test_runs_limit_reached') {
839
+ console.error('\n' + '='.repeat(80));
840
+
841
+ if (errorData?.error === 'test_cases_limit_reached') {
842
+ console.error('❌ TESTLENS ERROR: Test Cases Limit Reached');
843
+ } else if (errorData?.error === 'test_runs_limit_reached') {
844
+ console.error('❌ TESTLENS ERROR: Test Runs Limit Reached');
845
+ } else {
846
+ console.error('❌ TESTLENS ERROR: Your trial plan has ended');
847
+ }
848
+
849
+ console.error('='.repeat(80));
850
+ console.error('');
851
+ console.error(errorData?.message || 'Your trial period has expired.');
852
+ console.error('');
853
+ console.error('To continue using TestLens, please upgrade to Enterprise plan.');
854
+ console.error('Contact: ' + (errorData?.contactEmail || 'support@alternative-path.com'));
855
+ console.error('');
856
+ if (errorData?.trial_end_date) {
857
+ console.error(`Trial ended: ${new Date(errorData.trial_end_date).toLocaleDateString()}`);
858
+ console.error('');
859
+ }
860
+ console.error('='.repeat(80));
861
+ console.error('');
862
+ } else {
863
+ console.error(`❌ Authentication failed: ${errorData?.error || 'Invalid API key'}`);
864
+ }
865
+ } else if (status !== 403) {
866
+ // Log other errors (but not 403 which we handled above)
867
+ console.error(`❌ Failed to send ${payload.type} event to TestLens:`, {
868
+ message: error?.message || 'Unknown error',
869
+ status: status,
870
+ statusText: error?.response?.statusText,
871
+ data: errorData,
872
+ code: error?.code,
873
+ url: error?.config?.url,
874
+ method: error?.config?.method
875
+ });
876
+ }
692
877
 
693
878
  // Don't throw error to avoid breaking test execution
694
879
  }
695
880
  }
696
881
 
697
882
  private async processArtifacts(testId: string, result: TestResult): Promise<void> {
883
+ // Skip artifact processing if run creation failed
884
+ if (this.runCreationFailed) {
885
+ return;
886
+ }
887
+
698
888
  const attachments = result.attachments;
699
889
 
700
890
  for (const attachment of attachments) {
@@ -810,7 +1000,15 @@ export class TestLensReporter implements Reporter {
810
1000
 
811
1001
  console.log(`📝 Sent ${codeBlocks.length} code blocks for: ${path.basename(specPath)}`);
812
1002
  } catch (error: any) {
813
- console.error('Failed to send spec code blocks:', error?.response?.data || error?.message || 'Unknown error');
1003
+ const errorData = error?.response?.data;
1004
+
1005
+ // Handle duplicate spec code blocks gracefully (when re-running tests)
1006
+ if (errorData?.error && errorData.error.includes('duplicate key value violates unique constraint')) {
1007
+ console.log(`ℹ️ Spec code blocks already exist for: ${path.basename(specPath)} (skipped)`);
1008
+ return;
1009
+ }
1010
+
1011
+ console.error('Failed to send spec code blocks:', errorData || error?.message || 'Unknown error');
814
1012
  }
815
1013
  }
816
1014
 
@@ -917,7 +1115,7 @@ export class TestLensReporter implements Reporter {
917
1115
  remoteUrl
918
1116
  };
919
1117
  } catch (error: any) {
920
- console.warn('Could not collect Git information:', error.message);
1118
+ // Silently skip git information if not in a git repository
921
1119
  return null;
922
1120
  }
923
1121
  }
@@ -932,18 +1130,35 @@ export class TestLensReporter implements Reporter {
932
1130
  private extractTags(test: TestCase): string[] {
933
1131
  const tags: string[] = [];
934
1132
 
935
- test.annotations.forEach((annotation: any) => {
936
- if (annotation.type === 'tag' && annotation.description) {
937
- tags.push(annotation.description);
1133
+ // Playwright stores tags in the _tags property
1134
+ const testTags = (test as any)._tags;
1135
+ if (testTags && Array.isArray(testTags)) {
1136
+ tags.push(...testTags);
1137
+ }
1138
+
1139
+ // Also get tags from parent suites by walking up the tree
1140
+ let currentSuite: Suite | undefined = test.parent;
1141
+ while (currentSuite) {
1142
+ const suiteTags = (currentSuite as any)._tags;
1143
+ if (suiteTags && Array.isArray(suiteTags)) {
1144
+ tags.push(...suiteTags);
938
1145
  }
939
- });
1146
+ currentSuite = currentSuite.parent;
1147
+ }
940
1148
 
1149
+ // Also extract @tags from test title for backward compatibility
941
1150
  const tagMatches = test.title.match(/@[\w-]+/g);
942
1151
  if (tagMatches) {
943
1152
  tags.push(...tagMatches);
944
1153
  }
945
1154
 
946
- return tags;
1155
+ // Add testlensBuildTag from custom metadata if present
1156
+ if (this.config.customMetadata?.testlensBuildTag) {
1157
+ tags.push(`@${this.config.customMetadata.testlensBuildTag}`);
1158
+ }
1159
+
1160
+ // Remove duplicates and return
1161
+ return [...new Set(tags)];
947
1162
  }
948
1163
 
949
1164
  private getTestId(test: TestCase): string {
@@ -1038,6 +1253,39 @@ export class TestLensReporter implements Reporter {
1038
1253
  throw new Error(`Upload confirmation failed: ${confirmResponse.data.error || 'Unknown error'}`);
1039
1254
  }
1040
1255
  } catch (error: any) {
1256
+ // Check for trial expiration, subscription errors, or limit errors
1257
+ if (error?.response?.status === 401) {
1258
+ const errorData = error?.response?.data;
1259
+
1260
+ if (errorData?.error === 'trial_expired' || errorData?.error === 'subscription_inactive' ||
1261
+ errorData?.error === 'test_cases_limit_reached' || errorData?.error === 'test_runs_limit_reached') {
1262
+ console.error('\\n' + '='.repeat(80));
1263
+
1264
+ if (errorData?.error === 'test_cases_limit_reached') {
1265
+ console.error('❌ TESTLENS ERROR: Test Cases Limit Reached');
1266
+ } else if (errorData?.error === 'test_runs_limit_reached') {
1267
+ console.error('❌ TESTLENS ERROR: Test Runs Limit Reached');
1268
+ } else {
1269
+ console.error('❌ TESTLENS ERROR: Your trial plan has ended');
1270
+ }
1271
+
1272
+ console.error('='.repeat(80));
1273
+ console.error('');
1274
+ console.error(errorData?.message || 'Your trial period has expired.');
1275
+ console.error('');
1276
+ console.error('To continue using TestLens, please upgrade to Enterprise plan.');
1277
+ console.error('Contact: ' + (errorData?.contactEmail || 'support@alternative-path.com'));
1278
+ console.error('');
1279
+ if (errorData?.trial_end_date) {
1280
+ console.error(`Trial ended: ${new Date(errorData.trial_end_date).toLocaleDateString()}`);
1281
+ console.error('');
1282
+ }
1283
+ console.error('='.repeat(80));
1284
+ console.error('');
1285
+ return null;
1286
+ }
1287
+ }
1288
+
1041
1289
  // Better error messages for common issues
1042
1290
  let errorMsg = error.message;
1043
1291
 
package/package.json CHANGED
@@ -1,18 +1,24 @@
1
1
  {
2
2
  "name": "testlens-playwright-reporter",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Universal Playwright reporter for TestLens - works with both TypeScript and JavaScript projects",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
+ "bin": {
8
+ "testlens-cross-env": "cross-env.js"
9
+ },
7
10
  "files": [
8
11
  "index.js",
9
12
  "index.d.ts",
10
13
  "index.ts",
11
14
  "lib/",
15
+ "postinstall.js",
16
+ "cross-env.js",
12
17
  "README.md",
13
18
  "CHANGELOG.md"
14
19
  ],
15
20
  "scripts": {
21
+ "postinstall": "node postinstall.js",
16
22
  "prepack": "node build-embed-env.js",
17
23
  "prepublishOnly": "npm run lint && npm run test",
18
24
  "test": "echo 'Tests will be added in future versions'",
@@ -35,7 +41,7 @@
35
41
  ],
36
42
  "author": {
37
43
  "name": "TestLens Team",
38
- "email": "support@testlens.io",
44
+ "email": "support@alternative-path.com",
39
45
  "url": "https://testlens.qa-path.com"
40
46
  },
41
47
  "license": "MIT",
@@ -46,9 +52,11 @@
46
52
  "@aws-sdk/client-s3": "^3.624.0",
47
53
  "@aws-sdk/s3-request-presigner": "^3.624.0",
48
54
  "axios": "^1.11.0",
55
+ "cross-env": "^7.0.3",
49
56
  "dotenv": "^16.4.5",
50
57
  "form-data": "^4.0.1",
51
- "mime": "^4.0.4"
58
+ "mime": "^4.0.4",
59
+ "tslib": "^2.8.1"
52
60
  },
53
61
  "engines": {
54
62
  "node": ">=16.0.0",
package/postinstall.js ADDED
@@ -0,0 +1,39 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ try {
5
+ // Get the parent project's node_modules/.bin directory
6
+ // When installed, we are in: project/node_modules/testlens-playwright-reporter/
7
+ // We need to go to: project/node_modules/.bin/
8
+ const parentBinDir = path.resolve(__dirname, '..', '.bin');
9
+ const localBinDir = path.resolve(__dirname, 'node_modules', '.bin');
10
+
11
+ // Check if parent bin directory exists
12
+ if (!fs.existsSync(parentBinDir)) {
13
+ console.log('Parent .bin directory not found, skipping cross-env setup');
14
+ process.exit(0);
15
+ }
16
+
17
+ // Check if cross-env exists in our node_modules
18
+ const crossEnvCmd = process.platform === 'win32' ? 'cross-env.cmd' : 'cross-env';
19
+ const crossEnvPs1 = 'cross-env.ps1';
20
+ const sourceCmdPath = path.join(localBinDir, crossEnvCmd);
21
+ const sourcePs1Path = path.join(localBinDir, crossEnvPs1);
22
+
23
+ if (fs.existsSync(sourceCmdPath)) {
24
+ const targetCmdPath = path.join(parentBinDir, crossEnvCmd);
25
+
26
+ // Copy the file
27
+ fs.copyFileSync(sourceCmdPath, targetCmdPath);
28
+ console.log('✓ cross-env binary installed');
29
+
30
+ // Also copy PowerShell script on Windows
31
+ if (process.platform === 'win32' && fs.existsSync(sourcePs1Path)) {
32
+ const targetPs1Path = path.join(parentBinDir, crossEnvPs1);
33
+ fs.copyFileSync(sourcePs1Path, targetPs1Path);
34
+ }
35
+ }
36
+ } catch (error) {
37
+ // Don't fail installation if this doesn't work
38
+ console.log('Note: Could not setup cross-env automatically:', error.message);
39
+ }