Skip to content

Repository files navigation

QAPulseSK-report

npm version npm downloads License: MIT Node.js QAPulse by SK

The only test reporter you'll ever need. Playwright Β· Cypress Β· Jest Β· Vitest Β· Puppeteer Β· Selenium Β· WebdriverIO β€” one package, zero config, beautiful results.

qapulsesk-report demo


✨ Why QAPulseSK-report?

Most teams install 4–5 separate reporter packages β€” one per framework, one for Slack, one for AI, one for trends. We ship everything in one.

Feature QAPulseSK-report Others
7 test runners in one package βœ… ❌ 1 per package
Screenshots on failure (all runners) βœ… Partial
Failure clustering (dedup similar errors) βœ… ❌
7 built-in themes βœ… ❌
Trend + sparkline + histogram + timeline βœ… Paid
Diff vs previous run + failure state badges βœ… ❌
Auto git/CI metadata (branch, commit, PR link) βœ… ❌
AI failure analysis βœ… Opt-in, your key ❌
Slack + Teams + Discord webhooks βœ… Built-in, rich Basic
JSON export βœ… ❌
Zero cost to use βœ… Always Often paid

πŸš€ Install

npm install qapulsesk-report --save-dev

Node 18+ required.


πŸ“– Quick Start β€” Per Runner

Playwright

// playwright.config.ts
import { defineConfig } from '@playwright/test';
import path from 'path';

export default defineConfig({
  use: { screenshot: 'only-on-failure' },   // <- enables screenshot capture
  reporter: [
    ['list'],
    [
      path.resolve(__dirname, 'node_modules/qapulsesk-report/dist/adapters/playwright.js'),
      {
        outputDir:   'qapulse-report',
        reportTitle: 'My E2E Tests',
        history:     { enabled: true },
      }
    ],
  ],
});

Playwright screenshots ship natively β€” no extra hooks needed.

Cypress

// cypress.config.ts
import { defineConfig } from 'cypress';

export default defineConfig({
  reporter: 'node_modules/qapulsesk-report/dist/adapters/cypress.js',
  reporterOptions: {
    outputDir:   'qapulse-report',
    reportTitle: 'My Cypress Tests',
    history:     { enabled: true },
  },
  e2e: {
    screenshotOnRunFailure: true,   // <- default true, keep it on
    setupNodeEvents(on, config) { return config; }
  }
});

Jest

// jest.config.js
module.exports = {
  reporters: [
    'default',
    ['qapulsesk-report/jest', {
      outputDir:   'qapulse-report',
      reportTitle: 'My Jest Tests',
      history:     { enabled: true },
    }],
  ],
};

For screenshots (Jest + Puppeteer):

// jest.setup.js
const { attachScreenshot } = require('qapulsesk-report');

afterEach(async () => {
  const state = expect.getState();
  if (state.assertionCalls > 0 && state.numPassingAsserts < state.assertionCalls) {
    const p = `/tmp/${Date.now()}.png`;
    await page.screenshot({ path: p });
    attachScreenshot(state.currentTestName, p);
  }
});

Vitest

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import { QAPulseVitestReporter } from 'qapulsesk-report/vitest';

export default defineConfig({
  test: {
    reporters: [
      'verbose',
      new QAPulseVitestReporter({
        outputDir:   'qapulse-report',
        reportTitle: 'My Vitest Tests',
        history:     { enabled: true },
      }),
    ],
  },
});

Screenshots via attachScreenshot (same as Jest above).

Puppeteer (standalone, no runner)

import puppeteer from 'puppeteer';
import { QAPulsePuppeteerReporter } from 'qapulsesk-report/puppeteer';

const reporter = new QAPulsePuppeteerReporter({
  outputDir:   'qapulse-report',
  reportTitle: 'Smoke Suite',
  history:     { enabled: true },
});

const browser = await puppeteer.launch();
const page = await browser.newPage();

reporter.startTest('Home page loads', { suite: 'Public' });
try {
  await page.goto('https://example.com');
  await page.waitForSelector('h1');
  reporter.endTest('passed');
} catch (err) {
  const shot = `/tmp/${Date.now()}.png`;
  await page.screenshot({ path: shot });
  reporter.endTest('failed', { error: err, screenshotPath: shot });
}

await browser.close();
await reporter.finish();

Selenium (standalone)

