@arghajit/dummy 0.3.16 → 0.3.18

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 CHANGED
@@ -10,7 +10,7 @@ _The ultimate Playwright reporter — Interactive dashboard with historical tren
10
10
 
11
11
  ## [Live Demo](https://arghajit47.github.io/playwright-pulse/demo.html)
12
12
 
13
- ## ![Features](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/features.svg)
13
+ ## ![Features](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/features.svg)
14
14
 
15
15
  ## **Documentation**: [Pulse Report](https://arghajit47.github.io/playwright-pulse/)
16
16
 
@@ -21,7 +21,7 @@ The project provides these utility commands:
21
21
  | Command | Description |
22
22
  |------------------------|-----------------------------------------------------------------------------|
23
23
  | `generate-report` | Generates playwright-pulse-report.html, Loads screenshots and images dynamically from the attachments/ directory, Produces a lighter HTML file with faster initial load, Requires attachments/ directory to be present when viewing the report |
24
- | `generate-pulse-report`| Generates `playwright-pulse-static-report.html`, Self-contained, no server required, Preserves all dashboard functionality, all the attachments are embadded in the report, no need to have attachments/ directory when viewing the report, with a dark theme and better initial load handling |
24
+ | `generate-pulse-report`| Generates `playwright-pulse-static-report.html`, Self-contained, no server required, Preserves all dashboard functionality, all the attachments are embedded in the report, no need to have attachments/ directory when viewing the report, with a dark theme and better initial load handling |
25
25
  | `merge-pulse-report` | Combines multiple parallel test json reports, basically used in sharding |
26
26
  | `generate-trend` | Analyzes historical trends in test results |
27
27
  | `generate-email-report`| Generates email-friendly report versions |
@@ -101,24 +101,7 @@ npx merge-pulse-report --outputDir {YOUR_CUSTOM_REPORT_FOLDER}
101
101
 
102
102
  **Important:** Make sure your `playwright.config.ts` custom directory matches the CLI script:
103
103
 
104
- ```typescript
105
- import { defineConfig } from "@playwright/test";
106
- import * as path from "path";
107
-
108
- const CUSTOM_REPORT_DIR = path.resolve(__dirname, "{YOUR_CUSTOM_REPORT_FOLDER}");
109
-
110
- export default defineConfig({
111
- reporter: [
112
- ["list"],
113
- [
114
- "@arghajit/playwright-pulse-report",
115
- {
116
- outputDir: CUSTOM_REPORT_DIR, // Must match CLI --outputDir
117
- },
118
- ],
119
- ],
120
- });
121
- ```
104
+ ![Custom Output Directory](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/custom-output-directory-config.png)
122
105
 
123
106
  ## 📊 Report Options
124
107
 
@@ -240,76 +223,100 @@ Under the hood, this will:
240
223
  ### Basic Workflow
241
224
 
242
225
  ```yaml
243
- # Upload Pulse report from each shard (per matrix.config.type)
244
- - name: Upload Pulse Report results
245
- if: success() || failure()
246
- uses: actions/upload-artifact@v4
247
- with:
248
- name: pulse-report
249
- path: pulse-report/
250
-
251
- # Download all pulse-report-* artifacts after all shards complete
252
- - name: Download Pulse Report artifacts
253
- uses: actions/download-artifact@v4
254
- with:
255
- pattern: pulse-report
256
- path: downloaded-artifacts
257
-
258
- # Merge all sharded JSON reports into one final output
259
- - name: Generate Pulse Report
260
- run: |
261
- npm run script merge-report
262
- npm run generate-report [or, npm run generate-pulse-report]
263
-
264
- # Upload final merged report as CI artifact
265
- - name: Upload Pulse report
266
- uses: actions/upload-artifact@v4
267
- with:
268
- name: pulse-report
226
+ # .github/workflows/playwright.yml
227
+ name: Playwright Tests
228
+ on:
229
+ push:
230
+ branches: [ main, master ]
231
+ pull_request:
232
+ branches: [ main, master ]
233
+ jobs:
234
+ test:
235
+ timeout-minutes: 60
236
+ runs-on: ubuntu-latest
237
+ steps:
238
+ - uses: actions/checkout@v4
239
+ - uses: actions/setup-node@v4
240
+ with:
241
+ node-version: lts/*
242
+ - name: Install dependencies
243
+ run: npm ci
244
+ - name: Install Playwright Browsers
245
+ run: npx playwright install --with-deps
246
+ - name: Run Playwright tests
247
+ run: npm run test
248
+ - uses: actions/upload-artifact@v4
249
+ if: always()
250
+ with:
251
+ name: playwright-report
252
+ path: playwright-report/
253
+ retention-days: 30
269
254
  ```
