diff --git a/playwright/src/main/java/com/microsoft/playwright/Video.java b/playwright/src/main/java/com/microsoft/playwright/Video.java index a6720a57..6ccc0d78 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Video.java +++ b/playwright/src/main/java/com/microsoft/playwright/Video.java @@ -16,6 +16,7 @@ package com.microsoft.playwright; +import java.nio.file.Path; import java.util.*; /** @@ -26,6 +27,6 @@ public interface Video { /** * Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem upon closing the browser context. */ - String path(); + Path path(); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java index b7792b19..0b410b61 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserContextImpl.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -43,8 +44,9 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext { PageImpl ownerPage; private final ListenerCollection listeners = new ListenerCollection<>(); final TimeoutSettings timeoutSettings = new TimeoutSettings(); + Path videosDir; - protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { + BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); if (parent instanceof BrowserImpl) { browser = (BrowserImpl) parent; diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java index 1bd0d107..f875aa42 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserImpl.java @@ -88,6 +88,9 @@ class BrowserImpl extends ChannelOwner implements Browser { JsonObject params = gson().toJsonTree(options).getAsJsonObject(); JsonElement result = sendMessage("newContext", params); BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString()); + if (options.recordVideo != null) { + context.videosDir = options.recordVideo.dir; + } contexts.add(context); return context; } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java index 8ae66c83..ef60620b 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/BrowserTypeImpl.java @@ -47,14 +47,18 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType { @Override - public BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) { + public BrowserContextImpl launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) { if (options == null) { options = new LaunchPersistentContextOptions(); } JsonObject params = gson().toJsonTree(options).getAsJsonObject(); params.addProperty("userDataDir", userDataDir.toString()); JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject(); - return connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString()); + BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString()); + if (options.recordVideo != null) { + context.videosDir = options.recordVideo.dir; + } + return context; } public String name() { 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 c6b62e89..b088994e 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -49,6 +49,7 @@ public class PageImpl extends ChannelOwner implements Page { private boolean isClosed; final Set workers = new HashSet<>(); private final TimeoutSettings timeoutSettings; + private VideoImpl video; PageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) { super(parent, type, guid, initializer); @@ -168,6 +169,8 @@ public class PageImpl extends ChannelOwner implements Page { if (!handled) { route.continue_(); } + } else if ("video".equals(event)) { + video().setRelativePath(params.get("relativePath").getAsString()); } else if ("pageError".equals(event)) { SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class); listeners.notify(EventType.PAGEERROR, new ErrorImpl(error)); @@ -296,7 +299,7 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public BrowserContext context() { + public BrowserContextImpl context() { return browserContext; } @@ -705,8 +708,19 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public Video video() { - return null; + public VideoImpl video() { + if (video != null) { + return video; + } + if (browserContext.videosDir == null) { + return null; + } + video = new VideoImpl(this); + // In case of persistent profile, we already have it. + if (initializer.has("videoRelativePath")) { + video.setRelativePath(initializer.get("videoRelativePath").getAsString()); + } + return video; } @Override diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/VideoImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/VideoImpl.java new file mode 100644 index 00000000..69ed8c6e --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/impl/VideoImpl.java @@ -0,0 +1,42 @@ +/* + * 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.microsoft.playwright.Video; + +import java.nio.file.Path; + +class VideoImpl implements Video { + private final PageImpl page; + private Path fullPath; + + VideoImpl(PageImpl page) { + this.page = page; + } + + void setRelativePath(String path) { + fullPath = page.context().videosDir.resolve(path); + } + + @Override + public Path path() { + while (fullPath == null) { + page.connection.processOneMessage(); + } + return fullPath; + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestScreencast.java b/playwright/src/test/java/com/microsoft/playwright/TestScreencast.java new file mode 100644 index 00000000..3c07e138 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestScreencast.java @@ -0,0 +1,40 @@ +/* + * 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.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestScreencast extends TestBase { + @Test + void shouldExposeVideoPath(@TempDir Path videosDir) { + BrowserContext context = browser.newContext(new Browser.NewContextOptions() + .setRecordVideo().withDir(videosDir).withSize(320, 240).done() + .withViewport(320, 240)); + Page page = context.newPage(); + page.evaluate("() => document.body.style.backgroundColor = 'red'"); + Path path = page.video().path(); + assertTrue(path.startsWith(videosDir)); + context.close(); + assertTrue(Files.exists(path)); + }; +} diff --git a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index fef06f0f..2c50acd4 100644 --- a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -801,7 +801,7 @@ class Interface extends TypeDefinition { if ("Download".equals(jsonName)) { output.add("import java.io.InputStream;"); } - if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserContext", "BrowserType", "Download", "Route", "Selectors").contains(jsonName)) { + if (asList("Page", "Frame", "ElementHandle", "FileChooser", "Browser", "BrowserContext", "BrowserType", "Download", "Route", "Selectors", "Video").contains(jsonName)) { output.add("import java.nio.file.Path;"); } output.add("import java.util.*;"); diff --git a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java index fb73548a..54a927d6 100644 --- a/tools/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java +++ b/tools/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java @@ -121,6 +121,7 @@ class Types { add("BrowserType.launch.options.downloadsPath", "string", "Path"); add("BrowserContext.storageState.options.path", "string", "Path"); add("ChromiumBrowser.startTracing.options.path", "string", "Path"); + add("Video.path", "Promise", "Path"); // Route add("BrowserContext.route.handler", "function(Route, Request)", "Consumer");