diff --git a/README.md b/README.md
index f77b1f03..fd415585 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,69 @@ SelenideLogger.addListener("AllureSelenide", new AllureSelenide().enableLogs(Log
https://github.com/SeleniumHQ/selenium/wiki/Logging
```
-
+## Playwright Java
+
+AspectJ-based integration for Playwright Java that reports browser actions as Allure steps and attaches
+Playwright screenshots automatically:
+
+```xml
+
+ io.qameta.allure
+ allure-playwright
+ $LATEST_VERSION
+
+```
+
+Enable the AspectJ weaver for automatic action steps:
+```
+-javaagent:/path/to/aspectjweaver.jar
+```
+
+Usage example with Playwright Java JUnit fixtures:
+```java
+@UsePlaywright
+class UiTest {
+
+ @Test
+ void shouldOpenPage(Page page) {
+ page.navigate("https://playwright.dev");
+ page.screenshot();
+ }
+}
+```
+
+The module registers an Allure test lifecycle listener automatically, so per-test cleanup, failure diagnostics,
+and final trace/log flush work with any test framework that reports through Allure. Playwright pages and
+contexts are tracked by the AspectJ integration when they are created or used. Use
+`AllurePlaywright.register(...)` only for pages or contexts the aspect cannot observe.
+
+Frameworks or custom runners that do not use the Allure lifecycle can call the reporting hooks directly:
+```java
+AllurePlaywright.beforeTest();
+try {
+ testBody();
+} catch (Throwable e) {
+ AllurePlaywright.afterTestFailure(e);
+ throw e;
+} finally {
+ AllurePlaywright.afterTest();
+}
+```
+
+The following defaults can be overridden in `allure.properties`:
+```
+allure.playwright.steps.enabled=true
+allure.playwright.steps.mode=actions
+allure.playwright.parameters=redacted
+allure.playwright.screenshots.attach=true
+allure.playwright.failure.screenshot=true
+allure.playwright.failure.page-source=true
+allure.playwright.close.trace=true
+allure.playwright.close.video=true
+allure.playwright.close.page-logs=true
+```
+
+
## Rest Assured
Filter for rest-assured http client, that generates attachment for allure.
diff --git a/allure-model/src/main/java/io/qameta/allure/model/AttachmentType.java b/allure-model/src/main/java/io/qameta/allure/model/AttachmentType.java
new file mode 100644
index 00000000..806fb391
--- /dev/null
+++ b/allure-model/src/main/java/io/qameta/allure/model/AttachmentType.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016-2026 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.model;
+
+/**
+ * Common attachment media types and file extensions.
+ */
+public final class AttachmentType {
+
+ public static final AttachmentType PNG = new AttachmentType("image/png", "png");
+ public static final AttachmentType JPEG = new AttachmentType("image/jpeg", "jpg");
+ public static final AttachmentType TEXT = new AttachmentType("text/plain", "txt");
+ public static final AttachmentType HTML = new AttachmentType("text/html", "html");
+ public static final AttachmentType ZIP = new AttachmentType("application/zip", "zip");
+ public static final AttachmentType WEBM = new AttachmentType("video/webm", "webm");
+ public static final AttachmentType OCTET_STREAM = new AttachmentType("application/octet-stream", "");
+
+ private final String mediaType;
+ private final String extension;
+
+ private AttachmentType(final String mediaType, final String extension) {
+ this.mediaType = mediaType;
+ this.extension = extension;
+ }
+
+ public String getMediaType() {
+ return mediaType;
+ }
+
+ public String getExtension() {
+ return extension;
+ }
+}
diff --git a/allure-playwright/build.gradle.kts b/allure-playwright/build.gradle.kts
new file mode 100644
index 00000000..55bec923
--- /dev/null
+++ b/allure-playwright/build.gradle.kts
@@ -0,0 +1,36 @@
+description = "Allure Playwright Integration"
+
+val agent: Configuration by configurations.creating
+
+val playwrightVersion = "1.59.0"
+
+dependencies {
+ agent("org.aspectj:aspectjweaver")
+ api(project(":allure-java-commons"))
+ compileOnly("com.microsoft.playwright:playwright:$playwrightVersion")
+ compileOnly("org.aspectj:aspectjrt")
+ testAnnotationProcessor(project(":allure-descriptions-javadoc"))
+ testImplementation("com.microsoft.playwright:playwright:$playwrightVersion")
+ testImplementation("org.assertj:assertj-core")
+ testImplementation("org.junit.jupiter:junit-jupiter-api")
+ testImplementation("org.slf4j:slf4j-simple")
+ testImplementation(project(":allure-assertj"))
+ testImplementation(project(":allure-java-commons-test"))
+ testImplementation(project(":allure-junit-platform"))
+ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
+}
+
+tasks.jar {
+ manifest {
+ attributes(mapOf(
+ "Automatic-Module-Name" to "io.qameta.allure.playwright"
+ ))
+ }
+}
+
+tasks.test {
+ useJUnitPlatform()
+ doFirst {
+ jvmArgs("-javaagent:${agent.singleFile}")
+ }
+}
diff --git a/allure-playwright/src/main/java/io/qameta/allure/playwright/AllurePlaywright.java b/allure-playwright/src/main/java/io/qameta/allure/playwright/AllurePlaywright.java
new file mode 100644
index 00000000..ff5a9ad4
--- /dev/null
+++ b/allure-playwright/src/main/java/io/qameta/allure/playwright/AllurePlaywright.java
@@ -0,0 +1,571 @@
+/*
+ * Copyright 2016-2026 Qameta Software Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.qameta.allure.playwright;
+
+import com.microsoft.playwright.Browser;
+import com.microsoft.playwright.BrowserContext;
+import com.microsoft.playwright.ConsoleMessage;
+import com.microsoft.playwright.Page;
+import com.microsoft.playwright.Tracing;
+import com.microsoft.playwright.Video;
+import io.qameta.allure.Allure;
+import io.qameta.allure.AllureLifecycle;
+import io.qameta.allure.model.AttachmentType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Supplier;
+
+/**
+ * Utility methods for attaching Playwright Java diagnostics to the current Allure test.
+ */
+public final class AllurePlaywright {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AllurePlaywright.class);
+
+ private static final String SCREENSHOT = "Screenshot";
+ private static final String PAGE_SOURCE = "Page source";
+ private static final String TRACE = "Playwright trace";
+ private static final String VIDEO = "Playwright video";
+ private static final String CONSOLE_MESSAGES = "Console messages";
+ private static final String PAGE_ERRORS = "Page errors";
+
+ private static final ThreadLocal SUPPRESS_ASPECT = new ThreadLocal() {
+ @Override
+ protected Boolean initialValue() {
+ return Boolean.FALSE;
+ }
+ };
+
+ private AllurePlaywright() {
+ throw new IllegalStateException("Do not instance");
+ }
+
+ /**
+ * Clears Playwright reporting state before a test starts.
+ */
+ public static void beforeTest() {
+ clear();
+ }
+
+ /**
+ * Attaches final Playwright artifacts and clears reporting state after a test finishes.
+ */
+ public static void afterTest() {
+ attachRegisteredCloseArtifacts();
+ clear();
+ }
+
+ /**
+ * Attaches failure diagnostics for the current test.
+ */
+ public static void afterTestFailure() {
+ attachFailureArtifacts();
+ }
+
+ /**
+ * Attaches failure diagnostics for the current test.
+ *
+ * @param throwable the test failure.
+ */
+ public static void afterTestFailure(final Throwable throwable) {
+ LOGGER.debug("Attaching Playwright failure artifacts", throwable);
+ afterTestFailure();
+ }
+
+ /**
+ * Registers a Playwright page for failure diagnostics produced by reporting lifecycle hooks.
+ *
+ * @param page the page to register.
+ */
+ public static void register(final Page page) {
+ AllurePlaywrightRegistry.register(page);
+ }
+
+ /**
+ * Registers a Playwright browser context for failure diagnostics produced by reporting lifecycle hooks.
+ *
+ * @param context the browser context to register.
+ */
+ public static void register(final BrowserContext context) {
+ AllurePlaywrightRegistry.register(context);
+ }
+
+ /**
+ * Captures and attaches a page screenshot.
+ *
+ * @param name the attachment name.
+ * @param page the page to capture.
+ */
+ public static void attachScreenshot(final String name, final Page page) {
+ if (page == null || !hasAllureContext()) {
+ return;
+ }
+ try {
+ final byte[] screenshot = withAspectSuppressed(new Supplier() {
+ @Override
+ public byte[] get() {
+ return page.screenshot();
+ }
+ });
+ attachBytes(defaultName(name, SCREENSHOT), AttachmentType.PNG, screenshot);
+ } catch (RuntimeException e) {
+ LOGGER.warn("Could not capture Playwright screenshot", e);
+ }
+ }
+
+ /**
+ * Captures and attaches page source.
+ *
+ * @param name the attachment name.
+ * @param page the page to capture.
+ */
+ public static void attachPageSource(final String name, final Page page) {
+ if (page == null || !hasAllureContext()) {
+ return;
+ }
+ try {
+ final String content = withAspectSuppressed(new Supplier() {
+ @Override
+ public String get() {
+ return page.content();
+ }
+ });
+ attachBytes(defaultName(name, PAGE_SOURCE), AttachmentType.HTML, content.getBytes(StandardCharsets.UTF_8));
+ } catch (RuntimeException e) {
+ LOGGER.warn("Could not capture Playwright page source", e);
+ }
+ }
+
+ /**
+ * Captures and attaches Playwright console messages retained by the page.
+ *
+ * @param name the attachment name.
+ * @param page the page to capture.
+ */
+ public static void attachConsoleMessages(final String name, final Page page) {
+ if (page == null || !hasAllureContext()) {
+ return;
+ }
+ try {
+ final String content = formatConsoleMessages(page.consoleMessages());
+ attachText(defaultName(name, CONSOLE_MESSAGES), content);
+ } catch (RuntimeException e) {
+ LOGGER.warn("Could not capture Playwright console messages", e);
+ }
+ }
+
+ /**
+ * Captures and attaches Playwright page errors retained by the page.
+ *
+ * @param name the attachment name.
+ * @param page the page to capture.
+ */
+ public static void attachPageErrors(final String name, final Page page) {
+ if (page == null || !hasAllureContext()) {
+ return;
+ }
+ try {
+ final String content = formatLines(page.pageErrors());
+ attachText(defaultName(name, PAGE_ERRORS), content);
+ } catch (RuntimeException e) {
+ LOGGER.warn("Could not capture Playwright page errors", e);
+ }
+ }
+
+ /**
+ * Attaches a Playwright trace archive.
+ *
+ * @param name the attachment name.
+ * @param traceZip the path to the trace zip file.
+ */
+ public static void attachTrace(final String name, final Path traceZip) {
+ attachPath(defaultName(name, TRACE), AttachmentType.ZIP, traceZip);
+ }
+
+ /**
+ * Attaches a Playwright video file.
+ *
+ * @param name the attachment name.
+ * @param videoFile the path to the video file.
+ */
+ public static void attachVideo(final String name, final Path videoFile) {
+ attachPath(defaultName(name, VIDEO), videoType(videoFile), videoFile);
+ }
+
+ /**
+ * Starts Playwright tracing and registers the trace for failure diagnostics.
+ *
+ * @param context the browser context to trace.
+ * @return trace session that stops tracing and attaches the generated archive when closed.
+ */
+ public static TraceSession startTracing(final BrowserContext context) {
+ return startTracing(TRACE, context);
+ }
+
+ /**
+ * Starts Playwright tracing and registers the trace for failure diagnostics.
+ *
+ * @param name the attachment name to use when the trace is attached.
+ * @param context the browser context to trace.
+ * @return trace session that stops tracing and attaches the generated archive when closed.
+ */
+ public static TraceSession startTracing(final String name, final BrowserContext context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+ final Tracing.StartOptions options = new Tracing.StartOptions()
+ .setScreenshots(true)
+ .setSnapshots(true);
+ context.tracing().start(options);
+ final DefaultTraceSession traceSession = new DefaultTraceSession(context, defaultName(name, TRACE));
+ AllurePlaywrightRegistry.register(context);
+ AllurePlaywrightRegistry.register(traceSession);
+ return traceSession;
+ }
+
+ static CloseArtifacts beforeClose(final Object target) {
+ final CloseArtifacts closeArtifacts = new CloseArtifacts();
+ if (target instanceof Page) {
+ collectPageCloseArtifacts((Page) target, closeArtifacts);
+ } else if (target instanceof BrowserContext) {
+ collectContextCloseArtifacts((BrowserContext) target, closeArtifacts);
+ } else if (target instanceof Browser) {
+ collectBrowserCloseArtifacts((Browser) target, closeArtifacts);
+ }
+ return closeArtifacts;
+ }
+
+ static void attachScreenshotBytes(final String name, final AttachmentType type, final byte[] bytes) {
+ if (!AllurePlaywrightConfig.shouldAttachScreenshots()) {
+ return;
+ }
+ attachBytes(defaultName(name, SCREENSHOT), type, bytes);
+ }
+
+ static AttachmentType screenshotType(final Object... args) {
+ final Object options = args.length == 0 ? null : args[0];
+ if (isJpegType(options)) {
+ return AttachmentType.JPEG;
+ }
+ if (isJpegPath(options)) {
+ return AttachmentType.JPEG;
+ }
+ return AttachmentType.PNG;
+ }
+
+ static boolean hasAllureContext() {
+ return Allure.getLifecycle().getCurrentTestCaseOrStep().isPresent();
+ }
+
+ static boolean isAspectSuppressed() {
+ return SUPPRESS_ASPECT.get();
+ }
+
+ static T withAspectSuppressed(final Supplier supplier) {
+ final Boolean previous = SUPPRESS_ASPECT.get();
+ SUPPRESS_ASPECT.set(Boolean.TRUE);
+ try {
+ return supplier.get();
+ } finally {
+ SUPPRESS_ASPECT.set(previous);
+ }
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ static void clear() {
+ for (DefaultTraceSession traceSession : AllurePlaywrightRegistry.getTraceSessions()) {
+ traceSession.stopWithoutAttachment();
+ }
+ AllurePlaywrightRegistry.clear();
+ }
+
+ static void attachFailureArtifacts() {
+ if (!AllurePlaywrightRegistry.markFailureArtifactsAttached()) {
+ return;
+ }
+ if (AllurePlaywrightConfig.shouldAttachFailureScreenshot()) {
+ attachFailureScreenshots();
+ }
+ if (AllurePlaywrightConfig.shouldAttachFailurePageSource()) {
+ attachFailurePageSources();
+ }
+ attachFailureTraces();
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ static void attachRegisteredCloseArtifacts() {
+ if (!hasAllureContext()) {
+ return;
+ }
+ if (AllurePlaywrightConfig.shouldAttachClosePageLogs()) {
+ for (Page page : AllurePlaywrightRegistry.getPages()) {
+ attachPageCloseLogs(page);
+ }
+ }
+ if (AllurePlaywrightConfig.shouldAttachCloseTrace()) {
+ attachFailureTraces();
+ }
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ private static void collectBrowserCloseArtifacts(final Browser browser, final CloseArtifacts closeArtifacts) {
+ for (BrowserContext context : contexts(browser)) {
+ collectContextCloseArtifacts(context, closeArtifacts);
+ }
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ private static void collectContextCloseArtifacts(final BrowserContext context,
+ final CloseArtifacts closeArtifacts) {
+ register(context);
+ for (Page page : pages(context)) {
+ collectPageCloseArtifacts(page, closeArtifacts);
+ }
+ if (AllurePlaywrightConfig.shouldAttachCloseTrace()) {
+ attachCloseTraces(context);
+ }
+ }
+
+ private static void collectPageCloseArtifacts(final Page page, final CloseArtifacts closeArtifacts) {
+ register(page);
+ attachPageCloseLogs(page);
+ if (AllurePlaywrightRegistry.markCloseVideoAttached(page)) {
+ closeArtifacts.addVideo(video(page));
+ }
+ }
+
+ private static void attachPageCloseLogs(final Page page) {
+ if (AllurePlaywrightConfig.shouldAttachClosePageLogs()
+ && AllurePlaywrightRegistry.markClosePageLogsAttached(page)) {
+ attachConsoleMessages(CONSOLE_MESSAGES, page);
+ attachPageErrors(PAGE_ERRORS, page);
+ }
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ private static void attachCloseTraces(final BrowserContext context) {
+ for (DefaultTraceSession traceSession : AllurePlaywrightRegistry.getTraceSessions(context)) {
+ traceSession.attach();
+ }
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ private static void attachFailureScreenshots() {
+ for (Page page : AllurePlaywrightRegistry.getPages()) {
+ attachScreenshot(SCREENSHOT, page);
+ }
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ private static void attachFailurePageSources() {
+ for (Page page : AllurePlaywrightRegistry.getPages()) {
+ attachPageSource(PAGE_SOURCE, page);
+ }
+ }
+
+ @SuppressWarnings("PMD.CloseResource")
+ private static void attachFailureTraces() {
+ for (DefaultTraceSession traceSession : AllurePlaywrightRegistry.getTraceSessions()) {
+ traceSession.attach();
+ }
+ }
+
+ private static List contexts(final Browser browser) {
+ try {
+ return browser.contexts();
+ } catch (RuntimeException e) {
+ LOGGER.warn("Could not collect Playwright browser contexts", e);
+ return Collections.emptyList();
+ }
+ }
+
+ private static List pages(final BrowserContext context) {
+ try {
+ return context.pages();
+ } catch (RuntimeException e) {
+ LOGGER.warn("Could not collect Playwright pages", e);
+ return Collections.emptyList();
+ }
+ }
+
+ private static Video video(final Page page) {
+ try {
+ return page.video();
+ } catch (RuntimeException e) {
+ LOGGER.warn("Could not collect Playwright video", e);
+ return null;
+ }
+ }
+
+ private static AttachmentType videoType(final Path path) {
+ if (path == null) {
+ return AttachmentType.WEBM;
+ }
+ final String name = path.getFileName().toString().toLowerCase(Locale.ROOT);
+ if (name.endsWith(".webm")) {
+ return AttachmentType.WEBM;
+ }
+ return AttachmentType.OCTET_STREAM;
+ }
+
+ private static void attachPath(final String name, final AttachmentType type, final Path path) {
+ if (path == null || !hasAllureContext() || !Files.isRegularFile(path)) {
+ return;
+ }
+ try {
+ attachBytes(name, type, Files.readAllBytes(path));
+ } catch (IOException e) {
+ LOGGER.warn("Could not attach Playwright artifact {}", path, e);
+ }
+ }
+
+ private static void attachBytes(final String name, final AttachmentType type, final byte[] bytes) {
+ if (bytes == null || !hasAllureContext()) {
+ return;
+ }
+ final AllureLifecycle lifecycle = Allure.getLifecycle();
+ lifecycle.addAttachment(name, type.getMediaType(), type.getExtension(), bytes);
+ }
+
+ private static void attachText(final String name, final String content) {
+ if (content == null || content.isEmpty()) {
+ return;
+ }
+ attachBytes(name, AttachmentType.TEXT, content.getBytes(StandardCharsets.UTF_8));
+ }
+
+ private static String defaultName(final String name, final String fallback) {
+ return name == null || name.isEmpty() ? fallback : name;
+ }
+
+ private static boolean isJpegType(final Object options) {
+ final Object type = readField(options, "type");
+ return type != null && "JPEG".equals(type.toString());
+ }
+
+ private static boolean isJpegPath(final Object options) {
+ final Object path = readField(options, "path");
+ if (!(path instanceof Path)) {
+ return false;
+ }
+ final String name = ((Path) path).getFileName().toString().toLowerCase(Locale.ROOT);
+ return name.endsWith(".jpg") || name.endsWith(".jpeg");
+ }
+
+ private static Object readField(final Object target, final String name) {
+ if (target == null) {
+ return null;
+ }
+ try {
+ final Field field = target.getClass().getField(name);
+ return field.get(target);
+ } catch (ReflectiveOperationException ignored) {
+ return null;
+ }
+ }
+
+ private static String formatConsoleMessages(final List messages) {
+ if (messages == null || messages.isEmpty()) {
+ return "";
+ }
+ final StringBuilder builder = new StringBuilder();
+ for (ConsoleMessage message : messages) {
+ final String type = safe(new Supplier() {
+ @Override
+ public String get() {
+ return message.type();
+ }
+ });
+ final String text = safe(new Supplier() {
+ @Override
+ public String get() {
+ return message.text();
+ }
+ });
+ final String location = safe(new Supplier() {
+ @Override
+ public String get() {
+ return message.location();
+ }
+ });
+ builder.append('[').append(type).append("] ").append(text);
+ if (!location.isEmpty()) {
+ builder.append(" (").append(location).append(')');
+ }
+ builder.append(System.lineSeparator());
+ }
+ return builder.toString();
+ }
+
+ private static String formatLines(final List lines) {
+ if (lines == null || lines.isEmpty()) {
+ return "";
+ }
+ final StringBuilder builder = new StringBuilder();
+ for (String line : lines) {
+ builder.append(line).append(System.lineSeparator());
+ }
+ return builder.toString();
+ }
+
+ private static String safe(final Supplier supplier) {
+ try {
+ final String value = supplier.get();
+ return value == null ? "" : value;
+ } catch (RuntimeException ignored) {
+ return "";
+ }
+ }
+
+ static final class CloseArtifacts {
+
+ private final List