@playwright-labs/reporter-prometheus-remote-write
v1.2.1
Published
Prometheus Remote Write reporter for Playwright — send test metrics to Prometheus in real time
Maintainers
Readme
@playwright-labs/reporter-prometheus-remote-write
Prometheus Remote Write reporter for Playwright — send test metrics to Prometheus in real time.
Get Started
NOTE: We need to configure prometheus as a precondition.
- Enable feature flag - remote write receiver
- in
prometheus.ymlfile add next configuration:
---
remote_write:
# fill url for your prometheus config here
- url: http://localhost:9090/api/v1/writeInstallation
npm i @playwright-labs/reporter-prometheus-remote-write # npm
yarn add @playwright-labs/reporter-prometheus-remote-write # yarn
pnpm add @playwright-labs/reporter-prometheus-remote-write # pnpm
bun a @playwright-labs/reporter-prometheus-remote-write # bunConfigure reporter
In your playwright.config.ts add next lines:
import { defineConfig } from "@playwright/test";
import { type PrometheusOptions } from "@playwright-labs/reporter-prometheus-remote-write";
export default defineConfig({
// ...
reporter: [
[
"@playwright-labs/reporter-prometheus-remote-write",
{
serverUrl: "http://localhost:9090/api/v1/write", // same url as declared in precondition
} satisfies PrometheusOptions, // for autocomplete
],
],
// ...
});Reporter Options
| Option | Description | Default |
| ------------- | --------------------------------------------------------- | --------------------------------------- |
| serverUrl | Remote writer server URL [1] | Not set. But throws an error if not set |
| headers | Custom headers for prometheus. E.g. {header1: 'value1'} | undefined |
| prefix | Custom metric prefix name | pw_ |
| auth.username | Basic auth. username | undefined |
| auth.password | Basic auth. password | undefined |
| labels | Ext. labels for all metrics. E.g. {label1: 'value1'} | undefined |
| env | Node.js environments object [2] | {} |
[1]: docs - https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write
[2]: We send empty object due to security reasons, since sending all environment variables can be visible for any user.
Collected metrics
Each metric name starts from prefix. By default it's pw_. So every metric name described without prefix.
Unified metric names (shared with reporter-otel)
Next to the legacy names below, the reporter also emits otel-compatible aliases for the auto-collected metrics — the same names and label semantics that @playwright-labs/reporter-otel uses. Dashboards written against the unified names work with either reporter.
| Concept | Unified name (this reporter) | reporter-otel → via OTel Collector | Match |
| --- | --- | --- | --- |
| Tests by status/result | pw_tests_total{test_status,test_result,test_suite} | pw_tests_total{test_status,test_result,test_suite} | ✅ identical |
| Retries | pw_test_retries_total | pw_test_retries_total | ✅ identical |
| Global errors | pw_test_error_count_total | pw_test_error_count_total | ✅ identical |
| Steps by category | pw_test_step_count_total | pw_test_step_count_total | ✅ identical |
| expect.poll | pw_expect_poll_total / pw_expect_poll_attempts / pw_expect_poll_duration | same names | ✅ identical |
| Process memory | pw_process_memory_* | pw_process_memory_*_bytes | ⚠️ _bytes suffix on the OTel side |
| OS free memory | pw_os_memory_free | pw_os_memory_free_bytes | ⚠️ _bytes suffix on the OTel side |
| Process CPU | pw_process_cpu_user, pw_process_cpu_system | pw_process_cpu_*_microseconds | ⚠️ _microseconds suffix on the OTel side |
| Run wall-clock duration | pw_run_duration (gauge) | pw_run_duration_milliseconds_* (histogram) | ⚠️ type/suffix differs |
Note: the OTel Collector's Prometheus exporter appends _total to counters and a unit suffix (_milliseconds, _bytes, _microseconds) where a unit is declared — the ✅ names already account for that. The legacy series (pw_tests_total_count, pw_test_retry_count, pw_error_count, pw_node_*, pw_tests_total_duration, pw_test_step_total_count) are still emitted for backward compatibility and will be removed in the next major version.
Test(s)
this metrics below sends periodically and you may found when they sends
| Name | Description | When Sends (hook name) |
| ---------------------------- | ------------------------------------------------------------ | ---------------------- |
| config | playwright configuration object | onExit |
| project | playwright project object. E.g. chromium, firefox | onExit |
| test | test object | onTestEnd |
| test_attachment_count | test attachment information | onTestEnd |
| test_attachment_size | attachment size in bytes | onTestEnd |
| test_annotation_count | annotations for 1 test | onTestEnd |
| test_step_total_count | count of test steps across whole run | onTestEnd |
| test_step_total_duration | duration(in ms) how long test steps has been executed | onTestEnd |
| test_errors | test errors information | onTestEnd |
| test_duration | test duration in milliseconds | onTestEnd |
| test_retry_count | count of retries for 1 test | onTestEnd |
| test_step_total_error | Count of errors in all test steps | onTestEnd |
| test_step_duration | duration of test step | onTestStepEnd |
| test_step_error_count | Count of errors in test steps | onTestStepEnd |
| test_step | Individual test steps information | onTestStepEnd |
| tests_attachment_total_size | total attachment size in bytes for all tests | onExit |
| tests_total_duration | time for all tests | onExit |
| tests_total_count | total count of all tests | onExit |
| tests_passed_count | count of all passed tests | onExit |
| tests_timed_out_count | count of all tests with timedOut status | onExit |
| tests_skipped_count | count of all skipped tests | onExit |
| tests_failed_count | count of all failed tests | onExit |
| tests_attachment_total_count | count of attachments across all tests | onExit |
| error_count | count of errors across all tests | onExit |
| expect_poll_total | number of expect.poll / toPass assertions, label outcome (pass/timeout) | onStepEnd |
| expect_poll_attempts | attempts per expect.poll assertion (gauge) | onStepEnd |
| expect_poll_duration | total polling duration per assertion in ms (gauge) | onStepEnd |
| stdout | stdout for test. Reporter logs have label: internal="true" | onStdOut |
| stderr | stdout for test. Reporter logs have label: internal="true" | onStdErr |
Node.js internals
This metrics collects every reporter lifecycle.
| Name | Description | Value | | ------------------------- | ---------------------------------------------------------------------------------------- | ------------------- | | env | environment variables [1] [2]. | undefined | | node_argv | command-line arguments passed when the Node.js process was launched (playwright) [3] [4] | process.argv | | node_versions | version strings of Node.js and its dependencies [5] | process.versions | | node_os | information about current operation system [6] | os | | node_cpu_system | cpu system utilization [7] | process.cpuUsage | | node_cpu_user | cpu user utilization [7] | process.cpuUsage | | node_memory_external | memory usage of the Node.js process measured in bytes [8] | process.memoryUsage | | node_memory_array_buffers | memory usage of the Node.js process measured in bytes [8] | process.memoryUsage | | node_memory_heap_used | memory usage of the Node.js process measured in bytes [8] | process.memoryUsage | | node_memory_rss | memory usage of the Node.js process measured in bytes [8] | process.memoryUsage | | node_memory_heap_total | memory usage of the Node.js process measured in bytes [8] | process.memoryUsage |
[1]: Do not use "process.env.name" variable since it can overwrite your "env" metric.
[2]: docs: https://nodejs.org/docs/latest/api/process.html#processenv
[3]: docs: https://nodejs.org/docs/latest/api/process.html#processargv
[4]: Map process.argv into process.argv with labels arg_{index} = value (see issue #34)
[5]: docs: https://nodejs.org/docs/latest/api/process.html#processversions
[6]: docs: https://nodejs.org/docs/latest/api/os.html#osarch
[7]: docs: https://nodejs.org/docs/latest/api/process.html#processcpuusagepreviousvalue
[8]: docs: https://nodejs.org/docs/latest/api/process.html#processmemoryusage
Using custom metrics
Custom metrics are provided by the @playwright-labs/prometheus-core package
(Metric, Counter, Gauge, Histogram, Event) and are re-exported by this reporter,
so you can import them from either package.
Metrics recorded in test workers reach this reporter as newline-delimited
JSON events on stdout (collect() writes one { name: "prometheus-remote-writer", payload }
line per series). The reporter buffers partial lines across onStdOut chunks,
so several back-to-back events — e.g. a Histogram flush — are parsed
individually.
You can define own metrics following next code:
import { test } from "@playwright/test";
import { Counter, Gauge } from "@playwright-labs/reporter-prometheus-remote-write";
const countOfUrlCalls = new Counter(
{
// only name is required
name: "url_open", // will automatically appends prefix
},
0,
); // starts from 0
test("simple counter test", async ({ page }) => {
await page.goto("https://example.com");
countOfUrlCalls.inc();
// ... rest test
});
test.afterAll(() => {
countOfUrlCalls.collect(); // sends metrics to prometheus
});Counter
Counters go up, and reset when the process restarts.
Counter API
constructor(labels, initialValue)labels:Record<string, string>- collected metrics. onlynamefield is required.initialValue:number- default is 0. If metric is constant we recommend to set to1
inc([value])increments your counter.value:number | undefined- count of increasing
collect()- Send metrics to prometheuslabels(label)- append extra labelslabel:Record<string, string>. Do not overwritenameproperty.
Gauge
Gauges are similar to Counters but a Gauge's value can be decreased.
Gauge API
Same for Counter
set(value)- set gauge valuevalue:number
zero()- same asset(0)dec([value])- decrement gauge valuevalue:number | undefined
Best practice
The useCounterMetric / useGaugeMetric fixtures live in the sibling package
@playwright-labs/fixture-prometheus.
- Add (or create) in your fixture
// file: fixture.ts
import { mergeExpects, mergeTests } from "@playwright/test";
import {
expect as prometheusExpect,
test as prometheusTest,
} from "@playwright-labs/fixture-prometheus";
export const expect = mergeExpects(prometheusExpect);
export const test = mergeTests(prometheusTest);- Use it!
// filename: some.test.ts
import { test, expect } from "./fixture";
test("use some base metric", async ({ useCounterMetric, page }) => {
const urlCallsMetric = useCounterMetric("url_calls");
const cssInteractionsMetric = useCounterMetric("css_interactions", {
selector: "css", // custom label
});
// your logic
await page.goto("https://example.com");
urlCallsMetric.inc();
await page.locator("css=.example-class");
cssInteractionsMetric.inc();
// on test end
urlCallsMetric.collect();
cssInteractionsMetric.collect();
});You can read more about fixtures in official docs page
Other practices
We additional recommends to use one of 2 practices for send metrics to prometheus via collect method:
- on afterEach/afterAll hook
- on each test
- on base hook
You also can use using keyword to automatically call .collect and .reset methods after the end of lexical environment. See explicit resource management TC39 proposal.
example:
import { test as base } from "@playwright/test";
import { Counter, Gauge } from "@playwright-labs/reporter-prometheus-remote-write";
type Context = {
urlCalls: Counter;
};
const test = base.extend<Context>({
urlCalls: async ({}, use) => {
const counter = new Counter({ name: "url_calls" });
await use(counter);
// automatically sends metrics
counter.collect();
},
});
test("extended test", ({ urlCalls }) => {
// ...
urlCalls.inc();
});
// or with using keyword
test("Some long test", () => {
using someMetricDuringTheTest = new Counter({ name: "some_metric" });
someMetricDuringTheTest.inc();
page.goto("SomePage");
// ...
// automatically calls .collect and .reset methods, due to the using keyword
});
// or
const anotherCounter = new Counter({ name: "custom_counter" });
base("some test", () => {
anotherCounter.inc();
});
base.afterAll(() => {
anotherCounter.collect();
});Related packages
@playwright-labs/prometheus-core— core metric primitives (Metric,Counter,Gauge,Histogram,Event) used by this reporter.@playwright-labs/fixture-prometheus— Playwright fixtures (useCounterMetric,useGaugeMetric,useGlobalCounter,useGlobalHistogram) for creating metrics inside tests.
