From fc91cb7866bda9f3fb8972db43f17e9d59f1a43b Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 19 Oct 2020 17:39:16 -0700 Subject: [PATCH] test: convert page-expose-function.spec.ts (#22) --- .../TestBrowserContextExposeFunction.java | 6 - .../playwright/TestPageExposeFunction.java | 276 ++++++++++++++++++ 2 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/TestPageExposeFunction.java diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java index 286e5d7b..580e220d 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java @@ -19,15 +19,9 @@ package com.microsoft.playwright; import org.junit.jupiter.api.*; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.regex.Pattern; -import static com.microsoft.playwright.Page.EventType.FRAMENAVIGATED; -import static com.microsoft.playwright.Utils.expectedSSLError; 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/TestPageExposeFunction.java b/playwright/src/test/java/com/microsoft/playwright/TestPageExposeFunction.java new file mode 100644 index 00000000..51bd92d0 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/TestPageExposeFunction.java @@ -0,0 +1,276 @@ +/** + * 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.*; + +import java.io.IOException; +import java.util.Map; + +import static com.microsoft.playwright.Frame.LoadState.LOAD; +import static com.microsoft.playwright.Utils.mapOf; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +public class TestPageExposeFunction { + private static Playwright playwright; + private static Server server; + private static Server httpsServer; + private static BrowserType browserType; + private static Browser browser; + private static boolean isChromium; + private static boolean isWebKit; + private static boolean headful; + private BrowserContext context; + private Page page; + + @BeforeAll + static void launchBrowser() { + playwright = Playwright.create(); + BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); + browserType = playwright.chromium(); + browser = browserType.launch(options); + isChromium = true; + isWebKit = false; + headful = false; + } + + @BeforeAll + static void startServer() throws IOException { + server = Server.createHttp(8907); + httpsServer = Server.createHttps(8908); + } + + @AfterAll + static void stopServer() throws IOException { + browser.close(); + server.stop(); + server = null; + } + + @BeforeEach + void setUp() { + server.reset(); + context = browser.newContext(); + page = context.newPage(); + } + + @AfterEach + void tearDown() { + context.close(); + context = null; + page = null; + } + + @Test + void exposeBindingShouldWork() { + BrowserContext context = browser.newContext(); + Page page = context.newPage(); + Page.Binding.Source[] bindingSource = { null }; + page.exposeBinding("add", (source, args) -> { + bindingSource[0] = source; + return (Integer) args[0] + (Integer) args[1]; + }); + Object result = page.evaluate("async function() {\n" + + " return window['add'](5, 6);\n" + + "}"); + assertEquals(context, bindingSource[0].context()); + assertEquals(page, bindingSource[0].page()); + assertEquals(page.mainFrame(), bindingSource[0].frame()); + assertEquals(11, result); + context.close(); + } + + @Test + void shouldWork() { + page.exposeFunction("compute", args -> (Integer) args[0] * (Integer) args[1]); + Object result = page.evaluate("async function() {\n" + + " return await window['compute'](9, 4);\n" + + "}"); + assertEquals(36, result); + } + + @Test + void shouldWorkWithHandlesAndComplexObjects() { + JSHandle fooHandle = page.evaluateHandle("() => {\n" + + " window['fooValue'] = { bar: 2 };\n" + + " return window['fooValue'];\n" + + "}"); + page.exposeFunction("handle", args -> asList(mapOf("foo", fooHandle))); + Object equals = page.evaluate("async function() {\n" + + " const value = await window['handle']();\n" + + " const [{ foo }] = value;\n" + + " return foo === window['fooValue'];\n" + + "}"); + assertEquals(true, equals); + } + + + @Test + void shouldThrowExceptionInPageContext() { + page.exposeFunction("woof", args -> { + throw new RuntimeException("WOOF WOOF"); + }); + Object result = page.evaluate("async () => {\n" + + " try {\n" + + " await window[\"woof\"]();\n" + + " } catch (e) {\n" + + " return {message: e.message, stack: e.stack};\n" + + " }\n" + + "}"); + assertTrue(result instanceof Map); + Map m = (Map) result; + assertEquals("WOOF WOOF", m.get("message")); + assertTrue(m.get("stack").contains("shouldThrowExceptionInPageContext")); + } + + void shouldSupportThrowingNull() { + // Throwing null would lead to NullPointerException in Java. + } + @Test + void shouldBeCallableFromInsideAddInitScript() { + boolean[] called = { false }; + page.exposeFunction("woof", args -> called[0] = true); + page.addInitScript("window['woof']()"); + page.reload(); + assertTrue(called[0]); + } + + @Test + void shouldSurviveNavigation() { + page.exposeFunction("compute", args -> (Integer) args[0] * (Integer) args[1]); + Object result = page.evaluate("async function() {\n" + + " return await window['compute'](9, 4);\n" + + "}"); + page.navigate(server.EMPTY_PAGE); + assertEquals(36, result); + } + + void shouldAwaitReturnedPromise() { + // Java callback cannot return promise. + } + @Test + void shouldWorkOnFrames() { + page.exposeFunction("compute", args -> (Integer) args[0] * (Integer) args[1]); + page.navigate(server.PREFIX + "/frames/nested-frames.html"); + Frame frame = page.frames().get(1); + Object result = frame.evaluate("async function() {\n" + + " return window['compute'](3, 5);\n" + + "}"); + assertEquals(15, result); + } + + @Test + void shouldWorkOnFramesBeforeNavigation() { + page.navigate(server.PREFIX + "/frames/nested-frames.html"); + page.exposeFunction("compute", args -> (Integer) args[0] * (Integer) args[1]); + + Frame frame = page.frames().get(1); + Object result = frame.evaluate("async function() {\n" + + " return window['compute'](3, 5);\n" + + "}"); + assertEquals(15, result); + } + + @Test + void shouldWorkAfterCrossOriginNavigation() { + page.navigate(server.EMPTY_PAGE); + page.exposeFunction("compute", args -> (Integer) args[0] * (Integer) args[1]); + + page.navigate(server.CROSS_PROCESS_PREFIX + "/empty.html"); + Object result = page.evaluate("window['compute'](9, 4)"); + assertEquals(36, result); + } + + @Test + void shouldWorkWithComplexObjects() { + page.exposeFunction("complexObject", args -> { + Map a = (Map) args[0]; + Map b = (Map) args[1]; + int sum = a.get("x") + b.get("x"); + return mapOf("x", sum); + }); + Object result = page.evaluate("async () => window['complexObject']({x: 5}, {x: 2})"); + assertTrue(result instanceof Map); + assertEquals( 7, ((Map) result).get("x")); + } + + @Test + void exposeBindingHandleShouldWork() { + JSHandle[] target = { null }; + page.exposeBinding("logme", (source, args) -> { + target[0] = (JSHandle) args[0]; + return 17; + }, new Page.ExposeBindingOptions().withHandle(true)); + Object result = page.evaluate("async function() {\n" + + " return window['logme']({ foo: 42 });\n" + + "}"); + assertEquals(42, target[0].evaluate("x => x.foo")); + assertEquals(17, result); + } + + @Test + void exposeBindingHandleShouldNotThrowDuringNavigation() { + page.exposeBinding("logme", (source, args) -> { + return 17; + }, new Page.ExposeBindingOptions().withHandle(true)); + page.navigate(server.EMPTY_PAGE); + + Deferred navigation = page.waitForNavigation(new Page.WaitForNavigationOptions().withWaitUntil(LOAD)); + + page.evaluate("async url => {\n" + + " window['logme']({ foo: 42 });\n" + + " window.location.href = url;\n" + + "}", server.PREFIX + "/one-style.html"); + } + + @Test + void shouldThrowForDuplicateRegistrations() { + page.exposeFunction("foo", args -> null); + try { + page.exposeFunction("foo", args -> null); + fail("did not throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("Function \"foo\" has been already registered")); + } + } + + @Test + void exposeBindingHandleShouldThrowForMultipleArguments() { + page.exposeBinding("logme", (source, args) -> { + return 17; + }, new Page.ExposeBindingOptions().withHandle(true)); + assertEquals(17, page.evaluate("async function() {\n" + + " return window['logme']({ foo: 42 });\n" + + "}")); + assertEquals(17, page.evaluate("async function() {\n" + + " return window['logme']({ foo: 42 }, undefined, undefined);\n" + + "}")); + assertEquals(17, page.evaluate("async function() {\n" + + " return window['logme'](undefined, undefined, undefined);\n" + + "}")); + try { + page.evaluate("async function() {\n" + + " return window['logme'](1, 2);\n" + + "}"); + fail("did not throw"); + } catch (RuntimeException e) { + assertTrue(e.getMessage().contains("exposeBindingHandle supports a single argument, 2 received")); + } + } +} +