Skip to content

Repository files navigation

@m00nsolutions/playwright-reporter

npm CI node license

Official Playwright reporter for M00N Report, an AI-native test management platform. Steps, screenshots, videos, traces and retries land in M00N Report while the run is still executing, and each result can carry the manual test case it covers.

Features

  • Live, not post-run - steps appear as they execute, so a failure is visible before the suite finishes
  • Every Playwright artifact - screenshots, videos and traces upload automatically, large files straight to object storage
  • Links to your test cases - a caseId annotation ties an automated result to the manual case it covers
  • CI metadata with no wiring - branch, commit and build URL detected from seven providers

Requirements

Node 18 or newer, and @playwright/test 1.40 or newer (recount: grep -nE '"node"|"@playwright/test"' package.json).

The Playwright floor is a peer dependency, so npm refuses the install on an older version. Nothing checks it at runtime - the reporter never imports @playwright/test - so --legacy-peer-deps installs anyway, and any hook that older Playwright does not call simply never fires.

Installation

npm install --save-dev @m00nsolutions/playwright-reporter

Quick Start

1. Get your API key

In M00N Report, open Project Settings -> API Keys and copy the key. It starts with m00n_ followed by 48 hex characters, and it identifies both the organization and the project, so nothing else needs configuring.

Keep it out of the repository. In CI it belongs in a secret; locally, in an environment variable.

2. Configure Playwright

import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    // Without these three, there is nothing for the reporter to upload
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
  },
  reporter: [
    ['list'],
    ['@m00nsolutions/playwright-reporter', {
      serverUrl: 'https://m00nreport.com',  // or your own instance, root origin, no /api, no trailing slash
      apiKey: process.env.M00N_API_KEY,
      launch: 'Regression Suite',
    }],
  ],
});

3. Run your tests, and check it worked

npx playwright test

The reporter prints a summary at the end of every run, whether or not it reported anything:

════════════════════════════════════════════════════════════
[M00NReporter] Run Summary
────────────────────────────────────────────────────────────
  Run ID:       9f3c1a7e-4d21-4b8a-9e77-2c5b1d0a6f84
  Tests:        24/24 reported
  Steps:        318 collected
  Streamed:     318 step events
  Attachments:  7

A real Run ID and a non-zero reported count mean the run reached the server, and it is already in the project's Launches list, filling in as tests finish. Run ID: N/A means the reporter disabled itself; the warning explaining why is above the summary, and Troubleshooting lists them. The Streamed: line appears only while realtime is on, which is the default.

Configuration

Every option is passed in the reporter tuple. The reporter reads no environment variables of its own, so read them in playwright.config.ts, which is ordinary JavaScript.

Option Type Default Description
serverUrl string required M00N Report URL. Without it the reporter disables itself and the run continues unreported.
apiKey string required Project API key. Identifies both organization and project.
launch string 'Run {date}' Title for this run, shown on the launch card
tags string[] | string [] Tags for the run. A comma-separated string works too.
attributes object {} Custom metadata, merged over anything auto-detected from CI. Anything you set here wins.
realtime boolean true Stream steps as they execute instead of only at test end
binaryAttachments boolean true Upload small attachments as binary. false sends them base64-encoded inside the results payload, which is slower and larger. Files over the large-file threshold are uploaded out of band either way.
enable boolean true false turns the reporter off without removing it from the config
debug boolean false Log what the reporter is doing, including which upload path each attachment took
verbose boolean false Log timing and throughput, and print a performance summary at the end
logFile string none Also write structured events to this file

Steps

test.step() calls become steps in M00N Report, nested as you nest them.

test('checkout rejects an expired card', async ({ page }) => {
  await test.step('Add item to cart', async () => {
    await page.getByRole('button', { name: 'Add to cart' }).click();
  });

  await test.step('Pay', async () => {
    await page.getByRole('button', { name: 'Pay' }).click();
  });
});

Playwright's own actions arrive as steps too - every locator call, every expect, every fixture and hook - and the reporter records each one Playwright emits without filtering by category. That is why a 24-test run reports hundreds of steps rather than dozens. There is no category filter.

A step still open when the test ends is reported as skipped, so the step list shows how far the test got.

Attachments

Everything Playwright produces is uploaded: screenshots, videos, traces, and anything you attach yourself. They only exist if use: asks for them, which is why the Quick Start config sets all three.

