diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java index 35ced21c..055691a1 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java @@ -23,8 +23,6 @@ import com.google.gson.JsonObject; import java.io.*; import java.nio.file.FileSystems; import java.util.*; -import java.util.List; -import java.util.function.Predicate; import java.util.stream.Collectors; import static java.util.Arrays.asList; @@ -501,6 +499,11 @@ class Field extends Element { output.add(offset + "public byte[] bodyBytes;"); return; } + if (asList("Page.emulateMedia.options.media", + "Page.emulateMedia.options.colorScheme").contains(jsonPath)) { + output.add(offset + access + "Optional<" + type.toJava() + "> " + name + ";"); + return; + } output.add(offset + access + type.toJava() + " " + name + ";"); } @@ -557,7 +560,14 @@ class Field extends Element { output.add(offset + "}"); return; } - + if (asList("Page.emulateMedia.options.media", + "Page.emulateMedia.options.colorScheme").contains(jsonPath)) { + output.add(offset + "public " + parentClass + " with" + toTitle(name) + "(" + type.toJava() + " " + name + ") {"); + output.add(offset + " this." + name + " = Optional.ofNullable(" + name + ");"); + output.add(offset + " return this;"); + output.add(offset + "}"); + return; + } if ("Route.continue.overrides.postData".equals(jsonPath)) { output.add(offset + "public ContinueOverrides withPostData(String postData) {"); output.add(offset + " this.postData = postData.getBytes(StandardCharsets.UTF_8);"); diff --git a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java index 6f6b04b1..7b83fb7a 100644 --- a/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java +++ b/api-generator/src/main/java/com/microsoft/playwright/tools/Types.java @@ -53,14 +53,14 @@ class Types { add("Logger.log.severity", "\"error\"|\"info\"|\"verbose\"|\"warning\"", "Severity"); // Option enums - add("Browser.newContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); - add("Browser.newPage.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); + add("Browser.newContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty()); + add("Browser.newPage.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty()); add("Page.click.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty()); add("Page.click.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set", new Empty()); add("Page.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty()); add("Page.dblclick.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set", new Empty()); add("Page.emulateMedia.options.media", "null|\"print\"|\"screen\"", "Media"); - add("Page.emulateMedia.options.colorScheme", "null|\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); + add("Page.emulateMedia.options.colorScheme", "null|\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty()); add("Page.goBack.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty()); add("Page.goForward.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty()); add("Page.goto.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty()); @@ -92,7 +92,7 @@ class Types { add("Mouse.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty()); add("Mouse.down.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty()); add("Mouse.up.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty()); - add("BrowserType.launchPersistentContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme"); + add("BrowserType.launchPersistentContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty()); // File add("Page.addScriptTag.options.path", "string", "Path"); diff --git a/playwright/src/main/java/com/microsoft/playwright/Browser.java b/playwright/src/main/java/com/microsoft/playwright/Browser.java index 3a422ba0..0a17b3dd 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Browser.java +++ b/playwright/src/main/java/com/microsoft/playwright/Browser.java @@ -26,7 +26,6 @@ public interface Browser { void addListener(EventType type, Listener listener); void removeListener(EventType type, Listener listener); class NewContextOptions { - public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE } public class VideoSize { public int width; public int height; @@ -149,7 +148,6 @@ public interface Browser { } } class NewPageOptions { - public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE } public class VideoSize { public int width; public int height; diff --git a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java index fc56cdad..25195f3d 100644 --- a/playwright/src/main/java/com/microsoft/playwright/BrowserType.java +++ b/playwright/src/main/java/com/microsoft/playwright/BrowserType.java @@ -155,7 +155,6 @@ public interface BrowserType { } } class LaunchPersistentContextOptions { - public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE } public class Proxy { public String server; public String bypass; diff --git a/playwright/src/main/java/com/microsoft/playwright/ColorScheme.java b/playwright/src/main/java/com/microsoft/playwright/ColorScheme.java new file mode 100644 index 00000000..8b6bbfe6 --- /dev/null +++ b/playwright/src/main/java/com/microsoft/playwright/ColorScheme.java @@ -0,0 +1,3 @@ +package com.microsoft.playwright; + +public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE } diff --git a/playwright/src/main/java/com/microsoft/playwright/Page.java b/playwright/src/main/java/com/microsoft/playwright/Page.java index 90322a4e..83a93148 100644 --- a/playwright/src/main/java/com/microsoft/playwright/Page.java +++ b/playwright/src/main/java/com/microsoft/playwright/Page.java @@ -262,16 +262,15 @@ public interface Page { } class EmulateMediaOptions { public enum Media { PRINT, SCREEN } - public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE } - public Media media; - public ColorScheme colorScheme; + public Optional media; + public Optional colorScheme; public EmulateMediaOptions withMedia(Media media) { - this.media = media; + this.media = Optional.ofNullable(media); return this; } public EmulateMediaOptions withColorScheme(ColorScheme colorScheme) { - this.colorScheme = colorScheme; + this.colorScheme = Optional.ofNullable(colorScheme); return this; } } 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 9637f9d8..9d82c917 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -16,13 +16,11 @@ package com.microsoft.playwright.impl; -import com.google.gson.*; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import com.microsoft.playwright.*; -import java.io.File; -import java.io.IOException; import java.nio.file.Path; import java.util.*; import java.util.function.BiConsumer; @@ -295,34 +293,12 @@ public class PageImpl extends ChannelOwner implements Page { mainFrame.dispatchEvent(selector, type, eventInit, convertViaJson(options, Frame.DispatchEventOptions.class)); } - private static String toProtocol(EmulateMediaOptions.Media media) { - if (media == null) { - return "null"; - } - return media.toString().toLowerCase(); - } - - private static String toProtocol(EmulateMediaOptions.ColorScheme colorScheme) { - if (colorScheme == null) { - return "null"; - } - switch (colorScheme) { - case DARK: - return "dark"; - case LIGHT: - return "light"; - case NO_PREFERENCE: - return "no-preference"; - default: - throw new PlaywrightException("Unknown option: " + colorScheme); - } - } - @Override public void emulateMedia(EmulateMediaOptions options) { - JsonObject params = new JsonObject(); - params.addProperty("media", toProtocol(options.media)); - params.addProperty("colorScheme", toProtocol(options.colorScheme)); + if (options == null) { + options = new EmulateMediaOptions(); + } + JsonObject params = gson().toJsonTree(options).getAsJsonObject(); sendMessage("emulateMedia", params); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java index 72356e00..29bc3ca2 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Serialization.java @@ -17,6 +17,7 @@ package com.microsoft.playwright.impl; import com.google.gson.*; +import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.microsoft.playwright.*; @@ -36,7 +37,9 @@ class Serialization { if (gson == null) { gson = new GsonBuilder() .registerTypeAdapter(BrowserContext.SameSite.class, new SameSiteAdapter().nullSafe()) - .registerTypeAdapter(BrowserType.LaunchPersistentContextOptions.ColorScheme.class, new ColorSchemeAdapter().nullSafe()) + .registerTypeAdapter(ColorScheme.class, new ColorSchemeAdapter().nullSafe()) + .registerTypeAdapter(Page.EmulateMediaOptions.Media.class, new MediaSerializer()) + .registerTypeAdapter(Optional.class, new OptionalSerializer()) .registerTypeAdapter(Path.class, new PathSerializer()).create(); } return gson; @@ -87,7 +90,7 @@ class Serialization { result.s = (String) value; } else if (value instanceof List) { List list = new ArrayList<>(); - for (Object o : (List) value) { + for (Object o : (List) value) { list.add(serializeValue(o, handles, depth + 1)); } result.a = list.toArray(new SerializedValue[0]); @@ -245,6 +248,29 @@ class Serialization { return result; } + private static class OptionalSerializer implements JsonSerializer> { + private static boolean isSupported(Type type) { + return new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()) || + new TypeToken>() {}.getType().getTypeName().equals(type.getTypeName()); + } + + @Override + public JsonElement serialize(Optional src, Type typeOfSrc, JsonSerializationContext context) { + assert isSupported(typeOfSrc); + if (!src.isPresent()) { + return new JsonPrimitive("null"); + } + return context.serialize(src.get()); + } + } + + private static class MediaSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Page.EmulateMediaOptions.Media src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.toString().toLowerCase()); + } + } + private static class PathSerializer implements JsonSerializer { @Override public JsonElement serialize(Path src, Type typeOfSrc, JsonSerializationContext context) { @@ -279,9 +305,9 @@ class Serialization { } } - private static class ColorSchemeAdapter extends TypeAdapter { + private static class ColorSchemeAdapter extends TypeAdapter { @Override - public void write(JsonWriter out, BrowserType.LaunchPersistentContextOptions.ColorScheme value) throws IOException { + public void write(JsonWriter out, ColorScheme value) throws IOException { String stringValue; switch (value) { case DARK: @@ -300,12 +326,12 @@ class Serialization { } @Override - public BrowserType.LaunchPersistentContextOptions.ColorScheme read(JsonReader in) throws IOException { + public ColorScheme read(JsonReader in) throws IOException { String value = in.nextString(); switch (value) { - case "dark": return BrowserType.LaunchPersistentContextOptions.ColorScheme.DARK; - case "light": return BrowserType.LaunchPersistentContextOptions.ColorScheme.LIGHT; - case "no-preference": return BrowserType.LaunchPersistentContextOptions.ColorScheme.NO_PREFERENCE; + case "dark": return ColorScheme.DARK; + case "light": return ColorScheme.LIGHT; + case "no-preference": return ColorScheme.NO_PREFERENCE; default: throw new PlaywrightException("Unexpected value: " + value); } } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java index 183fad61..8d6e5550 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestDefaultBrowserContext2.java @@ -9,7 +9,7 @@ import java.nio.file.Path; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import static com.microsoft.playwright.BrowserType.LaunchPersistentContextOptions.ColorScheme.DARK; +import static com.microsoft.playwright.ColorScheme.DARK; import static com.microsoft.playwright.Utils.mapOf; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java new file mode 100644 index 00000000..1fee1d2d --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageEmulateMedia.java @@ -0,0 +1,149 @@ +/* + * 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 java.util.function.Supplier; + +import static com.microsoft.playwright.ColorScheme.DARK; +import static com.microsoft.playwright.ColorScheme.LIGHT; +import static com.microsoft.playwright.Page.EmulateMediaOptions.Media.PRINT; +import static com.microsoft.playwright.Page.EventType.POPUP; +import static com.microsoft.playwright.Utils.attachFrame; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TestPageEmulateMedia extends TestBase { + @Test + void shouldEmulateType() { + assertEquals(true, page.evaluate("() => matchMedia('screen').matches")); + assertEquals(false, page.evaluate("() => matchMedia('print').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().withMedia(PRINT)); + assertEquals(false, page.evaluate("() => matchMedia('screen').matches")); + assertEquals(true, page.evaluate("() => matchMedia('print').matches")); + page.emulateMedia(new Page.EmulateMediaOptions()); + assertEquals(false, page.evaluate("() => matchMedia('screen').matches")); + assertEquals(true, page.evaluate("() => matchMedia('print').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().withMedia(null)); + assertEquals(true, page.evaluate("() => matchMedia('screen').matches")); + assertEquals(false, page.evaluate("() => matchMedia('print').matches")); + } + + void shouldThrowInCaseOfBadTypeArgument() { + // Impossible in Java. + } + + @Test + void shouldEmulateSchemeWork() { + page.emulateMedia(new Page.EmulateMediaOptions().withColorScheme(LIGHT)); + assertEquals(true, page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches")); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + page.emulateMedia(new Page.EmulateMediaOptions().withColorScheme(DARK)); + assertEquals(true, page.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches")); + } + + @Test + void shouldDefaultToLight() { + assertEquals(true, page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches")); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + + page.emulateMedia(new Page.EmulateMediaOptions().withColorScheme(DARK)); + assertEquals(true, page.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches")); + + page.emulateMedia(new Page.EmulateMediaOptions().withColorScheme(null)); + assertEquals(false, page.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + assertEquals(true, page.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches")); + } + + void shouldThrowInCaseOfBadArgument() { + // Impossible in Java. + } + +// @Test + void shouldWorkDuringNavigation() { + // TODO: requires async API + } + + @Test + void shouldWorkInPopup() { + { + BrowserContext context = browser.newContext(new Browser.NewContextOptions().withColorScheme(DARK)); + Page page = context.newPage(); + page.navigate(server.EMPTY_PAGE); + Deferred> popupEvent = page.waitForEvent(POPUP); + page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE); + Page popup = (Page) popupEvent.get().data(); + assertEquals(false, popup.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches")); + assertEquals(true, popup.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + context.close(); + } + { + Page page = browser.newPage(new Browser.NewPageOptions().withColorScheme(LIGHT)); + page.navigate(server.EMPTY_PAGE); + Deferred> popupEvent = page.waitForEvent(POPUP); + page.evaluate("url => { window.open(url); }", server.EMPTY_PAGE); + Page popup = (Page) popupEvent.get().data(); + assertEquals(true, popup.evaluate("() => matchMedia('(prefers-color-scheme: light)').matches")); + assertEquals(false, popup.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + page.close(); + } + } + + @Test + void shouldWorkInCrossProcessIframe() { + Page page = browser.newPage(new Browser.NewPageOptions().withColorScheme(DARK)); + page.navigate(server.EMPTY_PAGE); + attachFrame(page, "frame1", server.CROSS_PROCESS_PREFIX + "/empty.html"); + Frame frame = page.frames().get(1); + assertEquals(true, frame.evaluate("() => matchMedia('(prefers-color-scheme: dark)').matches")); + page.close(); + } + + @Test + void shouldChangeTheActualColorsInCss() { + page.setContent("\n" + + "
Hello
"); + + Supplier backgroundColor = () -> { + return page.evalOnSelector("div", "div => window.getComputedStyle(div).backgroundColor"); + }; + + page.emulateMedia(new Page.EmulateMediaOptions().withColorScheme(LIGHT)); + assertEquals("rgb(255, 255, 255)", backgroundColor.get()); + + page.emulateMedia(new Page.EmulateMediaOptions().withColorScheme(DARK)); + assertEquals("rgb(0, 0, 0)", backgroundColor.get()); + + page.emulateMedia(new Page.EmulateMediaOptions().withColorScheme(LIGHT)); + assertEquals("rgb(255, 255, 255)", backgroundColor.get()); + } +}