270
255
 
256
+ For more details, please refer to the [Pulse Report Basic CI/CD Integration](https://arghajit47.github.io/playwright-pulse/advanced-usage.html).
257
+
271
258
  ### Sharded Workflow
272
259
 
273
260
  ```yaml
274
- # Upload Pulse report from each shard (per matrix.config.type)
275
- - name: Upload Pulse Report results
276
- if: success() || failure()
277
- uses: actions/upload-artifact@v4
278
- with:
279
- name: pulse-report-${{ matrix.config.type }}
280
- path: pulse-report/
281
-
282
- # Download all pulse-report-* artifacts after all shards complete
283
- - name: Download Pulse Report artifacts
284
- uses: actions/download-artifact@v4
285
- with:
286
- pattern: pulse-report-*
287
- path: downloaded-artifacts
288
-
289
- # Organize reports into a single folder and rename for merging
290
- - name: Organize Pulse Report
291
- run: |
292
- mkdir -p pulse-report
293
- for dir in downloaded-artifacts/pulse-report-*; do
294
- config_type=$(basename "$dir" | sed 's/pulse-report-//')
295
- cp -r "$dir/attachments" "pulse-report/attachments"
296
- cp "$dir/playwright-pulse-report.json" "pulse-report/playwright-pulse-report-${config_type}.json"
297
- done
298
-
299
- # Merge all sharded JSON reports into one final output
300
- - name: Generate Pulse Report
301
- run: |
302
- npm run merge-report
303
- npm run generate-report [or, npm run generate-pulse-report]
304
-
305
- # Upload final merged report as CI artifact
306
- - name: Upload Pulse report
307
- uses: actions/upload-artifact@v4
308
- with:
309
- name: pulse-report
310
- path: pulse-report/
261
+ # .github/workflows/playwright.yml
262
+ name: Playwright Tests with Pulse Report
263
+ on: [push]
264
+ jobs:
265
+ test:
266
+ timeout-minutes: 60
267
+ runs-on: ubuntu-latest
268
+ strategy:
269
+ fail-fast: false
270
+ matrix:
271
+ shard: [1, 2, 3, 4]
272
+ steps:
273
+ - uses: actions/checkout@v4
274
+ - uses: actions/setup-node@v4
275
+ with:
276
+ node-version: 18
277
+ - run: npm ci
278
+ - run: npx playwright install --with-deps
279
+ - run: npx playwright test --shard=${{ matrix.shard }}/${{ strategy.job-total }}
280
+ - uses: actions/upload-artifact@v4
281
+ if: always()
282
+ with:
283
+ name: pulse-report-shard-${{ matrix.shard }}
284
+ path: pulse-report/
285
+ retention-days: 1
286
+
287
+ merge-report:
288
+ needs: test
289
+ if: always()
290
+ runs-on: ubuntu-latest
291
+ steps:
292
+ - uses: actions/checkout@v4
293
+ - uses: actions/setup-node@v4
294
+ with:
295
+ node-version: 18
296
+ - run: npm ci
297
+
298
+ # Download all shard artifacts to a single directory
299
+ - uses: actions/download-artifact@v4
300
+ with:
301
+ path: all-reports
302
+ pattern: pulse-report-shard-*
303
+
304
+ # Merge all shard reports into a single report
305
+ - run: npx merge-pulse-report -o all-reports
306
+
307
+ # Generate the final HTML report
308
+ - run: npx generate-pulse-report -o all-reports
309
+
310
+ # Upload the final merged report
311
+ - uses: actions/upload-artifact@v4
312
+ with:
313
+ name: final-playwright-pulse-report
314
+ path: all-reports/
315
+ retention-days: 7
311
316
  ```
312
317
 
318
+ For more details, please refer to the [Pulse Report Sharded CI/CD Integration](https://arghajit47.github.io/playwright-pulse/sharding.html).
319
+
313
320
  ## 🧠 Notes
314
321
 
315
322
  - <strong>`npm run generate-report` generates a HTML report ( screenshots/images will be taken in realtime from 'attachments/' directory ).</strong>
@@ -319,13 +326,14 @@ Under the hood, this will:
319
326
  - After the test matrix completes, reports are downloaded, renamed, and merged.
320
327
  - merge-report is a custom Node.js script that combines all JSON files into one.
321
328
 
322
- ## ![Features](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images//pulse-folder-structures.svg)
329
+ ## ![Folder-Structure](https://ocpaxmghzmfbuhxzxzae.supabase.co/storage/v1/object/public/images/pulse-report/pulse-folder-structures.svg)
323
330
 
324
331
  ### 🚀 **Upgrade Now**
325
332
 
326
333
  ```bash
327
334
  npm install @arghajit/playwright-pulse-report@latest
328
335
  ```
336
+
329
337
  ---
330
338
 
331
339
  ## ⚙️ Advanced Configuration
@@ -340,7 +348,7 @@ npx playwright test test1.spec.ts && npx playwright test test2.spec.ts
340
348
 
341
349
  By default, In this above scenario, the report from test1 will be lost. To solve this, you can use the resetOnEachRun option.
342
350
 
343
- ```bash
351
+ ```javascript
344
352
  // playwright.config.ts
345
353
  import { defineConfig } from "@playwright/test";
346
354
  import * as path from "path";
@@ -17,6 +17,7 @@ export declare class PlaywrightPulseReporter implements Reporter {
17
17
  onBegin(config: FullConfig, suite: Suite): void;
18
18
  onTestBegin(test: TestCase): void;
19
19
  private _getSeverity;
20
+ private extractCodeSnippet;
20
21
  private getBrowserDetails;
21
22
  private processStep;
22
23
  onTestEnd(test: TestCase, result: PwTestResult): Promise<void>;
@@ -116,6 +116,30 @@ class PlaywrightPulseReporter {
116
116
  const severityAnnotation = annotations.find((a) => a.type === "pulse_severity");
117
117
  return (severityAnnotation === null || severityAnnotation === void 0 ? void 0 : severityAnnotation.description) || "Medium";
118
118
  }
119
+ async extractCodeSnippet(filePath, targetLine, targetColumn) {
120
+ try {
121
+ const fileContent = await fs.readFile(filePath, "utf-8");
122
+ const lines = fileContent.split("\n");
123
+ const contextLines = 1;
124
+ const startLine = Math.max(0, targetLine - contextLines - 1);
125
+ const endLine = Math.min(lines.length, targetLine + contextLines);
126
+ const snippetLines = [];
127
+ for (let i = startLine; i < endLine; i++) {
128
+ const lineNum = i + 1;
129
+ const isTargetLine = lineNum === targetLine;
130
+ const lineContent = lines[i] || "";
131
+ snippetLines.push(`${lineNum.toString().padStart(4)} | ${lineContent}`);
132
+ if (isTargetLine) {
133
+ const pointer = " ".repeat(4) + " | " + " ".repeat(targetColumn) + "^";
134
+ snippetLines.push(pointer);
135
+ }
136
+ }
137
+ return snippetLines.join("\n");
138
+ }
139
+ catch (error) {
140
+ return undefined;
141
+ }
142
+ }
119
143
  getBrowserDetails(test) {
120
144
  var _a, _b, _c, _d;
121
145
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
@@ -166,7 +190,7 @@ class PlaywrightPulseReporter {
166
190
  }
167
191
  return finalString.trim();
168
192
  }
169
- async processStep(step, testId, browserDetails, testCase) {
193
+ async processStep(step, testId, browserDetails, testCase, isFailedStep = false) {
170
194
  var _a, _b, _c, _d;
171
195
  let stepStatus = "passed";
172
196
  let errorMessage = ((_a = step.error) === null || _a === void 0 ? void 0 : _a.message) || undefined;
@@ -180,8 +204,10 @@ class PlaywrightPulseReporter {
180
204
  const startTime = new Date(step.startTime);
181
205
  const endTime = new Date(startTime.getTime() + Math.max(0, duration));
182
206
  let codeLocation = "";
207
+ let codeSnippet = undefined;
183
208
  if (step.location) {
184
209
  codeLocation = `${path.relative(this.config.rootDir, step.location.file)}:${step.location.line}:${step.location.column}`;
210
+ codeSnippet = await this.extractCodeSnippet(step.location.file, step.location.line, step.location.column);
185
211
  }
186
212
  return {
187
213
  id: `${testId}_step_${startTime.toISOString()}-${duration}-${(0, crypto_1.randomUUID)()}`,
@@ -194,29 +220,36 @@ class PlaywrightPulseReporter {
194
220
  errorMessage: errorMessage,
195
221
  stackTrace: ((_d = step.error) === null || _d === void 0 ? void 0 : _d.stack) || undefined,
196
222
  codeLocation: codeLocation || undefined,
223
+ codeSnippet: codeSnippet,
197
224
  isHook: step.category === "hook",
198
225
  hookType: step.category === "hook"
199
226
  ? step.title.toLowerCase().includes("before")
200
227
  ? "before"
201
228
  : "after"
202
229
  : undefined,
230
+ isFailedStep: isFailedStep,
203
231
  steps: [],
204
232
  };
205
233
  }
206
234
  async onTestEnd(test, result) {
207
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
235
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
208
236
  const project = (_a = test.parent) === null || _a === void 0 ? void 0 : _a.project();
209
237
  const browserDetails = this.getBrowserDetails(test);
210
238
  const testStatus = convertStatus(result.status, test);
211
239
  const startTime = new Date(result.startTime);
212
240
  const endTime = new Date(startTime.getTime() + result.duration);
213
- const processAllSteps = async (steps) => {
241
+ const processAllSteps = async (steps, parentFailed = false) => {
214
242
  let processed = [];
243
+ let foundFailedStep = false;
215
244
  for (const step of steps) {
216
- const processedStep = await this.processStep(step, test.id, browserDetails, test);
245
+ const isThisStepFailed = !foundFailedStep && (step.error !== undefined || (testStatus === "failed" && !foundFailedStep));
246
+ if (isThisStepFailed) {
247
+ foundFailedStep = true;
248
+ }
249
+ const processedStep = await this.processStep(step, test.id, browserDetails, test, isThisStepFailed);
217
250
  processed.push(processedStep);
218
251
  if (step.steps && step.steps.length > 0) {
219
- processedStep.steps = await processAllSteps(step.steps);
252
+ processedStep.steps = await processAllSteps(step.steps, isThisStepFailed);
220
253
  }
221
254
  }
222
255
  return processed;
@@ -268,10 +301,10 @@ class PlaywrightPulseReporter {
268
301
  endTime: endTime,
269
302
  browser: browserDetails,
270
303
  retries: result.retry,
271
- steps: ((_h = result.steps) === null || _h === void 0 ? void 0 : _h.length) ? await processAllSteps(result.steps) : [],
272
- errorMessage: (_j = result.error) === null || _j === void 0 ? void 0 : _j.message,
273
- stackTrace: (_k = result.error) === null || _k === void 0 ? void 0 : _k.stack,
274
- snippet: (_l = result.error) === null || _l === void 0 ? void 0 : _l.snippet,
304
+ steps: result.steps ? await processAllSteps(result.steps) : [],
305
+ errorMessage: (_h = result.error) === null || _h === void 0 ? void 0 : _h.message,
306
+ stackTrace: (_j = result.error) === null || _j === void 0 ? void 0 : _j.stack,
307
+ snippet: (_k = result.error) === null || _k === void 0 ? void 0 : _k.snippet,
275
308
  codeSnippet: codeSnippet,
276
309
  tags: test.tags.map((tag) => tag.startsWith("@") ? tag.substring(1) : tag),
277
310
  severity: this._getSeverity(test.annotations),
@@ -281,7 +314,7 @@ class PlaywrightPulseReporter {
281
314
  attachments: [],
282
315
  stdout: stdoutMessages.length > 0 ? stdoutMessages : undefined,
283
316
  stderr: stderrMessages.length > 0 ? stderrMessages : undefined,
284
- annotations: ((_m = test.annotations) === null || _m === void 0 ? void 0 : _m.length) > 0 ? test.annotations : undefined,
317
+ annotations: ((_l = test.annotations) === null || _l === void 0 ? void 0 : _l.length) > 0 ? test.annotations : undefined,
285
318
  ...testSpecificData,
286
319
  };
287
320
  for (const [index, attachment] of result.attachments.entries()) {
@@ -298,16 +331,16 @@ class PlaywrightPulseReporter {
298
331
  await this._ensureDirExists(path.dirname(absoluteDestPath));
299
332
  await fs.copyFile(attachment.path, absoluteDestPath);
300
333
  if (attachment.contentType.startsWith("image/")) {
301
- (_o = pulseResult.screenshots) === null || _o === void 0 ? void 0 : _o.push(relativeDestPath);
334
+ (_m = pulseResult.screenshots) === null || _m === void 0 ? void 0 : _m.push(relativeDestPath);
302
335
  }
303
336
  else if (attachment.contentType.startsWith("video/")) {
304
- (_p = pulseResult.videoPath) === null || _p === void 0 ? void 0 : _p.push(relativeDestPath);
337
+ (_o = pulseResult.videoPath) === null || _o === void 0 ? void 0 : _o.push(relativeDestPath);
305
338
  }
306
339
  else if (attachment.name === "trace") {
307
340
  pulseResult.tracePath = relativeDestPath;
308
341
  }
309
342
  else {
310
- (_q = pulseResult.attachments) === null || _q === void 0 ? void 0 : _q.push({
343
+ (_p = pulseResult.attachments) === null || _p === void 0 ? void 0 : _p.push({
311
344
  name: attachment.name,
312
345
  path: relativeDestPath,
313
346
  contentType: attachment.contentType,
@@ -11,8 +11,10 @@ export interface TestStep {
11
11
  errorMessage?: string;
12
12
  stackTrace?: string;
13
13
  codeLocation?: string;
14
+ codeSnippet?: string;
14
15
  isHook?: boolean;
15
16
  hookType?: "before" | "after";
17
+ isFailedStep?: boolean;
16
18
  steps?: TestStep[];
17
19
  }
18
20
  export interface TestResult {
@@ -67,7 +69,7 @@ export interface TestRun {
67
69
  failed: number;
68
70
  skipped: number;
69
71
  duration: number;
70
- environment?: EnvDetails;
72
+ environment?: EnvDetails | EnvDetails[];
71
73
  }
72
74
  export interface TrendDataPoint {
73
75
  date: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@arghajit/dummy",
3
3
  "author": "Arghajit Singha",
4
- "version": "0.3.16",
4
+ "version": "0.3.18",
5
5
  "description": "A Playwright reporter and dashboard for visualizing test results.",
6
6
  "homepage": "https://arghajit47.github.io/playwright-pulse/",
7
7
  "repository": {
@@ -87,6 +87,8 @@
87
87
  "@playwright/test": ">=1.40.0"
88
88
  },
89
89
  "overrides": {
90
- "glob": "^13.0.0"
90
+ "glob": "^13.0.0",
91
+ "minimatch": "^10.1.2",
92
+ "@isaacs/brace-expansion": "^5.0.1"
91
93
  }
92
94
  }
@@ -608,7 +608,7 @@ function generateMinifiedHTML(reportData) {
608
608
  <footer class="report-footer">
609
609
  <div style="display: inline-flex; align-items: center; gap: 0.5rem;">
610
610
  <span>Created for</span>
611
- <a href="https://playwright-pulse-report.netlify.app/" target="_blank" rel="noopener noreferrer">
611
+ <a href="https://arghajit47.github.io/playwright-pulse/" target="_blank" rel="noopener noreferrer">
612
612
  Pulse Email Report
613
613
  </a>
614
614
  </div>