import { Builder } from 'selenium-webdriver';
import { QAPulseSeleniumReporter } from 'qapulsesk-report/selenium';
import * as fs from 'fs';

const reporter = new QAPulseSeleniumReporter({ reportTitle: 'Selenium Suite' });
const driver = await new Builder().forBrowser('chrome').build();

reporter.startTest('Login works', { suite: 'Auth' });
try {
  await driver.get('https://example.com/login');
  reporter.endTest('passed');
} catch (err) {
  const shot = await driver.takeScreenshot();
  const p = `/tmp/${Date.now()}.png`;
  fs.writeFileSync(p, shot, 'base64');
  reporter.endTest('failed', { error: err, screenshotPath: p });
}

await driver.quit();
await reporter.finish();

WebdriverIO

// wdio.conf.ts
import QAPulseWDIOReporter from 'qapulsesk-report/webdriverio';

export const config = {
  reporters: [
    [QAPulseWDIOReporter, {
      outputDir:   'qapulse-report',
      reportTitle: 'WDIO Suite',
      history:     { enabled: true },
    }],
  ],
};

For screenshots, add an afterTest hook that calls attachScreenshot(test.fullTitle, path).


πŸ“Š What the Report Shows

Every report is a single portable HTML file with:

  • Metadata bar β€” auto-detected git branch, commit, author, PR link, CI job link
  • Diff banner β€” +N new Β· βˆ’N recovered Β· N still failing vs the previous run
  • Pass rate ring + sparkline β€” at-a-glance status and trajectory across runs
  • Stats cards β€” passed / failed / skipped / duration / total
  • 🧩 Failure clusters β€” similar errors grouped by normalized signature
  • πŸ” Insights
    • Suite health matrix (heatmap of per-suite pass rate)
    • Duration distribution histogram
    • Top 10 slowest tests
    • Per-suite execution timeline (colored strip)
  • πŸ“ˆ Trend chart β€” pass rate + passed/failed counts over your last N runs
  • ❌ Failed tests β€” expandable rows with error, stack, screenshots, AI analysis, and failure-state badge (πŸ†• new / πŸ’₯ regression / πŸ” recurring)
  • πŸ§ͺ All test suites β€” every test with its status

Plus a companion qapulse-report.json for downstream tools.


🎨 Themes

Seven built-in presets β€” set with a single line:

theme: { name: 'dracula' }
Name Look
qapulse-dark (default) Deep blue-black, blue accent
qapulse-light Warm off-white, blue accent
github-dark GitHub UI dark palette
github-light GitHub UI light palette
dracula Dracula editor theme
solarized-light Solarized parchment
minimal Pure monochrome

Override any color inline:

theme: { name: 'qapulse-dark', primaryColor: '#00ffcc' }

πŸ“· Screenshots on Failure

Handled per runner:

Runner How screenshots arrive
Playwright Native β€” reads result.attachments[]
Cypress Native β€” reads run.screenshots[] by testId
WebdriverIO Via attachScreenshot() in afterTest hook
Puppeteer Pass screenshotPath to endTest()
Selenium Same as Puppeteer
Jest / Vitest Via attachScreenshot() in afterEach

The collector inlines small images (≀200 KB by default) as base64 data URIs so the report stays a single portable file. Larger images are copied to <outputDir>/screenshots/. Click any thumbnail for a full-screen lightbox (ESC to close).

Config:

screenshots: {
  enabled:            true,   // default
  onFailure:          true,   // default
  onPass:             false,  // default
  inlineThresholdKb:  200,    // default
  outputSubdir:       'screenshots',
}

🧩 Failure Clustering

Failures with similar normalized error signatures collapse into a single card with a Γ—N badge. Under the hood:

  • Timestamps, UUIDs, hex, absolute paths, and integers are normalized out of the message + top stack frame
  • Failed tests are bucketed by signature
  • When AI is enabled, analysis runs once per cluster (representative test), not once per test β€” massive token savings

Example: three timeout tests with different test names but the same error string produce one Γ—3 cluster with a single AI-generated root cause + fix.


πŸ€– AI Failure Analysis (optional)

We never call any AI service by default. Bring your own key.

ai: {
  enabled: true,
  provider: 'anthropic',                        // or 'openai' | 'gemini'
  apiKey:   process.env.ANTHROPIC_API_KEY,
  model:    'claude-3-5-haiku-20241022',        // provider default used if omitted
  maxFailuresToAnalyze: 10,
}

