diff --git a/playwright/src/main/java/com/microsoft/playwright/Coverage.java b/playwright/src/main/java/com/microsoft/playwright/Coverage.java
new file mode 100644
index 000000000..2803eb9e5
--- /dev/null
+++ b/playwright/src/main/java/com/microsoft/playwright/Coverage.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ *
+ * 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 com.microsoft.playwright;
+
+import java.util.List;
+
+/**
+ * Coverage gathers information about parts of JavaScript and CSS that were used by the page.
+ *
+ *
Coverage APIs are only supported on Chromium-based browsers.
+ */
+public interface Coverage {
+ class StartJSCoverageOptions {
+ /**
+ * Whether anonymous scripts generated by the page should be reported. Defaults to {@code false}.
+ */
+ public Boolean reportAnonymousScripts;
+ /**
+ * Whether to reset coverage on every navigation. Defaults to {@code true}.
+ */
+ public Boolean resetOnNavigation;
+
+ public StartJSCoverageOptions setReportAnonymousScripts(boolean reportAnonymousScripts) {
+ this.reportAnonymousScripts = reportAnonymousScripts;
+ return this;
+ }
+
+ public StartJSCoverageOptions setResetOnNavigation(boolean resetOnNavigation) {
+ this.resetOnNavigation = resetOnNavigation;
+ return this;
+ }
+ }
+
+ class StartCSSCoverageOptions {
+ /**
+ * Whether to reset coverage on every navigation. Defaults to {@code true}.
+ */
+ public Boolean resetOnNavigation;
+
+ public StartCSSCoverageOptions setResetOnNavigation(boolean resetOnNavigation) {
+ this.resetOnNavigation = resetOnNavigation;
+ return this;
+ }
+ }
+
+ class ScriptCoverage {
+ /** Script URL. */
+ public String url;
+ /** Script ID. */
+ public String scriptId;
+ /** Script content, if applicable. */
+ public String source;
+ /** V8-specific function coverage. */
+ public List functions;
+ }
+
+ class FunctionCoverage {
+ public String functionName;
+ public boolean isBlockCoverage;
+ public List ranges;
+ }
+
+ class FunctionCoverageRange {
+ public int startOffset;
+ public int endOffset;
+ public int count;
+ }
+
+ class StyleSheetCoverage {
+ /** StyleSheet URL. */
+ public String url;
+ /** StyleSheet content, if available. */
+ public String text;
+ /** StyleSheet ranges that were used, sorted and non-overlapping. */
+ public List ranges;
+ }
+
+ class StyleSheetCoverageRange {
+ public int start;
+ public int end;
+ }
+
+ /**
+ * Starts JavaScript coverage with default options.
+ */
+ default void startJSCoverage() {
+ startJSCoverage(null);
+ }
+
+ /**
+ * Starts JavaScript coverage.
+ *
+ * Anonymous scripts do not have an associated URL and are created dynamically using {@code eval} or {@code new
+ * Function}. When {@link StartJSCoverageOptions#setReportAnonymousScripts(boolean)} is enabled, anonymous scripts are
+ * reported with an empty URL.
+ */
+ void startJSCoverage(StartJSCoverageOptions options);
+
+ /**
+ * Stops JavaScript coverage and returns reports for all scripts.
+ */
+ List stopJSCoverage();
+
+ /**
+ * Starts CSS coverage with default options.
+ */
+ default void startCSSCoverage() {
+ startCSSCoverage(null);
+ }
+
+ /**
+ * Starts CSS coverage.
+ */
+ void startCSSCoverage(StartCSSCoverageOptions options);
+
+ /**
+ * Stops CSS coverage and returns reports for all stylesheets.
+ *
+ * CSS coverage does not include dynamically injected style tags without source URLs.
+ */
+ List stopCSSCoverage();
+}
diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java
index db240171f..05c6a741c 100644
--- a/playwright/src/main/java/com/microsoft/playwright/Page.java
+++ b/playwright/src/main/java/com/microsoft/playwright/Page.java
@@ -3874,6 +3874,12 @@ public WaitForWorkerOptions setTimeout(double timeout) {
* @since v1.45
*/
Clock clock();
+ /**
+ * Browser-specific coverage implementation.
+ *
+ * Coverage APIs are only supported on Chromium-based browsers.
+ */
+ Coverage coverage();
/**
* Adds a script which would be evaluated in one of the following scenarios:
*
@@ -8721,4 +8727,3 @@ default Worker waitForWorker(Runnable callback) {
*/
List workers();
}
-
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/CoverageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/CoverageImpl.java
new file mode 100644
index 000000000..fc4570455
--- /dev/null
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/CoverageImpl.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ *
+ * 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 com.microsoft.playwright.impl;
+
+import com.google.gson.JsonObject;
+import com.microsoft.playwright.Coverage;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
+import static com.microsoft.playwright.impl.Serialization.gson;
+
+class CoverageImpl implements Coverage {
+ private final PageImpl page;
+
+ CoverageImpl(PageImpl page) {
+ this.page = page;
+ }
+
+ @Override
+ public void startJSCoverage(StartJSCoverageOptions options) {
+ JsonObject params = gson().toJsonTree(options == null ? new StartJSCoverageOptions() : options).getAsJsonObject();
+ page.sendMessage("startJSCoverage", params, NO_TIMEOUT);
+ }
+
+ @Override
+ public List stopJSCoverage() {
+ JsonObject result = page.sendMessage("stopJSCoverage", new JsonObject(), NO_TIMEOUT).getAsJsonObject();
+ return Arrays.asList(gson().fromJson(result.getAsJsonArray("entries"), ScriptCoverage[].class));
+ }
+
+ @Override
+ public void startCSSCoverage(StartCSSCoverageOptions options) {
+ JsonObject params = gson().toJsonTree(options == null ? new StartCSSCoverageOptions() : options).getAsJsonObject();
+ page.sendMessage("startCSSCoverage", params, NO_TIMEOUT);
+ }
+
+ @Override
+ public List stopCSSCoverage() {
+ JsonObject result = page.sendMessage("stopCSSCoverage", new JsonObject(), NO_TIMEOUT).getAsJsonObject();
+ return Arrays.asList(gson().fromJson(result.getAsJsonArray("entries"), StyleSheetCoverage[].class));
+ }
+}
diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
index 4956ea623..c1cf8c171 100644
--- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
+++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java
@@ -47,6 +47,7 @@ public class PageImpl extends ChannelOwner implements Page {
private final MouseImpl mouse;
private final TouchscreenImpl touchscreen;
private final ScreencastImpl screencast;
+ private final CoverageImpl coverage;
private final WebStorageImpl localStorage;
private final WebStorageImpl sessionStorage;
final Waitable> waitableClosedOrCrashed;
@@ -139,6 +140,7 @@ enum EventType {
mouse = new MouseImpl(this);
touchscreen = new TouchscreenImpl(this);
screencast = new ScreencastImpl(this);
+ coverage = new CoverageImpl(this);
localStorage = new WebStorageImpl(this, "local");
sessionStorage = new WebStorageImpl(this, "session");
frames.add(mainFrame);
@@ -457,6 +459,11 @@ public ClockImpl clock() {
return browserContext.clock();
}
+ @Override
+ public Coverage coverage() {
+ return coverage;
+ }
+
@Override
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
return withWaitLogging("Page.waitForClose", logger -> waitForCloseImpl(options, code));
diff --git a/playwright/src/test/java/com/microsoft/playwright/TestCoverage.java b/playwright/src/test/java/com/microsoft/playwright/TestCoverage.java
new file mode 100644
index 000000000..bebf39bc0
--- /dev/null
+++ b/playwright/src/test/java/com/microsoft/playwright/TestCoverage.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) Microsoft Corporation.
+ *
+ * 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 com.microsoft.playwright;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+public class TestCoverage extends TestBase {
+ @BeforeEach
+ void onlyChromium() {
+ assumeTrue(isChromium());
+ }
+
+ @Test
+ void shouldCollectJavaScriptCoverage() {
+ page.coverage().startJSCoverage();
+ page.evaluate("() => eval('function foo() { return 42; } foo(); //# sourceURL=nice-name.js')");
+ List coverage = page.coverage().stopJSCoverage();
+
+ assertEquals(1, coverage.size());
+ assertEquals("nice-name.js", coverage.get(0).url);
+ Coverage.FunctionCoverage function = coverage.get(0).functions.stream()
+ .filter(entry -> "foo".equals(entry.functionName))
+ .findFirst()
+ .orElseThrow(AssertionError::new);
+ assertEquals(1, function.ranges.get(0).count);
+ }
+
+ @Test
+ void shouldReportAnonymousScriptsWhenEnabled() {
+ page.coverage().startJSCoverage(new Coverage.StartJSCoverageOptions().setReportAnonymousScripts(true));
+ page.evaluate("() => eval('2 + 2')");
+ List coverage = page.coverage().stopJSCoverage();
+
+ assertTrue(coverage.stream().anyMatch(entry -> "2 + 2".equals(entry.source)));
+ }
+
+ @Test
+ void shouldResetJavaScriptCoverageOnNavigation() {
+ page.coverage().startJSCoverage();
+ page.evaluate("() => eval('2 + 2 //# sourceURL=before-navigation.js')");
+ page.navigate(server.EMPTY_PAGE);
+
+ assertTrue(page.coverage().stopJSCoverage().isEmpty());
+ }
+
+ @Test
+ void shouldCollectCSSCoverage() {
+ page.coverage().startCSSCoverage();
+ page.setContent("hello
");
+ List coverage = page.coverage().stopCSSCoverage();
+
+ assertEquals(1, coverage.size());
+ assertFalse(coverage.get(0).ranges.isEmpty());
+ Coverage.StyleSheetCoverageRange range = coverage.get(0).ranges.get(0);
+ assertEquals("div { color: green; }", coverage.get(0).text.substring(range.start, range.end));
+ }
+}