diff --git a/docs/src/test-reporter-api/class-teststep.md b/docs/src/test-reporter-api/class-teststep.md index 43b8474abe..4423606498 100644 --- a/docs/src/test-reporter-api/class-teststep.md +++ b/docs/src/test-reporter-api/class-teststep.md @@ -50,6 +50,16 @@ Start time of this particular test step. List of steps inside this step. +## property: TestStep.attachments +* since: v1.10 +- type: <[Array]<[Object]>> + - `name` <[string]> Attachment name. + - `contentType` <[string]> Content type of this attachment to properly present in the report, for example `'application/json'` or `'image/png'`. + - `path` ?<[string]> Optional path on the filesystem to the attached file. + - `body` ?<[Buffer]> Optional attachment body used instead of a file. + +The list of files or buffers attached in the step execution through [`property: TestInfo.attachments`]. + ## property: TestStep.title * since: v1.10 - type: <[string]> diff --git a/packages/playwright/src/common/ipc.ts b/packages/playwright/src/common/ipc.ts index 5ce1991f65..8daa380902 100644 --- a/packages/playwright/src/common/ipc.ts +++ b/packages/playwright/src/common/ipc.ts @@ -72,6 +72,7 @@ export type AttachmentPayload = { path?: string; body?: string; contentType: string; + stepId?: string; }; export type TestEndPayload = { diff --git a/packages/playwright/src/isomorphic/teleReceiver.ts b/packages/playwright/src/isomorphic/teleReceiver.ts index 0c4408096d..29435df439 100644 --- a/packages/playwright/src/isomorphic/teleReceiver.ts +++ b/packages/playwright/src/isomorphic/teleReceiver.ts @@ -512,6 +512,7 @@ class TeleTestStep implements reporterTypes.TestStep { parent: reporterTypes.TestStep | undefined; duration: number = -1; steps: reporterTypes.TestStep[] = []; + attachments = []; private _startTime: number = 0; diff --git a/packages/playwright/src/runner/dispatcher.ts b/packages/playwright/src/runner/dispatcher.ts index 4e971f2475..ccb71a0ce5 100644 --- a/packages/playwright/src/runner/dispatcher.ts +++ b/packages/playwright/src/runner/dispatcher.ts @@ -319,6 +319,7 @@ class JobDispatcher { startTime: new Date(params.wallTime), duration: -1, steps: [], + attachments: [], location: params.location, }; steps.set(params.stepId, step); @@ -358,6 +359,7 @@ class JobDispatcher { body: params.body !== undefined ? Buffer.from(params.body, 'base64') : undefined }; data.result.attachments.push(attachment); + data.steps.get(params.stepId ?? '')?.attachments.push(attachment); } private _failTestWithErrors(test: TestCase, errors: TestError[]) { diff --git a/packages/playwright/src/worker/testInfo.ts b/packages/playwright/src/worker/testInfo.ts index b3fe2172ac..85310db92e 100644 --- a/packages/playwright/src/worker/testInfo.ts +++ b/packages/playwright/src/worker/testInfo.ts @@ -237,20 +237,21 @@ export class TestInfoImpl implements TestInfo { } } + _parentStep(isStage?: boolean) { + if (isStage) { + // Predefined stages form a fixed hierarchy - use the current one as parent. + return this._findLastStageStep(this._steps); + } + return ( + zones.zoneData('stepZone') + ?? this._findLastStageStep(this._steps) // If no parent step on stack, assume the current stage as parent. + ); + } + _addStep(data: Omit): TestStepInternal { const stepId = `${data.category}@${++this._lastStepId}`; - let parentStep: TestStepInternal | undefined; - if (data.isStage) { - // Predefined stages form a fixed hierarchy - use the current one as parent. - parentStep = this._findLastStageStep(this._steps); - } else { - parentStep = zones.zoneData('stepZone'); - if (!parentStep) { - // If no parent step on stack, assume the current stage as parent. - parentStep = this._findLastStageStep(this._steps); - } - } + const parentStep = this._parentStep(data.isStage); const filteredStack = filteredStackTrace(captureRawStack()); data.boxedStack = parentStep?.boxedStack; @@ -414,7 +415,8 @@ export class TestInfoImpl implements TestInfo { name: attachment.name, contentType: attachment.contentType, path: attachment.path, - body: attachment.body?.toString('base64') + body: attachment.body?.toString('base64'), + stepId: this._parentStep()?.stepId, }); } diff --git a/packages/playwright/types/testReporter.d.ts b/packages/playwright/types/testReporter.d.ts index a9d1f020ae..936615d1fb 100644 --- a/packages/playwright/types/testReporter.d.ts +++ b/packages/playwright/types/testReporter.d.ts @@ -684,6 +684,33 @@ export interface TestStep { */ titlePath(): Array; + /** + * The list of files or buffers attached in the step execution through + * [testInfo.attachments](https://playwright.dev/docs/api/class-testinfo#test-info-attachments). + */ + attachments: Array<{ + /** + * Attachment name. + */ + name: string; + + /** + * Content type of this attachment to properly present in the report, for example `'application/json'` or + * `'image/png'`. + */ + contentType: string; + + /** + * Optional path on the filesystem to the attached file. + */ + path?: string; + + /** + * Optional attachment body used instead of a file. + */ + body?: Buffer; + }>; + /** * Step category to differentiate steps with different origin and verbosity. Built-in categories are: * - `hook` for fixtures and hooks initialization and teardown