Per cluster you get:

  • Summary β€” plain English
  • Root cause β€” what actually caused it
  • Suggestion β€” concrete fix
  • Confidence β€” high / medium / low

Getting a key:

  • Anthropic β†’ API keys β†’ Create β†’ ANTHROPIC_API_KEY=sk-ant-...
  • OpenAI β†’ OPENAI_API_KEY=...
  • Google Gemini β†’ GEMINI_API_KEY=...

πŸ”” Slack / Teams / Discord Webhooks

All three platforms get rich context, not just pass/fail counts:

  • Emoji + title with your reportTitle
  • Framework, pass rate, passed/failed/skipped, duration
  • Branch, commit hash, commit message (auto-detected)
  • +N new Β· βˆ’N recovered Β· N still failing vs previous run
  • Top 3 failure clusters with Γ—N counts
  • Top N failed test titles (configurable)
  • Clickable buttons: πŸ“Š View report, πŸ”€ PR #123, βš™οΈ CI job
  • Optional @mention on regressions
webhooks: {
  slack:   process.env.SLACK_WEBHOOK_URL,
  teams:   process.env.TEAMS_WEBHOOK_URL,
  discord: process.env.DISCORD_WEBHOOK_URL,

  reportUrl:          'https://reports.example.com/latest',
  notifyOnFailOnly:   true,
  maxFailedInCard:    5,
  mentionOnRegression: 'U0123ABC',   // Slack user or group id
}

Setup, per platform:

  • Slack β€” App Directory β†’ Incoming Webhooks β†’ add to a channel β†’ copy URL
  • Teams β€” Channel β†’ Connectors β†’ Incoming Webhook β†’ configure β†’ copy URL
  • Discord β€” Channel settings β†’ Integrations β†’ Webhooks β†’ New β†’ copy URL

πŸ“Š Cross-Run Insights

Enable history and the report gains a pass-rate sparkline, a trend line chart, failure-state badges (πŸ†• / πŸ’₯ / πŸ”), and a diff banner comparing to the previous run.

history: {
  enabled:     true,
  maxRuns:     20,                          // default
  historyFile: '.qapulse-history.json',     // auto-created inside outputDir
}

Each stored run includes per-test outcomes, so QAPulseSK-report can tell whether a currently-failing test is:

  • πŸ†• new β€” never seen before
  • πŸ’₯ regression β€” passed last time, fails now
  • πŸ” recurring (Γ—N) β€” failed for the last N runs in a row

🏷 Auto Git & CI Metadata

Every report auto-detects and displays:

  • Git: branch, short commit, commit message, author, tag
  • CI: provider, job URL, PR number + link

Detected providers: GitHub Actions, GitLab CI, CircleCI, Jenkins, Bitbucket Pipelines, and generic CI=true fallback.

Disable entirely with disableAutoMetadata: true, or override:

// Attach freely β€” user metadata always wins over auto-detected
run.metadata = { git: { branch: 'my-override' }, custom: { anything: 'ok' } };

πŸ“¦ JSON Export

Alongside qapulse-report.html you get qapulse-report.json with the full normalized TestRun, clusters, diff, and failure states. Great for:

  • Consumption by other tools (SAT, dashboards, CI gates)
  • Post-processing in Node/Python
  • Long-term storage / analytics

Disable with emitJson: false.


βš™οΈ Full Config Reference

{
  outputDir?:             string;     // default: 'qapulse-report'
  reportTitle?:           string;     // default: 'QAPulseSK Test Report'
  openAfterGeneration?:   boolean;    // default: false
  logo?:                  string;     // optional logo image path or URL

  theme?: {
    name?:               'qapulse-dark' | 'qapulse-light' | 'github-dark' |
                         'github-light' | 'dracula' | 'solarized-light' | 'minimal';
    primaryColor?:       string;
    backgroundColor?:    string;
    cardColor?:          string;
  };

  ai?: {
    enabled:               boolean;
    provider?:             'anthropic' | 'openai' | 'gemini';
    apiKey?:               string;
    model?:                string;
    maxFailuresToAnalyze?: number;  // default: 10
  };

  webhooks?: {
    slack?:                string;
    teams?:                string;
    discord?:              string;
    custom?:               Array<{ url: string; headers?: Record<string,string>; template?: (run) => object }>;
    notifyOnFailOnly?:     boolean;
    reportUrl?:            string;
    mentionOnRegression?:  string;
    mentionOnNewFailures?: boolean;
    maxFailedInCard?:      number;  // default: 5
  };

  history?: {
    enabled:      boolean;
    historyFile?: string;
    maxRuns?:     number;  // default: 20
  };

  screenshots?: {
    enabled?:            boolean;  // default: true
    onFailure?:          boolean;  // default: true
    onPass?:             boolean;  // default: false
    inlineThresholdKb?:  number;   // default: 200
    outputSubdir?:       string;   // default: 'screenshots'
  };

  emitJson?:             boolean;  // default: true
  disableAutoMetadata?:  boolean;  // default: false
}

