RUM JavaScript API - 1.345.1
    Preparing search index...

    Dynatrace RUM JavaScript Testing Framework for Playwright

    This framework provides a Playwright fixture for testing observability by interacting with the Dynatrace Real User Monitoring (RUM) API.

    npm install --save-dev @dynatrace/rum-javascript-sdk-playwright
    

    Follow these steps to configure and use the framework:

    1. Navigate to the Access Tokens app in Dynatrace.
    2. Create a new token with the following permission:
      • Permission: Read RUM manual insertion tags
      • Scope: API V2
    3. Copy the generated token for use in the next steps.

    To use the framework, you need to provide the following details:

    • Environment URL: The URL of the Dynatrace API endpoint to connect to, see RUM manual insertion tags API for details. Example: https://{your-environment-id}.live.dynatrace.com or https://{your-activegate-domain}/e/{your-environment-id}
      • Application ID: The application ID to identify the correct RUM JavaScript to fetch. You can find this in the URL if you open the Experience Vitals app and select the frontend you want to test. Example: APPLICATION-ABCDEF0123456789
    • Token: The API token created in Step 1.

    Populate your playwright config file with these fields or call test.use to provide them to a test suite (see DynatraceConfig for all options):

    import { test } from "@dynatrace/rum-javascript-sdk-playwright";

    test.describe("my suite", () => {
    test.use({
    dynatraceConfig: {
    appId: process.env.DT_APP_ID!,
    token: process.env.DT_TOKEN!,
    endpointUrl: process.env.DT_ENDPOINT_URL!
    }
    });
    });

    Use the provided expect functions to assert on events the RUM JavaScript should have sent:

    test("expect specific event", async ({ page, dynatraceTesting }) => {
    await page.goto("http://127.0.0.1:3000/ui");
    await page.getByText("Explore data").first().click();
    await dynatraceTesting.expectToHaveSentEvent(expect.objectContaining({
    "event_properties.component_rendered": "Data"
    }));
    });

    ⚠️ The order of fixture destructuring matters!

    The dynatraceTesting fixture must be destructured before any custom fixtures that navigate the page. This ensures the RUM JavaScript is injected before navigation occurs.

    test("my test", async ({ dynatraceTesting, myCustomFixture, page }) => {
    // dynatraceTesting is initialized first
    // RUM script is injected via page.addInitScript
    // Then myCustomFixture can safely navigate
    await page.goto("https://example.com");
    });
    test("my test", async ({ myCustomFixture, dynatraceTesting, page }) => {
    // If myCustomFixture navigates the page, it happens BEFORE
    // dynatraceTesting is initialized, so RUM script is not injected
    // This will cause the test to fail with "no events received"
    });

    Playwright initializes fixtures in the order they appear in the destructuring pattern. The dynatraceTesting fixture uses page.addInitScript() to inject the RUM JavaScript, which only affects subsequent page navigations.

    If you have custom fixtures that call page.goto(), always destructure dynatraceTesting first:

    // Define custom fixture
    const test = base.extend<{ myApp: MyApp }>({
    myApp: async ({ page }, use) => {
    await page.goto("https://myapp.com"); // Navigation happens here
    await use(new MyApp(page));
    }
    });

    // Use fixtures in correct order
    test("test", async ({ dynatraceTesting, myApp }) => {
    // ✅ Correct: dynatraceTesting initialized before myApp
    });

    The test fixture works by intercepting network requests to the RUM ingestion endpoint and capturing the events sent by the RUM JavaScript. Note that this has an impact on your capturing environment, since these beacons report real data.

    If you see an error like:

    [Dynatrace Testing] Missing required configuration fields: endpointUrl, appId, token
    

    This means the required configuration was not provided. Make sure to configure the fixture using test.use():

    test.use({
    dynatraceConfig: {
    endpointUrl: process.env.DT_ENDPOINT_URL!,
    appId: process.env.DT_APP_ID!,
    token: process.env.DT_TOKEN!
    }
    });

    If your tests fail with "Dynatrace didn't send any events":

    1. Check fixture ordering - Ensure dynatraceTesting is destructured before any fixtures that navigate the page
    2. Check the page URL - If you see a warning about the page already being navigated, fix the fixture order

    When dumpEventsOnFail is enabled, the framework will output detailed information about received events when assertions fail:

    [Dynatrace Testing] Event Dump (expectToHaveSentEvent - no match)
    Total events received: 3
    Total beacons received: 2

    Received events:

    Event 1: { "event_properties.custom_val": "load", ... }
    Event 2: { "event_properties.custom_val": "user-action", ... }
    Event 3: { "event_properties.custom_val": "custom", ... }

    This helps identify why expected events weren't matched.

    The DynatraceTesting interface provides utility methods for validating observability-related events and beacon requests during tests.

    The toMatchEventSnapshot method compares captured events against a stored snapshot file. On first run, it creates the snapshot. On subsequent runs, it compares and fails if different. Volatile fields (timestamps, IDs, etc.) are processed by default to prevent flaky tests. See SnapshotOptions for all available configuration options.

    test("captures user action events", async ({ page, dynatraceTesting }) => {
    await page.goto("http://127.0.0.1:3000/ui");
    await page.getByText("Submit").click();

    // Wait for events to be captured
    await dynatraceTesting.waitForBeacons({ minCount: 1 });

    // Compare against snapshot
    await dynatraceTesting.toMatchEventSnapshot();
    });
    import { DEFAULT_IGNORED_FIELDS, DEFAULT_REMOVED_FIELDS } from "@dynatrace/rum-javascript-sdk-playwright";

    test("captures events with custom property handling", async ({ page, dynatraceTesting }) => {
    await page.goto("http://127.0.0.1:3000/ui");
    await page.getByText("Submit").click();

    await dynatraceTesting.waitForBeacons({ minCount: 1 });

    // Extend defaults with additional patterns
    await dynatraceTesting.toMatchEventSnapshot({
    ignoredFields: [
    ...DEFAULT_IGNORED_FIELDS,
    "event_properties.request_id" // Value replaced with [IGNORED]
    ],
    removedFields: [
    ...DEFAULT_REMOVED_FIELDS,
    "user_action.resources.*" // All matching fields removed entirely
    ]
    });
    });
    import { DEFAULT_IGNORED_EVENTS } from "@dynatrace/rum-javascript-sdk-playwright";

    test("captures events excluding specific patterns", async ({ page, dynatraceTesting }) => {
    await page.goto("http://127.0.0.1:3000/ui");
    await page.getByText("Submit").click();

    await dynatraceTesting.waitForBeacons({ minCount: 1 });

    // Filter out specific event types
    await dynatraceTesting.toMatchEventSnapshot({
    ignoreEvents: [
    ...DEFAULT_IGNORED_EVENTS,
    { "characteristics.has_navigation": true }, // Exclude navigation events
    { "url.full": "http://example.com/favicon.ico" } // Exclude specific URL
    ]
    });
    });
    test("captures multiple event sequences", async ({ page, dynatraceTesting }) => {
    await page.goto("http://127.0.0.1:3000/ui");

    // First interaction
    await page.getByText("Login").click();
    await dynatraceTesting.waitForBeacons({ minCount: 1 });
    await dynatraceTesting.toMatchEventSnapshot({ name: "login-events" });

    dynatraceTesting.clearEvents();

    // Second interaction
    await page.getByText("Submit").click();
    await dynatraceTesting.waitForBeacons({ minCount: 1 });
    await dynatraceTesting.toMatchEventSnapshot({ name: "submit-events" });
    });

    Event snapshots may differ between browsers due to variations in timing, event ordering, or browser-specific behavior. To handle this, include the browserName fixture in your snapshot name to create separate snapshots per browser:

    test("captures events per browser", async ({ page, dynatraceTesting, browserName }) => {
    await page.goto("http://127.0.0.1:3000/ui");
    await page.getByText("Submit").click();

    await dynatraceTesting.waitForBeacons({ minCount: 1 });

    // Creates separate snapshots: basic-events-chromium, basic-events-firefox, basic-events-webkit
    await dynatraceTesting.toMatchEventSnapshot({
    name: `basic-events-${browserName}`
    });
    });

    This creates separate snapshot files for each browser:

    e2e/
    ├── my-test.spec.ts
    └── my-test.spec.ts-snapshots/
    ├── basic-events-chromium.events.snap
    ├── basic-events-firefox.events.snap
    └── basic-events-webkit.events.snap

    Snapshots are stored alongside your test files in a directory named <test-file>-snapshots/. For example:

    e2e/
    ├── my-test.spec.ts
    └── my-test.spec.ts-snapshots/
    └── events.events.snap

    The snapshot files contain JSON-formatted event data with ignored fields replaced by [IGNORED] and removed fields omitted:

    [
    {
    "duration": "[IGNORED]",
    "name": "click on Submit",
    "start_time": "[IGNORED]",
    "type": "user_action"
    }
    ]

    To update snapshots when event structures change intentionally, run Playwright with the --update-snapshots=all flag:

    npx playwright test --update-snapshots=all
    

    By default, snapshots are processed to prevent flaky tests:

    • DEFAULT_IGNORED_FIELDS — volatile values (timestamps, session IDs, trace IDs, etc.) are replaced with [IGNORED]
    • DEFAULT_REMOVED_FIELDS — fields that vary between runs (web vitals, performance metrics, viewport dimensions) are removed entirely
    • DEFAULT_IGNORED_EVENTS — noisy events (long tasks, self-monitoring) are filtered out completely

    When a snapshot comparison fails, the error message includes:

    1. The path to the snapshot file
    2. A line-by-line diff showing expected vs actual values
    3. Instructions for updating the snapshot

    Common causes of snapshot failures:

    • New fields added to events: Update the snapshot with --update-snapshots=all
    • Volatile field values: Add the field to ignoredFields to replace values with [IGNORED]
    • Varying field presence: Add the field to removedFields (use fieldname.* wildcard to remove all fields with that prefix)
    • Unwanted events: Add an event pattern to ignoreEvents to filter them out
    • Intentional changes: Review the diff and update the snapshot if correct