test('checkout rejects an expired card', async ({ page }) => {
  const response = await page.request.get('/api/cart');

  await test.info().attach('cart-state', {
    body: JSON.stringify(await response.json(), null, 2),
    contentType: 'application/json',
  });

  await test.info().attach('checkout-form', {
    body: await page.screenshot(),
    contentType: 'image/png',
  });
});

page.screenshot({ path }) writes a file to disk and Playwright never learns about it, so it does not become an attachment. Use test.info().attach(...), or let use: { screenshot } capture failures for you.

Where the bytes go

A file over 10 MB is never buffered in your test process (recount: grep -n 'LARGE_FILE_THRESHOLD =' src/constants.mjs). When the server advertises direct upload at run start - the hosted service does; an older or storage-less deployment does not - the reporter asks for a presigned URL and PUTs the file straight to your object storage, on a different host from serverUrl. That request carries no API key, and the bytes never pass through the reporting service.

Behind an egress allowlist, allow the storage host as well. If you do not, the PUT fails and that file falls back to streaming through serverUrl, so nothing is lost - but a blocked connection that hangs rather than refusing stalls the file for up to two minutes first (grep -n -A3 'export function directPutTimeoutMs' src/constants.mjs). Run with debug: true to see which path each file took.

Two caps apply. 200 MB per file, enforced by the reporter before it reads anything (grep -n 'MAX_ATTACHMENT_SIZE =' src/constants.mjs); one oversized file is skipped and nothing else is affected. And a per-run attachment budget from your plan, enforced by the server: once it is spent, every remaining attachment of that run is skipped, including ones already queued. Results and steps keep reporting either way.

Retries

Playwright's retries works unchanged. Every attempt is reported, not only the final verdict, so a flaky test is visible as flaky rather than as a pass. Each attempt keeps the same title path and carries an incrementing retry index.

export default defineConfig({
  retries: 2,
});

Linking Tests to Cases

A caseId annotation attaches the result to a manual test case, which is what turns a run into coverage rather than a list of green ticks. A tags annotation adds per-test tags on top of the run-level tags option.

test('checkout rejects an expired card', async ({ page }) => {
  test.info().annotations.push({ type: 'caseId', description: '42' });
  test.info().annotations.push({ type: 'tags', description: ['payments', 'smoke'] });

  // ...
});

caseId is the case's internal id, not the TC-42 number shown in the UI. Open the case in M00N Report and take the id from the address bar.

CI Auto-Detection

GitHub Actions, GitLab CI, Jenkins, Bitbucket Pipelines, Azure DevOps, CircleCI and Travis CI need no configuration (recount: grep -cE 'if \(env\.(GITHUB_ACTIONS|GITLAB_CI|JENKINS_URL|BITBUCKET_PIPELINE_UUID|TF_BUILD|CIRCLECI|TRAVIS)\)' src/ci-attributes.mjs). What is detected depends on what the provider exposes:

Attribute Detected from
branch, commit, build_number, build_url all seven
pipeline all but Bitbucket Pipelines and Travis
trigger GitHub Actions, GitLab CI, Azure DevOps, Travis
triggered_by GitHub Actions, GitLab CI, CircleCI only (recount: grep -c triggered_by src/ci-attributes.mjs)

Detected values are attached to the run and shown on the launch card. If you rely on triggered_by on one of the other four providers, set it yourself in attributes - anything you put there overrides a detected value, and any key you invent is passed through and displayed.

# .github/workflows/tests.yml
name: Tests
on: push

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx playwright test
        env:
          M00N_API_KEY: ${{ secrets.M00N_API_KEY }}

What Gets Sent

To serverUrl over HTTPS: test titles and file paths, step names and timings, error messages and stack traces, the CI attributes above, and every attachment that does not take the direct path below.

To your object storage over HTTPS, on a presigned URL and carrying no API key: attachments over 10 MB, when the server offers direct upload. A different host from serverUrl - see Where the bytes go.

Not sent: your source code, environment variables other than the CI attributes listed above, and the API key itself, which is never written to the log, including under debug: true.

Troubleshooting

Every message below is printed by the reporter and prefixed [M00NReporter]. The run stays green in all of them, which is why the Run Summary is the thing to check.