πŸ§ͺ See It In Action

The with-packages branch of the Playwright boilerplate wires qapulsesk-report and ships a dedicated demo spec:

git clone -b with-packages https://github.com/QAPulse-by-SK/playwright-boilerplate.git
cd playwright-boilerplate && npm install && npx playwright install

npx playwright test tests/packages/report.demo.spec.ts --project=chromium
open qapulse-report/qapulse-report.html

Live demo report: qapulse-report-sk.surge.sh


πŸ“‹ Changelog

v2.3.0

  • Rich Slack / Teams / Discord webhooks: failed test list, cluster summary, diff vs previous run, git/PR link, "View report" button, optional @-mention on regressions
  • reportUrl config for clickable button in cards
  • Live-fire webhook smoke test (npm run smoke:webhooks)

v2.2.0

  • 🧩 Failure clustering β€” local error-signature bucketing; AI runs once per cluster instead of per test
  • πŸ” Insights section β€” suite health matrix, duration histogram, top 10 slowest, per-suite execution timeline (all zero-dep SVG)
  • Diff vs previous run β€” +N new / βˆ’N recovered / N still failing banner
  • Failure-state badges β€” πŸ†• new / πŸ’₯ regression / πŸ” recurring (Γ—N) per failing test
  • Auto git/CI metadata bar β€” branch, commit, author, PR + job links; supports GitHub Actions / GitLab / CircleCI / Jenkins / Bitbucket
  • JSON export β€” qapulse-report.json alongside HTML
  • Sparkline in pass-rate card
  • Fix: history save now ensures output dir exists

v2.1.0

  • 7 built-in themes β€” qapulse-dark, qapulse-light, github-dark, github-light, dracula, solarized-light, minimal
  • Legacy color overrides still work

v2.0.0

  • Screenshots on failure across all runners (inline base64 ≀200 KB, else copied) with click-to-zoom lightbox
  • 3 new runners: Puppeteer, Selenium, WebdriverIO β€” 4 β†’ 7
  • New attachScreenshot(fullTestName, path) registry for runner-agnostic capture
  • Extracted shared orchestrator from adapter pipelines

v1.0.x

  • Initial releases: Playwright, Cypress, Jest, Vitest adapters
  • Dark-theme HTML report
  • Slack / Teams webhooks
  • AI failure analysis
  • Trend history

πŸ”— Related Packages

Package Description
qapulsesk-assert Fuzzy assertions, schema validation, AI-powered checks
qapulsesk-gen HAR β†’ tests, recordings β†’ tests, plain English β†’ tests
qapulsesk-healer Self-healing locators for Playwright/Selenium

MIT Β© QA Pulse by SK

Created by QA Pulse by SK Β· skakarh.com


🌐 More from QA Pulse by SK

🌐 Website www.skakarh.com
πŸ“¦ All Open Source Products skakarh.com/products
✍️ QA Automation Blog skakarh.com/blog
πŸ› οΈ QA Consulting Services skakarh.com/services
🏒 GitHub Organisation github.com/QAPulse-by-SK
🎭 Playwright Boilerplate github.com/QAPulse-by-SK/playwright-boilerplate
🌲 Cypress Boilerplate github.com/QAPulse-by-SK/cypress-boilerplate
🐍 Selenium Boilerplate github.com/QAPulse-by-SK/selenium-boilerplate
πŸ“¦ qapulsesk-assert npmjs.com/package/qapulsesk-assert
πŸ€– qapulsesk-gen npmjs.com/package/qapulsesk-gen

About

All-in-one test reporter for Playwright, Cypress, Jest & Vitest. Dark-theme HTML reports, AI failure anaysis, Slack/Teams webhooks. By QAPulse by SK.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages