From 14f12e0328f0864e41cf3a788bc983a4b0a95cb0 Mon Sep 17 00:00:00 2001 From: cpadm <57954026+cpAdm@users.noreply.github.com> Date: Sat, 28 Feb 2026 16:32:02 +0100 Subject: [PATCH 1/2] feat: Add test timeout to trace and display it in metadata of UI --- .../src/utils/isomorphic/trace/entries.ts | 1 + .../src/utils/isomorphic/trace/traceModel.ts | 2 ++ .../utils/isomorphic/trace/traceModernizer.ts | 1 + packages/playwright/src/worker/testInfo.ts | 1 + packages/playwright/src/worker/testTracing.ts | 5 +++ packages/trace-viewer/src/ui/metadataView.tsx | 1 + packages/trace/src/trace.ts | 1 + .../playwright-test/playwright.trace.spec.ts | 31 +++++++++++++++++++ 8 files changed, 43 insertions(+) diff --git a/packages/playwright-core/src/utils/isomorphic/trace/entries.ts b/packages/playwright-core/src/utils/isomorphic/trace/entries.ts index b203acb9a19dd..05db6fe60f519 100644 --- a/packages/playwright-core/src/utils/isomorphic/trace/entries.ts +++ b/packages/playwright-core/src/utils/isomorphic/trace/entries.ts @@ -41,6 +41,7 @@ export type ContextEntry = { errors: trace.ErrorTraceEvent[]; hasSource: boolean; contextId: string; + timeout?: number; }; export type PageEntry = { diff --git a/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts b/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts index 91cfeb14bff52..d8ef874b0d747 100644 --- a/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts +++ b/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts @@ -89,6 +89,7 @@ export class TraceModel { resources: ResourceEntry[]; readonly actionCounters: Map; readonly traceUri: string; + readonly timeout?: number; constructor(traceUri: string, contexts: ContextEntry[]) { @@ -104,6 +105,7 @@ export class TraceModel { this.playwrightVersion = contexts.find(c => c.playwrightVersion)?.playwrightVersion; this.title = libraryContext?.title || ''; this.options = libraryContext?.options || {}; + this.timeout = contexts.find(c => c.origin === 'testRunner')?.timeout; // Next call updates all timestamps for all events in library contexts, so it must be done first. this.actions = mergeActionsAndUpdateTiming(contexts); this.pages = ([] as PageEntry[]).concat(...contexts.map(c => c.pages)); diff --git a/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts b/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts index f2b4f159deef0..c62dea8ae22be 100644 --- a/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts +++ b/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts @@ -98,6 +98,7 @@ export class TraceModernizer { contextEntry.options = event.options; contextEntry.testIdAttributeName = event.testIdAttributeName; contextEntry.contextId = event.contextId ?? ''; + contextEntry.timeout = event.timeout; break; } case 'screencast-frame': { diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 30e044bded66b..3cd9d180a2235 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -645,6 +645,7 @@ export class TestInfoImpl implements TestInfo { setTimeout(timeout: number) { this._timeoutManager.setTimeout(timeout); + this._tracing.updateTimeout(timeout); } async _cloneStorage(storageFile: string): Promise { diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index 2935415f7f015..3b224f2a82776 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -63,6 +63,7 @@ export class TestTracing { wallTime: Date.now(), monotonicTime: monotonicTime(), sdkLanguage: 'javascript', + timeout: testInfo.timeout, }; this._appendTraceEvent(this._contextCreatedEvent); } @@ -116,6 +117,10 @@ export class TestTracing { this._didFinishTestFunctionAndAfterEachHooks = true; } + updateTimeout(timeout: number) { + this._contextCreatedEvent.timeout = timeout; + } + artifactsDir() { return this._artifactsDir; } diff --git a/packages/trace-viewer/src/ui/metadataView.tsx b/packages/trace-viewer/src/ui/metadataView.tsx index 20d3b6c5a9a27..26bf6814b31ea 100644 --- a/packages/trace-viewer/src/ui/metadataView.tsx +++ b/packages/trace-viewer/src/ui/metadataView.tsx @@ -31,6 +31,7 @@ export const MetadataView: React.FunctionComponent<{
Time
{!!wallTime &&
start time:{wallTime}
}
duration:{msToString(model.endTime - model.startTime)}
+ {model.timeout !== undefined &&
timeout:{msToString(model.timeout)}
}
Browser
engine:{model.browserName}
{model.channel &&
channel:{model.channel}
} diff --git a/packages/trace/src/trace.ts b/packages/trace/src/trace.ts index 3ffa6c4586874..2964b026881f8 100644 --- a/packages/trace/src/trace.ts +++ b/packages/trace/src/trace.ts @@ -46,6 +46,7 @@ export type ContextCreatedTraceEvent = { sdkLanguage?: Language, testIdAttributeName?: string, contextId?: string, + timeout?: number, }; export type ScreencastFrameTraceEvent = { diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index 17a0bc609f6c6..7afbfb0a8770c 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -1349,3 +1349,34 @@ test('should record trace snapshot for more obscure commands', async ({ runInlin expect(trace.snapshots.snapshotByName(snapshotFrameOrPageId, boundingBoxAction.beforeSnapshot)).toBeTruthy(); expect(trace.snapshots.snapshotByName(snapshotFrameOrPageId, boundingBoxAction.afterSnapshot)).toBeTruthy(); }); + +test('should record default test timeout in trace', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('pass', async ({}) => { + }); + `, + }, { trace: 'on' }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip')); + expect(trace.model.timeout).toBe(30_000); +}); + +test('should record custom test timeout in trace', async ({ runInlineTest }, testInfo) => { + const result = await runInlineTest({ + 'a.spec.ts': ` + import { test, expect } from '@playwright/test'; + test('pass', async ({}) => { + test.setTimeout(120_000); + }); + `, + }, { trace: 'on' }); + + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); + const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip')); + expect(trace.model.timeout).toBe(120_000); +}); From fe272e9e28a0972391be07376ee66e2faa70c0ce Mon Sep 17 00:00:00 2001 From: cpadm <57954026+cpAdm@users.noreply.github.com> Date: Tue, 3 Mar 2026 21:03:34 +0100 Subject: [PATCH 2/2] chore: Rename to `testTimeout` & only save at end --- .../playwright-core/src/utils/isomorphic/trace/entries.ts | 2 +- .../src/utils/isomorphic/trace/traceModel.ts | 4 ++-- .../src/utils/isomorphic/trace/traceModernizer.ts | 2 +- packages/playwright/src/worker/testInfo.ts | 1 - packages/playwright/src/worker/testTracing.ts | 7 ++----- packages/trace-viewer/src/ui/metadataView.tsx | 2 +- packages/trace/src/trace.ts | 2 +- tests/playwright-test/playwright.trace.spec.ts | 4 ++-- 8 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/playwright-core/src/utils/isomorphic/trace/entries.ts b/packages/playwright-core/src/utils/isomorphic/trace/entries.ts index 05db6fe60f519..5fb51f63f4ca4 100644 --- a/packages/playwright-core/src/utils/isomorphic/trace/entries.ts +++ b/packages/playwright-core/src/utils/isomorphic/trace/entries.ts @@ -41,7 +41,7 @@ export type ContextEntry = { errors: trace.ErrorTraceEvent[]; hasSource: boolean; contextId: string; - timeout?: number; + testTimeout?: number; }; export type PageEntry = { diff --git a/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts b/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts index d8ef874b0d747..0ca3b802ba4c5 100644 --- a/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts +++ b/packages/playwright-core/src/utils/isomorphic/trace/traceModel.ts @@ -89,7 +89,7 @@ export class TraceModel { resources: ResourceEntry[]; readonly actionCounters: Map; readonly traceUri: string; - readonly timeout?: number; + readonly testTimeout?: number; constructor(traceUri: string, contexts: ContextEntry[]) { @@ -105,7 +105,7 @@ export class TraceModel { this.playwrightVersion = contexts.find(c => c.playwrightVersion)?.playwrightVersion; this.title = libraryContext?.title || ''; this.options = libraryContext?.options || {}; - this.timeout = contexts.find(c => c.origin === 'testRunner')?.timeout; + this.testTimeout = contexts.find(c => c.origin === 'testRunner')?.testTimeout; // Next call updates all timestamps for all events in library contexts, so it must be done first. this.actions = mergeActionsAndUpdateTiming(contexts); this.pages = ([] as PageEntry[]).concat(...contexts.map(c => c.pages)); diff --git a/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts b/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts index c62dea8ae22be..20e703145f553 100644 --- a/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts +++ b/packages/playwright-core/src/utils/isomorphic/trace/traceModernizer.ts @@ -98,7 +98,7 @@ export class TraceModernizer { contextEntry.options = event.options; contextEntry.testIdAttributeName = event.testIdAttributeName; contextEntry.contextId = event.contextId ?? ''; - contextEntry.timeout = event.timeout; + contextEntry.testTimeout = event.testTimeout; break; } case 'screencast-frame': { diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index 3cd9d180a2235..30e044bded66b 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -645,7 +645,6 @@ export class TestInfoImpl implements TestInfo { setTimeout(timeout: number) { this._timeoutManager.setTimeout(timeout); - this._tracing.updateTimeout(timeout); } async _cloneStorage(storageFile: string): Promise { diff --git a/packages/playwright/src/worker/testTracing.ts b/packages/playwright/src/worker/testTracing.ts index 3b224f2a82776..0eb73750640f8 100644 --- a/packages/playwright/src/worker/testTracing.ts +++ b/packages/playwright/src/worker/testTracing.ts @@ -63,7 +63,6 @@ export class TestTracing { wallTime: Date.now(), monotonicTime: monotonicTime(), sdkLanguage: 'javascript', - timeout: testInfo.timeout, }; this._appendTraceEvent(this._contextCreatedEvent); } @@ -117,10 +116,6 @@ export class TestTracing { this._didFinishTestFunctionAndAfterEachHooks = true; } - updateTimeout(timeout: number) { - this._contextCreatedEvent.timeout = timeout; - } - artifactsDir() { return this._artifactsDir; } @@ -168,6 +163,8 @@ export class TestTracing { } async stopIfNeeded() { + this._contextCreatedEvent.testTimeout = this._testInfo.timeout; + if (!this._options) return; diff --git a/packages/trace-viewer/src/ui/metadataView.tsx b/packages/trace-viewer/src/ui/metadataView.tsx index 26bf6814b31ea..1a863f4c4ce53 100644 --- a/packages/trace-viewer/src/ui/metadataView.tsx +++ b/packages/trace-viewer/src/ui/metadataView.tsx @@ -31,7 +31,7 @@ export const MetadataView: React.FunctionComponent<{
Time
{!!wallTime &&
start time:{wallTime}
}
duration:{msToString(model.endTime - model.startTime)}
- {model.timeout !== undefined &&
timeout:{msToString(model.timeout)}
} + {model.testTimeout !== undefined &&
test timeout:{msToString(model.testTimeout)}
}
Browser
engine:{model.browserName}
{model.channel &&
channel:{model.channel}
} diff --git a/packages/trace/src/trace.ts b/packages/trace/src/trace.ts index 2964b026881f8..d11ac4dc91045 100644 --- a/packages/trace/src/trace.ts +++ b/packages/trace/src/trace.ts @@ -46,7 +46,7 @@ export type ContextCreatedTraceEvent = { sdkLanguage?: Language, testIdAttributeName?: string, contextId?: string, - timeout?: number, + testTimeout?: number, }; export type ScreencastFrameTraceEvent = { diff --git a/tests/playwright-test/playwright.trace.spec.ts b/tests/playwright-test/playwright.trace.spec.ts index 7afbfb0a8770c..770dcb88bdfe9 100644 --- a/tests/playwright-test/playwright.trace.spec.ts +++ b/tests/playwright-test/playwright.trace.spec.ts @@ -1362,7 +1362,7 @@ test('should record default test timeout in trace', async ({ runInlineTest }, te expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip')); - expect(trace.model.timeout).toBe(30_000); + expect(trace.model.testTimeout).toBe(30_000); }); test('should record custom test timeout in trace', async ({ runInlineTest }, testInfo) => { @@ -1378,5 +1378,5 @@ test('should record custom test timeout in trace', async ({ runInlineTest }, tes expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); const trace = await parseTrace(testInfo.outputPath('test-results', 'a-pass', 'trace.zip')); - expect(trace.model.timeout).toBe(120_000); + expect(trace.model.testTimeout).toBe(120_000); });