Message Cause
"apiKey" is required. Reporter disabled. The key never reached the config. In CI, most often a forked pull request: GitHub does not expose secrets to them, so secrets.M00N_API_KEY is an empty string.
"serverUrl" is required. Reporter disabled. The option is missing from the reporter tuple. The reporter reads no environment variables, so an unset M00N_SERVER_URL shows up here only if your config passes it.
INVALID_API_KEY. Reporter disabled - tests will continue without reporting. The server refused the run with an error no retry can fix, and the message is the bare code. See Permanent error codes for what each one means.
Server unavailable (<serverUrl>). Reporter disabled - tests will continue without reporting. Starting the run failed and neither GET {serverUrl}/api/ingest/health nor GET {serverUrl}/healthz answered a body of {"ok":true} within 2 seconds (grep -n 'AbortSignal.timeout' src/reporter.mjs). A wrong URL or port. The probe is advisory: it never disables anything by itself, it only chooses between this message and the Failed to start run: one below.
Failed to start run: <error>. Reporter disabled - tests will continue without reporting. The server is alive but answered with something other than a run. Three attempts were made first, so this is not a blip.
Reporting service appears unavailable. Continuing tests without reporting. A result failed to post mid-run. One success resets the count; five consecutive failures print Multiple service failures detected. Disabling reporter for remaining tests. and stop reporting for the rest of the run (grep -n '_serviceUnavailableCount >=' src/reporter.mjs).
Attachment skipped: "<name>" (N.NMB) exceeds 200MB limit. One file over the per-file cap. Nothing else is affected.
Attachment skipped: run attachment limit reached (X/Y MB used). Remaining attachments for this run will be skipped. The plan's attachment budget for this run is spent. Every remaining attachment of the run is skipped, queued ones included. Results and steps keep reporting.
Video/attachment file not found: <path> - file may have been cleaned up by Playwright Normal with retain-on-failure: Playwright deletes a passing attempt's artifacts and the reporter reaches the path just after. That one attachment is lost.
Timeout waiting for attachment uploads after 5 minutes - some attachments (videos/traces) may be missing! Uploads were still in flight when the run ended. Smaller videos, or a faster link to storage.
Reporter disabled via enable: false Expected. enable: false is still in the config.

Nothing at all in the output means the reporter never loaded. Check that the tuple is in reporter: and that the package name is spelled in full.

Permanent error codes

Twelve server codes are never retried, and the warning prints the code by itself (recount: sed -n '/PERMANENT_ERROR_CODES = new Set/,/^]);/p' src/constants.mjs | grep -cE "^ '[A-Z_]+'").

Code Meaning
API_KEY_REQUIRED The request carried no X-API-Key header.
INVALID_API_KEY_FORMAT The key does not start with m00n_. Usually the wrong secret was wired in; an empty one is caught earlier, by "apiKey" is required.
INVALID_API_KEY Well-formed but unknown or revoked. A key truncated by a CI secret still has the prefix, so it lands here rather than on the format error. Regenerate it in Project Settings.
SUBSCRIPTION_INACTIVE The organization's subscription is paused or canceled, and the service is not accepting results.
RUN_NOT_FOUND The run id is unknown to the server.
RUN_ACCESS_DENIED The run belongs to a different organization than the API key does.
RUN_NOT_ACTIVE The run is no longer running - completed, stopped or interrupted - so it accepts no more tests.
ATTACHMENT_TOO_LARGE One file above the server's per-file cap. Only that file is skipped.
RUN_ATTACHMENT_LIMIT_EXCEEDED The run's attachment budget is spent, and the rest of the run's attachments are skipped.
PRESIGN_BINDING_UNKNOWN A direct upload could not be confirmed because its presigned binding expired. The reporter re-sends that file through serverUrl, so nothing is lost.
UPLOAD_NOT_FOUND Storage had no object to confirm. Same fallback as above.
PROJECT_NOT_FOUND Carried for older self-hosted servers. The current service does not send it.

Known Limitations

  • Sharding produces one run per shard. Each --shard is a separate process constructing its own reporter, and there is no merge step, so four shards appear as four launches. Give each a distinct launch title, or report from an unsharded job.
  • A reporting failure is always silent. This is deliberate, and it means a misconfigured reporter and a working one produce the same green suite. The Run Summary is the only difference.
  • Runs are not linked to releases. The reporter reports a launch; attaching launches to a release is done in the app or over MCP.

Support

License

MIT License. The full text is in the LICENSE file inside the package.

About

Official Playwright reporter for M00N Report: streams live steps, attachments, traces and retries into your test cases and releases while the run is still executing.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages