Skip to content

[Feature]: Expose public API for merging external context traces #40915

@veykos

Description

@veykos

🚀 Feature Request

Expose a public API for merging an externally launched Playwright context - for example an Electron app launched through electron.launch(), but also any secondary browser.newContext() started inside a test - into the test runner's per test trace.zip, so the final trace file contains both test.step / expect entries and the context's snapshots, network, screenshots, and sources.

Example

In an Electron E2E suite, the fixture that launches the app would tell Playwright to merge that app's trace into the runner trace. I've experimented with this and it works pretty nice, but it requires accessing private (not exposed through TS types) properties off testInfo._tracing:

A code sample of what I'm doing:

import {
    _electron as electron,
    type ElectronApplication,
    test as base,
    type TestInfo,
} from '@playwright/test';

// Private surface — no public API to inject an external trace into the
// runner's per-test trace.zip, so we reach into `_tracing`.
type RunnerTracing = {
    tracesDir?(): string;
    generateNextTraceRecordingName?(): string;
    maybeGenerateNextTraceRecordingPath?(): string | undefined;
    _options?: { live?: boolean };
};

function runnerTracing(testInfo: TestInfo): RunnerTracing | undefined {
    return (testInfo as unknown as { _tracing?: RunnerTracing })._tracing;
}

async function startMergedTrace(
    app: ElectronApplication,
    testInfo: TestInfo,
): Promise<void> {
    const runner = runnerTracing(testInfo);
    const name = runner?.generateNextTraceRecordingName?.();
    const live = runner?._options?.live === true;

    await app.context().tracing.start({
        screenshots: true,
        snapshots: true,
        sources: true,
        title: testInfo.title,
        ...(name ? { name } : {}),
        live,
    });

    // Save-on-close: every teardown path (fixture, direct call, app self-quit)
    // must flush the chunk before Playwright IPC tears down.
    const originalClose = app.close.bind(app);
    app.close = async () => {
        const path =
            runnerTracing(testInfo)?.maybeGenerateNextTraceRecordingPath?.();
        if (path) {
            try {
                await app.context().tracing.stopChunk({ path });
            } catch {
                /* context may already be gone */
            }
        }
        await originalClose();
    };
}

export const test = base.extend<{ app: ElectronApplication }>({
    app: async ({}, use, testInfo) => {
        // Feed the runner's tracesDir to Electron so UI mode's live preview
        // can find the chunks (otherwise they land in an unrelated tmp dir).
        const tracesDir = runnerTracing(testInfo)?.tracesDir?.();

        const app = await electron.launch({
            executablePath: '/path/to/built/electron-app',
            args: ['./main.js'],
            ...(tracesDir ? { tracesDir } : {}),
        });

        await startMergedTrace(app, testInfo);
        await use(app);
        await app.close();
    },
});

I imagine some more ergonomic API can be exposed:

// A — opt-in flag on electron.launch
const app = await electron.launch({ executablePath, args, tracing : 'inherit'});

// B — explicit attach on testInfo, also covers secondary browser contexts
const app = await electron.launch({});
await testInfo.attachTracing(app.context());

// C - just minimum exposure of the tracing internals that are currently underscored
// so it can be manually wired in with some confidence

Motivation

We have hundreds of Electron tests and they run beautifully, but when something breaks we need to depend on plain test runner traces - we do not have snapshots, network requests or anything else to depend on (although screenshots on failures work). This causes a lot of friction in debugging tests.
We also have some tests that go from an Electron app to a browser context and back - testing reactivity, registration flows (verifying email will take users to next step, etc.) so having the full picture of what is happening in the Electron app would be extremely useful.

Another thing that the above examples manages to get working is the live tracing (snapshots being populated live while the test is running) in Playwright's UI mode which I use almost exclusively.

The option to just record traces is available now, but it means creating a separate trace file which does not contain any of test runner's traces - test steps, annotation, etc, it only shows the pure expect/actions made by Playwright.

Is something on the roadmap for an API like this or there's already a good way to do this and I've tried to recreate something that already exists?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions