From b7319c629d246b00f8fdf22ae256bbdabea5fb3c Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 21 Oct 2021 18:22:00 -0700 Subject: [PATCH] feat: add web-first assertions for page (#657) --- assertions/pom.xml | 72 +++++++++ .../playwright/assertions/PageAssertions.java | 145 ++++++++++++++++++ .../assertions/PlaywrightAssertions.java | 53 +++++++ .../playwright/impl/PageAssertionsImpl.java | 100 ++++++++++++ .../playwright/TestPageAssertions.java | 131 ++++++++++++++++ playwright/pom.xml | 18 +++ .../microsoft/playwright/impl/FrameImpl.java | 2 +- .../playwright/impl/LocatorImpl.java | 24 +++ .../microsoft/playwright/impl/PageImpl.java | 2 +- .../microsoft/playwright/impl/Protocol.java | 67 ++++---- .../microsoft/playwright/impl/UrlMatcher.java | 2 +- .../playwright/HttpsConfiguratorImpl.java | 4 +- .../java/com/microsoft/playwright/Server.java | 15 +- .../playwright/TestPageWaitForNavigation.java | 2 - pom.xml | 7 + scripts/CLI_VERSION | 2 +- .../playwright/tools/ApiGenerator.java | 54 ++++++- 17 files changed, 635 insertions(+), 65 deletions(-) create mode 100644 assertions/pom.xml create mode 100644 assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java create mode 100644 assertions/src/main/java/com/microsoft/playwright/assertions/PlaywrightAssertions.java create mode 100644 assertions/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java create mode 100644 assertions/src/test/java/com/microsoft/playwright/TestPageAssertions.java diff --git a/assertions/pom.xml b/assertions/pom.xml new file mode 100644 index 00000000..a0b16dee --- /dev/null +++ b/assertions/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.microsoft.playwright + parent-pom + 1.17.0-SNAPSHOT + + + assertions + Playwright - Assertions + + This module provides AssertJ Matchers specific to Playwright. + + + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + true + + + + + + + org.opentest4j + opentest4j + + + com.microsoft.playwright + playwright + ${project.version} + + + org.junit.jupiter + junit-jupiter-engine + + + com.microsoft.playwright + playwright + ${project.version} + test-jar + test + + + + diff --git a/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java b/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java new file mode 100644 index 00000000..070a4690 --- /dev/null +++ b/assertions/src/main/java/com/microsoft/playwright/assertions/PageAssertions.java @@ -0,0 +1,145 @@ +/* + * 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.assertions; + +import java.util.*; +import java.util.regex.Pattern; +import com.microsoft.playwright.Page; + +/** + * The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the + * tests. + */ +public interface PageAssertions { + class HasTitleOptions { + /** + * Time to retry assertion for. + */ + public Double timeout; + + /** + * Time to retry assertion for. + */ + public HasTitleOptions setTimeout(double timeout) { + this.timeout = timeout; + return this; + } + } + class HasURLOptions { + /** + * Time to retry the assertion for. + */ + public Double timeout; + + /** + * Time to retry the assertion for. + */ + public HasURLOptions setTimeout(double timeout) { + this.timeout = timeout; + return this; + } + } + /** + * Ensures the page has a given title. + *
{@code
+   * assertThat(page).hasTitle("Playwright");
+   * }
+ * + * @param titleOrRegExp Expected title or RegExp. + */ + default void hasTitle(String titleOrRegExp) { + hasTitle(titleOrRegExp, null); + } + /** + * Ensures the page has a given title. + *
{@code
+   * assertThat(page).hasTitle("Playwright");
+   * }
+ * + * @param titleOrRegExp Expected title or RegExp. + */ + void hasTitle(String titleOrRegExp, HasTitleOptions options); + /** + * Ensures the page has a given title. + *
{@code
+   * assertThat(page).hasTitle("Playwright");
+   * }
+ * + * @param titleOrRegExp Expected title or RegExp. + */ + default void hasTitle(Pattern titleOrRegExp) { + hasTitle(titleOrRegExp, null); + } + /** + * Ensures the page has a given title. + *
{@code
+   * assertThat(page).hasTitle("Playwright");
+   * }
+ * + * @param titleOrRegExp Expected title or RegExp. + */ + void hasTitle(Pattern titleOrRegExp, HasTitleOptions options); + /** + * Ensures the page is navigated to the given URL. + *
{@code
+   * assertThat(page).hasURL('.com');
+   * }
+ * + * @param urlOrRegExp Expected substring or RegExp. + */ + default void hasURL(String urlOrRegExp) { + hasURL(urlOrRegExp, null); + } + /** + * Ensures the page is navigated to the given URL. + *
{@code
+   * assertThat(page).hasURL('.com');
+   * }
+ * + * @param urlOrRegExp Expected substring or RegExp. + */ + void hasURL(String urlOrRegExp, HasURLOptions options); + /** + * Ensures the page is navigated to the given URL. + *
{@code
+   * assertThat(page).hasURL('.com');
+   * }
+ * + * @param urlOrRegExp Expected substring or RegExp. + */ + default void hasURL(Pattern urlOrRegExp) { + hasURL(urlOrRegExp, null); + } + /** + * Ensures the page is navigated to the given URL. + *
{@code
+   * assertThat(page).hasURL('.com');
+   * }
+ * + * @param urlOrRegExp Expected substring or RegExp. + */ + void hasURL(Pattern urlOrRegExp, HasURLOptions options); + /** + * Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain + * {@code "error"}: + *
{@code
+   * assertThat(page).not().hasURL('error');
+   * }
+ */ + PageAssertions not(); +} + diff --git a/assertions/src/main/java/com/microsoft/playwright/assertions/PlaywrightAssertions.java b/assertions/src/main/java/com/microsoft/playwright/assertions/PlaywrightAssertions.java new file mode 100644 index 00000000..880bb2f0 --- /dev/null +++ b/assertions/src/main/java/com/microsoft/playwright/assertions/PlaywrightAssertions.java @@ -0,0 +1,53 @@ +/* + * 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.assertions; + +import java.util.*; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Locator; +import com.microsoft.playwright.impl.PageAssertionsImpl; + +/** + * The {@code PlaywrightAssertions} class provides convenience methods for creating assertions that will wait until the expected + * condition is met. + * + *

Consider the following example: + *

{@code
+ * assertThat(page.locator('.status')).hasText('Submitted');
+ * }
+ * + *

Playwright will be re-testing the node with the selector {@code .status} until fetched Node has the {@code "Submitted"} text. It + * will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is reached. + * You can pass this timeout as an option. + * + *

By default, the timeout for assertions is set to 5 seconds. + */ +public interface PlaywrightAssertions { + /** + * Creates a {@code PageAssertions} object for the given {@code Page}. + *

{@code
+   * PlaywrightAssertions.assertThat(page).hasTitle("News");
+   * }
+ * + * @param page {@code Page} object to use for assertions. + */ + static PageAssertions assertThat(Page page) { + return new PageAssertionsImpl(page); + } + +} + diff --git a/assertions/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java b/assertions/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java new file mode 100644 index 00000000..7a1bfd26 --- /dev/null +++ b/assertions/src/main/java/com/microsoft/playwright/impl/PageAssertionsImpl.java @@ -0,0 +1,100 @@ +/* + * 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.Page; +import com.microsoft.playwright.assertions.PageAssertions; +import org.opentest4j.AssertionFailedError; + +import java.util.regex.Pattern; + +import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl; +import static java.util.Arrays.asList; + +public class PageAssertionsImpl implements PageAssertions { + private final PageImpl actual; + private final boolean isNot; + + public PageAssertionsImpl(Page page) { + this(page, false); + } + + private PageAssertionsImpl(Page page, boolean isNot) { + this.actual = (PageImpl) page; + this.isNot = isNot; + } + + @Override + public void hasTitle(String title, HasTitleOptions options) { + ExpectedTextValue expected = new ExpectedTextValue(); + expected.string = title; + expectImpl("to.have.title", expected, title, "Page title expected to be", options == null ? null : options.timeout); + } + + @Override + public void hasTitle(Pattern pattern, HasTitleOptions options) { + //urlOrRegExp.flags(); + ExpectedTextValue expected = new ExpectedTextValue(); + expected.regexSource = pattern.pattern(); + // expected.regexFlags = + expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", options == null ? null : options.timeout); + } + + @Override + public void hasURL(String url, HasURLOptions options) { + ExpectedTextValue expected = new ExpectedTextValue(); + if (actual.context().baseUrl != null) { + url = resolveUrl(actual.context().baseUrl, url); + } + expected.string = url; + expectImpl("to.have.url", expected, url, "Page URL expected to be", options == null ? null : options.timeout); + } + + @Override + public void hasURL(Pattern pattern, HasURLOptions options) { + //urlOrRegExp.flags(); + ExpectedTextValue expected = new ExpectedTextValue(); + expected.regexSource = pattern.pattern(); + // expected.regexFlags = + expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", options == null ? null : options.timeout); + } + + private void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, Double timeout) { + LocatorImpl locator = actual.locator(":root"); + FrameExpectOptions expectOptions = new FrameExpectOptions(); + expectOptions.expectedText = asList(textValue); + expectOptions.isNot = isNot; + expectOptions.timeout = timeout; + FrameExpectResult result = locator.expect(expression, expectOptions); + if (result.matches == isNot) { + Object actual = result.received == null ? null : Serialization.deserialize(result.received); + String log = String.join("\n", result.log); + if (!log.isEmpty()) { + log = "\nCall log:\n" + log; + } + throw new AssertionFailedError(message + log, expected, actual); + } + } + + @Override + public PageAssertions not() { + return new PageAssertionsImpl(actual, !isNot); + } + + +} + diff --git a/assertions/src/test/java/com/microsoft/playwright/TestPageAssertions.java b/assertions/src/test/java/com/microsoft/playwright/TestPageAssertions.java new file mode 100644 index 00000000..925a0752 --- /dev/null +++ b/assertions/src/test/java/com/microsoft/playwright/TestPageAssertions.java @@ -0,0 +1,131 @@ +/* + * 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 com.microsoft.playwright.assertions.PageAssertions; +import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; + +import java.util.regex.Pattern; + +import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +public class TestPageAssertions extends TestBase { + @Test + void hasURLTextPass() { + page.navigate("data:text/html,
A
"); + assertThat(page).hasURL("data:text/html,
A
"); + } + + @Test + void hasURLTextFail() { + page.navigate("data:text/html,
B
"); + try { + assertThat(page).hasURL("foo", new PageAssertions.HasURLOptions().setTimeout(1_000)); + fail("did not throw"); + } catch (AssertionFailedError e) { + assertEquals("foo", e.getExpected().getValue()); + assertEquals("data:text/html,
B
", e.getActual().getValue()); + assertTrue(e.getMessage().contains("Page URL expected to be"), e.getMessage()); + } + } + + @Test + void shouldSupportHasUrlWithBaseUrl() { + try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setBaseURL(server.PREFIX))) { + Page page = context.newPage(); + page.navigate(server.EMPTY_PAGE); + assertThat(page).hasURL("/empty.html", new PageAssertions.HasURLOptions().setTimeout(1_000)); + } + } + + @Test + void notHasUrlText() { + page.navigate("data:text/html,
B
"); + assertThat(page).not().hasURL("about:blank", new PageAssertions.HasURLOptions().setTimeout(1000)); + } + + @Test + void hasURLRegexPass() { + page.navigate("data:text/html,
A
"); + assertThat(page).hasURL(Pattern.compile("text")); + } + + @Test + void hasURLRegexFail() { + page.navigate(server.EMPTY_PAGE); + try { + assertThat(page).hasURL(Pattern.compile(".*foo.*"), new PageAssertions.HasURLOptions().setTimeout(1_000)); + fail("did not throw"); + } catch (AssertionFailedError e) { + assertEquals(".*foo.*", e.getExpected().getStringRepresentation()); + assertEquals(server.EMPTY_PAGE, e.getActual().getValue()); + assertTrue(e.getMessage().contains("Page URL expected to match regex"), e.getMessage()); + } + } + + @Test + void notHasUrlRegEx() { + page.navigate("data:text/html,
B
"); + assertThat(page).not().hasURL(Pattern.compile("about"), new PageAssertions.HasURLOptions().setTimeout(1000)); + } + + @Test + void hasTitleTextPass() { + page.navigate(server.PREFIX + "/title.html"); + assertThat(page).hasTitle("Woof-Woof", new PageAssertions.HasTitleOptions().setTimeout(1_000)); + } + + @Test + void hasTitleTextFail() { + page.navigate(server.PREFIX + "/title.html"); + try { + assertThat(page).hasTitle("foo", new PageAssertions.HasTitleOptions().setTimeout(1_000)); + fail("did not throw"); + } catch (AssertionFailedError e) { + assertEquals("foo", e.getExpected().getValue()); + assertEquals("Woof-Woof", e.getActual().getValue()); + assertTrue(e.getMessage().contains("Page title expected to be"), e.getMessage()); + } + } + + @Test + void hasTitleRegexPass() { + page.navigate(server.PREFIX + "/title.html"); + assertThat(page).hasTitle(Pattern.compile("^.oof.+oof$")); + } + + @Test + void hasTitleRegexFail() { + page.navigate(server.PREFIX + "/title.html"); + try { + assertThat(page).hasTitle(Pattern.compile("^foo[AB]"), new PageAssertions.HasTitleOptions().setTimeout(1_000)); + fail("did not throw"); + } catch (AssertionFailedError e) { + assertEquals("^foo[AB]", e.getExpected().getStringRepresentation()); + assertEquals("Woof-Woof", e.getActual().getValue()); + assertTrue(e.getMessage().contains("Page title expected to match regex"), e.getMessage()); + } + } + + @Test + void notHasTitleRegEx() { + page.navigate(server.PREFIX + "/title.html"); + assertThat(page).not().hasTitle(Pattern.compile("ab.ut")); + } +} diff --git a/playwright/pom.xml b/playwright/pom.xml index 32da0891..0a123aac 100644 --- a/playwright/pom.xml +++ b/playwright/pom.xml @@ -51,7 +51,25 @@ org.apache.maven.plugins maven-surefire-plugin + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + + test-jar + + + + + + + src/test/resources + resources + + diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java index f201822b..ab6e1feb 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/FrameImpl.java @@ -560,7 +560,7 @@ public class FrameImpl extends ChannelOwner implements Frame { } @Override - public Locator locator(String selector) { + public LocatorImpl locator(String selector) { return new LocatorImpl(this, selector); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java index b5f35ee1..7136f908 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/LocatorImpl.java @@ -1,5 +1,7 @@ package com.microsoft.playwright.impl; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.microsoft.playwright.ElementHandle; import com.microsoft.playwright.Frame; import com.microsoft.playwright.JSHandle; @@ -13,6 +15,8 @@ import java.nio.file.Path; import java.util.List; import java.util.function.BiFunction; +import static com.microsoft.playwright.impl.Serialization.gson; +import static com.microsoft.playwright.impl.Serialization.serializeArgument; import static com.microsoft.playwright.impl.Utils.convertViaJson; class LocatorImpl implements Locator { @@ -415,4 +419,24 @@ class LocatorImpl implements Locator { public String toString() { return "Locator@" + selector; } + + FrameExpectResult expect(String expression, FrameExpectOptions options) { + return frame.withLogging("Locator.expect", () -> expectImpl(expression, options)); + } + + private FrameExpectResult expectImpl(String expression, FrameExpectOptions options) { + if (options == null) { + options = new FrameExpectOptions(); + } + JsonObject params = gson().toJsonTree(options).getAsJsonObject(); + params.addProperty("selector", selector); + params.addProperty("expression", expression); + if (options.expectedValue != null) { + params.remove("expectedValue"); + params.add("expectedValue", gson().toJsonTree(serializeArgument(options.expectedValue))); + } + JsonElement json = frame.sendMessage("expect", params); + FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class); + return result; + } } 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 ab1bc235..bb364110 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/PageImpl.java @@ -875,7 +875,7 @@ public class PageImpl extends ChannelOwner implements Page { } @Override - public Locator locator(String selector) { + public LocatorImpl locator(String selector) { return mainFrame.locator(selector); } diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java b/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java index fdfccf0b..48162f69 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/Protocol.java @@ -18,18 +18,12 @@ package com.microsoft.playwright.impl; -class Binary { -} +import java.util.List; class Channel { String guid; } -class Metadata{ - String stack; -} - - class SerializedValue{ Number n; Boolean b; @@ -51,45 +45,11 @@ class SerializedValue{ Number h; } - class SerializedArgument{ SerializedValue value; Channel[] handles; } -class AXNode{ - String role; - String name; - String valueString; - Number valueNumber; - String description; - String keyshortcuts; - String roledescription; - String valuetext; - Boolean disabled; - Boolean expanded; - Boolean focused; - Boolean modal; - Boolean multiline; - Boolean multiselectable; - Boolean readonly; - Boolean required; - Boolean selected; - // Possible values: { 'checked, 'unchecked, 'mixed } - String checked; - // Possible values: { 'pressed, 'released, 'mixed } - String pressed; - Number level; - Number valuemin; - Number valuemax; - String autocomplete; - String haspopup; - String invalid; - String orientation; - AXNode[] children; -} - - class SerializedError{ public static class Error { String message; @@ -119,3 +79,28 @@ class SerializedError{ } } +class ExpectedTextValue { + String string; + String regexSource; + String regexFlags; + Boolean matchSubstring; + Boolean normalizeWhiteSpace; +} + +class FrameExpectOptions { + Object expressionArg; + List expectedText; + Integer expectedNumber; + SerializedArgument expectedValue; + Boolean useInnerText; + boolean isNot; + Double timeout; +} + +class FrameExpectResult { + boolean matches; + SerializedValue received; + List log; +} + + diff --git a/playwright/src/main/java/com/microsoft/playwright/impl/UrlMatcher.java b/playwright/src/main/java/com/microsoft/playwright/impl/UrlMatcher.java index c0c599e9..de865195 100644 --- a/playwright/src/main/java/com/microsoft/playwright/impl/UrlMatcher.java +++ b/playwright/src/main/java/com/microsoft/playwright/impl/UrlMatcher.java @@ -54,7 +54,7 @@ class UrlMatcher { throw new PlaywrightException("Url must be String, Pattern or Predicate, found: " + object.getClass().getTypeName()); } - private static String resolveUrl(URL baseUrl, String spec) { + static String resolveUrl(URL baseUrl, String spec) { if (baseUrl == null) { return spec; } diff --git a/playwright/src/test/java/com/microsoft/playwright/HttpsConfiguratorImpl.java b/playwright/src/test/java/com/microsoft/playwright/HttpsConfiguratorImpl.java index ab7e6ba0..d96d05b3 100644 --- a/playwright/src/test/java/com/microsoft/playwright/HttpsConfiguratorImpl.java +++ b/playwright/src/test/java/com/microsoft/playwright/HttpsConfiguratorImpl.java @@ -23,7 +23,6 @@ import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import javax.net.ssl.TrustManagerFactory; -import java.io.FileInputStream; import java.security.KeyStore; class HttpsConfiguratorImpl extends HttpsConfigurator { @@ -52,8 +51,7 @@ class HttpsConfiguratorImpl extends HttpsConfigurator { String password = "password"; // Generated via // keytool -genkey -keyalg RSA -validity 36500 -keysize 4096 -dname cn=Playwright,ou=Playwright,o=Playwright,c=US -keystore keystore.jks -storepass password -keypass password - ks.load(new FileInputStream("src/test/resources/keys/keystore.jks"), password.toCharArray()); - + ks.load(HttpsConfiguratorImpl.class.getClassLoader().getResourceAsStream("resources/keys/keystore.jks"), password.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, password.toCharArray()); diff --git a/playwright/src/test/java/com/microsoft/playwright/Server.java b/playwright/src/test/java/com/microsoft/playwright/Server.java index 6010f103..1a7e2c51 100644 --- a/playwright/src/test/java/com/microsoft/playwright/Server.java +++ b/playwright/src/test/java/com/microsoft/playwright/Server.java @@ -35,7 +35,6 @@ public class Server implements HttpHandler { public final String CROSS_PROCESS_PREFIX; public final int PORT; public final String EMPTY_PAGE; - private final File resourcesDir; private final Map> requestSubscribers = Collections.synchronizedMap(new HashMap<>()); private final Map auths = Collections.synchronizedMap(new HashMap<>()); @@ -79,7 +78,6 @@ public class Server implements HttpHandler { server.setExecutor(null); // creates a default executor File cwd = FileSystems.getDefault().getPath(".").toFile(); - resourcesDir = new File(cwd, "src/test/resources"); server.start(); } @@ -192,21 +190,24 @@ public class Server implements HttpHandler { if ("/".equals(path)) { path = "/index.html"; } - File file = new File(resourcesDir, path.substring(1)); - if (!file.exists()) { + + // Resources from "src/test/resources/" are copied to "resources/" directory in the jar. + String resourcePath = "resources" + path; + InputStream resource = getClass().getClassLoader().getResourceAsStream(resourcePath); + if (resource == null) { exchange.sendResponseHeaders(404, 0); try (Writer writer = new OutputStreamWriter(exchange.getResponseBody())) { - writer.write("File not found: " + file.getCanonicalPath()); + writer.write("File not found: " + resourcePath); } return; } - exchange.getResponseHeaders().add("Content-Type", mimeType(file)); + exchange.getResponseHeaders().add("Content-Type", mimeType(new File(resourcePath))); ByteArrayOutputStream body = new ByteArrayOutputStream(); OutputStream output = body; if (gzipRoutes.contains(path)) { exchange.getResponseHeaders().add("Content-Encoding", "gzip"); } - try (FileInputStream input = new FileInputStream(file)) { + try (InputStream input = resource) { if (gzipRoutes.contains(path)) { output = new GZIPOutputStream(output); } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestPageWaitForNavigation.java b/playwright/src/test/java/com/microsoft/playwright/TestPageWaitForNavigation.java index 9f8a96f4..e5de4a32 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestPageWaitForNavigation.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageWaitForNavigation.java @@ -47,8 +47,6 @@ public class TestPageWaitForNavigation extends TestBase { fail("did not throw"); } catch (TimeoutError e) { assertTrue(e.getMessage().contains("Timeout 5000ms exceeded")); -// assertTrue(e.getMessage().contains("waiting for navigation to '**/frame.html' until 'load'")); -// assertTrue(e.getMessage().contains("navigated to '${server.EMPTY_PAGE}'")); } } diff --git a/pom.xml b/pom.xml index 0f6aac65..8162e586 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ driver driver-bundle playwright + assertions @@ -47,6 +48,7 @@ 5.7.0 UTF-8 1.5.1 + 1.2.0 @@ -66,6 +68,11 @@ gson ${gson.version} + + org.opentest4j + opentest4j + ${opentest4j.version} + org.junit.jupiter junit-jupiter-engine diff --git a/scripts/CLI_VERSION b/scripts/CLI_VERSION index dac6511f..fcb14299 100644 --- a/scripts/CLI_VERSION +++ b/scripts/CLI_VERSION @@ -1 +1 @@ -1.16.0-1634781227000 +1.17.0-next-1634863457000 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 2c702184..c3e53bd4 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 @@ -24,6 +24,7 @@ import com.google.gson.JsonObject; import java.io.*; import java.nio.file.FileSystems; import java.util.*; +import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -627,6 +628,14 @@ class Method extends Element { output.add(offset + "}"); return; } + if ("PlaywrightAssertions.assertThat".equals(jsonPath)) { + writeJavadoc(params, output, offset); + output.add(offset + "static PageAssertions assertThat(Page page) {"); + output.add(offset + " return new PageAssertionsImpl(page);"); + output.add(offset + "}"); + output.add(""); + return; + } int numOverloads = 1; for (int i = 0; i < params.size(); i++) { if (params.get(i).type.isTypeUnion()) { @@ -844,9 +853,7 @@ class Interface extends TypeDefinition { " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + " * See the License for the specific language governing permissions and\n" + " * limitations under the License.\n" + - " */\n" + - "\n" + - "package com.microsoft.playwright;\n"; + " */\n"; private static Set allowedBaseInterfaces = new HashSet<>(asList("Browser", "JSHandle", "BrowserContext")); private static Set autoCloseableInterfaces = new HashSet<>(asList("Playwright", "Browser", "BrowserContext", "Page")); @@ -877,7 +884,6 @@ class Interface extends TypeDefinition { } void writeTo(List output, String offset) { - output.add(header); if ("Playwright".equals(jsonName)) { output.add("import com.microsoft.playwright.impl.PlaywrightImpl;"); } @@ -900,9 +906,20 @@ class Interface extends TypeDefinition { if (asList("Page", "Frame", "BrowserContext", "WebSocket").contains(jsonName)) { output.add("import java.util.function.Predicate;"); } - if (asList("Page", "Frame", "BrowserContext").contains(jsonName)) { + if (asList("Page", "Frame", "BrowserContext", "PageAssertions").contains(jsonName)) { output.add("import java.util.regex.Pattern;"); } + if ("PlaywrightAssertions".equals(jsonName)) { + output.add("import com.microsoft.playwright.Page;"); + output.add("import com.microsoft.playwright.Locator;"); + output.add("import com.microsoft.playwright.impl.PageAssertionsImpl;"); + } + if ("PageAssertions".equals(jsonName)) { + output.add("import com.microsoft.playwright.Page;"); + } + if ("LocatorAssertions".equals(jsonName)) { + output.add("import com.microsoft.playwright.Locator;"); + } output.add(""); List superInterfaces = new ArrayList<>(); @@ -1062,10 +1079,22 @@ public class ApiGenerator { ApiGenerator(Reader reader) throws IOException { JsonArray api = new Gson().fromJson(reader, JsonArray.class); File cwd = FileSystems.getDefault().getPath(".").toFile(); + filterOtherLangs(api, new Stack<>()); + File dir = new File(cwd, "playwright/src/main/java/com/microsoft/playwright"); System.out.println("Writing files to: " + dir.getCanonicalPath()); - Stack path = new Stack<>(); - filterOtherLangs(api, path); + generate(api, dir, "com.microsoft.playwright", isAssertion().negate()); + + File assertionsDir = new File(cwd,"assertions/src/main/java/com/microsoft/playwright/assertions"); + System.out.println("Writing assertion files to: " + dir.getCanonicalPath()); + generate(api, assertionsDir, "com.microsoft.playwright.assertions", isAssertion()); + } + + private static Predicate isAssertion() { + return className -> className.toLowerCase().contains("assert"); + } + + private void generate(JsonArray api, File dir, String packageName, Predicate classFilter) throws IOException { Map topLevelTypes = new HashMap<>(); for (JsonElement entry: api) { String name = entry.getAsJsonObject().get("name").getAsString(); @@ -1073,17 +1102,26 @@ public class ApiGenerator { if (asList("PlaywrightException", "TimeoutError").contains(name)) { continue; } + if (!classFilter.test(name)) { + continue; + } List lines = new ArrayList<>(); + lines.add(Interface.header); + lines.add("package " + packageName + ";"); + lines.add(""); new Interface(entry.getAsJsonObject(), topLevelTypes).writeTo(lines, ""); String text = String.join("\n", lines); try (FileWriter writer = new FileWriter(new File(dir, name + ".java"))) { writer.write(text); } } + dir = new File(dir, "options"); for (TypeDefinition e : topLevelTypes.values()) { List lines = new ArrayList<>(); - lines.add(Interface.header.replace("package com.microsoft.playwright;", "package com.microsoft.playwright.options;")); + lines.add(Interface.header); + lines.add("package " + packageName + ".options;"); + lines.add(""); e.writeTo(lines, ""); String text = String.join("\n", lines); try (FileWriter writer = new FileWriter(new File(dir, e.name() + ".java"))) {