From 847dc03c23210877959cca5d5f5123500bfec78a Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 23 Oct 2026 13:51:10 -0700 Subject: [PATCH 1/2] test(fixtures): inject Playwright objects through JUnit 5 fixtures Replace the TestBase base class and @UsePlaywright with a fixture layer in com.microsoft.playwright.fixtures modeled on upstream browserTest.ts: - @PlaywrightTest runs each test once per browser (BROWSER env selects) - Playwright, Browser, BrowserContext, Page, Server etc. are injected as method parameters and created lazily, each test in a new context - @Fixture methods add or override fixtures (test or worker scope) - worker-scoped objects live in a pool so tests run in parallel without sharing Playwright objects across threads - @DisabledOnBrowser/@EnabledOnBrowser replace @DisabledIf conditions - @Nested @ParameterizedClass runs a test with different context options Claude-Session: https://claude.ai/code/session_018p7xPf3r5VjAM7V46nzFTM --- CONTRIBUTING.md | 40 ++ .../microsoft/playwright/BrowserServer.java | 81 ++++ .../com/microsoft/playwright/HttpsServer.java | 28 ++ .../java/com/microsoft/playwright/Server.java | 41 +- .../ServerWithClientCertificate.java | 9 +- .../playwright/TestAPIResponseAssertions.java | 16 +- .../TestAssertThatIsInViewport.java | 14 +- .../microsoft/playwright/TestAutoClose.java | 10 +- .../com/microsoft/playwright/TestBase.java | 190 -------- .../playwright/TestBeforeunload.java | 8 +- .../microsoft/playwright/TestBrowser1.java | 26 +- .../microsoft/playwright/TestBrowser2.java | 73 ++- .../microsoft/playwright/TestBrowserBind.java | 12 +- .../TestBrowserContextAddCookies.java | 68 ++- .../playwright/TestBrowserContextBaseUrl.java | 18 +- .../playwright/TestBrowserContextBasic.java | 63 ++- .../TestBrowserContextCDPSession.java | 53 +- .../TestBrowserContextClearCookies.java | 20 +- .../playwright/TestBrowserContextCookies.java | 76 ++- .../TestBrowserContextCredentials.java | 73 +-- .../playwright/TestBrowserContextEvents.java | 96 ++-- .../TestBrowserContextExposeFunction.java | 20 +- .../playwright/TestBrowserContextFetch.java | 330 ++++++------- .../playwright/TestBrowserContextHar.java | 123 ++--- .../playwright/TestBrowserContextLocale.java | 40 +- .../TestBrowserContextNetworkEvents.java | 32 +- .../playwright/TestBrowserContextProxy.java | 43 +- .../playwright/TestBrowserContextRoute.java | 60 +-- ...TestBrowserContextServiceWorkerPolicy.java | 12 +- .../TestBrowserContextStorageState.java | 50 +- .../playwright/TestBrowserContextStrict.java | 23 +- .../TestBrowserContextViewport.java | 40 +- .../TestBrowserContextWebAuthn.java | 24 +- .../playwright/TestBrowserTypeBasic.java | 35 +- .../playwright/TestBrowserTypeConnect.java | 195 +++----- .../com/microsoft/playwright/TestCheck.java | 40 +- .../microsoft/playwright/TestChromium.java | 43 +- .../playwright/TestChromiumTracing.java | 37 +- .../com/microsoft/playwright/TestClick.java | 181 +++---- .../playwright/TestClientCertificates.java | 80 ++- .../microsoft/playwright/TestDebugger.java | 20 +- .../TestDefaultBrowserContext2.java | 121 ++--- .../com/microsoft/playwright/TestDialog.java | 42 +- .../playwright/TestDispatchEvent.java | 47 +- .../microsoft/playwright/TestDownload.java | 114 ++--- .../TestElementHandleBoundingBox.java | 43 +- .../playwright/TestElementHandleClick.java | 40 +- .../TestElementHandleContentFrame.java | 24 +- .../TestElementHandleConvenience.java | 68 +-- .../playwright/TestElementHandleMisc.java | 48 +- .../TestElementHandleOwnerFrame.java | 34 +- .../playwright/TestElementHandlePress.java | 28 +- .../TestElementHandleQuerySelector.java | 32 +- .../TestElementHandleSelectText.java | 25 +- .../playwright/TestElementHandleType.java | 28 +- .../TestElementHandleWaitForElementState.java | 57 ++- .../playwright/TestEvalOnSelector.java | 108 ++--- .../playwright/TestEvalOnSelectorAll.java | 36 +- .../microsoft/playwright/TestExpectMisc.java | 12 +- .../playwright/TestFirefoxLauncher.java | 51 +- .../playwright/TestFrameNavigate.java | 12 +- .../microsoft/playwright/TestGeolocation.java | 32 +- .../microsoft/playwright/TestGlobalFetch.java | 200 ++++---- .../com/microsoft/playwright/TestHar.java | 91 ++-- .../TestJavaSourceLocationInConstructor.java | 14 +- .../playwright/TestLocatorAnyFrame.java | 80 +-- .../playwright/TestLocatorAssertions.java | 457 +++++++++--------- .../playwright/TestLocatorAssertions2.java | 84 ++-- .../playwright/TestLocatorClick.java | 36 +- .../playwright/TestLocatorConvenience.java | 84 ++-- .../playwright/TestLocatorElementHandle.java | 24 +- .../playwright/TestLocatorEvaluate.java | 24 +- .../playwright/TestLocatorFrame.java | 80 +-- .../playwright/TestLocatorHighlight.java | 12 +- .../microsoft/playwright/TestLocatorList.java | 8 +- .../microsoft/playwright/TestLocatorMisc.java | 68 +-- .../playwright/TestNetworkPostData.java | 16 +- .../playwright/TestNetworkRequest.java | 97 ++-- .../playwright/TestNetworkResponse.java | 40 +- .../playwright/TestOptionsFactories.java | 69 --- .../playwright/TestPageAddLocatorHandler.java | 52 +- .../playwright/TestPageAddScriptTag.java | 13 +- .../playwright/TestPageAriaSnapshot.java | 62 ++- .../playwright/TestPageAriaSnapshotAI.java | 32 +- .../playwright/TestPageAssertions.java | 64 +-- .../playwright/TestPageAutowaitingBasic.java | 16 +- .../microsoft/playwright/TestPageBasic.java | 154 +++--- .../microsoft/playwright/TestPageCheck.java | 20 +- .../microsoft/playwright/TestPageClock.java | 94 ++-- .../microsoft/playwright/TestPageDialog.java | 56 +-- .../microsoft/playwright/TestPageDrag.java | 36 +- .../microsoft/playwright/TestPageDrop.java | 42 +- .../playwright/TestPageEmulateMedia.java | 46 +- .../playwright/TestPageEvaluate.java | 311 ++++++------ .../playwright/TestPageEventConsole.java | 68 +-- .../playwright/TestPageEventNetwork.java | 44 +- .../playwright/TestPageEventPageError.java | 12 +- .../playwright/TestPageEventPopup.java | 8 +- .../playwright/TestPageEventRequest.java | 8 +- .../playwright/TestPageExposeFunction.java | 52 +- .../microsoft/playwright/TestPageFill.java | 116 ++--- .../playwright/TestPageInterception.java | 193 ++++---- .../playwright/TestPageKeyboard.java | 156 +++--- .../TestPageLocatorConvenience.java | 8 +- .../playwright/TestPageLocatorQuery.java | 92 ++-- .../playwright/TestPageNavigate.java | 75 +-- .../playwright/TestPageNetworkRequest.java | 39 +- .../playwright/TestPageNetworkResponse.java | 28 +- .../playwright/TestPageNetworkSizes.java | 24 +- .../playwright/TestPageRequestContinue.java | 31 +- .../playwright/TestPageRequestFallback.java | 56 +-- .../playwright/TestPageRequestFulfill.java | 16 +- .../playwright/TestPageRequestIntercept.java | 8 +- .../microsoft/playwright/TestPageRoute.java | 198 ++++---- .../playwright/TestPageScreenshot.java | 110 +++-- .../playwright/TestPageSelectOption.java | 103 ++-- .../playwright/TestPageSetContent.java | 56 +-- .../TestPageSetExtraHttpHeaders.java | 24 +- .../playwright/TestPageSetInputFiles.java | 132 ++--- .../microsoft/playwright/TestPageStrict.java | 28 +- .../playwright/TestPageWaitForNavigation.java | 72 +-- .../playwright/TestPageWaitForRequest.java | 28 +- .../playwright/TestPageWaitForResponse.java | 24 +- .../playwright/TestPageWaitForUrl.java | 44 +- .../com/microsoft/playwright/TestPdf.java | 33 +- .../playwright/TestPlaywrightCreate.java | 10 +- .../com/microsoft/playwright/TestPopup.java | 58 +-- .../playwright/TestQuerySelector.java | 68 +-- .../playwright/TestRequestContinue.java | 48 +- .../playwright/TestRequestFulfill.java | 43 +- .../microsoft/playwright/TestRequestGC.java | 8 +- .../playwright/TestRouteWebSocket.java | 228 +++++---- .../microsoft/playwright/TestScreencast.java | 50 +- .../playwright/TestSelectorsCss.java | 84 ++-- .../playwright/TestSelectorsGetBy.java | 66 +-- .../playwright/TestSelectorsMisc.java | 24 +- .../playwright/TestSelectorsRegister.java | 24 +- .../playwright/TestSelectorsRole.java | 54 +-- .../playwright/TestSelectorsText.java | 12 +- .../com/microsoft/playwright/TestTap.java | 35 +- .../com/microsoft/playwright/TestTracing.java | 131 +++-- .../playwright/TestTracingOverConnect.java | 39 +- .../playwright/TestUnresponsiveDriver.java | 11 +- .../playwright/TestUnrouteBehavior.java | 12 +- .../com/microsoft/playwright/TestVideo.java | 20 +- .../playwright/TestWaitForFunction.java | 142 +++--- .../com/microsoft/playwright/TestWebMCP.java | 71 +-- .../microsoft/playwright/TestWebSocket.java | 84 ++-- .../microsoft/playwright/TestWebStorage.java | 40 +- .../com/microsoft/playwright/TestWheel.java | 39 +- .../com/microsoft/playwright/TestWorkers.java | 80 +-- .../microsoft/playwright/TraceViewerPage.java | 3 +- .../java/com/microsoft/playwright/Utils.java | 32 ++ .../fixtures/BrowserInvocation.java | 124 +++++ .../playwright/fixtures/BrowserName.java | 58 +++ .../playwright/fixtures/BuiltinFixture.java | 127 +++++ .../fixtures/DisabledOnBrowser.java | 55 +++ .../playwright/fixtures/EnabledOnBrowser.java | 35 ++ .../microsoft/playwright/fixtures/Env.java | 82 ++++ .../playwright/fixtures/Fixture.java | 55 +++ .../fixtures/FixtureDefinition.java | 35 ++ .../playwright/fixtures/FixtureRegistry.java | 119 +++++ .../playwright/fixtures/FixtureResolver.java | 32 ++ .../playwright/fixtures/MethodFixture.java | 102 ++++ .../fixtures/PlaywrightExtension.java | 51 ++ .../playwright/fixtures/PlaywrightTest.java | 59 +++ .../microsoft/playwright/fixtures/Scope.java | 31 ++ .../fixtures/TestPlaywrightFixtures.java | 180 +++++++ .../playwright/fixtures/TestScope.java | 167 +++++++ .../fixtures/TestWorkerFixtures.java | 75 +++ .../microsoft/playwright/fixtures/Worker.java | 119 +++++ .../playwright/fixtures/WorkerPool.java | 92 ++++ .../playwright/fixtures/package-info.java | 49 ++ 173 files changed, 6219 insertions(+), 4757 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/BrowserServer.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/HttpsServer.java delete mode 100644 playwright/src/test/java/com/microsoft/playwright/TestBase.java delete mode 100644 playwright/src/test/java/com/microsoft/playwright/TestOptionsFactories.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserInvocation.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserName.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/BuiltinFixture.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/DisabledOnBrowser.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/EnabledOnBrowser.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/Env.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/Fixture.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureDefinition.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureRegistry.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureResolver.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/MethodFixture.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightTest.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/Scope.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/TestPlaywrightFixtures.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a54c0547..30eb6d9b4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,13 +47,53 @@ Each driver is assembled from the [`playwright-core`](https://www.npmjs.com/pack ```bash mvn compile +# Runs every test in Chromium, Firefox and WebKit mvn test +# Selecting browsers +BROWSER=chromium mvn test +BROWSER=firefox,webkit mvn test # Executing a single test BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize # Executing a single test class BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes ``` +Set `HEADED=1` to watch the browsers and `BROWSER_CHANNEL=chrome` (or `msedge`, ...) to test a stable channel. + +### Writing tests + +Tests use the fixtures from `playwright/src/test/java/com/microsoft/playwright/fixtures`, modeled after the +upstream Playwright Test fixtures. A test is a `@PlaywrightTest` method that declares the fixtures it needs +as parameters; it runs once per selected browser, in a fresh context: + +```java +@PlaywrightTest +void shouldWork(Page page, Server server) { + page.navigate(server.EMPTY_PAGE); +} +``` + +The built-in fixtures are `Playwright`, `BrowserName`, `BrowserType`, `BrowserType.LaunchOptions`, `Browser`, +`Browser.NewContextOptions`, `BrowserContext`, `Page`, `Server` and `HttpsServer`. Playwright, the browser and +the test servers are shared by the tests that run one after another on the same thread; the context and the +page are created for each test and closed afterwards. A test class adds or overrides fixtures with +`@Fixture` methods, whose parameters are the fixtures they depend on: + +```java +@Fixture +Browser.NewContextOptions contextOptions() { + return new Browser.NewContextOptions().setHasTouch(true); +} + +@Fixture(scope = Scope.WORKER) +static BrowserType.LaunchOptions launchOptions(BrowserType.LaunchOptions defaults) { + return defaults.setArgs(asList("--enable-features=WebMCP")); +} +``` + +Use `@DisabledOnBrowser` / `@EnabledOnBrowser` for browser-specific tests, and a `@Nested @ParameterizedClass` +with a `@Fixture` method reading its `@Parameter` field to run the same tests with different options. + ### Generating API Public Java API is generated from api.json, which is generated from the upstream Playwright source at the exact commit that produced the driver version in [scripts/DRIVER_VERSION](scripts/DRIVER_VERSION) (resolved via `npm view playwright@ gitHead`). `scripts/generate_api.sh` fetches a minimal upstream checkout automatically; set `PW_SRC_DIR` to reuse an existing `microsoft/playwright` checkout instead. To regenerate Java interfaces for the current driver run: diff --git a/playwright/src/test/java/com/microsoft/playwright/BrowserServer.java b/playwright/src/test/java/com/microsoft/playwright/BrowserServer.java new file mode 100644 index 000000000..4e7f4d5d8 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/BrowserServer.java @@ -0,0 +1,81 @@ +/* + * 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.impl.driver.Driver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.util.Collections; + +import static com.microsoft.playwright.fixtures.Env.isWindows; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * A {@code playwright launch-server} process that tests connect to, the counterpart of the + * upstream {@code startRemoteServer} fixture. + */ +class BrowserServer implements AutoCloseable { + final Process process; + final String wsEndpoint; + + static BrowserServer launch(BrowserType browserType) { + try { + Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false); + Path dir = driver.driverDir(); + String node = dir.resolve(isWindows ? "node.exe" : "node").toString(); + String cliJs = dir.resolve("package/cli.js").toString(); + // We launch node process directly instead of using playwright.sh script as killing the script + // process will leave node process running and killing it would be more hassle. + ProcessBuilder pb = new ProcessBuilder(node, cliJs, "launch-server", "--browser", browserType.name()); + pb.directory(dir.toFile()); + pb.redirectError(ProcessBuilder.Redirect.INHERIT); + Process process = pb.start(); + BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); + String wsEndpoint = input.readLine(); + if (!wsEndpoint.startsWith("ws://")) { + throw new RuntimeException("Invalid web socket address: " + wsEndpoint); + } + return new BrowserServer(process, wsEndpoint); + } catch (IOException e) { + throw new PlaywrightException("Failed to launch server", e); + } + } + + private BrowserServer(Process process, String wsEndpoint) { + this.process = process; + this.wsEndpoint = wsEndpoint; + } + + void kill() throws InterruptedException { + process.destroy(); + int exitCode = process.waitFor(); + // FIXME: 2 tests are failing this check on windows: + // disconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed + // shouldThrowWhenUsedAfterIsConnectedReturnsFalse + if (!isWindows) { + assertEquals(0, exitCode); + } + } + + @Override + public void close() { + process.destroyForcibly(); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/HttpsServer.java b/playwright/src/test/java/com/microsoft/playwright/HttpsServer.java new file mode 100644 index 000000000..a8724b30e --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/HttpsServer.java @@ -0,0 +1,28 @@ +/* + * 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.io.IOException; + +/** + * The HTTPS test server, a distinct type so that it can be injected next to the HTTP {@link Server}. + */ +public class HttpsServer extends Server { + HttpsServer(int port) throws IOException { + super(port, true); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/Server.java b/playwright/src/test/java/com/microsoft/playwright/Server.java index c8b6838a7..3c1946068 100644 --- a/playwright/src/test/java/com/microsoft/playwright/Server.java +++ b/playwright/src/test/java/com/microsoft/playwright/Server.java @@ -53,27 +53,36 @@ private Auth(String user, String password) { } } + /** Listens on a port picked by the OS, which parallel tests cannot race for. */ + public static Server createHttp() throws IOException { + return new Server(0, false); + } + public static Server createHttp(int port) throws IOException { return new Server(port, false); } - static Server createHttps(int port) throws IOException { - return new Server(port, true); + public static HttpsServer createHttps() throws IOException { + return new HttpsServer(0); } - private Server(int port, boolean https) throws IOException { - PORT = port; - PREFIX = "http" + (https ? "s" : "") + "://localhost:" + PORT; - CROSS_PROCESS_PREFIX = "http" + (https ? "s" : "") + "://127.0.0.1:" + PORT; - EMPTY_PAGE = PREFIX + "/empty.html"; + public static HttpsServer createHttps(int port) throws IOException { + return new HttpsServer(port); + } + protected Server(int port, boolean https) throws IOException { if (https) { - HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress("localhost", port), 0); + com.sun.net.httpserver.HttpsServer httpsServer = com.sun.net.httpserver.HttpsServer.create(new InetSocketAddress("localhost", port), 0); httpsServer.setHttpsConfigurator(HttpsConfiguratorImpl.create()); server = httpsServer; } else { server = HttpServer.create(new InetSocketAddress("localhost", port), 0); } + PORT = server.getAddress().getPort(); + PREFIX = "http" + (https ? "s" : "") + "://localhost:" + PORT; + CROSS_PROCESS_PREFIX = "http" + (https ? "s" : "") + "://127.0.0.1:" + PORT; + EMPTY_PAGE = PREFIX + "/empty.html"; + server.createContext("/", this); server.setExecutor(null); // creates a default executor server.start(); @@ -85,19 +94,19 @@ public void stop() { server.stop(0); } - void setAuth(String path, String user, String password) { + public void setAuth(String path, String user, String password) { auths.put(path, new Auth(user, password)); } - void setCSP(String path, String csp) { + public void setCSP(String path, String csp) { this.csp.put(path, csp); } - void enableGzip(String path) { + public void enableGzip(String path) { gzipRoutes.add(path); } - void setResourceProvider(Function resourceProvider) { + public void setResourceProvider(Function resourceProvider) { this.resourceProvider = resourceProvider; } @@ -118,7 +127,7 @@ static class Request { } } - Future futureRequest(String path) { + public Future futureRequest(String path) { CompletableFuture future = requestSubscribers.get(path); if (future == null) { future = new CompletableFuture<>(); @@ -128,11 +137,11 @@ Future futureRequest(String path) { } - void setRoute(String path, HttpHandler handler) { + public void setRoute(String path, HttpHandler handler) { routes.put(path, handler); } - void setRedirect(String from, String to) { + public void setRedirect(String from, String to) { setRoute(from, exchange -> { exchange.getResponseHeaders().put("location", Arrays.asList(to)); exchange.sendResponseHeaders(302, -1); @@ -140,7 +149,7 @@ void setRedirect(String from, String to) { }); } - void reset() { + public void reset() { requestSubscribers.clear(); auths.clear(); csp.clear(); diff --git a/playwright/src/test/java/com/microsoft/playwright/ServerWithClientCertificate.java b/playwright/src/test/java/com/microsoft/playwright/ServerWithClientCertificate.java index ed8916e57..974196e34 100644 --- a/playwright/src/test/java/com/microsoft/playwright/ServerWithClientCertificate.java +++ b/playwright/src/test/java/com/microsoft/playwright/ServerWithClientCertificate.java @@ -26,7 +26,7 @@ import java.security.cert.*; import java.util.*; -public class ServerWithClientCertificate implements HttpHandler { +public class ServerWithClientCertificate implements HttpHandler, AutoCloseable { private final HttpServer server; final String origin; final String crossOrigin; @@ -41,7 +41,7 @@ private ServerWithClientCertificate(int port) throws IOException { origin = "https://localhost:" + port; crossOrigin = "https://127.0.0.1:" + port;; url = origin + "/index.html"; - HttpsServer httpsServer = HttpsServer.create(new InetSocketAddress("localhost", port), 0); + com.sun.net.httpserver.HttpsServer httpsServer = com.sun.net.httpserver.HttpsServer.create(new InetSocketAddress("localhost", port), 0); httpsServer.setHttpsConfigurator(new HttpsConfiguratorImpl(loadCertificates())); server = httpsServer; server.createContext("/", this); @@ -53,6 +53,11 @@ public void stop() { server.stop(0); } + @Override + public void close() { + stop(); + } + private SSLContext loadCertificates() { try { // Create an SSL context diff --git a/playwright/src/test/java/com/microsoft/playwright/TestAPIResponseAssertions.java b/playwright/src/test/java/com/microsoft/playwright/TestAPIResponseAssertions.java index 3f124b9e2..e5ed6b2c2 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestAPIResponseAssertions.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestAPIResponseAssertions.java @@ -16,9 +16,7 @@ package com.microsoft.playwright; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.opentest4j.AssertionFailedError; import java.io.OutputStreamWriter; @@ -27,22 +25,20 @@ import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) public class TestAPIResponseAssertions { - @Test + @PlaywrightTest void passWithResponse(Page page, Server server) { APIResponse res = page.request().get(server.EMPTY_PAGE); assertThat(res).isOK(); } - @Test + @PlaywrightTest void passWithNot(Page page, Server server) { APIResponse res = page.request().get(server.PREFIX + "/unknown"); assertThat(res).not().isOK(); } - @Test + @PlaywrightTest void fail(Page page, Server server) { APIResponse res = page.request().get(server.PREFIX + "/unknown"); AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK()); @@ -50,14 +46,14 @@ void fail(Page page, Server server) { assertTrue(e.getMessage().contains("← 404 Not Found"), "Actual error: " + e.toString()); } - @Test + @PlaywrightTest void shouldPrintResponseTextIfIdOkFails(Page page, Server server) { APIResponse res = page.request().get(server.PREFIX + "/unknown"); AssertionFailedError e = assertThrows(AssertionFailedError.class, () -> assertThat(res).isOK()); assertTrue(e.getMessage().contains("File not found"), "Actual error: " + e.toString()); } - @Test + @PlaywrightTest void shouldOnlyPrintResponseWithTextContentTypeIfIsOkFails(Page page, Server server) { { server.setRoute("/text-content-type", exchange -> { diff --git a/playwright/src/test/java/com/microsoft/playwright/TestAssertThatIsInViewport.java b/playwright/src/test/java/com/microsoft/playwright/TestAssertThatIsInViewport.java index b2b0e56d0..cb631f72f 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestAssertThatIsInViewport.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestAssertThatIsInViewport.java @@ -17,19 +17,15 @@ package com.microsoft.playwright; import com.microsoft.playwright.assertions.LocatorAssertions; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.opentest4j.AssertionFailedError; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static org.junit.jupiter.api.Assertions.*; // Copied from expect-misc.spec.ts > toBeInViewport -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) public class TestAssertThatIsInViewport { - @Test + @PlaywrightTest void shouldWork(Page page) { page.setContent("
\n" + "
foo
"); @@ -40,7 +36,7 @@ void shouldWork(Page page) { assertThat(page.locator("#small")).isInViewport(new LocatorAssertions.IsInViewportOptions().setRatio(1)); } - @Test + @PlaywrightTest void shouldRespectRatioOption(Page page) { page.setContent("\n" + "
"); @@ -58,14 +54,14 @@ void shouldRespectRatioOption(Page page) { assertThat(page.locator("div")).not().isInViewport(new LocatorAssertions.IsInViewportOptions().setRatio(0.8)); } - @Test + @PlaywrightTest void shouldHaveGoodStack(Page page) { AssertionFailedError error = assertThrows(AssertionFailedError.class, () -> assertThat(page.locator("body")).not().isInViewport(new LocatorAssertions.IsInViewportOptions().setTimeout(100))); assertNotNull(error); assertTrue(error.getMessage().contains("Locator expected not to be in viewport"), error.getMessage()); } - @Test + @PlaywrightTest void shouldReportIntersectionEvenIfFullyCoveredByOtherElement(Page page) { page.setContent("

hello

\n" + "
"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestAutoClose.java b/playwright/src/test/java/com/microsoft/playwright/TestAutoClose.java index 8521bf3ee..179abe5e0 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestAutoClose.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestAutoClose.java @@ -16,7 +16,9 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.Env; +import com.microsoft.playwright.fixtures.PlaywrightTest; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -24,10 +26,10 @@ * This test simply makes sure that all 4 main interfaces implement AutoCloseable. If it compiles, then it passes. */ public class TestAutoClose { - @Test - void shouldAllowUsingTryWithResources() { + @PlaywrightTest + void shouldAllowUsingTryWithResources(BrowserName browserName) { try (Playwright playwright = Playwright.create(); - Browser browser = Utils.getBrowserTypeFromEnv(playwright).launch(); + Browser browser = browserName.browserType(playwright).launch(Env.launchOptions()); BrowserContext context = browser.newContext(); Page page = context.newPage()) { assertEquals(2021, page.evaluate("() => 2021")); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBase.java b/playwright/src/test/java/com/microsoft/playwright/TestBase.java deleted file mode 100644 index 9d3981f74..000000000 --- a/playwright/src/test/java/com/microsoft/playwright/TestBase.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * 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 com.microsoft.playwright.options.SameSiteAttribute; - -import javax.sql.rowset.Predicate; -import java.io.IOException; -import java.io.InputStream; -import java.security.Provider; -import java.time.Duration; -import java.time.Instant; -import java.util.function.BooleanSupplier; -import java.util.function.Supplier; - -import static com.microsoft.playwright.Utils.getBrowserNameFromEnv; -import static com.microsoft.playwright.Utils.nextFreePort; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestBase { - // Fields reset once before all tests in a class. - Server server; - Server httpsServer; - BrowserType browserType; - Playwright playwright; - Browser browser; - - static final boolean isMac = Utils.getOS() == Utils.OS.MAC; - static final boolean isLinux = Utils.getOS() == Utils.OS.LINUX; - static final boolean isWindows = Utils.getOS() == Utils.OS.WINDOWS; - static final boolean headed; - static final SameSiteAttribute defaultSameSiteCookieValue; - - static { - String headedEnv = System.getenv("HEADED"); - headed = headedEnv != null && !"0".equals(headedEnv) && !"false".equals(headedEnv); - defaultSameSiteCookieValue = initSameSiteAttribute(); - } - - // Fields reset before each test. - Page page; - BrowserContext context; - - static boolean isHeaded() { - return headed; - } - - static boolean isChromium() { - return "chromium".equals(getBrowserNameFromEnv()); - } - - static boolean isWebKit() { - return "webkit".equals(getBrowserNameFromEnv()); - } - - static boolean isFirefox() { - return "firefox".equals(getBrowserNameFromEnv()); - } - - static String getBrowserChannelFromEnv() { - return System.getenv("BROWSER_CHANNEL"); - } - - static BrowserType.LaunchOptions createLaunchOptions() { - BrowserType.LaunchOptions options; - options = new BrowserType.LaunchOptions(); - options.headless = !headed; - options.channel = getBrowserChannelFromEnv(); - return options; - } - - Playwright.CreateOptions playwrightOptions() { - return null; - } - - void initBrowserType() { - playwright = Playwright.create(playwrightOptions()); - browserType = Utils.getBrowserTypeFromEnv(playwright); - } - - void launchBrowser(BrowserType.LaunchOptions launchOptions) { - initBrowserType(); - browser = browserType.launch(launchOptions); - } - - @BeforeAll - void launchBrowser() { - launchBrowser(createLaunchOptions()); - } - - @AfterAll - void closeBrowser() { - if (browser != null) { - browser.close(); - browser = null; - } - } - - @BeforeAll - void startServer() throws IOException { - server = Server.createHttp(nextFreePort()); - httpsServer = Server.createHttps(nextFreePort()); - } - - @AfterAll - void stopServer() { - if (server != null) { - server.stop(); - server = null; - } - if (httpsServer != null) { - httpsServer.stop(); - httpsServer = null; - } - } - - @AfterAll - void closePlaywright() { - if (playwright != null) { - playwright.close(); - playwright = null; - } - } - - BrowserContext createContext() { - return browser.newContext(); - } - - @BeforeEach - void createContextAndPage() { - server.reset(); - httpsServer.reset(); - context = createContext(); - page = context.newPage(); - } - - @AfterEach - void closeContext() { - if (context != null) { - context.close(); - context = null; - page = null; - } - } - - void waitForCondition(BooleanSupplier predicate) { - waitForCondition(predicate, 5_000); - } - - void waitForCondition(BooleanSupplier predicate, int timeoutMs) { - page.waitForCondition(predicate, new Page.WaitForConditionOptions().setTimeout(timeoutMs)); - } - - private static SameSiteAttribute initSameSiteAttribute() { - if (isChromium()) return SameSiteAttribute.LAX; - if (isWebKit() && isLinux) return SameSiteAttribute.LAX; - if (isWebKit() && !isLinux) return SameSiteAttribute.NONE; - // for firefox version >= 103 'None' is used. - return SameSiteAttribute.NONE; - } - - static boolean chromiumVersionLessThan(String a, String b) { - String[] aParts = a.split("\\."); - String[] bParts = b.split("\\."); - for (int i = 0; i < 4; i++) { - int aPart = Integer.parseInt(aParts[i]); - int bPart = Integer.parseInt(bParts[i]); - if (aPart > bPart) return false; - if (aPart < bPart) return true; - } - return false; - } -} diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBeforeunload.java b/playwright/src/test/java/com/microsoft/playwright/TestBeforeunload.java index 112b49ac0..4d858c21f 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBeforeunload.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBeforeunload.java @@ -16,14 +16,10 @@ package com.microsoft.playwright; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.PlaywrightTest; -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) public class TestBeforeunload { - @Test + @PlaywrightTest void shouldBeAbleToNavigateAwayFromPageWithBeforeunload(Page page, Server server) { page.navigate(server.PREFIX + "/beforeunload.html"); // We have to interact with a page so that "beforeunload" handlers diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowser1.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowser1.java index 4d3ef7203..1ae59b41e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowser1.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowser1.java @@ -18,23 +18,20 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.fixtures.EnabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; import static org.junit.jupiter.api.Assertions.*; -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) @Tag("smoke") public class TestBrowser1 { - @Test + @PlaywrightTest void shouldCreateNewPage(Browser browser) { Page page1 = browser.newPage(); assertEquals(1, browser.contexts().size()); @@ -49,7 +46,7 @@ void shouldCreateNewPage(Browser browser) { assertEquals(0, browser.contexts().size()); } - @Test + @PlaywrightTest void shouldThrowUponSecondCreateNewPage(Browser browser) { Page page = browser.newPage(); PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.context().newPage()); @@ -57,7 +54,7 @@ void shouldThrowUponSecondCreateNewPage(Browser browser) { page.close(); } - @Test + @PlaywrightTest void versionShouldWork(Browser browser) { switch (browser.browserType().name()) { case "chromium": @@ -75,14 +72,13 @@ void versionShouldWork(Browser browser) { } } - @Test + @PlaywrightTest void shouldReturnBrowserType(BrowserType browserType, Browser browser) { assertEquals(browserType, browser.browserType()); } - @Test - @EnabledIf(value = "com.microsoft.playwright.TestOptionsFactories#isChromium", - disabledReason = "Chrome Devtools Protocol supported by chromium only") + @PlaywrightTest + @EnabledOnBrowser(value = CHROMIUM, reason = "Chrome Devtools Protocol supported by chromium only") void shouldWorkWithNewBrowserCDPSession(Browser browser) { CDPSession session = browser.newBrowserCDPSession(); @@ -106,7 +102,7 @@ void shouldWorkWithNewBrowserCDPSession(Browser browser) { session.detach(); } - @Test + @PlaywrightTest void shouldPropagateCloseReasonToPendingActions(Browser browser) { BrowserContext context = browser.newContext(); PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.waitForPage(() -> { @@ -115,7 +111,7 @@ void shouldPropagateCloseReasonToPendingActions(Browser browser) { assertTrue(e.getMessage().contains("The reason."), e.getMessage()); } - @Test + @PlaywrightTest void shouldFireContextEvent(Browser browser) { BrowserContext[] contextEvent = { null }; browser.onContext(c -> contextEvent[0] = c); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowser2.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowser2.java index f82fadd4e..989960f4b 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowser2.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowser2.java @@ -16,66 +16,47 @@ package com.microsoft.playwright; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.Options; -import com.microsoft.playwright.junit.OptionsFactory; -import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.fixtures.Env; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.BrowserChannel; import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; -import static com.microsoft.playwright.TestBrowser2.ChannelOptionsFactory.getBrowserChannelEnumFromEnv; -import static com.microsoft.playwright.TestOptionsFactories.createLaunchOptions; -import static com.microsoft.playwright.TestOptionsFactories.getBrowserChannelFromEnv; import static org.junit.jupiter.api.Assertions.assertNotNull; -@FixtureTest -@UsePlaywright(TestBrowser2.ChannelOptionsFactory.class) public class TestBrowser2 { - public static class ChannelOptionsFactory implements OptionsFactory { - @Override - public Options getOptions() { - BrowserChannel channel = getBrowserChannelEnumFromEnv(); - - BrowserType.LaunchOptions launchOptions = createLaunchOptions(); - launchOptions.channel = channel; - return new Options().setLaunchOptions(launchOptions); + private static BrowserChannel getBrowserChannelEnumFromEnv() { + String channel = Env.channel(); + if (channel == null) { + return null; } - - public static BrowserChannel getBrowserChannelEnumFromEnv() { - String channel = getBrowserChannelFromEnv(); - if (channel == null) { - return null; - } - switch (channel) { - case "chrome": - return BrowserChannel.CHROME; - case "chrome-beta": - return BrowserChannel.CHROME_BETA; - case "chrome-dev": - return BrowserChannel.CHROME_DEV; - case "chrome-canary": - return BrowserChannel.CHROME_CANARY; - case "msedge": - return BrowserChannel.MSEDGE; - case "msedge-beta": - return BrowserChannel.MSEDGE_BETA; - case "msedge-dev": - return BrowserChannel.MSEDGE_DEV; - case "msedge-canary": - return BrowserChannel.MSEDGE_CANARY; - default: - throw new IllegalArgumentException("Unknown BROWSER_CHANNEL " + channel); - } + switch (channel) { + case "chrome": + return BrowserChannel.CHROME; + case "chrome-beta": + return BrowserChannel.CHROME_BETA; + case "chrome-dev": + return BrowserChannel.CHROME_DEV; + case "chrome-canary": + return BrowserChannel.CHROME_CANARY; + case "msedge": + return BrowserChannel.MSEDGE; + case "msedge-beta": + return BrowserChannel.MSEDGE_BETA; + case "msedge-dev": + return BrowserChannel.MSEDGE_DEV; + case "msedge-canary": + return BrowserChannel.MSEDGE_CANARY; + default: + throw new IllegalArgumentException("Unknown BROWSER_CHANNEL " + channel); } } - @Test + @PlaywrightTest void shouldSupportDeprecatedChannelEnum(BrowserType browserType) { BrowserChannel channel = getBrowserChannelEnumFromEnv(); Assumptions.assumeTrue(channel != null); - BrowserType.LaunchOptions options = createLaunchOptions(); + BrowserType.LaunchOptions options = Env.launchOptions(); options.setChannel(channel); Browser browser = browserType.launch(options); assertNotNull(browser); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserBind.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserBind.java index b849293ed..e6a7e5e9c 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserBind.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserBind.java @@ -16,14 +16,14 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.BindResult; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserBind extends TestBase { - @Test - void shouldBindAndUnbindBrowser() { +public class TestBrowserBind { + @PlaywrightTest + void shouldBindAndUnbindBrowser(Browser browser) { BindResult serverInfo = browser.bind("default"); try { assertNotNull(serverInfo); @@ -34,8 +34,8 @@ void shouldBindAndUnbindBrowser() { } } - @Test - void shouldBindWithCustomTitleAndOptions() { + @PlaywrightTest + void shouldBindWithCustomTitleAndOptions(Browser browser) { BindResult serverInfo = browser.bind("my-title", new Browser.BindOptions().setHost("127.0.0.1").setPort(0)); try { diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextAddCookies.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextAddCookies.java index 1bc1fe15b..76ddcc2e6 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextAddCookies.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextAddCookies.java @@ -16,27 +16,24 @@ package com.microsoft.playwright; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.Cookie; -import org.junit.jupiter.api.Test; import java.time.Instant; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; -import static com.microsoft.playwright.TestBase.defaultSameSiteCookieValue; -import static com.microsoft.playwright.TestOptionsFactories.isFirefox; import static com.microsoft.playwright.Utils.assertJsonEquals; +import static com.microsoft.playwright.Utils.defaultSameSiteCookieValue; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.*; -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) public class TestBrowserContextAddCookies { - @Test + @PlaywrightTest void shouldWork(Page page, Server server, BrowserContext context) { page.navigate(server.EMPTY_PAGE); context.addCookies(asList( @@ -52,7 +49,7 @@ private static List normalizeExpires(List cookies) { return cookies; } - @Test + @PlaywrightTest void shouldRoundtripCookie(Page page, Server server, BrowserContext context) { page.navigate(server.EMPTY_PAGE); // @see https://en.wikipedia.org/wiki/Year_2038_problem @@ -70,7 +67,7 @@ void shouldRoundtripCookie(Page page, Server server, BrowserContext context) { assertJsonEquals(normalizeExpires(cookies), normalizeExpires(context.cookies())); } - @Test + @PlaywrightTest void shouldSendCookieHeader(Server server, BrowserContext context) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/empty.html"); context.addCookies(asList( @@ -81,7 +78,7 @@ void shouldSendCookieHeader(Server server, BrowserContext context) throws Execut assertEquals(singletonList("cookie=value"), cookies); } - @Test + @PlaywrightTest void shouldIsolateCookiesInBrowserContexts(Browser browser, BrowserContext context, Server server) { BrowserContext anotherContext = browser.newContext(); context.addCookies(asList( @@ -99,7 +96,7 @@ void shouldIsolateCookiesInBrowserContexts(Browser browser, BrowserContext conte anotherContext.close(); } - @Test + @PlaywrightTest void shouldIsolateSessionCookies(Server server, BrowserContext context, Browser browser) { server.setRoute("/setcookie.html", exchange -> { exchange.getResponseHeaders().add("Set-Cookie", "session=value"); @@ -127,7 +124,7 @@ void shouldIsolateSessionCookies(Server server, BrowserContext context, Browser } } - @Test + @PlaywrightTest void shouldIsolatePersistentCookies(Server server, BrowserContext context, Browser browser) { server.setRoute("/setcookie.html", exchange -> { exchange.getResponseHeaders().add("Set-Cookie", "persistent=persistent-value; max-age=3600"); @@ -152,7 +149,7 @@ void shouldIsolatePersistentCookies(Server server, BrowserContext context, Brows context2.close(); } - @Test + @PlaywrightTest void shouldIsolateSendCookieHeader(BrowserContext context, Server server, Browser browser) throws ExecutionException, InterruptedException { context.addCookies(asList( new Cookie("sendcookie", "value").setUrl(server.EMPTY_PAGE))); @@ -173,11 +170,10 @@ void shouldIsolateSendCookieHeader(BrowserContext context, Server server, Browse newContext.close(); } } - @Test - void shouldIsolateCookiesBetweenLaunches(Playwright playwright, Server server) { + @PlaywrightTest + void shouldIsolateCookiesBetweenLaunches(BrowserType browserType, Server server) { // test.slow(); // TODO: const browser1 = browserType.launch(browserOptions); - BrowserType browserType = Utils.getBrowserTypeFromEnv(playwright); Browser browser1 = browserType.launch(); BrowserContext context1 = browser1.newContext(); context1.addCookies(asList(new Cookie("cookie-in-context-1", "value") @@ -193,7 +189,7 @@ void shouldIsolateCookiesBetweenLaunches(Playwright playwright, Server server) { browser2.close(); } - @Test + @PlaywrightTest void shouldSetMultipleCookies(Page page, BrowserContext context, Server server) { page.navigate(server.EMPTY_PAGE); context.addCookies(asList( @@ -206,7 +202,7 @@ void shouldSetMultipleCookies(Page page, BrowserContext context, Server server) "}")); } - @Test + @PlaywrightTest void shouldHaveExpiresSetTo1ForSessionCookies(BrowserContext context, Server server) { context.addCookies(asList( new Cookie("expires", "123456").setUrl(server.EMPTY_PAGE))); @@ -214,8 +210,8 @@ void shouldHaveExpiresSetTo1ForSessionCookies(BrowserContext context, Server ser assertEquals(-1, cookies.get(0).expires); } - @Test - void shouldSetCookieWithReasonableDefaults(BrowserContext context, Server server) { + @PlaywrightTest + void shouldSetCookieWithReasonableDefaults(BrowserName browserName, BrowserContext context, Server server) { context.addCookies(asList( new Cookie("defaults", "123456").setUrl(server.EMPTY_PAGE))); List cookies = context.cookies(); @@ -227,12 +223,12 @@ void shouldSetCookieWithReasonableDefaults(BrowserContext context, Server server " expires: -1,\n" + " httpOnly: false,\n" + " secure: false,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + "}]", cookies); } - @Test - void shouldSetACookieWithAPath(Page page, Server server, BrowserContext context) { + @PlaywrightTest + void shouldSetACookieWithAPath(BrowserName browserName, Page page, Server server, BrowserContext context) { page.navigate(server.PREFIX + "/grid.html"); context.addCookies(asList(new Cookie("gridcookie", "GRID") .setDomain("localhost") @@ -246,7 +242,7 @@ void shouldSetACookieWithAPath(Page page, Server server, BrowserContext context) " expires: -1,\n" + " httpOnly: false,\n" + " secure: false,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + "}]", cookies); assertEquals("gridcookie=GRID", page.evaluate("document.cookie")); page.navigate(server.EMPTY_PAGE); @@ -255,7 +251,7 @@ void shouldSetACookieWithAPath(Page page, Server server, BrowserContext context) assertEquals("gridcookie=GRID", page.evaluate("document.cookie")); } - @Test + @PlaywrightTest void shouldNotSetACookieWithBlankPageURL(BrowserContext context, Server server) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.addCookies(asList( new Cookie("example-cookie", "best").setUrl(server.EMPTY_PAGE), @@ -264,7 +260,7 @@ void shouldNotSetACookieWithBlankPageURL(BrowserContext context, Server server) assertTrue(e.getMessage().contains("Blank page can not have cookie \"example-cookie-blank\"")); } - @Test + @PlaywrightTest void shouldNotSetACookieOnADataURLPage(BrowserContext context) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.addCookies(asList( new Cookie("example-cookie", "best").setUrl("data:,Hello%2C%20World!") @@ -272,7 +268,7 @@ void shouldNotSetACookieOnADataURLPage(BrowserContext context) { assertTrue(e.getMessage().contains("Data URL page can not have cookie \"example-cookie\"")); } - @Test + @PlaywrightTest void shouldDefaultToSettingSecureCookieForHTTPSWebsites(Page page, Server server, BrowserContext context) { page.navigate(server.EMPTY_PAGE); String SECURE_URL = "https://example.com"; @@ -284,7 +280,7 @@ void shouldDefaultToSettingSecureCookieForHTTPSWebsites(Page page, Server server assertTrue(cookies.get(0).secure); } - @Test + @PlaywrightTest void shouldBeAbleToSetUnsecureCookieForHTTPWebsite(Page page, Server server, BrowserContext context) { page.navigate(server.EMPTY_PAGE); String HTTP_URL = "http://example.com"; @@ -296,8 +292,8 @@ void shouldBeAbleToSetUnsecureCookieForHTTPWebsite(Page page, Server server, Bro assertFalse(cookies.get(0).secure); } - @Test - void shouldSetACookieOnADifferentDomain(Page page, Server server, BrowserContext context) { + @PlaywrightTest + void shouldSetACookieOnADifferentDomain(BrowserName browserName, Page page, Server server, BrowserContext context) { page.navigate(server.EMPTY_PAGE); context.addCookies(asList( new Cookie("example-cookie", "best").setUrl("https://www.example.com") @@ -311,11 +307,11 @@ void shouldSetACookieOnADifferentDomain(Page page, Server server, BrowserContext " expires: -1,\n" + " httpOnly: false,\n" + " secure: true,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + "}]", context.cookies("https://www.example.com")); } - @Test + @PlaywrightTest void shouldSetCookiesForAFrame(Page page, Server server, BrowserContext context) { page.navigate(server.EMPTY_PAGE); context.addCookies(asList( @@ -333,8 +329,8 @@ void shouldSetCookiesForAFrame(Page page, Server server, BrowserContext context) assertEquals("frame-cookie=value", page.frames().get(1).evaluate("document.cookie")); } - @Test - void shouldNotBlockThirdPartyCookies(Page page, Server server, BrowserContext context) { + @PlaywrightTest + void shouldNotBlockThirdPartyCookies(BrowserName browserName, Page page, Server server, BrowserContext context) { page.navigate(server.EMPTY_PAGE); page.evaluate("src => {\n" + " let fulfill;\n" + @@ -347,7 +343,7 @@ void shouldNotBlockThirdPartyCookies(Page page, Server server, BrowserContext co "}", server.CROSS_PROCESS_PREFIX + "/grid.html"); page.frames().get(1).evaluate("document.cookie = 'username=John Doe'"); page.waitForTimeout(2000); - boolean allowsThirdParty = isFirefox(); + boolean allowsThirdParty = browserName == FIREFOX; List cookies = context.cookies(server.CROSS_PROCESS_PREFIX + "/grid.html"); if (allowsThirdParty) { assertJsonEquals("[{\n" + diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBaseUrl.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBaseUrl.java index 91e127df1..32b2b8c5e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBaseUrl.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBaseUrl.java @@ -16,19 +16,15 @@ package com.microsoft.playwright; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.PlaywrightTest; import java.net.MalformedURLException; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) public class TestBrowserContextBaseUrl { - @Test + @PlaywrightTest void shouldConstructANewURLWhenABaseURLInBrowserNewContextIsPassedToPageGoto(Browser browser, Server server) { try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setBaseURL(server.PREFIX))) { Page page = context.newPage(); @@ -36,13 +32,13 @@ void shouldConstructANewURLWhenABaseURLInBrowserNewContextIsPassedToPageGoto(Bro } } - @Test + @PlaywrightTest void shouldConstructANewURLWhenABaseURLInBrowserNewPageIsPassedToPageGoto(Browser browser, Server server) { try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX))) { assertEquals(server.EMPTY_PAGE, page.navigate("/empty.html").url()); } } - @Test + @PlaywrightTest void shouldConstructTheURLsCorrectlyWhenABaseURLWithoutATrailingSlashInBrowserNewPageIsPassedToPageGoto(Browser browser, Server server) { try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/url-construction"))) { assertEquals(server.PREFIX + "/mypage.html", page.navigate("mypage.html").url()); @@ -51,7 +47,7 @@ void shouldConstructTheURLsCorrectlyWhenABaseURLWithoutATrailingSlashInBrowserNe } } - @Test + @PlaywrightTest void shouldConstructTheURLsCorrectlyWhenABaseURLWithATrailingSlashInBrowserNewPageIsPassedToPageGoto(Browser browser, Server server) { try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/url-construction/"))) { assertEquals(server.PREFIX + "/url-construction/mypage.html", page.navigate("mypage.html").url()); @@ -62,7 +58,7 @@ void shouldConstructTheURLsCorrectlyWhenABaseURLWithATrailingSlashInBrowserNewPa } } - @Test + @PlaywrightTest void shouldNotConstructANewURLWhenValidURLsArePassed(Browser browser, Server server) { try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL("http://microsoft.com"))) { assertEquals(server.EMPTY_PAGE, page.navigate(server.EMPTY_PAGE).url()); @@ -75,7 +71,7 @@ void shouldNotConstructANewURLWhenValidURLsArePassed(Browser browser, Server ser } } - @Test + @PlaywrightTest void shouldBeAbleToMatchAURLRelativeToItsGivenURLWithUrlMatcher(Browser browser, Server server) { try (Page page = browser.newPage(new Browser.NewPageOptions().setBaseURL(server.PREFIX + "/foobar/"))) { page.navigate("/kek/index.html"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBasic.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBasic.java index f78dd8703..f0b7f9c07 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBasic.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextBasic.java @@ -16,26 +16,23 @@ package com.microsoft.playwright; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; import java.io.OutputStreamWriter; import java.util.ArrayList; import java.util.List; -import static com.microsoft.playwright.TestOptionsFactories.isFirefox; -import static com.microsoft.playwright.TestOptionsFactories.isWebKit; import static com.microsoft.playwright.Utils.verifyViewport; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; +import static com.microsoft.playwright.fixtures.BrowserName.WEBKIT; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) @Tag("smoke") public class TestBrowserContextBasic { - @Test + @PlaywrightTest void shouldCreateNewContext(Browser browser) { assertEquals(0, browser.contexts().size()); BrowserContext context = browser.newContext(); @@ -47,7 +44,7 @@ void shouldCreateNewContext(Browser browser) { assertEquals(context.browser(), browser); } - @Test + @PlaywrightTest void windowOpenShouldUseParentTabContext(Browser browser, Server server) { BrowserContext context = browser.newContext(); Page page = context.newPage(); @@ -57,7 +54,7 @@ void windowOpenShouldUseParentTabContext(Browser browser, Server server) { context.close(); } - @Test + @PlaywrightTest void shouldIsolateLocalStorageAndCookies(Browser browser, Server server) { // Create two incognito contexts. BrowserContext context1 = browser.newContext(); @@ -102,7 +99,7 @@ void shouldIsolateLocalStorageAndCookies(Browser browser, Server server) { assertEquals(0, browser.contexts().size()); } - @Test + @PlaywrightTest void shouldPropagateDefaultViewportToThePage(Browser browser) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setViewportSize(456, 789)); Page page = context.newPage(); @@ -114,7 +111,7 @@ void shouldMakeACopyOfDefaultViewport() { // Doesn't make sense in Java. } - @Test + @PlaywrightTest void shouldRespectDeviceScaleFactor(Browser browser) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(3.0)); Page page = context.newPage(); @@ -123,7 +120,7 @@ void shouldRespectDeviceScaleFactor(Browser browser) { } - @Test + @PlaywrightTest void shouldNotAllowDeviceScaleFactorWithNullViewport(Browser browser) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> { browser.newContext(new Browser.NewContextOptions().setDeviceScaleFactor(1.0).setViewportSize(null)); @@ -131,7 +128,7 @@ void shouldNotAllowDeviceScaleFactorWithNullViewport(Browser browser) { assertTrue(e.getMessage().contains("\"deviceScaleFactor\" option is not supported with null \"viewport\"")); } - @Test + @PlaywrightTest void shouldNotAllowIsMobileWithNullViewport(Browser browser) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> { browser.newContext(new Browser.NewContextOptions().setIsMobile(true).setViewportSize(null)); @@ -139,13 +136,13 @@ void shouldNotAllowIsMobileWithNullViewport(Browser browser) { assertTrue(e.getMessage().contains("\"isMobile\" option is not supported with null \"viewport\"")); } - @Test + @PlaywrightTest void closeShouldWorkForEmptyContext(Browser browser) { BrowserContext context = browser.newContext(); context.close(); } - @Test + @PlaywrightTest void closeShouldAbortFutureEvent(Browser browser) { BrowserContext context = browser.newContext(); PlaywrightException e = assertThrows(PlaywrightException.class, () -> { @@ -154,7 +151,7 @@ void closeShouldAbortFutureEvent(Browser browser) { assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage()); } - @Test + @PlaywrightTest void closeShouldBeCallableTwice(Browser browser) { BrowserContext context = browser.newContext(); context.close(); @@ -162,7 +159,7 @@ void closeShouldBeCallableTwice(Browser browser) { context.close(); } - @Test + @PlaywrightTest void shouldNotReportFramelessPagesOnError(Browser browser, Server server) { BrowserContext context = browser.newContext(); Page page = context.newPage(); @@ -187,7 +184,7 @@ void shouldNotReportFramelessPagesOnError(Browser browser, Server server) { } } - @Test + @PlaywrightTest void shouldReturnAllOfThePages(Browser browser) { BrowserContext context = browser.newContext(); Page page = context.newPage(); @@ -199,7 +196,7 @@ void shouldReturnAllOfThePages(Browser browser) { context.close(); } - @Test + @PlaywrightTest void shouldCloseAllBelongingPagesOnceClosingContext(Browser browser) { BrowserContext context = browser.newContext(); context.newPage(); @@ -208,14 +205,14 @@ void shouldCloseAllBelongingPagesOnceClosingContext(Browser browser) { assertEquals(0, context.pages().size()); } - @Test - void shouldDisableJavascript(Browser browser) { + @PlaywrightTest + void shouldDisableJavascript(BrowserName browserName, Browser browser) { { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setJavaScriptEnabled(false)); Page page = context.newPage(); page.navigate("data:text/html, "); PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.evaluate("something")); - if (isWebKit()) + if (browserName == WEBKIT) assertTrue(e.getMessage().contains("Can\'t find variable: something")); else assertTrue(e.getMessage().contains("something is not defined")); @@ -231,7 +228,7 @@ void shouldDisableJavascript(Browser browser) { } } - @Test + @PlaywrightTest void shouldBeAbleToNavigateAfterDisablingJavascript(Browser browser, Server server) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setJavaScriptEnabled(false)); Page page = context.newPage(); @@ -239,11 +236,11 @@ void shouldBeAbleToNavigateAfterDisablingJavascript(Browser browser, Server serv context.close(); } - @Test - void shouldWorkWithOfflineOption(Browser browser, Server server) { + @PlaywrightTest + void shouldWorkWithOfflineOption(BrowserName browserName, Browser browser, Server server) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setOffline(true)); Page page = context.newPage(); - if (isFirefox()) { + if (browserName == FIREFOX) { // Firefox navigates to an error page, and this navigation might conflict with the // next navigation we do in test. // So we need to wait for the navigation explicitly. @@ -260,7 +257,7 @@ void shouldWorkWithOfflineOption(Browser browser, Server server) { context.close(); } - @Test + @PlaywrightTest void shouldEmulateNavigatorOnLine(Browser browser) { BrowserContext context = browser.newContext(); Page page = context.newPage(); @@ -272,7 +269,7 @@ void shouldEmulateNavigatorOnLine(Browser browser) { context.close(); } - @Test + @PlaywrightTest void shouldWaitForCondition(Page page, BrowserContext context) { List messages = new ArrayList<>(); page.onConsoleMessage(m -> messages.add(m.text())); @@ -284,14 +281,14 @@ void shouldWaitForCondition(Page page, BrowserContext context) { assertEquals(asList("foo", "bar"), messages); } - @Test + @PlaywrightTest void waitForConditionTimeout(BrowserContext context) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.waitForCondition(() -> false, new BrowserContext.WaitForConditionOptions().setTimeout(100))); assertTrue(e.getMessage().contains("Timeout"), e.getMessage()); } - @Test + @PlaywrightTest void waitForConditionPageClosed(BrowserContext context) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.waitForCondition(() -> { @@ -301,14 +298,14 @@ void waitForConditionPageClosed(BrowserContext context) { assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage()); } - @Test + @PlaywrightTest void waitForConditionThatMayChangeToFalse(BrowserContext context) { int[] var = {0}; context.waitForCondition(() -> ++var[0] == 1); assertEquals(1, var[0], "The predicate should be called only once."); } - @Test + @PlaywrightTest void shouldPropagateCloseReasonToPendingActions(Browser browser) { BrowserContext context = browser.newContext(); Page page = context.newPage(); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCDPSession.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCDPSession.java index bf0ef7d5a..dcef55b54 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCDPSession.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCDPSession.java @@ -19,20 +19,21 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; +import com.microsoft.playwright.fixtures.EnabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; import static org.junit.jupiter.api.Assertions.*; -@EnabledIf(value = "com.microsoft.playwright.TestBase#isChromium", disabledReason = "Chrome Devtools Protocol supported by chromium only") -public class TestBrowserContextCDPSession extends TestBase { +@EnabledOnBrowser(value = CHROMIUM, reason = "Chrome Devtools Protocol supported by chromium only") +public class TestBrowserContextCDPSession { - @Test - void shouldWork() { + @PlaywrightTest + void shouldWork(Page page) { CDPSession cdpSession = page.context().newCDPSession(page); cdpSession.send("Runtime.enable"); @@ -44,8 +45,8 @@ void shouldWork() { assertEquals("bar", foo); } - @Test - void shouldSendEvents() { + @PlaywrightTest + void shouldSendEvents(Page page, Server server) { CDPSession cdpSession = page.context().newCDPSession(page); cdpSession.send("Network.enable"); @@ -61,8 +62,8 @@ void shouldSendEvents() { assertEquals(1, events.size()); } - @Test - void shouldDetachSession() { + @PlaywrightTest + void shouldDetachSession(Page page) { CDPSession cdpSession = page.context().newCDPSession(page); cdpSession.send("Runtime.enable"); @@ -81,8 +82,8 @@ void shouldDetachSession() { assertTrue(exception.getMessage().contains("Target page, context or browser has been closed")); } - @Test - void shouldThrowNiceErrors() { + @PlaywrightTest + void shouldThrowNiceErrors(Page page) { CDPSession cdpSession = page.context().newCDPSession(page); PlaywrightException exception = assertThrows(PlaywrightException.class, () -> { @@ -91,8 +92,8 @@ void shouldThrowNiceErrors() { assertTrue(exception.getMessage().contains("'ThisCommand.DoesNotExist' wasn't found")); } - @Test - void shouldWorkWithMainFrame() { + @PlaywrightTest + void shouldWorkWithMainFrame(Page page) { CDPSession cdpSession = page.context().newCDPSession(page.mainFrame()); JsonObject params = new JsonObject(); params.addProperty("expression", "window.foo = 'bar'"); @@ -102,8 +103,8 @@ void shouldWorkWithMainFrame() { assertEquals("bar", foo); } - @Test - void shouldThrowIfTargetIsPartOfMain() { + @PlaywrightTest + void shouldThrowIfTargetIsPartOfMain(Page page, Server server) { page.navigate(server.PREFIX + "/frames/one-frame.html"); assertEquals(server.PREFIX + "/frames/one-frame.html", page.frames().get(0).url()); assertEquals(server.PREFIX + "/frames/frame.html", page.frames().get(1).url()); @@ -114,8 +115,8 @@ void shouldThrowIfTargetIsPartOfMain() { assertTrue(exception.getMessage().contains("This frame does not have a separate CDP session, it is a part of the parent frame's session")); } - @Test - void shouldNotBreakPageClose() { + @PlaywrightTest + void shouldNotBreakPageClose(Browser browser) { BrowserContext context = browser.newContext(); Page page = context.newPage(); CDPSession session = page.context().newCDPSession(page); @@ -124,8 +125,8 @@ void shouldNotBreakPageClose() { context.close(); } - @Test - void shouldDetachWhenPageCloses() { + @PlaywrightTest + void shouldDetachWhenPageCloses(Browser browser) { BrowserContext context = browser.newContext(); Page page = context.newPage(); CDPSession session = page.context().newCDPSession(page); @@ -137,8 +138,8 @@ void shouldDetachWhenPageCloses() { context.close(); } - @Test - void shouldAddMultipleEventListeners() { + @PlaywrightTest + void shouldAddMultipleEventListeners(Page page, Server server) { CDPSession cdpSession = page.context().newCDPSession(page); cdpSession.send("Network.enable"); @@ -156,8 +157,8 @@ void shouldAddMultipleEventListeners() { assertEquals(2, events.size()); } - @Test - void shouldEmitEventForEachCDPEvent() { + @PlaywrightTest + void shouldEmitEventForEachCDPEvent(Page page, Server server) { CDPSession client = page.context().newCDPSession(page); client.send("Network.enable"); List events = new ArrayList<>(); @@ -176,8 +177,8 @@ void shouldEmitEventForEachCDPEvent() { requestEvent.getAsJsonObject("params").getAsJsonObject("request").get("url").getAsString()); } - @Test - void shouldRemoveEventListeners() { + @PlaywrightTest + void shouldRemoveEventListeners(Page page, Server server) { CDPSession cdpSession = page.context().newCDPSession(page); cdpSession.send("Network.enable"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java index 7d8d24536..7f37dc421 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextClearCookies.java @@ -16,10 +16,8 @@ package com.microsoft.playwright; -import com.microsoft.playwright.junit.FixtureTest; -import com.microsoft.playwright.junit.UsePlaywright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.Cookie; -import org.junit.jupiter.api.Test; import java.net.MalformedURLException; import java.net.URL; @@ -30,10 +28,8 @@ import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; -@FixtureTest -@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class) public class TestBrowserContextClearCookies { - @Test + @PlaywrightTest void shouldClearCookies(Page page, BrowserContext context, Server server) { page.navigate(server.EMPTY_PAGE); context.addCookies(asList( @@ -45,7 +41,7 @@ void shouldClearCookies(Page page, BrowserContext context, Server server) { assertEquals("", page.evaluate("document.cookie")); } - @Test + @PlaywrightTest void shouldIsolateCookiesWhenClearing(BrowserContext context, Browser browser, Server server) { BrowserContext anotherContext = browser.newContext(); context.addCookies(asList( @@ -66,7 +62,7 @@ void shouldIsolateCookiesWhenClearing(BrowserContext context, Browser browser, S anotherContext.close(); } - @Test + @PlaywrightTest void shouldRemoveCookiesByName(Page page, BrowserContext context, Server server) throws MalformedURLException { context.addCookies(Arrays.asList( new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"), @@ -79,7 +75,7 @@ void shouldRemoveCookiesByName(Page page, BrowserContext context, Server server) assertEquals("cookie2=2", page.evaluate("document.cookie")); } - @Test + @PlaywrightTest public void shouldRemoveCookiesByNameRegex(Page page, BrowserContext context, Server server) throws MalformedURLException { context.addCookies(Arrays.asList( new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"), @@ -92,7 +88,7 @@ public void shouldRemoveCookiesByNameRegex(Page page, BrowserContext context, Se assertEquals("cookie2=2", page.evaluate("document.cookie")); } - @Test + @PlaywrightTest public void shouldRemoveCookiesByDomain(Page page, BrowserContext context, Server server) throws MalformedURLException { context.addCookies(Arrays.asList( new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"), @@ -108,7 +104,7 @@ public void shouldRemoveCookiesByDomain(Page page, BrowserContext context, Serve assertEquals("cookie1=1", page.evaluate("document.cookie")); } - @Test + @PlaywrightTest public void shouldRemoveCookiesByPath(Page page, BrowserContext context, Server server) throws MalformedURLException { context.addCookies(Arrays.asList( new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/api/v1"), @@ -125,7 +121,7 @@ public void shouldRemoveCookiesByPath(Page page, BrowserContext context, Server assertEquals("cookie3=3", page.evaluate("document.cookie")); } - @Test + @PlaywrightTest public void shouldRemoveCookiesByNameAndDomain(Page page, BrowserContext context, Server server) throws MalformedURLException { context.addCookies(Arrays.asList( new Cookie("cookie1", "1").setDomain(new URL(server.PREFIX).getHost()).setPath("/"), diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java index a61ee5d87..450b00314 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCookies.java @@ -16,23 +16,29 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.DisabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.Cookie; import com.microsoft.playwright.options.SameSiteAttribute; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.condition.OS; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; import static com.microsoft.playwright.Utils.assertJsonEquals; +import static com.microsoft.playwright.Utils.defaultSameSiteCookieValue; import static com.microsoft.playwright.Utils.getOS; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; +import static com.microsoft.playwright.fixtures.BrowserName.WEBKIT; +import static com.microsoft.playwright.fixtures.Env.isLinux; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextCookies extends TestBase { - @Test - void shouldGetACookie() { +public class TestBrowserContextCookies { + @PlaywrightTest + void shouldGetACookie(Page page, BrowserContext context, Server server, BrowserName browserName) { page.navigate(server.EMPTY_PAGE); Object documentCookie = page.evaluate("() => {\n" + " document.cookie = 'username=John Doe';\n" + @@ -48,13 +54,13 @@ void shouldGetACookie() { " expires: -1,\n" + " httpOnly: false,\n" + " secure: false,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + " }]", cookies); } - @Test - void shouldGetANonSessionCookie() { + @PlaywrightTest + void shouldGetANonSessionCookie(Page page, BrowserContext context, Server server, BrowserName browserName) { page.navigate(server.EMPTY_PAGE); // @see https://en.wikipedia.org/wiki/Year_2038_problem Object documentCookie = page.evaluate("() => {\n" + @@ -71,8 +77,8 @@ void shouldGetANonSessionCookie() { assertEquals("/", cookies.get(0).path); assertFalse(cookies.get(0).httpOnly); assertFalse(cookies.get(0).secure); - assertEquals(defaultSameSiteCookieValue, cookies.get(0).sameSite); - assertEquals(defaultSameSiteCookieValue, cookies.get(0).sameSite); + assertEquals(defaultSameSiteCookieValue(browserName), cookies.get(0).sameSite); + assertEquals(defaultSameSiteCookieValue(browserName), cookies.get(0).sameSite); // Browsers start to cap cookies with 400 days max expires value. // See https://github.com/httpwg/http-extensions/pull/1732 @@ -83,8 +89,8 @@ void shouldGetANonSessionCookie() { assertTrue(cookies.get(0).expires > ((System.currentTimeMillis() + FOUR_HUNDRED_DAYS - FIVE_MINUTES) / 1000)); } - @Test - void shouldProperlyReportHttpOnlyCookie() { + @PlaywrightTest + void shouldProperlyReportHttpOnlyCookie(Page page, BrowserContext context, Server server) { server.setRoute("/empty.html", exchange -> { exchange.getResponseHeaders().add("Set-Cookie", "name=value;HttpOnly; Path=/"); exchange.sendResponseHeaders(200, 0); @@ -96,13 +102,9 @@ void shouldProperlyReportHttpOnlyCookie() { assertTrue(cookies.get(0).httpOnly); } - static boolean isWebKitWindows() { - return isWebKit() && getOS() == Utils.OS.WINDOWS; - } - - @Test - @DisabledIf(value="isWebKitWindows", disabledReason="fail") - void shouldProperlyReportStrictSameSiteCookie() { + @PlaywrightTest + @DisabledOnBrowser(value = WEBKIT, os = OS.WINDOWS, reason = "fail") + void shouldProperlyReportStrictSameSiteCookie(Page page, BrowserContext context, Server server) { server.setRoute("/empty.html", exchange -> { exchange.getResponseHeaders().add("Set-Cookie", "name=value;SameSite=Strict"); exchange.sendResponseHeaders(200, 0); @@ -114,9 +116,9 @@ void shouldProperlyReportStrictSameSiteCookie() { assertEquals(SameSiteAttribute.STRICT, cookies.get(0).sameSite); } - @Test - @DisabledIf(value="isWebKitWindows", disabledReason="fail") - void shouldProperlyReportLaxSameSiteCookie() { + @PlaywrightTest + @DisabledOnBrowser(value = WEBKIT, os = OS.WINDOWS, reason = "fail") + void shouldProperlyReportLaxSameSiteCookie(Page page, BrowserContext context, Server server) { server.setRoute("/empty.html", exchange -> { exchange.getResponseHeaders().add("Set-Cookie", "name=value;SameSite=Lax"); exchange.sendResponseHeaders(200, 0); @@ -128,8 +130,8 @@ void shouldProperlyReportLaxSameSiteCookie() { assertEquals(SameSiteAttribute.LAX, cookies.get(0).sameSite); } - @Test - void shouldGetMultipleCookies() { + @PlaywrightTest + void shouldGetMultipleCookies(Page page, BrowserContext context, Server server, BrowserName browserName) { page.navigate(server.EMPTY_PAGE); Object documentCookie = page.evaluate("() => {\n" + " document.cookie = 'username=John Doe';\n" + @@ -148,7 +150,7 @@ void shouldGetMultipleCookies() { " expires: -1,\n" + " httpOnly: false,\n" + " secure: false,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + " },\n" + " {\n" + " name: 'username',\n" + @@ -158,13 +160,13 @@ void shouldGetMultipleCookies() { " expires: -1,\n" + " httpOnly: false,\n" + " secure: false,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + " }\n" + "]", cookies); } - @Test - void shouldGetCookiesFromMultipleUrls() { + @PlaywrightTest + void shouldGetCookiesFromMultipleUrls(BrowserContext context, BrowserName browserName) { context.addCookies(asList( new Cookie("doggo", "woofs").setUrl("https://foo.com"), new Cookie("catto", "purrs").setUrl("https://bar.com"), @@ -179,7 +181,7 @@ void shouldGetCookiesFromMultipleUrls() { " expires: -1.0,\n" + " httpOnly: false,\n" + " secure: true,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + "}, {\n" + " name: 'doggo',\n" + " value: 'woofs',\n" + @@ -188,17 +190,13 @@ void shouldGetCookiesFromMultipleUrls() { " expires: -1.0,\n" + " httpOnly: false,\n" + " secure: true,\n" + - " sameSite: '" + defaultSameSiteCookieValue +"'\n" + + " sameSite: '" + defaultSameSiteCookieValue(browserName) +"'\n" + "}]", cookies); } - static boolean isWebkitWindows() { - return isWebKit() && isWindows; - } - - @Test - @DisabledIf(value="isWebkitWindows", disabledReason="Same site is not implemented in curl") - void shouldAcceptSameSiteAttribute() { + @PlaywrightTest + @DisabledOnBrowser(value = WEBKIT, os = OS.WINDOWS, reason = "Same site is not implemented in curl") + void shouldAcceptSameSiteAttribute(Page page, BrowserContext context, Server server, BrowserName browserName) { context.addCookies(asList( new Cookie("one", "uno").setUrl(server.EMPTY_PAGE).setSameSite(SameSiteAttribute.LAX), new Cookie("two", "dos").setUrl(server.EMPTY_PAGE).setSameSite(SameSiteAttribute.STRICT), @@ -206,14 +204,14 @@ void shouldAcceptSameSiteAttribute() { page.navigate(server.EMPTY_PAGE); Object documentCookie = page.evaluate("document.cookie.split('; ').sort().join('; ')"); - if (isChromium() || (isLinux && isWebKit())) { + if (browserName == CHROMIUM || (isLinux && browserName == WEBKIT)) { assertEquals("one=uno; two=dos", documentCookie); } else { assertEquals("one=uno; three=tres; two=dos", documentCookie); } List list = context.cookies().stream().map(c -> c.sameSite).sorted().collect(Collectors.toList()); - if (isChromium() || (isLinux && isWebKit())) { + if (browserName == CHROMIUM || (isLinux && browserName == WEBKIT)) { assertEquals(asList(SameSiteAttribute.STRICT, SameSiteAttribute.LAX), list); } else { assertEquals(asList(SameSiteAttribute.STRICT, SameSiteAttribute.LAX, SameSiteAttribute.NONE), list); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCredentials.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCredentials.java index cba39f28d..aa9241d66 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCredentials.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextCredentials.java @@ -16,24 +16,27 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.Env; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.HttpCredentials; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.Assumptions; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class TestBrowserContextCredentials extends TestBase { +public class TestBrowserContextCredentials { - static boolean isChromiumHeadedLike() { + static boolean isChromiumHeadedLike(BrowserName browserName) { // --headless=new, the default in all Chromium channels, is like headless. - return isChromium() && (isHeaded() || getBrowserChannelFromEnv() != null); + return browserName == CHROMIUM && (Env.headed() || Env.channel() != null); } - @Test - @DisabledIf(value="isChromiumHeadedLike", disabledReason="fail") - void shouldFailWithoutCredentials() { + @PlaywrightTest + void shouldFailWithoutCredentials(Page page, Server server, BrowserName browserName) { + Assumptions.assumeFalse(isChromiumHeadedLike(browserName), "fail"); server.setAuth("/empty.html", "user", "pass"); Response response = page.navigate(server.EMPTY_PAGE); assertEquals(401, response.status()); @@ -43,8 +46,8 @@ void shouldWorkWithSetHTTPCredentials() { // The method is not exposed in Java. } - @Test - void shouldWorkWithCorrectCredentials() { + @PlaywrightTest + void shouldWorkWithCorrectCredentials(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials("user", "pass"))) { @@ -54,8 +57,8 @@ void shouldWorkWithCorrectCredentials() { } } - @Test - void shouldFailWithWrongCredentials() { + @PlaywrightTest + void shouldFailWithWrongCredentials(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials("foo", "bar"))) { @@ -65,8 +68,8 @@ void shouldFailWithWrongCredentials() { } } - @Test - void shouldReturnResourceBody() { + @PlaywrightTest + void shouldReturnResourceBody(Browser browser, Server server) { server.setAuth("/playground.html", "user", "pass"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials("user", "pass"))) { @@ -78,8 +81,8 @@ void shouldReturnResourceBody() { } } - @Test - void shouldWorkWithCorrectCredentialsAndMatchingOrigin() { + @PlaywrightTest + void shouldWorkWithCorrectCredentialsAndMatchingOrigin(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(server.PREFIX); @@ -91,8 +94,8 @@ void shouldWorkWithCorrectCredentialsAndMatchingOrigin() { } } - @Test - void shouldWorkWithCorrectCredentialsAndMatchingOriginCaseInsensitive() { + @PlaywrightTest + void shouldWorkWithCorrectCredentialsAndMatchingOriginCaseInsensitive(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(server.PREFIX.toUpperCase()); @@ -104,9 +107,9 @@ void shouldWorkWithCorrectCredentialsAndMatchingOriginCaseInsensitive() { } } - @Test - @DisabledIf(value="isChromiumHeadedLike", disabledReason="fail") - void shouldFailWithCorrectCredentialsAndWrongOriginScheme() { + @PlaywrightTest + void shouldFailWithCorrectCredentialsAndWrongOriginScheme(Browser browser, Server server, BrowserName browserName) { + Assumptions.assumeFalse(isChromiumHeadedLike(browserName), "fail"); server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(Utils.generateDifferentOriginScheme(server)); @@ -117,9 +120,9 @@ void shouldFailWithCorrectCredentialsAndWrongOriginScheme() { } } - @Test - @DisabledIf(value="isChromiumHeadedLike", disabledReason="fail") - void shouldFailWithCorrectCredentialsAndWrongOriginHostname() { + @PlaywrightTest + void shouldFailWithCorrectCredentialsAndWrongOriginHostname(Browser browser, Server server, BrowserName browserName) { + Assumptions.assumeFalse(isChromiumHeadedLike(browserName), "fail"); server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(Utils.generateDifferentOriginHostname(server)); @@ -130,9 +133,9 @@ void shouldFailWithCorrectCredentialsAndWrongOriginHostname() { } } - @Test - @DisabledIf(value="isChromiumHeadedLike", disabledReason="fail") - void shouldFailWithCorrectCredentialsAndWrongOriginPort() { + @PlaywrightTest + void shouldFailWithCorrectCredentialsAndWrongOriginPort(Browser browser, Server server, BrowserName browserName) { + Assumptions.assumeFalse(isChromiumHeadedLike(browserName), "fail"); server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(Utils.generateDifferentOriginPort(server)); @@ -143,8 +146,8 @@ void shouldFailWithCorrectCredentialsAndWrongOriginPort() { } } - @Test - void shouldWorkWithASingleCredentialInAnArray() { + @PlaywrightTest + void shouldWorkWithASingleCredentialInAnArray(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials(asList(new HttpCredentials("user", "pass"))))) { @@ -154,8 +157,8 @@ void shouldWorkWithASingleCredentialInAnArray() { } } - @Test - void shouldWorkWithMultipleCredentialsForDifferentOrigins() { + @PlaywrightTest + void shouldWorkWithMultipleCredentialsForDifferentOrigins(Browser browser, Server server) { server.setAuth("/empty.html", "user1", "pass1"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials(asList( @@ -170,8 +173,8 @@ void shouldWorkWithMultipleCredentialsForDifferentOrigins() { } } - @Test - void shouldFallBackToCredentialsWithoutOrigin() { + @PlaywrightTest + void shouldFallBackToCredentialsWithoutOrigin(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials(asList( @@ -186,8 +189,8 @@ void shouldFallBackToCredentialsWithoutOrigin() { } } - @Test - void shouldUseTheFirstMatchingCredential() { + @PlaywrightTest + void shouldUseTheFirstMatchingCredential(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials(asList( diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextEvents.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextEvents.java index 6f7b85eaf..26b41f2ec 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextEvents.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextEvents.java @@ -16,18 +16,20 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import com.microsoft.playwright.fixtures.DisabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import java.io.OutputStreamWriter; import java.io.Writer; +import static com.microsoft.playwright.Utils.waitForCondition; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class TestBrowserContextEvents extends TestBase { - @Test - void consoleEventShouldWorkSmoke() { +public class TestBrowserContextEvents { + @PlaywrightTest + void consoleEventShouldWorkSmoke(Page page, BrowserContext context) { ConsoleMessage message = context.waitForConsoleMessage(() -> { page.evaluate("console.log('hello')"); }); @@ -35,8 +37,8 @@ void consoleEventShouldWorkSmoke() { assertEquals(page, message.page()); } - @Test - void consoleEventShouldWorkInPopup() { + @PlaywrightTest + void consoleEventShouldWorkInPopup(Page page, BrowserContext context) { Page[] popup = { null }; ConsoleMessage message = context.waitForConsoleMessage(() -> { popup[0] = page.waitForPopup(() -> { @@ -48,9 +50,9 @@ void consoleEventShouldWorkInPopup() { assertEquals(popup[0], message.page()); } - @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="console message from javascript: url is not reported at all") - void consoleEventShouldWorkInPopup2() { + @PlaywrightTest + @DisabledOnBrowser(value = FIREFOX, reason = "console message from javascript: url is not reported at all") + void consoleEventShouldWorkInPopup2(Page page, BrowserContext context) { Page[] popup = { null }; ConsoleMessage message = context.waitForConsoleMessage( new BrowserContext.WaitForConsoleMessageOptions().setPredicate(msg -> "log".equals(msg.type())), @@ -67,9 +69,9 @@ void consoleEventShouldWorkInPopup2() { assertEquals(popup[0], message.page()); } - @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="console message is not reported at all") - void consoleEventShouldWorkInImmediatelyClosedPopup() { + @PlaywrightTest + @DisabledOnBrowser(value = FIREFOX, reason = "console message is not reported at all") + void consoleEventShouldWorkInImmediatelyClosedPopup(Page page, BrowserContext context) { Page[] popup = { null }; ConsoleMessage message = context.waitForConsoleMessage(() -> { popup[0] = page.waitForPopup(() -> { @@ -84,8 +86,8 @@ void consoleEventShouldWorkInImmediatelyClosedPopup() { assertEquals(popup[0], message.page()); } - @Test - void dialogEventShouldWorkSmoke() { + @PlaywrightTest + void dialogEventShouldWorkSmoke(Page page, BrowserContext context) { Dialog[] dialog = { null }; context.onDialog(d -> { dialog[0] = d; @@ -98,8 +100,8 @@ void dialogEventShouldWorkSmoke() { assertEquals(page, dialog[0].page()); } - @Test - void dialogEventShouldWorkInPopup() { + @PlaywrightTest + void dialogEventShouldWorkInPopup(Page page, BrowserContext context) { Dialog[] dialog = { null }; context.onDialog(d -> { dialog[0] = d; @@ -116,9 +118,9 @@ void dialogEventShouldWorkInPopup() { assertEquals(popup, dialog[0].page()); } - @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="dialog from javascript: url is not reported at all") - void dialogEventShouldWorkInPopup2() { + @PlaywrightTest + @DisabledOnBrowser(value = FIREFOX, reason = "dialog from javascript: url is not reported at all") + void dialogEventShouldWorkInPopup2(Page page, BrowserContext context) { Dialog[] dialog = { null }; context.onDialog(d -> { dialog[0] = d; @@ -130,8 +132,8 @@ void dialogEventShouldWorkInPopup2() { assertEquals(null, dialog[0].page()); } - @Test - void dialogEventShouldWorkInImmdiatelyClosedPopup() { + @PlaywrightTest + void dialogEventShouldWorkInImmdiatelyClosedPopup(Page page, BrowserContext context) { Dialog[] dialog = { null }; context.onDialog(d -> { dialog[0] = d; @@ -150,8 +152,8 @@ void dialogEventShouldWorkInImmdiatelyClosedPopup() { assertEquals(popup, dialog[0].page()); } - @Test - void dialogEventShouldWorkWithInlineScriptTag() { + @PlaywrightTest + void dialogEventShouldWorkWithInlineScriptTag(Page page, BrowserContext context, Server server) { server.setRoute("/popup.html", exchange -> { exchange.getResponseHeaders().add("content-type", "text/html"); exchange.sendResponseHeaders(200, 0); @@ -174,20 +176,20 @@ void dialogEventShouldWorkWithInlineScriptTag() { new Page.WaitForConditionOptions().setTimeout(5_000)); } - @Test - void pageErrorEventShouldWork() { + @PlaywrightTest + void pageErrorEventShouldWork(Page page, BrowserContext context) { WebError[] webError = { null }; context.onWebError(e -> { webError[0] = e; }); page.setContent(""); - waitForCondition(() -> webError[0] != null); + waitForCondition(page, () -> webError[0] != null); assertEquals(page, webError[0].page()); assertTrue(webError[0].error().contains("boom"), webError[0].error()); } - @Test - void weberrorEventShouldIncludeLocation() { + @PlaywrightTest + void weberrorEventShouldIncludeLocation(Page page, BrowserContext context, Server server) { server.setRoute("/error.js", exchange -> { exchange.getResponseHeaders().add("content-type", "application/javascript"); exchange.sendResponseHeaders(200, 0); @@ -205,44 +207,44 @@ void weberrorEventShouldIncludeLocation() { WebError[] webError = { null }; context.onWebError(e -> webError[0] = e); page.navigate(server.PREFIX + "/error.html"); - waitForCondition(() -> webError[0] != null); + waitForCondition(page, () -> webError[0] != null); com.microsoft.playwright.options.WebErrorLocation location = webError[0].location(); assertEquals(server.PREFIX + "/error.js", location.url); assertEquals(2, location.line); assertTrue(location.column > 0, "expected column > 0, got " + location.column); } - @Test - void pageLoadEventShouldWork() { + @PlaywrightTest + void pageLoadEventShouldWork(Page page, BrowserContext context, Server server) { Page[] loaded = { null }; context.onPageLoad(p -> loaded[0] = p); page.navigate(server.EMPTY_PAGE); - waitForCondition(() -> loaded[0] != null); + waitForCondition(page, () -> loaded[0] != null); assertEquals(page, loaded[0]); } - @Test - void frameNavigatedEventShouldWork() { + @PlaywrightTest + void frameNavigatedEventShouldWork(Page page, BrowserContext context, Server server) { Frame[] navigated = { null }; context.onFrameNavigated(f -> navigated[0] = f); page.navigate(server.EMPTY_PAGE); - waitForCondition(() -> navigated[0] != null); + waitForCondition(page, () -> navigated[0] != null); assertEquals(page.mainFrame(), navigated[0]); assertEquals(server.EMPTY_PAGE, navigated[0].url()); } - @Test - void pageCloseEventShouldWork() { + @PlaywrightTest + void pageCloseEventShouldWork(Page page, BrowserContext context) { Page newPage = context.newPage(); Page[] closed = { null }; context.onPageClose(p -> closed[0] = p); newPage.close(); - waitForCondition(() -> closed[0] != null); + waitForCondition(page, () -> closed[0] != null); assertEquals(newPage, closed[0]); } - @Test - void frameAttachedEventShouldWork() { + @PlaywrightTest + void frameAttachedEventShouldWork(Page page, BrowserContext context, Server server) { page.navigate(server.EMPTY_PAGE); Frame[] attached = { null }; context.onFrameAttached(f -> attached[0] = f); @@ -251,12 +253,12 @@ void frameAttachedEventShouldWork() { " iframe.src = 'about:blank';\n" + " document.body.appendChild(iframe);\n" + "}"); - waitForCondition(() -> attached[0] != null); + waitForCondition(page, () -> attached[0] != null); assertEquals(page.mainFrame(), attached[0].parentFrame()); } - @Test - void frameDetachedEventShouldWork() { + @PlaywrightTest + void frameDetachedEventShouldWork(Page page, BrowserContext context, Server server) { page.navigate(server.EMPTY_PAGE); page.evaluate("() => {\n" + " const iframe = document.createElement('iframe');\n" + @@ -268,12 +270,12 @@ void frameDetachedEventShouldWork() { Frame[] detached = { null }; context.onFrameDetached(f -> detached[0] = f); page.evaluate("() => document.getElementById('x').remove()"); - waitForCondition(() -> detached[0] != null); + waitForCondition(page, () -> detached[0] != null); assertEquals(page.mainFrame(), detached[0].parentFrame()); } - @Test - void dialogClosedEventShouldWork() { + @PlaywrightTest + void dialogClosedEventShouldWork(Page page, BrowserContext context) { Dialog[] dialog = {null}; Dialog[] closedOnContext = {null}; Dialog[] closedOnPage = {null}; diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java index e9c0c6385..9d3402772 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextExposeFunction.java @@ -16,8 +16,8 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.BindingCallback; -import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; @@ -26,10 +26,10 @@ import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextExposeFunction extends TestBase { +public class TestBrowserContextExposeFunction { - @Test - void exposeBindingShouldWork() { + @PlaywrightTest + void exposeBindingShouldWork(BrowserContext context) { BindingCallback.Source[] bindingSource = {null}; context.exposeBinding("add", (source, args) -> { bindingSource[0] = source; @@ -51,8 +51,8 @@ void exposeBindingShouldWork() { assertEquals("failure in console listener", e.getMessage()); } - @Test - void shouldWork() { + @PlaywrightTest + void shouldWork(BrowserContext context) { context.exposeFunction("add", args -> (Integer) args[0] + (Integer) args[1]); Page page = context.newPage(); page.exposeFunction("mul", args -> (Integer) args[0] * (Integer) args[1]); @@ -62,8 +62,8 @@ void shouldWork() { assertEquals(mapOf("mul", 36, "add", 13, "sub", 5, "addHandle", 11), result); } - @Test - void shouldThrowForDuplicateRegistrations() { + @PlaywrightTest + void shouldThrowForDuplicateRegistrations(BrowserContext context) { context.exposeFunction("foo", args -> null); context.exposeFunction("bar", args -> null); PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.exposeFunction("foo", args -> null)); @@ -78,8 +78,8 @@ void shouldThrowForDuplicateRegistrations() { assertTrue(e.getMessage().contains("Function \"baz\" has been already registered in one of the pages")); } - @Test - void shouldBeCallableFromInsideAddInitScript() { + @PlaywrightTest + void shouldBeCallableFromInsideAddInitScript(BrowserContext context) { List actualArgs = new ArrayList<>(); context.exposeFunction("woof", args -> actualArgs.add(args[0])); context.addInitScript("window['woof']('context')"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java index 52b961ec0..7c874041e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextFetch.java @@ -17,9 +17,9 @@ package com.microsoft.playwright; import com.google.gson.Gson; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.*; import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.FileOutputStream; @@ -41,9 +41,9 @@ import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextFetch extends TestBase { - @Test - void getShouldWork() { +public class TestBrowserContextFetch { + @PlaywrightTest + void getShouldWork(BrowserContext context, Server server) { APIResponse response = context.request().get(server.PREFIX + "/simple.json"); assertEquals(server.PREFIX + "/simple.json", response.url()); assertEquals(200, response.status()); @@ -57,8 +57,8 @@ void getShouldWork() { assertEquals("{\"foo\": \"bar\"}\n", response.text()); } - @Test - void getShouldReturnTiming() { + @PlaywrightTest + void getShouldReturnTiming(BrowserContext context, Server server) { APIResponse response = context.request().get(server.PREFIX + "/simple.json"); assertTrue(response.ok()); Timing timing = response.timing(); @@ -73,8 +73,8 @@ void getShouldReturnTiming() { assertTrue(timing.responseEnd < 60_000, "responseEnd = " + timing.responseEnd); } - @Test - void fetchShouldWork() { + @PlaywrightTest + void fetchShouldWork(BrowserContext context, Server server) { APIResponse response = context.request().fetch(server.PREFIX + "/simple.json"); assertEquals(server.PREFIX + "/simple.json", response.url()); assertEquals(200, response.status()); @@ -88,23 +88,23 @@ void fetchShouldWork() { } // server.setRoute("/one-style.css", exchange -> exchange.getResponseBody().close()); - @Test - void shouldThrowOnNetworkError() { + @PlaywrightTest + void shouldThrowOnNetworkError(BrowserContext context, Server server) { server.setRoute("/test", exchange -> exchange.getResponseBody().close()); PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/test")); assertTrue(e.getMessage().contains("socket hang up"), e.getMessage()); } - @Test - void shouldThrowOnNetworkErrorAfterRedirect() { + @PlaywrightTest + void shouldThrowOnNetworkErrorAfterRedirect(BrowserContext context, Server server) { server.setRedirect("/redirect", "/test"); server.setRoute("/test", exchange -> exchange.getResponseBody().close()); PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.PREFIX + "/redirect")); assertTrue(e.getMessage().contains("socket hang up"), e.getMessage()); } - @Test - void shouldThrowOnNetworkErrorWhenSendingBody() { + @PlaywrightTest + void shouldThrowOnNetworkErrorWhenSendingBody(BrowserContext context, Server server) { server.setRoute("/test", exchange -> { exchange.getResponseHeaders().add("content-type", "text/html"); exchange.sendResponseHeaders(200, 4096); @@ -116,8 +116,8 @@ void shouldThrowOnNetworkErrorWhenSendingBody() { assertTrue(e.getMessage().contains("aborted"), e.getMessage()); } - @Test - void shouldThrowOnNetworkErrorWhenSendingBodyAfterRedirect() { + @PlaywrightTest + void shouldThrowOnNetworkErrorWhenSendingBodyAfterRedirect(BrowserContext context, Server server) { server.setRedirect("/redirect", "/test"); server.setRoute("/test", exchange -> { exchange.getResponseHeaders().add("content-type", "text/html"); @@ -130,8 +130,8 @@ void shouldThrowOnNetworkErrorWhenSendingBodyAfterRedirect() { assertTrue(e.getMessage().contains("aborted"), e.getMessage()); } - @Test - void shouldAddSessionCookiesToRequest() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldAddSessionCookiesToRequest(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Cookie cookie = new Cookie("username", "John Doe"); cookie.domain = "localhost"; cookie.path = "/"; @@ -145,8 +145,8 @@ void shouldAddSessionCookiesToRequest() throws ExecutionException, InterruptedEx assertEquals(asList("username=John Doe"), req.get().headers.get("cookie")); } - @Test - void getShouldSupportQueryParams() throws ExecutionException, InterruptedException { + @PlaywrightTest + void getShouldSupportQueryParams(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future req = server.futureRequest("/empty.html"); context.request().get(server.EMPTY_PAGE + "?p1=foo", RequestOptions.create().setQueryParam("p1", "v1").setQueryParam("парам2", "знач2")); @@ -156,23 +156,23 @@ void getShouldSupportQueryParams() throws ExecutionException, InterruptedExcepti ; - @Test - void getShouldSupportFailOnStatusCode() { + @PlaywrightTest + void getShouldSupportFailOnStatusCode(BrowserContext context, Server server) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> { context.request().get(server.PREFIX + "/does-not-exist.html", RequestOptions.create().setFailOnStatusCode(true)); }); assertTrue(e.getMessage().contains("404 Not Found"), e.getMessage()); } - @Test + @PlaywrightTest @Disabled("Error: socket hang up") - void getShouldSupportIgnoreHTTPSErrorsOption() { + void getShouldSupportIgnoreHTTPSErrorsOption(BrowserContext context, HttpsServer httpsServer) { APIResponse response = context.request().get(httpsServer.EMPTY_PAGE, RequestOptions.create().setIgnoreHTTPSErrors(true)); assertEquals(200, response.status()); } - @Test - void shouldNotAddContextCookieIfCookieHeaderPassedAsAParameter() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldNotAddContextCookieIfCookieHeaderPassedAsAParameter(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Cookie cookie = new Cookie("username", "John Doe"); cookie.domain = "localhost"; cookie.path = "/"; @@ -186,8 +186,8 @@ void shouldNotAddContextCookieIfCookieHeaderPassedAsAParameter() throws Executio assertEquals(asList("foo=bar"), req.get().headers.get("cookie")); } - @Test - void shouldFollowRedirects() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldFollowRedirects(BrowserContext context, Server server) throws ExecutionException, InterruptedException { server.setRedirect("/redirect1", "/redirect2"); server.setRedirect("/redirect2", "/simple.json"); Cookie cookie = new Cookie("username", "John Doe"); @@ -210,8 +210,8 @@ private static List sortedCookiePairs(String cookieHeader) { return Arrays.stream(cookieHeader.split(";")).map(String::trim).sorted().collect(Collectors.toList()); } - @Test - void pageRequestAddCookiesShouldAddCookiesToTheBrowserContext() throws ExecutionException, InterruptedException { + @PlaywrightTest + void pageRequestAddCookiesShouldAddCookiesToTheBrowserContext(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException { page.request().addCookies(asList( new Cookie("a", "b").setUrl(server.EMPTY_PAGE), new Cookie("c", "d").setDomain("localhost").setPath("/").setExpires(System.currentTimeMillis() / 1000 + 3600))); @@ -224,8 +224,8 @@ void pageRequestAddCookiesShouldAddCookiesToTheBrowserContext() throws Execution assertEquals(asList("a=b", "c=d"), sortedCookiePairs((String) page.evaluate("document.cookie"))); } - @Test - void pageRequestCookiesShouldReturnBrowserContextCookies() { + @PlaywrightTest + void pageRequestCookiesShouldReturnBrowserContextCookies(Page page, BrowserContext context, Server server) { context.addCookies(asList( new Cookie("a", "b").setUrl(server.EMPTY_PAGE), new Cookie("c", "d").setDomain("example.com").setPath("/"))); @@ -237,8 +237,8 @@ void pageRequestCookiesShouldReturnBrowserContextCookies() { new Gson().toJson(page.request().cookies(server.EMPTY_PAGE))); } - @Test - void pageRequestClearCookiesShouldClearBrowserContextCookies() throws ExecutionException, InterruptedException { + @PlaywrightTest + void pageRequestClearCookiesShouldClearBrowserContextCookies(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException { context.addCookies(asList( new Cookie("a", "b").setUrl(server.EMPTY_PAGE), new Cookie("c", "d").setUrl(server.EMPTY_PAGE))); @@ -251,8 +251,8 @@ void pageRequestClearCookiesShouldClearBrowserContextCookies() throws ExecutionE assertNull(req.get().headers.get("cookie")); } - @Test - void shouldAddCookiesFromSetCookieHeader() { + @PlaywrightTest + void shouldAddCookiesFromSetCookieHeader(Page page, BrowserContext context, Server server) { server.setRoute("/setcookie.html", exchange -> { exchange.getResponseHeaders().add("Set-Cookie", "session=value"); exchange.getResponseHeaders().add("Set-Cookie", "foo=bar; max-age=3600"); @@ -272,9 +272,9 @@ void shouldAddCookiesFromSetCookieHeader() { assertEquals(asList("foo=bar", "session=value"), page.evaluate("() => document.cookie.split(';').map(s => s.trim()).sort()")); } - @Test + @PlaywrightTest @Disabled("Default Java's HTTP server throws on 'CONNECT non-existent.com:80 HTTP/1.1' because path is null.") - void shouldWorkWithContextLevelProxy() throws ExecutionException, InterruptedException { + void shouldWorkWithContextLevelProxy(BrowserType browserType, Server server) throws ExecutionException, InterruptedException { server.setRoute("/target.html", exchange -> { exchange.getResponseHeaders().add("Content-Type", "text/plain; charset=utf-8"); exchange.sendResponseHeaders(200, 0); @@ -292,8 +292,8 @@ void shouldWorkWithContextLevelProxy() throws ExecutionException, InterruptedExc } } - @Test - void shouldWorkWithHttpCredentials() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldWorkWithHttpCredentials(BrowserContext context, Server server) throws ExecutionException, InterruptedException { server.setAuth("/empty.html", "user", "pass"); String base64 = Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8));; @@ -304,8 +304,8 @@ void shouldWorkWithHttpCredentials() throws ExecutionException, InterruptedExcep assertEquals("/empty.html", request.get().url); } - @Test - void shouldWorkWithSetHTTPCredentials() { + @PlaywrightTest + void shouldWorkWithSetHTTPCredentials(BrowserContext context, Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); APIResponse response1 = context.request().get(server.EMPTY_PAGE); assertEquals(401, response1.status()); @@ -317,8 +317,8 @@ void shouldWorkWithSetHTTPCredentials() { } } - @Test - void shouldReturnErrorWithWrongCredentials() { + @PlaywrightTest + void shouldReturnErrorWithWrongCredentials(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); try (BrowserContext context = browser.newContext( new Browser.NewContextOptions().setHttpCredentials("user", "wrong"))) { @@ -327,8 +327,8 @@ void shouldReturnErrorWithWrongCredentials() { } } - @Test - void postShouldSupportPostData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void postShouldSupportPostData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/simple.json"); APIResponse response = context.request().post(server.PREFIX + "/simple.json", RequestOptions.create().setData("My request")); @@ -338,8 +338,8 @@ void postShouldSupportPostData() throws ExecutionException, InterruptedException assertEquals("/simple.json", request.get().url); } - @Test - void deleteShouldSupportPostData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void deleteShouldSupportPostData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/simple.json"); APIResponse response = context.request().delete(server.PREFIX + "/simple.json", RequestOptions.create().setData("My request")); @@ -349,8 +349,8 @@ void deleteShouldSupportPostData() throws ExecutionException, InterruptedExcepti assertEquals("/simple.json", request.get().url); } - @Test - void patchShouldSupportPostData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void patchShouldSupportPostData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/simple.json"); APIResponse response = context.request().patch(server.PREFIX + "/simple.json", RequestOptions.create().setData("My request")); @@ -360,8 +360,8 @@ void patchShouldSupportPostData() throws ExecutionException, InterruptedExceptio assertEquals("/simple.json", request.get().url); } - @Test - void putShouldSupportPostData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void putShouldSupportPostData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/simple.json"); APIResponse response = context.request().put(server.PREFIX + "/simple.json", RequestOptions.create().setData("My request")); @@ -371,8 +371,8 @@ void putShouldSupportPostData() throws ExecutionException, InterruptedException assertEquals("/simple.json", request.get().url); } - @Test - void getShouldSupportPostData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void getShouldSupportPostData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/simple.json"); APIResponse response = context.request().get(server.PREFIX + "/simple.json", RequestOptions.create().setData("My request")); @@ -382,8 +382,8 @@ void getShouldSupportPostData() throws ExecutionException, InterruptedException assertEquals("/simple.json", request.get().url); } - @Test - void headShouldSupportPostData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void headShouldSupportPostData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/simple.json"); APIResponse response = context.request().head(server.PREFIX + "/simple.json", RequestOptions.create().setData("My request")); @@ -393,8 +393,8 @@ void headShouldSupportPostData() throws ExecutionException, InterruptedException assertEquals("/simple.json", request.get().url); } - @Test - void shouldAddDefaultHeaders() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldAddDefaultHeaders(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/empty.html"); context.request().get(server.EMPTY_PAGE); @@ -404,8 +404,8 @@ void shouldAddDefaultHeaders() throws ExecutionException, InterruptedException { assertEquals(asList("gzip,deflate,br"), request.get().headers.get("accept-encoding")); } - @Test - void shouldSendContentLength() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldSendContentLength(BrowserContext context, Server server) throws ExecutionException, InterruptedException { byte[] bytes = new byte[256]; for (int i = 0; i < 256; i++) { bytes[i] = (byte) i; @@ -416,8 +416,8 @@ void shouldSendContentLength() throws ExecutionException, InterruptedException { assertEquals(asList("application/octet-stream"), request.get().headers.get("content-type")); } - @Test - void shouldAddDefaultHeadersToRedirects() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldAddDefaultHeadersToRedirects(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException { server.setRedirect("/redirect", "/empty.html"); Future request = server.futureRequest("/empty.html"); context.request().get(server.PREFIX + "/redirect"); @@ -428,8 +428,8 @@ void shouldAddDefaultHeadersToRedirects() throws ExecutionException, Interrupted assertEquals(asList("gzip,deflate,br"), request.get().headers.get("accept-encoding")); } - @Test - void shouldAllowToOverrideDefaultHeaders() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldAllowToOverrideDefaultHeaders(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future request = server.futureRequest("/empty.html"); context.request().get(server.EMPTY_PAGE, RequestOptions.create() .setHeader( @@ -441,8 +441,8 @@ void shouldAllowToOverrideDefaultHeaders() throws ExecutionException, Interrupte assertEquals(asList("br"), request.get().headers.get("accept-encoding")); } - @Test - void shouldPropagateCustomHeadersWithRedirects() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldPropagateCustomHeadersWithRedirects(BrowserContext context, Server server) throws ExecutionException, InterruptedException { server.setRedirect("/a/redirect1", "/b/c/redirect2"); server.setRedirect("/b/c/redirect2", "/simple.json"); Future req1 = server.futureRequest("/a/redirect1"); @@ -455,8 +455,8 @@ void shouldPropagateCustomHeadersWithRedirects() throws ExecutionException, Inte assertEquals(asList("bar"), req3.get().headers.get("foo")); } - @Test - void shouldPropagateExtraHttpHeadersWithRedirects() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldPropagateExtraHttpHeadersWithRedirects(BrowserContext context, Server server) throws ExecutionException, InterruptedException { server.setRedirect("/a/redirect1", "/b/c/redirect2"); server.setRedirect("/b/c/redirect2", "/simple.json"); context.setExtraHTTPHeaders(mapOf("My-Secret", "Value")); @@ -470,8 +470,8 @@ void shouldPropagateExtraHttpHeadersWithRedirects() throws ExecutionException, I assertEquals(asList("Value"), req3.get().headers.get("my-secret")); } - @Test - void shouldThrowOnInvalidHeaderValue() { + @PlaywrightTest + void shouldThrowOnInvalidHeaderValue(BrowserContext context, Server server) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> { context.request().get(server.EMPTY_PAGE, RequestOptions.create() .setHeader("foo", "недопустимое значение")); @@ -479,8 +479,8 @@ void shouldThrowOnInvalidHeaderValue() { assertTrue(e.getMessage().contains("Invalid character in header content"), e.getMessage()); } - @Test - void shouldThrowOnNonHttpSProtocol() { + @PlaywrightTest + void shouldThrowOnNonHttpSProtocol(BrowserContext context) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get("data:text/plain,test")); assertTrue(e.getMessage().contains("Protocol \"data:\" not supported"), e.getMessage()); @@ -488,8 +488,8 @@ void shouldThrowOnNonHttpSProtocol() { assertTrue(e.getMessage().contains("Protocol \"file:\" not supported"), e.getMessage()); } - @Test - void shouldSupportTimeoutOption() { + @PlaywrightTest + void shouldSupportTimeoutOption(BrowserContext context, Server server) { server.setRoute("/slow", exchange -> { exchange.getResponseHeaders().add("content-type", "text/html"); exchange.sendResponseHeaders(200, 4096); @@ -501,8 +501,8 @@ void shouldSupportTimeoutOption() { assertTrue(e.getMessage().contains("Timeout 100ms exceeded"), e.getMessage()); } - @Test - void shouldSupportATimeoutOf0() { + @PlaywrightTest + void shouldSupportATimeoutOf0(BrowserContext context, Server server) { server.setRoute("/slow", exchange -> { exchange.getResponseHeaders().add("content-type", "text/html"); exchange.sendResponseHeaders(200, 4); @@ -520,8 +520,8 @@ void shouldSupportATimeoutOf0() { assertEquals("done", response.text()); } - @Test - void shouldRespectTimeoutAfterRedirects() { + @PlaywrightTest + void shouldRespectTimeoutAfterRedirects(BrowserContext context, Server server) { server.setRedirect("/redirect", "/slow"); server.setRoute("/slow", exchange -> { exchange.getResponseHeaders().add("content-type", "text/html"); @@ -533,8 +533,8 @@ void shouldRespectTimeoutAfterRedirects() { assertTrue(e.getMessage().contains("Timeout 100ms exceeded"), e.getMessage()); } - @Test - void shouldDispose() { + @PlaywrightTest + void shouldDispose(BrowserContext context, Server server) { APIResponse response = context.request().get(server.PREFIX + "/simple.json"); assertEquals("{\"foo\": \"bar\"}\n", response.text()); response.dispose(); @@ -542,8 +542,8 @@ void shouldDispose() { assertTrue(e.getMessage().contains("Response has been disposed"), e.getMessage()); } - @Test - void shouldDisposeWhenContextCloses() { + @PlaywrightTest + void shouldDisposeWhenContextCloses(BrowserContext context, Server server) { APIResponse response = context.request().get(server.PREFIX + "/simple.json"); assertEquals("{\"foo\": \"bar\"}\n", response.text()); context.close(); @@ -551,8 +551,8 @@ void shouldDisposeWhenContextCloses() { assertTrue(e.getMessage().contains("Response has been disposed") || e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage()); } - @Test - void shouldOverrideRequestParameters() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldOverrideRequestParameters(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException { Request pageReq = page.waitForRequest("**/*", () -> page.navigate(server.EMPTY_PAGE)); Future req = server.futureRequest("/empty.html"); context.request().fetch(pageReq, RequestOptions.create().setMethod("POST") @@ -571,8 +571,8 @@ public static class TestData { public Date nullDate; } - @Test - void shouldSerializeDateAndLocalDateTime() throws ExecutionException, InterruptedException, ParseException { + @PlaywrightTest + void shouldSerializeDateAndLocalDateTime(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException, ParseException { Request pageReq = page.waitForRequest("**/*", () -> page.navigate(server.EMPTY_PAGE)); Future req = server.futureRequest("/empty.html"); TestData testData = new TestData(); @@ -585,8 +585,8 @@ void shouldSerializeDateAndLocalDateTime() throws ExecutionException, Interrupte new String(req.get().postBody)); } - @Test - void shouldSupportOffsetDateTimeInData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldSupportOffsetDateTimeInData(Playwright playwright, Server server) throws ExecutionException, InterruptedException { APIRequestContext request = playwright.request().newContext(); OffsetDateTime offsetDateTime = OffsetDateTime.parse("2024-07-10T10:15:30-08:00"); Future serverRequest = server.futureRequest("/empty.html"); @@ -600,8 +600,8 @@ public static class LocalDateData { public LocalDate date; } - @Test - void shouldSupportLocalDateInData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldSupportLocalDateInData(Playwright playwright, Server server) throws ExecutionException, InterruptedException { APIRequestContext request = playwright.request().newContext(); LocalDateData testData = new LocalDateData(); testData.name = "foo"; @@ -612,8 +612,8 @@ void shouldSupportLocalDateInData() throws ExecutionException, InterruptedExcept assertEquals("{\"name\":\"foo\",\"date\":\"2022-12-23\"}", new String(body)); } - @Test - void shouldSupportApplicationXWwwFormUrlencoded() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldSupportApplicationXWwwFormUrlencoded(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future req = server.futureRequest("/empty.html"); context.request().post(server.EMPTY_PAGE, RequestOptions.create().setForm( FormData.create() @@ -633,8 +633,8 @@ void shouldSupportApplicationXWwwFormUrlencoded() throws ExecutionException, Int assertTrue(body.contains("file=f.js"), body); } - @Test - void shouldEncodeToApplicationJsonByDefault() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldEncodeToApplicationJsonByDefault(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Map data = mapOf( "firstName", "John", "lastName", "Doe", @@ -650,8 +650,8 @@ void shouldEncodeToApplicationJsonByDefault() throws ExecutionException, Interru assertEquals(new Gson().toJson(data), body); } - @Test - void shouldSupportMultipartFormData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldSupportMultipartFormData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future serverRequest = server.futureRequest("/empty.html"); FilePayload file = new FilePayload("f.js", "text/javascript", @@ -683,8 +683,8 @@ void shouldSupportMultipartFormData() throws ExecutionException, InterruptedExce assertEquals(200, response.status()); } - @Test - public void shouldSupportRepeatingNamesInMultipartFormData() throws InterruptedException, ExecutionException { + @PlaywrightTest + public void shouldSupportRepeatingNamesInMultipartFormData(BrowserContext context, Server server) throws InterruptedException, ExecutionException { Future serverRequest = server.futureRequest("/empty.html"); FilePayload file1 = new FilePayload("f.js", "text/javascript", @@ -723,8 +723,8 @@ public void shouldSupportRepeatingNamesInMultipartFormData() throws InterruptedE assertEquals(200, response.status()); } - @Test - void shouldSupportMultipartFormDataWithPathValues(@TempDir Path tmp) throws ExecutionException, InterruptedException, IOException { + @PlaywrightTest + void shouldSupportMultipartFormDataWithPathValues(BrowserContext context, Server server, @TempDir Path tmp) throws ExecutionException, InterruptedException, IOException { Future serverRequest = server.futureRequest("/empty.html"); Path path = tmp.resolve("simplezip.json"); @@ -757,8 +757,8 @@ void shouldSupportMultipartFormDataWithPathValues(@TempDir Path tmp) throws Exec assertEquals(200, response.status()); } - @Test - void shouldSerializeDataToJsonRegardlessOfContentType() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldSerializeDataToJsonRegardlessOfContentType(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Map data = mapOf( "firstName", "John", "lastName", "Doe"); @@ -772,14 +772,14 @@ void shouldSerializeDataToJsonRegardlessOfContentType() throws ExecutionExceptio assertEquals(new Gson().toJson(data), body); } - @Test - void shouldNotThrowWhenDataPassedForUnsupportedRequest() { + @PlaywrightTest + void shouldNotThrowWhenDataPassedForUnsupportedRequest(BrowserContext context, Server server) { context.request().fetch(server.EMPTY_PAGE, RequestOptions.create() .setMethod("GET").setData("bar")); } - @Test - void contextRequestShouldExportSameStorageStateAsContext() { + @PlaywrightTest + void contextRequestShouldExportSameStorageStateAsContext(Page page, BrowserContext context, Server server) { server.setRoute("/setcookie.html", exchange -> { exchange.getResponseHeaders().add("Set-Cookie", "a=b"); exchange.getResponseHeaders().add("Set-Cookie", "c=d"); @@ -795,8 +795,8 @@ void contextRequestShouldExportSameStorageStateAsContext() { assertEquals(contextState, pageState); } - @Test - void shouldAcceptBoolAndNumericParams() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldAcceptBoolAndNumericParams(Page page, Server server) throws ExecutionException, InterruptedException { Future req = server.futureRequest("/empty.html"); page.request().get(server.EMPTY_PAGE, RequestOptions.create() .setQueryParam("str", "s") @@ -806,8 +806,8 @@ void shouldAcceptBoolAndNumericParams() throws ExecutionException, InterruptedEx assertEquals("/empty.html?str=s&num=10&bool=true&bool2=false", req.get().url); } - @Test - void shouldAbortRequestsWhenBrowserContextCloses() { + @PlaywrightTest + void shouldAbortRequestsWhenBrowserContextCloses(Browser browser, Server server) { server.setRoute("/empty.html", exchange -> { }); @@ -828,8 +828,8 @@ void shouldAbortRequestsWhenBrowserContextCloses() { e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage()); } - @Test - void shouldWorkWithSetHTTPCredentialsAndMatchingOrigin() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldWorkWithSetHTTPCredentialsAndMatchingOrigin(BrowserContext context, Browser browser, Server server) throws ExecutionException, InterruptedException { server.setAuth("/empty.html", "user", "pass"); APIResponse response1 = context.request().get(server.EMPTY_PAGE); assertEquals(401, response1.status()); @@ -843,8 +843,8 @@ void shouldWorkWithSetHTTPCredentialsAndMatchingOrigin() throws ExecutionExcepti } } - @Test - void shouldWorkWithSetHTTPCredentialsAndMatchingOriginCaseInsensitive() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldWorkWithSetHTTPCredentialsAndMatchingOriginCaseInsensitive(BrowserContext context, Browser browser, Server server) throws ExecutionException, InterruptedException { server.setAuth("/empty.html", "user", "pass"); APIResponse response1 = context.request().get(server.EMPTY_PAGE); assertEquals(401, response1.status()); @@ -858,8 +858,8 @@ void shouldWorkWithSetHTTPCredentialsAndMatchingOriginCaseInsensitive() throws E } } - @Test - void shouldReturnErrorWithCorrectCredentialsAndWrongOriginScheme() { + @PlaywrightTest + void shouldReturnErrorWithCorrectCredentialsAndWrongOriginScheme(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(Utils.generateDifferentOriginScheme(server)); @@ -869,8 +869,8 @@ void shouldReturnErrorWithCorrectCredentialsAndWrongOriginScheme() { } } - @Test - void shouldReturnErrorWithCorrectCredentialsAndWrongOriginHostname() { + @PlaywrightTest + void shouldReturnErrorWithCorrectCredentialsAndWrongOriginHostname(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(Utils.generateDifferentOriginHostname(server)); @@ -880,8 +880,8 @@ void shouldReturnErrorWithCorrectCredentialsAndWrongOriginHostname() { } } - @Test - void shouldReturnErrorWithCorrectCredentialsAndWrongOriginPort() { + @PlaywrightTest + void shouldReturnErrorWithCorrectCredentialsAndWrongOriginPort(Browser browser, Server server) { server.setAuth("/empty.html", "user", "pass"); final HttpCredentials httpCredentials = new HttpCredentials("user", "pass"); httpCredentials.setOrigin(Utils.generateDifferentOriginPort(server)); @@ -891,65 +891,65 @@ void shouldReturnErrorWithCorrectCredentialsAndWrongOriginPort() { } } - @Test - void shouldSerializeNullValuesInPostData() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldSerializeNullValuesInPostData(BrowserContext context, Server server) throws ExecutionException, InterruptedException { Future req = server.futureRequest("/empty.html"); APIResponse response = context.request().post(server.EMPTY_PAGE, RequestOptions.create().setData(mapOf("foo", null))); assertEquals(200, response.status()); assertEquals("{\"foo\":null}", new String(req.get().postBody)); } - @Test - void shouldSupportHTTPCredentialsSendImmediatelyForNewContext() throws ExecutionException, InterruptedException { - Browser.NewContextOptions options = new Browser.NewContextOptions().setHttpCredentials( - new HttpCredentials("user", "pass") - .setOrigin(server.PREFIX.toUpperCase()) - .setSend(HttpCredentialsSend.ALWAYS)); - try (BrowserContext context = browser.newContext(options)) { - Future serverRequest = server.futureRequest("/empty.html"); - APIResponse response = context.request().get(server.EMPTY_PAGE); - assertEquals("Basic " + java.util.Base64.getEncoder().encodeToString("user:pass".getBytes()), - serverRequest.get().headers.getFirst("authorization")); - assertEquals(200, response.status()); - - serverRequest = server.futureRequest("/empty.html"); - response = context.request().get(server.CROSS_PROCESS_PREFIX + "/empty.html"); - // Not sent to another origin. - assertNull(serverRequest.get().headers.get("authorization")); - assertEquals(200, response.status()); - } - } - - @Test - void shouldSupportHTTPCredentialsSendImmediatelyForBrowserNewPage() throws ExecutionException, InterruptedException { - Browser.NewPageOptions options = new Browser.NewPageOptions().setHttpCredentials( + @PlaywrightTest + void shouldSupportHTTPCredentialsSendImmediatelyForNewContext(Browser browser, Server server) throws ExecutionException, InterruptedException { + Browser.NewContextOptions options = new Browser.NewContextOptions().setHttpCredentials( new HttpCredentials("user", "pass") .setOrigin(server.PREFIX.toUpperCase()) .setSend(HttpCredentialsSend.ALWAYS)); - try (Page newPage = browser.newPage(options)) { - Future serverRequest = server.futureRequest("/empty.html"); - APIResponse response = newPage.request().get(server.EMPTY_PAGE); - assertEquals("Basic " + java.util.Base64.getEncoder().encodeToString("user:pass".getBytes()), - serverRequest.get().headers.getFirst("authorization")); - assertEquals(200, response.status()); - - serverRequest = server.futureRequest("/empty.html"); - response = newPage.request().get(server.CROSS_PROCESS_PREFIX + "/empty.html"); - // Not sent to another origin. - assertNull(serverRequest.get().headers.get("authorization")); - assertEquals(200, response.status()); - } + try (BrowserContext context = browser.newContext(options)) { + Future serverRequest = server.futureRequest("/empty.html"); + APIResponse response = context.request().get(server.EMPTY_PAGE); + assertEquals("Basic " + java.util.Base64.getEncoder().encodeToString("user:pass".getBytes()), + serverRequest.get().headers.getFirst("authorization")); + assertEquals(200, response.status()); + + serverRequest = server.futureRequest("/empty.html"); + response = context.request().get(server.CROSS_PROCESS_PREFIX + "/empty.html"); + // Not sent to another origin. + assertNull(serverRequest.get().headers.get("authorization")); + assertEquals(200, response.status()); } + } + + @PlaywrightTest + void shouldSupportHTTPCredentialsSendImmediatelyForBrowserNewPage(Browser browser, Server server) throws ExecutionException, InterruptedException { + Browser.NewPageOptions options = new Browser.NewPageOptions().setHttpCredentials( + new HttpCredentials("user", "pass") + .setOrigin(server.PREFIX.toUpperCase()) + .setSend(HttpCredentialsSend.ALWAYS)); + try (Page newPage = browser.newPage(options)) { + Future serverRequest = server.futureRequest("/empty.html"); + APIResponse response = newPage.request().get(server.EMPTY_PAGE); + assertEquals("Basic " + java.util.Base64.getEncoder().encodeToString("user:pass".getBytes()), + serverRequest.get().headers.getFirst("authorization")); + assertEquals(200, response.status()); + + serverRequest = server.futureRequest("/empty.html"); + response = newPage.request().get(server.CROSS_PROCESS_PREFIX + "/empty.html"); + // Not sent to another origin. + assertNull(serverRequest.get().headers.get("authorization")); + assertEquals(200, response.status()); + } + } - @Test - void shouldNotWorkAfterContextDispose() { + @PlaywrightTest + void shouldNotWorkAfterContextDispose(BrowserContext context, Server server) { context.close(new BrowserContext.CloseOptions().setReason("Test ended.")); PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.EMPTY_PAGE)); assertTrue(e.getMessage().contains("Test ended."), e.getMessage()); } - @Test - public void shouldRetryECONNRESET() { + @PlaywrightTest + public void shouldRetryECONNRESET(BrowserContext context, Server server) { int[] requestCount = {0}; server.setRoute("/test", exchange -> { if (requestCount[0]++ < 3) { @@ -972,8 +972,8 @@ public void shouldRetryECONNRESET() { assertEquals(4, requestCount[0]); } - @Test - void shouldSupportMultipleHttpCredentials() { + @PlaywrightTest + void shouldSupportMultipleHttpCredentials(Browser browser, Server server) { server.setAuth("/empty.html", "user1", "pass1"); try (BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setHttpCredentials(asList( diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java index 02b072c19..2891233c2 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextHar.java @@ -16,12 +16,12 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.DisabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.HarMode; import com.microsoft.playwright.options.HarNotFound; import com.microsoft.playwright.options.RouteFromHarUpdateContentPolicy; import com.microsoft.playwright.options.Timing; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.io.TempDir; import java.io.IOException; @@ -39,12 +39,13 @@ import static com.microsoft.playwright.Utils.copy; import static com.microsoft.playwright.Utils.extractZip; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; import static com.microsoft.playwright.options.HarContentPolicy.ATTACH; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextHar extends TestBase { - @Test - void shouldContextRouteFromHARMatchingTheMethodAndFollowingRedirects() { +public class TestBrowserContextHar { + @PlaywrightTest + void shouldContextRouteFromHARMatchingTheMethodAndFollowingRedirects(BrowserContext context) { Path path = Paths.get("src/test/resources/har-fulfill.har"); context.routeFromHAR(path); Page page = context.newPage(); @@ -55,8 +56,8 @@ void shouldContextRouteFromHARMatchingTheMethodAndFollowingRedirects() { assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)"); } - @Test - void shouldPageRouteFromHARMatchingTheMethodAndFollowingRedirects() { + @PlaywrightTest + void shouldPageRouteFromHARMatchingTheMethodAndFollowingRedirects(BrowserContext context) { Path path = Paths.get("src/test/resources/har-fulfill.har"); Page page = context.newPage(); page.routeFromHAR(path); @@ -67,8 +68,8 @@ void shouldPageRouteFromHARMatchingTheMethodAndFollowingRedirects() { assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)"); } - @Test - void fallbackContinueShouldContinueWhenNotFoundInHar() { + @PlaywrightTest + void fallbackContinueShouldContinueWhenNotFoundInHar(BrowserContext context, Server server) { Path path = Paths.get("src/test/resources/har-fulfill.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK)); Page page = context.newPage(); @@ -76,16 +77,16 @@ void fallbackContinueShouldContinueWhenNotFoundInHar() { assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)"); } - @Test - void byDefaultShouldAbortRequestsNotFoundInHar() { + @PlaywrightTest + void byDefaultShouldAbortRequestsNotFoundInHar(BrowserContext context, Server server) { Path path = Paths.get("src/test/resources/har-fulfill.har"); context.routeFromHAR(path); Page page = context.newPage(); assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE)); } - @Test - void fallbackContinueShouldContinueRequestsOnBadHar(@TempDir Path tmpDir) throws IOException { + @PlaywrightTest + void fallbackContinueShouldContinueRequestsOnBadHar(BrowserContext context, Server server, @TempDir Path tmpDir) throws IOException { Path path = tmpDir.resolve("test.har"); try (Writer stream = new OutputStreamWriter(Files.newOutputStream(path))) { stream.write("{ \"log\" : {} }"); @@ -96,8 +97,8 @@ void fallbackContinueShouldContinueRequestsOnBadHar(@TempDir Path tmpDir) throws assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)"); } - @Test - void shouldOnlyHandleRequestsMatchingUrlFilter() { + @PlaywrightTest + void shouldOnlyHandleRequestsMatchingUrlFilter(BrowserContext context) { Path path = Paths.get("src/test/resources/har-fulfill.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK).setUrl("**/*.js")); Page page = context.newPage(); @@ -114,8 +115,8 @@ void shouldOnlyHandleRequestsMatchingUrlFilter() { assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)"); } - @Test - void shouldOnlyContextRouteFromHARRequestsMatchingUrlFilter() { + @PlaywrightTest + void shouldOnlyContextRouteFromHARRequestsMatchingUrlFilter(BrowserContext context) { Path path = Paths.get("src/test/resources/har-fulfill.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl("**/*.js")); Page page = context.newPage(); @@ -132,8 +133,8 @@ void shouldOnlyContextRouteFromHARRequestsMatchingUrlFilter() { assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)"); } - @Test - void shouldOnlyPageRouteFromHARRequestsMatchingUrlFilter() { + @PlaywrightTest + void shouldOnlyPageRouteFromHARRequestsMatchingUrlFilter(BrowserContext context) { Path path = Paths.get("src/test/resources/har-fulfill.har"); Page page = context.newPage(); page.routeFromHAR(path, new Page.RouteFromHAROptions().setUrl("**/*.js")); @@ -150,8 +151,8 @@ void shouldOnlyPageRouteFromHARRequestsMatchingUrlFilter() { assertThat(page.locator("body")).hasCSS("background-color", "rgba(0, 0, 0, 0)"); } - @Test - void shouldSupportRegexFilter() { + @PlaywrightTest + void shouldSupportRegexFilter(BrowserContext context) { Path path = Paths.get("src/test/resources/har-fulfill.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*(\\.js|.*\\.css|no.playwright\\/)$"))); Page page = context.newPage(); @@ -160,8 +161,8 @@ void shouldSupportRegexFilter() { assertThat(page.locator("body")).hasCSS("background-color", "rgb(255, 0, 0)"); } - @Test - void newPageShouldFulfillFromHarMatchingTheMethodAndFollowingRedirects() { + @PlaywrightTest + void newPageShouldFulfillFromHarMatchingTheMethodAndFollowingRedirects(Browser browser) { Path path = Paths.get("src/test/resources/har-fulfill.har"); Page page = browser.newPage(); page.routeFromHAR(path); @@ -173,8 +174,8 @@ void newPageShouldFulfillFromHarMatchingTheMethodAndFollowingRedirects() { page.close(); } - @Test - void shouldChangeDocumentURLAfterRedirectedNavigation() { + @PlaywrightTest + void shouldChangeDocumentURLAfterRedirectedNavigation(BrowserContext context) { Path path = Paths.get("src/test/resources/har-redirect.har"); context.routeFromHAR(path); Page page = context.newPage(); @@ -187,8 +188,8 @@ void shouldChangeDocumentURLAfterRedirectedNavigation() { assertEquals("https://www.theverge.com/", page.evaluate("location.href")); } - @Test - void shouldChangeDocumentURLAfterRedirectedNavigationOnClick() { + @PlaywrightTest + void shouldChangeDocumentURLAfterRedirectedNavigationOnClick(BrowserContext context, Server server) { Path path = Paths.get("src/test/resources/har-redirect.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*"))); Page page = context.newPage(); @@ -200,8 +201,8 @@ void shouldChangeDocumentURLAfterRedirectedNavigationOnClick() { assertEquals("https://www.theverge.com/", page.evaluate("location.href")); } - @Test - void shouldGoBackToRedirectedNavigation() { + @PlaywrightTest + void shouldGoBackToRedirectedNavigation(BrowserContext context, Server server) { Path path = Paths.get("src/test/resources/har-redirect.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*"))); Page page = context.newPage(); @@ -214,9 +215,9 @@ void shouldGoBackToRedirectedNavigation() { assertEquals("https://www.theverge.com/", page.evaluate("location.href")); } - @Test - @DisabledIf(value="isFirefox", disabledReason="Flaky in Firefox, upstream as well") - void shouldGoForwardToRedirectedNavigation() { + @PlaywrightTest + @DisabledOnBrowser(value = FIREFOX, reason = "Flaky in Firefox, upstream as well") + void shouldGoForwardToRedirectedNavigation(BrowserContext context, Server server) { Path path = Paths.get("src/test/resources/har-redirect.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*"))); Page page = context.newPage(); @@ -232,8 +233,8 @@ void shouldGoForwardToRedirectedNavigation() { assertEquals("https://www.theverge.com/", page.evaluate("() => location.href")); } - @Test - void shouldReloadRedirectedNavigation() { + @PlaywrightTest + void shouldReloadRedirectedNavigation(BrowserContext context) { Path path = Paths.get("src/test/resources/har-redirect.har"); context.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUrl(Pattern.compile(".*theverge.*"))); Page page = context.newPage(); @@ -245,8 +246,8 @@ void shouldReloadRedirectedNavigation() { assertEquals("https://www.theverge.com/", page.evaluate("() => location.href")); } - @Test - void shouldFulfillFromHarWithContentInAFile() { + @PlaywrightTest + void shouldFulfillFromHarWithContentInAFile(BrowserContext context) { Path path = Paths.get("src/test/resources/har-sha1.har"); context.routeFromHAR(path); Page page = context.newPage(); @@ -254,8 +255,8 @@ void shouldFulfillFromHarWithContentInAFile() { assertEquals("Hello, world", page.content()); } - @Test - void shouldRoundTripHarZip(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldRoundTripHarZip(Browser browser, Server server, @TempDir Path tmpDir) { Path harPath = tmpDir.resolve("har.zip"); try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions() .setRecordHarPath(harPath) @@ -272,8 +273,8 @@ void shouldRoundTripHarZip(@TempDir Path tmpDir) { } } - @Test - void shouldProduceExtractedZip(@TempDir Path tmpDir) throws IOException { + @PlaywrightTest + void shouldProduceExtractedZip(Browser browser, Server server, @TempDir Path tmpDir) throws IOException { Path harPath = tmpDir.resolve("har.har"); try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions() .setRecordHarPath(harPath) @@ -294,8 +295,8 @@ void shouldProduceExtractedZip(@TempDir Path tmpDir) throws IOException { } } - @Test - void shouldRoundTripExtractedHarZip(@TempDir Path tmpDir) throws IOException { + @PlaywrightTest + void shouldRoundTripExtractedHarZip(Browser browser, Server server, @TempDir Path tmpDir) throws IOException { Path harPath = tmpDir.resolve("har.zip"); try (BrowserContext context1 = browser.newContext(new Browser.NewContextOptions() .setRecordHarPath(harPath) @@ -316,8 +317,8 @@ void shouldRoundTripExtractedHarZip(@TempDir Path tmpDir) throws IOException { } } - @Test - void shouldRoundTripHarWithPostData(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldRoundTripHarWithPostData(Browser browser, Server server, @TempDir Path tmpDir) { server.setRoute("/echo", exchange -> { exchange.sendResponseHeaders(200, 0); try (OutputStream out = exchange.getResponseBody()) { @@ -352,8 +353,8 @@ void shouldRoundTripHarWithPostData(@TempDir Path tmpDir) { } } - @Test - void shouldDisambiguateByHeader(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldDisambiguateByHeader(Browser browser, Server server, @TempDir Path tmpDir) { server.setRoute("/echo", exchange -> { exchange.sendResponseHeaders(200, 0); try (OutputStream out = exchange.getResponseBody()) { @@ -398,8 +399,8 @@ void shouldDisambiguateByHeader(@TempDir Path tmpDir) { } } - @Test - void shouldUpdateHarZipForContext(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldUpdateHarZipForContext(Browser browser, Server server, @TempDir Path tmpDir) { Path harPath = tmpDir.resolve("har.zip"); try (BrowserContext context1 = browser.newContext()) { context1.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setUpdate(true)); @@ -415,8 +416,8 @@ void shouldUpdateHarZipForContext(@TempDir Path tmpDir) { } } - @Test - void shouldUpdateHarZipForPage(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldUpdateHarZipForPage(Browser browser, Server server, @TempDir Path tmpDir) { Path harPath = tmpDir.resolve("har.zip"); try (BrowserContext context1 = browser.newContext()) { Page page1 = context1.newPage(); @@ -432,8 +433,8 @@ void shouldUpdateHarZipForPage(@TempDir Path tmpDir) { } } - @Test - void shouldUpdateHarZipForPageWithDifferentOptions(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldUpdateHarZipForPageWithDifferentOptions(Browser browser, Server server, @TempDir Path tmpDir) { Path harPath = tmpDir.resolve("har.zip"); try (BrowserContext context1 = browser.newContext()) { Page page1 = context1.newPage(); @@ -453,8 +454,8 @@ void shouldUpdateHarZipForPageWithDifferentOptions(@TempDir Path tmpDir) { } } - @Test - void shouldUpdateExtractedHarZipForPage(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldUpdateExtractedHarZipForPage(Browser browser, Server server, @TempDir Path tmpDir) { Path harPath = tmpDir.resolve("har.har"); try (BrowserContext context1 = browser.newContext()) { Page page1 = context1.newPage(); @@ -470,8 +471,8 @@ void shouldUpdateExtractedHarZipForPage(@TempDir Path tmpDir) { } } - @Test - void shouldIgnoreAbortedRequests(@TempDir Path tmpDir) { + @PlaywrightTest + void shouldIgnoreAbortedRequests(Page page, Browser browser, Server server, @TempDir Path tmpDir) { Path path = tmpDir.resolve("test.har"); try (BrowserContext context1 = browser.newContext()) { server.setRoute("/x", exchange -> exchange.close()); @@ -503,7 +504,7 @@ void shouldIgnoreAbortedRequests(@TempDir Path tmpDir) { } } - private void setJsonRoute(String path, String json) { + private void setJsonRoute(Server server, String path, String json) { server.setRoute(path, exchange -> { exchange.getResponseHeaders().add("Content-Type", "application/json"); byte[] body = json.getBytes(StandardCharsets.UTF_8); @@ -514,9 +515,9 @@ private void setJsonRoute(String path, String json) { }); } - @Test - void shouldNotInterceptAPIRequestContextRequestsByDefault(@TempDir Path tmpDir) { - setJsonRoute("/api/data", "{\"hello\": \"live\"}"); + @PlaywrightTest + void shouldNotInterceptAPIRequestContextRequestsByDefault(Browser browser, Server server, @TempDir Path tmpDir) { + setJsonRoute(server, "/api/data", "{\"hello\": \"live\"}"); Path harPath = tmpDir.resolve("api.har"); try (BrowserContext context1 = browser.newContext()) { context1.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setUpdate(true)); @@ -526,7 +527,7 @@ void shouldNotInterceptAPIRequestContextRequestsByDefault(@TempDir Path tmpDir) } // Without the option, the live network is hit. - setJsonRoute("/api/data", "{\"hello\": \"fresh\"}"); + setJsonRoute(server, "/api/data", "{\"hello\": \"fresh\"}"); try (BrowserContext context2 = browser.newContext()) { context2.routeFromHAR(harPath, new BrowserContext.RouteFromHAROptions().setNotFound(HarNotFound.FALLBACK)); Page page2 = context2.newPage(); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextLocale.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextLocale.java index d2dfefec1..398d01cc7 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextLocale.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextLocale.java @@ -16,8 +16,8 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.LoadState; -import org.junit.jupiter.api.Test; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -25,9 +25,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -public class TestBrowserContextLocale extends TestBase { - @Test - void shouldAffectAcceptLanguageHeader() throws ExecutionException, InterruptedException { +public class TestBrowserContextLocale { + @PlaywrightTest + void shouldAffectAcceptLanguageHeader(Browser browser, Server server) throws ExecutionException, InterruptedException { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR")); Page page = context.newPage(); Future request = server.futureRequest("/empty.html"); @@ -36,8 +36,8 @@ void shouldAffectAcceptLanguageHeader() throws ExecutionException, InterruptedEx context.close(); } - @Test - void shouldAffectNavigatorLanguage() { + @PlaywrightTest + void shouldAffectNavigatorLanguage(Browser browser) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR")); Page page = context.newPage(); assertEquals("fr-FR", page.evaluate("() => navigator.language")); @@ -45,8 +45,8 @@ void shouldAffectNavigatorLanguage() { } - @Test - void shouldFormatNumber() { + @PlaywrightTest + void shouldFormatNumber(Browser browser, Server server) { { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("en-US")); Page page = context.newPage(); @@ -63,8 +63,8 @@ void shouldFormatNumber() { } } - @Test - void shouldFormatDate() { + @PlaywrightTest + void shouldFormatDate(Browser browser, Server server) { { BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setLocale("en-US").setTimezoneId("America/Los_Angeles")); @@ -85,8 +85,8 @@ void shouldFormatDate() { } } - @Test - void shouldFormatNumberInPopups() { + @PlaywrightTest + void shouldFormatNumberInPopups(Browser browser, Server server) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR")); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); @@ -98,8 +98,8 @@ void shouldFormatNumberInPopups() { context.close(); } - @Test - void shouldAffectNavigatorLanguageInPopups() { + @PlaywrightTest + void shouldAffectNavigatorLanguageInPopups(Browser browser, Server server) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("fr-FR")); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); @@ -111,8 +111,8 @@ void shouldAffectNavigatorLanguageInPopups() { context.close(); } - @Test - void shouldWorkForMultiplePagesSharingSameProcess() { + @PlaywrightTest + void shouldWorkForMultiplePagesSharingSameProcess(Browser browser, Server server) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("ru-RU")); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); @@ -123,8 +123,8 @@ void shouldWorkForMultiplePagesSharingSameProcess() { context.close(); } - @Test - void shouldBeIsolatedBetweenContexts() { + @PlaywrightTest + void shouldBeIsolatedBetweenContexts(Browser browser) { BrowserContext context1 = browser.newContext(new Browser.NewContextOptions().setLocale("en-US")); // By default firefox limits number of child web processes to 8. for (int i = 0; i < 8; i++) @@ -142,8 +142,8 @@ void shouldBeIsolatedBetweenContexts() { context2.close(); } - @Test - void shouldNotChangeDefaultLocaleInAnotherContext() { + @PlaywrightTest + void shouldNotChangeDefaultLocaleInAnotherContext(Browser browser) { Function getContextLocale = (context) -> { Page page = context.newPage(); return (String) page.evaluate("(new Intl.NumberFormat()).resolvedOptions().locale"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextNetworkEvents.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextNetworkEvents.java index 29c08bfe6..b87597bee 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextNetworkEvents.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextNetworkEvents.java @@ -16,18 +16,20 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.sun.net.httpserver.Filter; -import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextNetworkEvents extends TestBase { - @Test - void BrowserContextEventsRequest() { +public class TestBrowserContextNetworkEvents { + @PlaywrightTest + void BrowserContextEventsRequest(Page page, BrowserContext context, Server server, BrowserName browserName) { List requests = new ArrayList<>(); context.onRequest(request -> requests.add(request.url())); page.navigate(server.EMPTY_PAGE); @@ -35,7 +37,7 @@ void BrowserContextEventsRequest() { Page page1 = context.waitForPage(() -> page.click("a")); page1.waitForLoadState(); // In firefox one-style.css is requested multiple times. - if (isFirefox()) { + if (browserName == FIREFOX) { assertEquals(asList( server.EMPTY_PAGE, server.PREFIX + "/one-style.html", @@ -49,8 +51,8 @@ void BrowserContextEventsRequest() { } } - @Test - void BrowserContextEventsResponse() { + @PlaywrightTest + void BrowserContextEventsResponse(Page page, BrowserContext context, Server server, BrowserName browserName) { List responses = new ArrayList<>(); context.onResponse(response -> responses.add(response.url())); page.navigate(server.EMPTY_PAGE); @@ -58,7 +60,7 @@ void BrowserContextEventsResponse() { Page page1 = context.waitForPage(() -> page.click("a")); page1.waitForLoadState(); // In firefox one-style.css is requested multiple times. - if (isFirefox()) { + if (browserName == FIREFOX) { assertEquals(asList( server.EMPTY_PAGE, server.PREFIX + "/one-style.html", @@ -72,14 +74,14 @@ void BrowserContextEventsResponse() { } } - @Test - void BrowserContextEventsRequestFailed() { + @PlaywrightTest + void BrowserContextEventsRequestFailed(Page page, BrowserContext context, Server server, BrowserName browserName) { server.setRoute("/one-style.css", exchange -> exchange.getResponseBody().close()); List failedRequests = new ArrayList<>(); context.onRequestFailed(request -> failedRequests.add(request)); page.navigate(server.PREFIX + "/one-style.html"); // In firefox one-style.css is requested multiple times. - if (isFirefox()) { + if (browserName == FIREFOX) { assertTrue(failedRequests.size() > 0); } else { assertEquals(1, failedRequests.size()); @@ -91,8 +93,8 @@ void BrowserContextEventsRequestFailed() { } - @Test - void BrowserContextEventsRequestFinished() { + @PlaywrightTest + void BrowserContextEventsRequestFinished(Page page, BrowserContext context, Server server) { Request[] requestRef = {null}; context.onRequestFinished(r -> requestRef[0] = r); Response response = page.navigate(server.EMPTY_PAGE); @@ -104,8 +106,8 @@ void BrowserContextEventsRequestFinished() { assertNull(request.failure()); } - @Test - void shouldFireEventsInProperOrder() { + @PlaywrightTest + void shouldFireEventsInProperOrder(Page page, BrowserContext context, Server server) { List events = new ArrayList<>(); context.onRequest(r -> events.add("request")); context.onResponse(r -> events.add("response")); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java index 7f2f2657f..4de92225e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextProxy.java @@ -16,25 +16,24 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.DisabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.Proxy; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; -import org.junit.jupiter.api.condition.EnabledIf; import java.io.OutputStreamWriter; import java.util.Base64; import java.util.List; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextProxy extends TestBase { +public class TestBrowserContextProxy { void shouldThrowForBadServerValue() { // Enforced by compiler in Java } - @Test - void shouldUseProxy() { + @PlaywrightTest + void shouldUseProxy(Browser browser, Server server) { server.setRoute("/target.html", exchange -> { exchange.sendResponseHeaders(200, 0); try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { @@ -48,8 +47,8 @@ void shouldUseProxy() { context.close(); } - @Test - void shouldUseProxyTwice() { + @PlaywrightTest + void shouldUseProxyTwice(Browser browser, Server server) { server.setRoute("/target.html", exchange -> { exchange.sendResponseHeaders(200, 0); try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { @@ -65,8 +64,8 @@ void shouldUseProxyTwice() { context.close(); } - @Test - void shouldUseProxyForSecondPage() { + @PlaywrightTest + void shouldUseProxyForSecondPage(Browser browser, Server server) { server.setRoute("/target.html", exchange -> { exchange.sendResponseHeaders(200, 0); try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { @@ -87,8 +86,8 @@ void shouldUseProxyForSecondPage() { context.close(); } - @Test - void shouldWorkWithIPPORTNotion() { + @PlaywrightTest + void shouldWorkWithIPPORTNotion(Browser browser, Server server) { server.setRoute("/target.html", exchange -> { exchange.sendResponseHeaders(200, 0); try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { @@ -104,8 +103,8 @@ void shouldWorkWithIPPORTNotion() { context.close(); } - @Test - void shouldAuthenticate() { + @PlaywrightTest + void shouldAuthenticate(Browser browser, Server server) { server.setRoute("/target.html", exchange -> { List auth = exchange.getRequestHeaders().get("proxy-authorization"); if (auth == null) { @@ -129,13 +128,9 @@ void shouldAuthenticate() { context.close(); } - static boolean isChromiumHeaded() { - return isChromium() && isHeaded(); - } - - @Test - @DisabledIf(value="isChromiumHeaded", disabledReason="fixme") - void shouldExcludePatterns() { + @PlaywrightTest + @DisabledOnBrowser(value = CHROMIUM, headed = true, reason = "fixme") + void shouldExcludePatterns(Browser browser, Server server) { server.setRoute("/target.html", exchange -> { exchange.sendResponseHeaders(200, 0); try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) { @@ -188,8 +183,8 @@ void shouldUseSocksProxyInSecondPage() { // TODO: implement socks server } - @Test - void doesLaunchWithoutAPort() { + @PlaywrightTest + void doesLaunchWithoutAPort(Browser browser) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setProxy( new Proxy("http://localhost"))); context.close(); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java index be4b21432..239da5940 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextRoute.java @@ -16,8 +16,8 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -29,10 +29,10 @@ import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextRoute extends TestBase { +public class TestBrowserContextRoute { - @Test - void shouldIntercept() { + @PlaywrightTest + void shouldIntercept(Browser browser, Server server) { BrowserContext context = browser.newContext(); boolean[] intercepted = {false}; Page page = context.newPage(); @@ -55,8 +55,8 @@ void shouldIntercept() { context.close(); } - @Test - void shouldUnroute() { + @PlaywrightTest + void shouldUnroute(Browser browser, Server server) { BrowserContext context = browser.newContext(); Page page = context.newPage(); @@ -94,8 +94,8 @@ void shouldUnroute() { context.close(); } - @Test - void shouldYieldToPageRoute() { + @PlaywrightTest + void shouldYieldToPageRoute(Browser browser, Server server) { BrowserContext context = browser.newContext(); context.route("**/empty.html", route -> { route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("context")); @@ -110,8 +110,8 @@ void shouldYieldToPageRoute() { context.close(); } - @Test - void shouldFallBackToContextRoute() { + @PlaywrightTest + void shouldFallBackToContextRoute(Browser browser, Server server) { BrowserContext context = browser.newContext(); context.route("**/empty.html", route -> { route.fulfill(new Route.FulfillOptions().setStatus(200).setBody("context")); @@ -126,8 +126,8 @@ void shouldFallBackToContextRoute() { context.close(); } - @Test - void shouldSupportTheTimesParameterWithRouteMatching() { + @PlaywrightTest + void shouldSupportTheTimesParameterWithRouteMatching(Page page, BrowserContext context, Server server) { int[] intercepted = {0}; context.route("**/empty.html", route -> { ++intercepted[0]; @@ -139,8 +139,8 @@ void shouldSupportTheTimesParameterWithRouteMatching() { assertEquals(2, intercepted[0]); } - @Test - void shouldOverwritePostBodyWithEmptyString() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldOverwritePostBodyWithEmptyString(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException { boolean[] routeHandled = {false}; context.route("**/empty.html", route -> { route.resume(new Route.ResumeOptions().setPostData("")); @@ -163,8 +163,8 @@ void shouldOverwritePostBodyWithEmptyString() throws ExecutionException, Interru assertEquals(0, body.length); } - @Test - void shouldNotSwallowExceptionsInRoute() throws ExecutionException, InterruptedException { + @PlaywrightTest + void shouldNotSwallowExceptionsInRoute(Page page, BrowserContext context, Server server) throws ExecutionException, InterruptedException { context.route("**/empty.html", route -> { throw new RuntimeException("My Exception"); }); @@ -173,9 +173,9 @@ void shouldNotSwallowExceptionsInRoute() throws ExecutionException, InterruptedE assertTrue(e.getMessage().contains("My Exception"), e.getMessage()); } - @Test + @PlaywrightTest @Disabled("Conflicts with https://github.com/microsoft/playwright-java/pull/680") - void shouldNotSwallowExceptionsInFulfill() throws ExecutionException, InterruptedException { + void shouldNotSwallowExceptionsInFulfill(Page page, Playwright playwright, Server server) throws ExecutionException, InterruptedException { APIRequestContext request = playwright.request().newContext(); APIResponse response = request.get(server.EMPTY_PAGE); response.dispose(); @@ -187,9 +187,9 @@ void shouldNotSwallowExceptionsInFulfill() throws ExecutionException, Interrupte assertTrue(e.getMessage().contains("Fetch response has been disposed"), e.getMessage()); } - @Test + @PlaywrightTest @Disabled("Conflicts with https://github.com/microsoft/playwright-java/pull/680") - void shouldNotSwallowExceptionsInResume() throws ExecutionException, InterruptedException { + void shouldNotSwallowExceptionsInResume(Page page, Server server) throws ExecutionException, InterruptedException { page.route("**/*", route -> { route.resume(new Route.ResumeOptions().setUrl("file:///tmp")); }); @@ -198,8 +198,8 @@ void shouldNotSwallowExceptionsInResume() throws ExecutionException, Interrupted } - @Test - void shouldChainFallback() { + @PlaywrightTest + void shouldChainFallback(Page page, BrowserContext context, Server server) { List intercepted = new ArrayList<>(); context.route("**/empty.html", route -> { intercepted.add(1); @@ -217,8 +217,8 @@ void shouldChainFallback() { assertEquals(asList(3, 2, 1), intercepted); } - @Test - void shouldNotChainFulfill() { + @PlaywrightTest + void shouldNotChainFulfill(Page page, BrowserContext context, Server server) { boolean[] failed = {false}; context.route("**/empty.html", route -> { failed[0] = true; @@ -235,8 +235,8 @@ void shouldNotChainFulfill() { assertFalse(failed[0]); } - @Test - void shouldNotChainAbort() { + @PlaywrightTest + void shouldNotChainAbort(Page page, BrowserContext context, Server server) { boolean[] failed = {false}; context.route("**/empty.html", route -> { failed[0] = true; @@ -252,8 +252,8 @@ void shouldNotChainAbort() { assertFalse(failed[0]); } - @Test - void shouldChainFallbackIntoPage() { + @PlaywrightTest + void shouldChainFallbackIntoPage(Page page, BrowserContext context, Server server) { List intercepted = new ArrayList<>(); context.route("**/empty.html", route -> { intercepted.add(1); @@ -283,8 +283,8 @@ void shouldChainFallbackIntoPage() { assertEquals(asList(6, 5, 4, 3, 2, 1), intercepted); } - @Test - void shouldFallBackAsync() { + @PlaywrightTest + void shouldFallBackAsync(Page page, BrowserContext context, Server server) { List intercepted = new ArrayList<>(); context.route("**/empty.html", route -> { intercepted.add(1); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextServiceWorkerPolicy.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextServiceWorkerPolicy.java index 351b65199..55e182851 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextServiceWorkerPolicy.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextServiceWorkerPolicy.java @@ -16,20 +16,20 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.ServiceWorkerPolicy; -import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertNotNull; -public class TestBrowserContextServiceWorkerPolicy extends TestBase { - @Test - void shouldAllowServiceWorkersByDefault() { +public class TestBrowserContextServiceWorkerPolicy { + @PlaywrightTest + void shouldAllowServiceWorkersByDefault(Page page, Server server) { page.navigate(server.PREFIX + "/serviceworkers/empty/sw.html"); assertNotNull(page.evaluate("() => window['registrationPromise']")); } - @Test - void blocksServiceWorkerRegistration() { + @PlaywrightTest + void blocksServiceWorkerRegistration(Browser browser, Server server) { try (BrowserContext context = browser.newContext(new Browser.NewContextOptions().setServiceWorkers(ServiceWorkerPolicy.BLOCK))) { Page page = context.newPage(); ConsoleMessage message = page.waitForConsoleMessage(new Page.WaitForConsoleMessageOptions() diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java index ffff1dccd..49d7c3862 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStorageState.java @@ -19,8 +19,9 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.DisabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.junit.jupiter.api.io.TempDir; import java.io.FileInputStream; @@ -31,15 +32,18 @@ import java.nio.file.Path; import static com.microsoft.playwright.Utils.assertJsonEquals; +import static com.microsoft.playwright.Utils.defaultSameSiteCookieValue; import static com.microsoft.playwright.Utils.mapOf; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; +import static com.microsoft.playwright.fixtures.BrowserName.WEBKIT; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class TestBrowserContextStorageState extends TestBase { +public class TestBrowserContextStorageState { - @Test - void shouldCaptureLocalStorage() { + @PlaywrightTest + void shouldCaptureLocalStorage(Page page, BrowserContext context) { page.route("**/*", route -> { route.fulfill(new Route.FulfillOptions().setBody("")); }); @@ -65,8 +69,8 @@ void shouldCaptureLocalStorage() { "}]}", new Gson().fromJson(storageState, JsonObject.class)); } - @Test - void shouldSetLocalStorage() { + @PlaywrightTest + void shouldSetLocalStorage(Browser browser) { String storageState = "{\n" + " origins: [\n" + " {\n" + @@ -89,8 +93,8 @@ void shouldSetLocalStorage() { context.close(); } - @Test - void shouldRoundTripThroughTheFile(@TempDir Path tempDir) throws IOException { + @PlaywrightTest + void shouldRoundTripThroughTheFile(BrowserContext context, Browser browser, BrowserName browserName, @TempDir Path tempDir) throws IOException { Page page1 = context.newPage(); page1.route("**/*", route -> { route.fulfill(new Route.FulfillOptions().setBody("")); @@ -105,7 +109,7 @@ void shouldRoundTripThroughTheFile(@TempDir Path tempDir) throws IOException { context.storageState(new BrowserContext.StorageStateOptions().setPath(path)); String sameSiteCamelCase = "Lax"; - switch (defaultSameSiteCookieValue) { + switch (defaultSameSiteCookieValue(browserName)) { case STRICT: sameSiteCamelCase = "Strict"; break; @@ -151,7 +155,7 @@ void shouldRoundTripThroughTheFile(@TempDir Path tempDir) throws IOException { page2.navigate("https://www.example.com"); Object localStorage = page2.evaluate("window.localStorage"); assertEquals(mapOf("name1", "value1"), localStorage); - if (!isFirefox()) { + if (browserName != FIREFOX) { // TODO: fails on bots with expected: but was: <> Object cookie = page2.evaluate("document.cookie"); assertEquals("username=John Doe", cookie); @@ -159,8 +163,8 @@ void shouldRoundTripThroughTheFile(@TempDir Path tempDir) throws IOException { context2.close(); } - @Test - void shouldSerialiseStorageStateWithLoneSurrogates() { + @PlaywrightTest + void shouldSerialiseStorageStateWithLoneSurrogates(Page page, BrowserContext context, Server server) { page.navigate(server.EMPTY_PAGE); page.evaluate("chars => window.localStorage.setItem('foo', String.fromCharCode(55934))"); String storageState = context.storageState(); @@ -175,8 +179,8 @@ void shouldSerialiseStorageStateWithLoneSurrogates() { "}]}", new Gson().fromJson(storageState, JsonObject.class)); } - @Test - void shouldSupportIndexedDB() { + @PlaywrightTest + void shouldSupportIndexedDB(Page page, Browser browser, Server server) { page.navigate(server.PREFIX + "/to-do-notifications/index.html"); assertThat(page.locator("#notifications")).matchesAriaSnapshot( @@ -281,17 +285,17 @@ void shouldSupportIndexedDB() { new BrowserContext.StorageStateOptions().setIndexedDB(false))); } - @Test - void setStorageStateShouldHandleMissingFile(@TempDir Path tempDir) { + @PlaywrightTest + void setStorageStateShouldHandleMissingFile(BrowserContext context, @TempDir Path tempDir) { Path file = tempDir.resolve("does-not-exist.json"); PlaywrightException e = org.junit.jupiter.api.Assertions.assertThrows( PlaywrightException.class, () -> context.setStorageState(file)); assertTrue(e.getMessage().contains("Failed to read storage state from file"), e.getMessage()); } - @Test - void shouldRoundTripOPFS(@TempDir Path tempDir) throws IOException { - Assumptions.assumeFalse(isWebKit(), "OPFS is unavailable in non-persistent WebKit contexts"); + @PlaywrightTest + @DisabledOnBrowser(value = WEBKIT, reason = "OPFS is unavailable in non-persistent WebKit contexts") + void shouldRoundTripOPFS(Page page, BrowserContext context, Browser browser, Server server, @TempDir Path tempDir) throws IOException { page.navigate(server.EMPTY_PAGE); page.evaluate("async () => {\n" + " const root = await navigator.storage.getDirectory();\n" + @@ -326,7 +330,7 @@ void shouldRoundTripOPFS(@TempDir Path tempDir) throws IOException { assertJsonEquals(expected, JsonParser.parseString(context.request().storageState(new APIRequestContext.StorageStateOptions().setOpfs(true)))); try (BrowserContext context2 = browser.newContext(new Browser.NewContextOptions().setStorageStatePath(path))) { - checkOPFSContext(context2, expected); + checkOPFSContext(server, context2, expected); } try (BrowserContext context3 = browser.newContext()) { @@ -337,11 +341,11 @@ void shouldRoundTripOPFS(@TempDir Path tempDir) throws IOException { " await root.getFileHandle('stale.txt', { create: true });\n" + "}"); context3.setStorageState(path); - checkOPFSContext(context3, expected); + checkOPFSContext(server, context3, expected); } } - private void checkOPFSContext(BrowserContext context, String expectedStorageState) { + private void checkOPFSContext(Server server, BrowserContext context, String expectedStorageState) { assertJsonEquals(expectedStorageState, JsonParser.parseString(context.storageState(new BrowserContext.StorageStateOptions().setOpfs(true)))); Page checkPage = context.newPage(); checkPage.navigate(server.EMPTY_PAGE); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStrict.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStrict.java index c107e242a..435cdb244 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStrict.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextStrict.java @@ -16,18 +16,19 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.Fixture; +import com.microsoft.playwright.fixtures.PlaywrightTest; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextStrict extends TestBase { - @Override - BrowserContext createContext() { - return browser.newContext(new Browser.NewContextOptions().setStrictSelectors(true)); +public class TestBrowserContextStrict { + @Fixture + Browser.NewContextOptions contextOptions() { + return new Browser.NewContextOptions().setStrictSelectors(true); } - @Test - void shouldNotFailPageTextContentInNonStrictMode() { + @PlaywrightTest + void shouldNotFailPageTextContentInNonStrictMode(Browser browser) { try (BrowserContext context = browser.newContext()) { Page page = context.newPage(); page.setContent("span1
target
"); @@ -35,15 +36,15 @@ void shouldNotFailPageTextContentInNonStrictMode() { } } - @Test - void shouldFailPageTextContentInStrictMode() { + @PlaywrightTest + void shouldFailPageTextContentInStrictMode(Page page) { page.setContent("span1
target
"); PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.textContent("span")); assertTrue(e.getMessage().contains("strict mode violation")); } - @Test - void shouldOptOutOfStrictMode() { + @PlaywrightTest + void shouldOptOutOfStrictMode(Page page) { page.setContent("span1
target
"); assertEquals("span1", page.textContent("span", new Page.TextContentOptions().setStrict(false))); } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextViewport.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextViewport.java index a55623f4b..23f33070e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextViewport.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextViewport.java @@ -16,29 +16,29 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.PlaywrightTest; import java.util.Map; import static com.microsoft.playwright.Utils.verifyViewport; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextViewport extends TestBase { +public class TestBrowserContextViewport { - @Test - void shouldGetTheProperDefaultViewPortSize() { + @PlaywrightTest + void shouldGetTheProperDefaultViewPortSize(Page page) { verifyViewport(page, 1280, 720); } - @Test - void shouldSetTheProperViewportSize() { + @PlaywrightTest + void shouldSetTheProperViewportSize(Page page) { verifyViewport(page, 1280, 720); page.setViewportSize(123, 456); verifyViewport(page,123, 456); } - @Test - void shouldReturnCorrectOuterWidthAndOuterHeight() { + @PlaywrightTest + void shouldReturnCorrectOuterWidthAndOuterHeight(Page page) { Map size = (Map) page.evaluate("() => {\n" + " return {\n" + " innerWidth: window.innerWidth,\n" + @@ -53,8 +53,8 @@ void shouldReturnCorrectOuterWidthAndOuterHeight() { assertTrue(size.get("outerHeight") >= size.get("innerHeight")); } - @Test - void shouldEmulateDeviceWidth() { + @PlaywrightTest + void shouldEmulateDeviceWidth(Page page) { verifyViewport(page, 1280, 720); page.setViewportSize(200, 200); assertEquals(200, page.evaluate("() => window.screen.width")); @@ -74,8 +74,8 @@ void shouldEmulateDeviceWidth() { assertEquals(true, page.evaluate("() => matchMedia('(device-width: 500px)').matches")); } - @Test - void shouldEmulateDeviceHeight() { + @PlaywrightTest + void shouldEmulateDeviceHeight(Page page) { verifyViewport(page, 1280, 720); page.setViewportSize(200, 200); assertEquals(200, page.evaluate("() => window.screen.height")); @@ -95,23 +95,23 @@ void shouldEmulateDeviceHeight() { assertEquals(true, page.evaluate("() => matchMedia('(device-height: 500px)').matches")); } - @Test - void shouldEmulateAvailWidthAndAvailHeight() { + @PlaywrightTest + void shouldEmulateAvailWidthAndAvailHeight(Page page) { page.setViewportSize(500, 600); assertEquals(500, page.evaluate("() => window.screen.availWidth")); assertEquals(600, page.evaluate("() => window.screen.availHeight")); } - @Test - void shouldNotHaveTouchByDefault() { + @PlaywrightTest + void shouldNotHaveTouchByDefault(Page page, Server server) { page.navigate(server.PREFIX + "/mobile.html"); assertEquals(false, page.evaluate("() => 'ontouchstart' in window")); page.navigate(server.PREFIX + "/detect-touch.html"); assertEquals("NO", page.evaluate("() => document.body.textContent.trim()")); } - @Test - void shouldSupportTouchWithNullViewport() { + @PlaywrightTest + void shouldSupportTouchWithNullViewport(Browser browser, Server server) { Browser.NewContextOptions options = new Browser.NewContextOptions() .setHasTouch(true).setViewportSize(null); BrowserContext context = browser.newContext(options); @@ -121,8 +121,8 @@ void shouldSupportTouchWithNullViewport() { context.close(); } - @Test - void shouldReportNullViewPortSizeWhenGivenNullViewport() { + @PlaywrightTest + void shouldReportNullViewPortSizeWhenGivenNullViewport(Browser browser) { Browser.NewContextOptions options = new Browser.NewContextOptions().setViewportSize(null); BrowserContext context = browser.newContext(options); Page page = context.newPage(); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextWebAuthn.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextWebAuthn.java index 9fe4f8d9c..584ac3fe7 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextWebAuthn.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserContextWebAuthn.java @@ -16,8 +16,8 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.VirtualCredential; -import org.junit.jupiter.api.Test; import java.util.List; import java.util.Map; @@ -25,7 +25,7 @@ import static com.microsoft.playwright.Utils.mapOf; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserContextWebAuthn extends TestBase { +public class TestBrowserContextWebAuthn { private static final String B64URL_TO_BYTES_JS = " const b64UrlToBytes = s => {\n" + " let str = s.replace(/-/g, '+').replace(/_/g, '/');\n" + @@ -38,8 +38,8 @@ public class TestBrowserContextWebAuthn extends TestBase { " return u8;\n" + " };\n"; - @Test - void shouldNotInterceptNavigatorCredentialsWithoutInstall() { + @PlaywrightTest + void shouldNotInterceptNavigatorCredentialsWithoutInstall(Page page, BrowserContext context, Server server) { // Seed a credential, but do not install the interceptor. context.credentials().create("localhost"); page.navigate(server.EMPTY_PAGE); @@ -48,8 +48,8 @@ void shouldNotInterceptNavigatorCredentialsWithoutInstall() { assertEquals(false, intercepted); } - @Test - void shouldSeedKnownCredentialAndAuthenticate() { + @PlaywrightTest + void shouldSeedKnownCredentialAndAuthenticate(Page page, BrowserContext context, Browser browser, Server server) { // This is the easiest way to create credentials. In practice, this // probably comes from environment. VirtualCredential known; @@ -133,8 +133,8 @@ private static int assertAndGetSignCount(Page page, String rpId) { "}", mapOf("rpId", rpId)); } - @Test - void shouldSeedAndReportSignCount() { + @PlaywrightTest + void shouldSeedAndReportSignCount(Page page, BrowserContext context, Browser browser, Server server) { VirtualCredential fresh = context.credentials().create("fresh.example.com"); assertEquals(0, fresh.signCount); @@ -167,16 +167,16 @@ void shouldSeedAndReportSignCount() { } } - @Test - void shouldRejectInvalidSignCount() { + @PlaywrightTest + void shouldRejectInvalidSignCount(BrowserContext context) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.credentials().create("example.com", new Credentials.CreateOptions().setSignCount(-1))); assertTrue(e.getMessage().contains("signCount must be between 0 and 4294967295, got -1"), e.getMessage()); assertEquals(0, context.credentials().get().size()); } - @Test - void shouldCapturePageCreatedCredentialAndReuseItInAnotherContext() { + @PlaywrightTest + void shouldCapturePageCreatedCredentialAndReuseItInAnotherContext(Page page, BrowserContext context, Browser browser, Server server) { // Setup context: the app registers a passkey via navigator.credentials.create(). String createdId; VirtualCredential captured; diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeBasic.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeBasic.java index 1d81dd6b1..1d4818fad 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeBasic.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeBasic.java @@ -16,37 +16,36 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.DisabledOnBrowser; +import com.microsoft.playwright.fixtures.Env; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; import java.nio.file.Files; import java.nio.file.Paths; -import static com.microsoft.playwright.Utils.getBrowserNameFromEnv; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; +import static com.microsoft.playwright.fixtures.BrowserName.WEBKIT; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserTypeBasic extends TestBase { - @Test - void browserTypeExecutablePathShouldWork() { - Assumptions.assumeTrue(getBrowserChannelFromEnv() == null); - Assumptions.assumeTrue(createLaunchOptions().executablePath == null, "Skip with custom executable path"); +public class TestBrowserTypeBasic { + @PlaywrightTest + void browserTypeExecutablePathShouldWork(BrowserType browserType) { + Assumptions.assumeTrue(Env.channel() == null); + Assumptions.assumeTrue(Env.launchOptions().executablePath == null, "Skip with custom executable path"); String executablePath = browserType.executablePath(); assertTrue(Files.exists(Paths.get(executablePath))); } - @Test - void browserTypeNameShouldWork() { - assertEquals(getBrowserNameFromEnv(), browserType.name()); + @PlaywrightTest + void browserTypeNameShouldWork(BrowserType browserType, BrowserName browserName) { + assertEquals(browserName.toString(), browserType.name()); } - static boolean isChromiumOrWebKit() { - return isChromium() || isWebKit(); - } - - @Test - @DisabledIf(value="isChromiumOrWebKit", disabledReason="Connecting over CDP is supported in Chromium and WebKit") - void shouldThrowWhenTryingToConnectWithNotChromium() { + @PlaywrightTest + @DisabledOnBrowser(value = {CHROMIUM, WEBKIT}, reason = "Connecting over CDP is supported in Chromium and WebKit") + void shouldThrowWhenTryingToConnectWithNotChromium(BrowserType browserType) { PlaywrightException e = assertThrows(PlaywrightException.class, () -> browserType.connectOverCDP("foo")); assertTrue(e.getMessage().contains("Connecting over CDP is only supported in Chromium and WebKit.")); } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java index 3ee111bf3..9052e384a 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestBrowserTypeConnect.java @@ -16,11 +16,11 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.Fixture; +import com.microsoft.playwright.fixtures.PlaywrightTest; +import com.microsoft.playwright.fixtures.Scope; import com.microsoft.playwright.impl.driver.Driver; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import java.io.*; @@ -35,83 +35,28 @@ import java.util.stream.Collectors; import static com.microsoft.playwright.Utils.*; +import static com.microsoft.playwright.fixtures.Env.isWindows; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -public class TestBrowserTypeConnect extends TestBase { +public class TestBrowserTypeConnect { static Path FILE_TO_UPLOAD = Paths.get("src/test/resources/file-to-upload.txt"); - private Process browserServer; - private String wsEndpoint; - - static class BrowserServer { - Process process; - String wsEndpoint; - - void kill() throws InterruptedException { - process.destroy(); - int exitCode = process.waitFor(); - // FIXME: 2 tests are failing this check on windows: - // disconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed - // shouldThrowWhenUsedAfterIsConnectedReturnsFalse - if (!isWindows) { - assertEquals(0, exitCode); - } - } + @Fixture(scope = Scope.WORKER) + static BrowserServer browserServer(BrowserType browserType) { + return BrowserServer.launch(browserType); } - static BrowserServer launchBrowserServer(BrowserType browserType) { - try { - Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false); - Path dir = driver.driverDir(); - String node = dir.resolve(isWindows ? "node.exe" : "node").toString(); - String cliJs = dir.resolve("package/cli.js").toString(); - // We launch node process directly instead of using playwright.sh script as killing the script - // process will leave node process running and killing it would be more hassle. - ProcessBuilder pb = new ProcessBuilder(node, cliJs, "launch-server", "--browser", browserType.name()); - pb.directory(dir.toFile()); - pb.redirectError(ProcessBuilder.Redirect.INHERIT); - BrowserServer result = new BrowserServer(); - result.process = pb.start(); - BufferedReader input = new BufferedReader(new InputStreamReader(result.process.getInputStream())); - result.wsEndpoint = input.readLine(); - if (!result.wsEndpoint.startsWith("ws://")) { - throw new RuntimeException("Invalid web socket address: " + result.wsEndpoint); - } - return result; - } catch (IOException e) { - throw new PlaywrightException("Failed to launch server", e); - } - } - - @Override - @BeforeAll - // Hide base class method to launch browser server and connect to it. - void launchBrowser() { - initBrowserType(); - BrowserServer r = launchBrowserServer(browserType); - wsEndpoint = r.wsEndpoint; - browserServer = r.process; - browser = browserType.connect(wsEndpoint); - // Do not actually connect to browser, the tests will do it manually. - } - - @Override - @AfterAll - void closeBrowser() { - super.closeBrowser(); - if (browserServer != null) { - browserServer.destroyForcibly(); - browserServer = null; - wsEndpoint = null; - } + @Fixture(scope = Scope.WORKER) + static Browser browser(BrowserType browserType, BrowserServer browserServer) { + return browserType.connect(browserServer.wsEndpoint); } - @Test - void shouldBeAbleToReconnectToABrowser() { + @PlaywrightTest + void shouldBeAbleToReconnectToABrowser(BrowserType browserType, Server server, BrowserServer browserServer) { { - Browser browser = browserType.connect(wsEndpoint); + Browser browser = browserType.connect(browserServer.wsEndpoint); BrowserContext browserContext = browser.newContext(); assertEquals(0, browserContext.pages().size()); Page page = browserContext.newPage(); @@ -120,7 +65,7 @@ void shouldBeAbleToReconnectToABrowser() { browser.close(); } { - Browser browser = browserType.connect(wsEndpoint); + Browser browser = browserType.connect(browserServer.wsEndpoint); BrowserContext browserContext = browser.newContext(); Page page = browserContext.newPage(); page.navigate(server.EMPTY_PAGE); @@ -128,9 +73,9 @@ void shouldBeAbleToReconnectToABrowser() { } } - @Test - void shouldSupportSlowMo() { - Browser browser = browserType.connect(wsEndpoint, + @PlaywrightTest + void shouldSupportSlowMo(BrowserType browserType, Server server, BrowserServer browserServer) { + Browser browser = browserType.connect(browserServer.wsEndpoint, new BrowserType.ConnectOptions().setSlowMo(1)); BrowserContext browserContext = browser.newContext(); Page page = browserContext.newPage(); @@ -139,14 +84,14 @@ void shouldSupportSlowMo() { browser.close(); } - @Test - void shouldBeAbleToConnectTwoBrowsersAtTheSameTime() { - Browser browser1 = browserType.connect(wsEndpoint); + @PlaywrightTest + void shouldBeAbleToConnectTwoBrowsersAtTheSameTime(BrowserType browserType, BrowserServer browserServer) { + Browser browser1 = browserType.connect(browserServer.wsEndpoint); assertEquals(0, browser1.contexts().size()); browser1.newContext(); assertEquals(1, browser1.contexts().size()); - Browser browser2 = browserType.connect(wsEndpoint); + Browser browser2 = browserType.connect(browserServer.wsEndpoint); assertEquals(0, browser2.contexts().size()); browser2.newContext(); assertEquals(1, browser2.contexts().size()); @@ -159,8 +104,8 @@ void shouldBeAbleToConnectTwoBrowsersAtTheSameTime() { browser2.close(); } - @Test - void shouldSendExtraHeadersWithConnectRequest() throws Exception { + @PlaywrightTest + void shouldSendExtraHeadersWithConnectRequest(BrowserType browserType) throws Exception { try (WebSocketServerImpl webSocketServer = WebSocketServerImpl.create()) { try { browserType.connect("ws://localhost:" + webSocketServer.getPort() + "/ws", @@ -174,10 +119,10 @@ void shouldSendExtraHeadersWithConnectRequest() throws Exception { } } - @Test - void disconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed() throws InterruptedException { + @PlaywrightTest + void disconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed(BrowserType browserType) throws InterruptedException { // Launch another server to not affect other tests. - BrowserServer remote = launchBrowserServer(browserType); + BrowserServer remote = BrowserServer.launch(browserType); Browser browser1 = browserType.connect(remote.wsEndpoint); Browser browser2 = browserType.connect(remote.wsEndpoint); @@ -200,9 +145,9 @@ void disconnectedEventShouldBeEmittedWhenBrowserIsClosedOrServerIsClosed() throw assertEquals(1, disconnected2[0]); } - @Test - void disconnectedEventShouldHaveBrowserAsArgument() { - Browser browser = browserType.connect(wsEndpoint); + @PlaywrightTest + void disconnectedEventShouldHaveBrowserAsArgument(BrowserType browserType, BrowserServer browserServer) { + Browser browser = browserType.connect(browserServer.wsEndpoint); Browser[] disconnected = {null}; browser.onDisconnected(b -> disconnected[0] = b); browser.close(); @@ -213,18 +158,18 @@ void shouldHandleExceptionsDuringConnect() { // This is an implementation detail test } - @Test - void shouldSetTheBrowserConnectedState() { - Browser remote = browserType.connect(wsEndpoint); + @PlaywrightTest + void shouldSetTheBrowserConnectedState(BrowserType browserType, BrowserServer browserServer) { + Browser remote = browserType.connect(browserServer.wsEndpoint); assertTrue(remote.isConnected()); remote.close(); assertFalse(remote.isConnected()); } - @Test - void shouldThrowWhenUsedAfterIsConnectedReturnsFalse() throws InterruptedException { + @PlaywrightTest + void shouldThrowWhenUsedAfterIsConnectedReturnsFalse(BrowserType browserType) throws InterruptedException { // Launch another server to not affect other tests. - BrowserServer server = launchBrowserServer(browserType); + BrowserServer server = BrowserServer.launch(browserType); Browser remote = browserType.connect(server.wsEndpoint); Page page = remote.newPage(); boolean[] disconnected = {false}; @@ -242,9 +187,9 @@ void shouldThrowWhenUsedAfterIsConnectedReturnsFalse() throws InterruptedExcepti assertFalse(remote.isConnected()); } - @Test - void shouldThrowWhenCallingWaitForNavigationAfterDisconnect() throws InterruptedException { - BrowserServer server = launchBrowserServer(browserType); + @PlaywrightTest + void shouldThrowWhenCallingWaitForNavigationAfterDisconnect(BrowserType browserType) throws InterruptedException { + BrowserServer server = BrowserServer.launch(browserType); Browser browser = browserType.connect(server.wsEndpoint); Page page = browser.newPage(); @@ -266,9 +211,9 @@ void shouldThrowWhenCallingWaitForNavigationAfterDisconnect() throws Interrupted e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage()); } - @Test - void shouldRejectNavigationWhenBrowserCloses() { - Browser remote = browserType.connect(wsEndpoint); + @PlaywrightTest + void shouldRejectNavigationWhenBrowserCloses(BrowserType browserType, Server server, BrowserServer browserServer) { + Browser remote = browserType.connect(browserServer.wsEndpoint); Page page = remote.newPage(); server.setRoute("/one-style.css", r -> {}); @@ -279,9 +224,9 @@ void shouldRejectNavigationWhenBrowserCloses() { assertTrue(e.getMessage().contains("Browser has been closed")); } - @Test - void shouldEmitCloseEventsOnPagesAndContexts() throws InterruptedException { - BrowserServer server = launchBrowserServer(browserType); + @PlaywrightTest + void shouldEmitCloseEventsOnPagesAndContexts(BrowserType browserType) throws InterruptedException { + BrowserServer server = BrowserServer.launch(browserType); Browser browser = browserType.connect(server.wsEndpoint); BrowserContext context = browser.newContext(); Page page = context.newPage(); @@ -300,8 +245,8 @@ void shouldEmitCloseEventsOnPagesAndContexts() throws InterruptedException { assertEquals(Arrays.asList("page", "context"), events); } - @Test - void shouldRespectSelectors() { + @PlaywrightTest + void shouldRespectSelectors(BrowserType browserType, Playwright playwright, BrowserServer browserServer) { String mycss = "{\n" + " query(root, selector) {\n" + " return root.querySelector(selector);\n" + @@ -313,7 +258,7 @@ void shouldRespectSelectors() { // Register one engine before connecting. playwright.selectors().register("mycss1", mycss); - Browser browser1 = browserType.connect(wsEndpoint); + Browser browser1 = browserType.connect(browserServer.wsEndpoint); BrowserContext context1 = browser1.newContext(); // Register another engine after creating context. @@ -325,7 +270,7 @@ void shouldRespectSelectors() { assertEquals("hello", page1.innerHTML("mycss1=div")); assertEquals("hello", page1.innerHTML("mycss2=div")); - Browser browser2 = browserType.connect(wsEndpoint); + Browser browser2 = browserType.connect(browserServer.wsEndpoint); // Register third engine after second connect. playwright.selectors().register("mycss3", mycss); @@ -341,9 +286,9 @@ void shouldRespectSelectors() { browser2.close(); } - @Test - void shouldNotThrowOnCloseAfterDisconnect() throws InterruptedException { - BrowserServer remoteServer = launchBrowserServer(browserType); + @PlaywrightTest + void shouldNotThrowOnCloseAfterDisconnect(BrowserType browserType) throws InterruptedException { + BrowserServer remoteServer = BrowserServer.launch(browserType); Browser browser = browserType.connect(remoteServer.wsEndpoint); Page page = browser.newPage(); @@ -357,8 +302,8 @@ void shouldNotThrowOnCloseAfterDisconnect() throws InterruptedException { browser.close(); } - @Test - void shouldSaveAsVideosFromRemoteBrowser(@TempDir Path tempDir) { + @PlaywrightTest + void shouldSaveAsVideosFromRemoteBrowser(Browser browser, @TempDir Path tempDir) { Path videosPath = tempDir.resolve("videosPath"); BrowserContext context = browser.newContext(new Browser.NewContextOptions() .setRecordVideoDir(videosPath).setRecordVideoSize(320, 240)); @@ -374,8 +319,8 @@ void shouldSaveAsVideosFromRemoteBrowser(@TempDir Path tempDir) { } - @Test - void shouldSaveDownload(@TempDir Path tempDir) throws IOException { + @PlaywrightTest + void shouldSaveDownload(Browser browser, Server server, @TempDir Path tempDir) throws IOException { server.setRoute("/download", exchange -> { exchange.getResponseHeaders().add("Content-Type", "application/octet-stream"); exchange.getResponseHeaders().add("Content-Disposition", "attachment"); @@ -397,8 +342,8 @@ void shouldSaveDownload(@TempDir Path tempDir) throws IOException { page.close(); } - @Test - void shouldErrorWhenSavingDownloadAfterDeletion(@TempDir Path tempDir) { + @PlaywrightTest + void shouldErrorWhenSavingDownloadAfterDeletion(Browser browser, Server server, @TempDir Path tempDir) { server.setRoute("/download", exchange -> { exchange.getResponseHeaders().add("Content-Type", "application/octet-stream"); exchange.getResponseHeaders().add("Content-Disposition", "attachment"); @@ -417,11 +362,9 @@ void shouldErrorWhenSavingDownloadAfterDeletion(@TempDir Path tempDir) { page.close(); } - @Test - void shouldSupportTracingOverWebSocket(@TempDir Path tempDir) throws IOException { - List contexts = browser.contexts(); - assertEquals(1, contexts.size()); - BrowserContext context = contexts.get(0); + @PlaywrightTest + void shouldSupportTracingOverWebSocket(Browser browser, BrowserContext context, Server server, @TempDir Path tempDir) throws IOException { + assertEquals(asList(context), browser.contexts()); Page page = context.newPage(); context.tracing().start(new Tracing.StartOptions().setName("test") @@ -437,8 +380,8 @@ void shouldSupportTracingOverWebSocket(@TempDir Path tempDir) throws IOException assertTrue(Files.size(traceFile) > 0); } - @Test - void shouldRecordTraceWithSources(@TempDir Path tmpDir) throws IOException { + @PlaywrightTest + void shouldRecordTraceWithSources(Page page, BrowserContext context, Server server, @TempDir Path tmpDir) throws IOException { Assumptions.assumeTrue(System.getenv("PLAYWRIGHT_JAVA_SRC") != null, "PLAYWRIGHT_JAVA_SRC must point to the directory containing this test source."); context.tracing().start(new Tracing.StartOptions().setSources(true)); page.navigate(server.EMPTY_PAGE); @@ -457,8 +400,8 @@ void shouldRecordTraceWithSources(@TempDir Path tmpDir) throws IOException { assertEquals(new String(thisFile, UTF_8), new String(sources.values().iterator().next(), UTF_8)); } - @Test - void shouldFulfillWithGlobalFetchResult() { + @PlaywrightTest + void shouldFulfillWithGlobalFetchResult(Page page, Playwright playwright, Server server) { page.route("**/*", route -> { APIRequestContext request = playwright.request().newContext(); APIResponse response = request.get(server.PREFIX + "/simple.json"); @@ -469,8 +412,8 @@ void shouldFulfillWithGlobalFetchResult() { assertEquals("{\"foo\": \"bar\"}\n", response.text()); } - @Test - void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException { + @PlaywrightTest + void shouldUploadLargeFile(Page page, Server server, @TempDir Path tmpDir) throws IOException, ExecutionException, InterruptedException { Assumptions.assumeTrue(3 <= (Runtime.getRuntime().maxMemory() >> 30), "Fails if max heap size is < 3Gb"); page.navigate(server.PREFIX + "/input/fileupload.html"); Path uploadFile = tmpDir.resolve("200MB.zip"); @@ -509,8 +452,8 @@ void shouldUploadLargeFile(@TempDir Path tmpDir) throws IOException, ExecutionEx assertEquals(200 * 1024 * 1024, fields.get(0).content.length()); } - @Test - void setInputFilesShouldPreserveLastModifiedTimestamp() throws IOException { + @PlaywrightTest + void setInputFilesShouldPreserveLastModifiedTimestamp(Page page) throws IOException { page.setContent(""); Locator input = page.locator("input"); input.setInputFiles(FILE_TO_UPLOAD); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestCheck.java b/playwright/src/test/java/com/microsoft/playwright/TestCheck.java index 12928f762..01471c524 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestCheck.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestCheck.java @@ -16,64 +16,64 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.PlaywrightTest; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -public class TestCheck extends TestBase { +public class TestCheck { - @Test - void shouldCheckTheBox() { + @PlaywrightTest + void shouldCheckTheBox(Page page) { page.setContent(""); page.check("input"); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } - @Test - void shouldNotCheckTheCheckedBox() { + @PlaywrightTest + void shouldNotCheckTheCheckedBox(Page page) { page.setContent(""); page.check("input"); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } - @Test - void shouldUncheckTheBox() { + @PlaywrightTest + void shouldUncheckTheBox(Page page) { page.setContent(""); page.uncheck("input"); assertEquals(false, page.evaluate("() => window['checkbox'].checked")); } - @Test - void shouldNotUncheckTheUncheckedBox() { + @PlaywrightTest + void shouldNotUncheckTheUncheckedBox(Page page) { page.setContent(""); page.uncheck("input"); assertEquals(false, page.evaluate("() => window['checkbox'].checked")); } - @Test - void shouldCheckTheBoxByLabel() { + @PlaywrightTest + void shouldCheckTheBoxByLabel(Page page) { page.setContent(""); page.check("label"); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } - @Test - void shouldCheckTheBoxOutsideLabel() { + @PlaywrightTest + void shouldCheckTheBoxOutsideLabel(Page page) { page.setContent("
"); page.check("label"); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } - @Test - void shouldCheckTheBoxInsideLabelWOId() { + @PlaywrightTest + void shouldCheckTheBoxInsideLabelWOId(Page page) { page.setContent(""); page.check("label"); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } - @Test - void shouldCheckRadio() { + @PlaywrightTest + void shouldCheckRadio(Page page) { page.setContent("one\n" + "two\n" + "three"); @@ -81,8 +81,8 @@ void shouldCheckRadio() { assertTrue((Boolean) page.evaluate("() => window['two'].checked")); } - @Test - void shouldCheckTheBoxByAriaRole() { + @PlaywrightTest + void shouldCheckTheBoxByAriaRole(Page page) { page.setContent("\n" + " "; } - private void serve(String path, String body) { + private void serve(Server server, String path, String body) { server.setRoute(path, exchange -> { exchange.getResponseHeaders().add("Content-Type", "text/html"); exchange.sendResponseHeaders(200, 0); @@ -73,9 +76,9 @@ private void serve(String path, String body) { }); } - @Test - void shouldListToolsRegisteredByThePage() { - serve("/", registerScript(ADD_TOOL + + @PlaywrightTest + void shouldListToolsRegisteredByThePage(Page page, Server server) { + serve(server, "/", registerScript(ADD_TOOL + "modelContext.registerTool({\n" + " name: 'noop',\n" + " description: 'Does nothing',\n" + @@ -98,24 +101,24 @@ void shouldListToolsRegisteredByThePage() { assertTrue(tools.get(0).annotations.readOnly); } - @Test - void shouldReportNoToolsWhenThePageRegistersNone() { + @PlaywrightTest + void shouldReportNoToolsWhenThePageRegistersNone(Page page, Server server) { page.navigate(server.EMPTY_PAGE); assertEquals(0, page.webmcp().tools().size()); } - @Test - void shouldCallATool() { - serve("/", registerScript(ADD_TOOL)); + @PlaywrightTest + void shouldCallATool(Page page, Server server) { + serve(server, "/", registerScript(ADD_TOOL)); page.navigate(server.PREFIX + "/"); Object result = page.webmcp().callTool("add", mapOf("a", 2, "b", 40)); assertEquals(mapOf("content", asList(mapOf("type", "text", "text", "42"))), result); } - @Test - void shouldReturnIsErrorResultsAsIs() { - serve("/", registerScript( + @PlaywrightTest + void shouldReturnIsErrorResultsAsIs(Page page, Server server) { + serve(server, "/", registerScript( "modelContext.registerTool({\n" + " name: 'broken',\n" + " description: 'Always fails',\n" + @@ -127,9 +130,9 @@ void shouldReturnIsErrorResultsAsIs() { assertEquals(mapOf("content", asList(mapOf("type", "text", "text", "nope")), "isError", true), result); } - @Test - void shouldThrowForAnUnknownTool() { - serve("/", registerScript(ADD_TOOL)); + @PlaywrightTest + void shouldThrowForAnUnknownTool(Page page, Server server) { + serve(server, "/", registerScript(ADD_TOOL)); page.navigate(server.PREFIX + "/"); PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.webmcp().callTool("missing")); @@ -137,9 +140,9 @@ void shouldThrowForAnUnknownTool() { assertTrue(e.getMessage().contains("Available tools: add"), e.getMessage()); } - @Test - void shouldTimeOutWhenTheToolDoesNotSettle() { - serve("/", registerScript( + @PlaywrightTest + void shouldTimeOutWhenTheToolDoesNotSettle(Page page, Server server) { + serve(server, "/", registerScript( "modelContext.registerTool({\n" + " name: 'stuck',\n" + " description: 'Never resolves',\n" + @@ -151,11 +154,11 @@ void shouldTimeOutWhenTheToolDoesNotSettle() { assertTrue(e.getMessage().contains("Timeout 500ms exceeded"), e.getMessage()); } - @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="Firefox does not support registering WebMCP tools in iframes yet") - void shouldScopeToolsToTheirFrame() { - serve("/", registerScript(ADD_TOOL) + ""); - serve("/frame.html", registerScript( + @PlaywrightTest + @DisabledOnBrowser(value = FIREFOX, reason = "Firefox does not support registering WebMCP tools in iframes yet") + void shouldScopeToolsToTheirFrame(Page page, Server server) { + serve(server, "/", registerScript(ADD_TOOL) + ""); + serve(server, "/frame.html", registerScript( "modelContext.registerTool({\n" + " name: 'subscribe',\n" + " description: 'Subscribes to the newsletter',\n" + diff --git a/playwright/src/test/java/com/microsoft/playwright/TestWebSocket.java b/playwright/src/test/java/com/microsoft/playwright/TestWebSocket.java index 08dcd9c0e..fcdc82035 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestWebSocket.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestWebSocket.java @@ -16,9 +16,9 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.Fixture; +import com.microsoft.playwright.fixtures.PlaywrightTest; import java.io.IOException; import java.time.Duration; @@ -27,24 +27,18 @@ import java.util.List; import java.util.function.Consumer; +import static com.microsoft.playwright.Utils.waitForCondition; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; import static java.util.Arrays.asList; import static org.junit.jupiter.api.Assertions.*; -public class TestWebSocket extends TestBase { - private static WebSocketServerImpl webSocketServer; - - @BeforeAll - static void startWebSockerServer() throws InterruptedException { - webSocketServer = WebSocketServerImpl.create(); - } - - @AfterAll - static void stopWebSockerServer() throws IOException, InterruptedException { - webSocketServer.stop(); +public class TestWebSocket { + @Fixture + WebSocketServerImpl webSocketServer() throws InterruptedException { + return WebSocketServerImpl.create(); } - - private void waitForCondition(boolean[] condition) { + private void waitForCondition(Page page, boolean[] condition) { assertEquals(1, condition.length); Instant start = Instant.now(); while (!condition[0]) { @@ -53,8 +47,8 @@ private void waitForCondition(boolean[] condition) { } } - @Test - void shouldWork() { + @PlaywrightTest + void shouldWork(Page page, WebSocketServerImpl webSocketServer) { Object value = page.evaluate("port => {\n" + " let cb;\n" + " const result = new Promise(f => cb = f);\n" + @@ -65,8 +59,8 @@ void shouldWork() { assertEquals("incoming", value); } - @Test - void shouldEmitCloseEvents() { + @PlaywrightTest + void shouldEmitCloseEvents(Page page, WebSocketServerImpl webSocketServer) { boolean[] socketClosed = {false}; List log = new ArrayList<>(); com.microsoft.playwright.WebSocket[] webSocket = {null}; @@ -82,13 +76,13 @@ void shouldEmitCloseEvents() { " const ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + " ws.addEventListener('open', () => ws.close());\n" + "}", webSocketServer.getPort()); - waitForCondition(socketClosed); + waitForCondition(page, socketClosed); assertEquals(asList("open", "close"), log); assertTrue(webSocket[0].isClosed()); } - @Test - void shouldEmitFrameEvents() { + @PlaywrightTest + void shouldEmitFrameEvents(Page page, WebSocketServerImpl webSocketServer) { boolean[] socketClosed = {false}; List log = new ArrayList<>(); page.onWebSocket(ws -> { @@ -102,15 +96,15 @@ void shouldEmitFrameEvents() { " ws.addEventListener('open', () => ws.send('outgoing'));\n" + " ws.addEventListener('message', () => { ws.close(); });\n" + " }", webSocketServer.getPort()); - waitForCondition(socketClosed); + waitForCondition(page, socketClosed); assertEquals("open", log.get(0), "Events: " + log); assertEquals("close", log.get(3), "Events: " + log); log.sort(String::compareTo); assertEquals(asList("close", "open", "received", "sent"), log); } - @Test - void shouldEmitBinaryFrameEvents() { + @PlaywrightTest + void shouldEmitBinaryFrameEvents(Page page, WebSocketServerImpl webSocketServer) { boolean[] socketClosed = {false}; List sent = new ArrayList<>(); page.onWebSocket(ws -> { @@ -128,15 +122,15 @@ void shouldEmitBinaryFrameEvents() { " ws.close();\n" + " });\n" + "}", webSocketServer.getPort()); - waitForCondition(socketClosed); + waitForCondition(page, socketClosed); assertEquals("text", sent.get(0).text()); for (int i = 0; i < 5; ++i) { assertEquals(i, sent.get(1).binary()[i]); } } - @Test - void shouldEmitError() { + @PlaywrightTest + void shouldEmitError(Page page, Server server, BrowserName browserName) { boolean[] socketError = {false}; String[] error = {null}; page.onWebSocket(ws -> { @@ -152,16 +146,16 @@ public void accept(String e) { page.evaluate("port => {\n" + " new WebSocket('ws://localhost:' + port + '/bogus-ws');\n" + "}", server.PORT); - waitForCondition(socketError); - if (isFirefox()) { + waitForCondition(page, socketError); + if (browserName == FIREFOX) { assertEquals("CLOSE_ABNORMAL", error[0]); } else { assertTrue(error[0].contains("404"), error[0]); } } - @Test - void shouldNotHaveStrayErrorEvents() { + @PlaywrightTest + void shouldNotHaveStrayErrorEvents(Page page, WebSocketServerImpl webSocketServer) { com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> { page.evaluate("port => {\n" + " window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + @@ -174,8 +168,8 @@ void shouldNotHaveStrayErrorEvents() { assertFalse(error[0]); } - @Test - void shouldRejectFutureEventOnSocketClose() { + @PlaywrightTest + void shouldRejectFutureEventOnSocketClose(Page page, WebSocketServerImpl webSocketServer) { com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> { page.evaluate("port => {\n" + " window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + @@ -188,8 +182,8 @@ void shouldRejectFutureEventOnSocketClose() { assertTrue(e.getMessage().matches("Socket closed|Socket error"), e.getMessage()); } - @Test - void shouldRejectFutureEventOnPageClose() { + @PlaywrightTest + void shouldRejectFutureEventOnPageClose(Page page, WebSocketServerImpl webSocketServer) { com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> { page.evaluate("port => {\n" + " window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + @@ -202,8 +196,8 @@ void shouldRejectFutureEventOnPageClose() { assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage()); } - @Test - void shouldCallFrameReceivedPredicate() { + @PlaywrightTest + void shouldCallFrameReceivedPredicate(Page page, WebSocketServerImpl webSocketServer) { com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> { page.evaluate("port => {\n" + " window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + @@ -223,8 +217,8 @@ void shouldCallFrameReceivedPredicate() { assertEquals("incoming", frame.text()); } - @Test - void shouldCallFrameSentPredicate() { + @PlaywrightTest + void shouldCallFrameSentPredicate(Page page, WebSocketServerImpl webSocketServer) { com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> { page.evaluate("port => {\n" + " window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + @@ -245,8 +239,8 @@ void shouldCallFrameSentPredicate() { assertEquals("outgoing", frame.text()); } - @Test - void shouldRespectFrameReceivedTimeout() { + @PlaywrightTest + void shouldRespectFrameReceivedTimeout(Page page, WebSocketServerImpl webSocketServer) { com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> { page.evaluate("port => {\n" + " window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + @@ -261,8 +255,8 @@ void shouldRespectFrameReceivedTimeout() { assertTrue(e.getMessage().contains("Timeout"), e.getMessage()); } - @Test - void shouldRespectFrameSentTimeout() { + @PlaywrightTest + void shouldRespectFrameSentTimeout(Page page, WebSocketServerImpl webSocketServer) { com.microsoft.playwright.WebSocket ws = page.waitForWebSocket(() -> { page.evaluate("port => {\n" + " window.ws = new WebSocket('ws://localhost:' + port + '/ws');\n" + diff --git a/playwright/src/test/java/com/microsoft/playwright/TestWebStorage.java b/playwright/src/test/java/com/microsoft/playwright/TestWebStorage.java index 98d1c6e17..b29c4713a 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestWebStorage.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestWebStorage.java @@ -16,8 +16,8 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.PlaywrightTest; import com.microsoft.playwright.options.WebStorageItem; -import org.junit.jupiter.api.Test; import java.util.HashMap; import java.util.List; @@ -26,7 +26,7 @@ import static com.microsoft.playwright.Utils.mapOf; import static org.junit.jupiter.api.Assertions.*; -public class TestWebStorage extends TestBase { +public class TestWebStorage { private static Map asMap(List items) { Map map = new HashMap<>(); for (WebStorageItem item : items) { @@ -35,20 +35,20 @@ private static Map asMap(List items) { return map; } - @Test - void localStorageItemsReturnsEmptyListOnFreshOrigin() { + @PlaywrightTest + void localStorageItemsReturnsEmptyListOnFreshOrigin(Page page, Server server) { page.navigate(server.EMPTY_PAGE); assertEquals(0, page.localStorage().items().size()); } - @Test - void localStorageGetItemReturnsNullForMissingKey() { + @PlaywrightTest + void localStorageGetItemReturnsNullForMissingKey(Page page, Server server) { page.navigate(server.EMPTY_PAGE); assertNull(page.localStorage().getItem("absent")); } - @Test - void localStorageSetItemPersistsAndSurfacesInItemsAndGetItem() { + @PlaywrightTest + void localStorageSetItemPersistsAndSurfacesInItemsAndGetItem(Page page, Server server) { page.navigate(server.EMPTY_PAGE); page.localStorage().setItem("alpha", "1"); page.localStorage().setItem("beta", "2"); @@ -58,16 +58,16 @@ void localStorageSetItemPersistsAndSurfacesInItemsAndGetItem() { assertEquals("1", page.evaluate("() => localStorage.getItem('alpha')")); } - @Test - void localStorageSetItemOverwritesExistingValue() { + @PlaywrightTest + void localStorageSetItemOverwritesExistingValue(Page page, Server server) { page.navigate(server.EMPTY_PAGE); page.localStorage().setItem("k", "first"); page.localStorage().setItem("k", "second"); assertEquals("second", page.localStorage().getItem("k")); } - @Test - void localStorageRemoveItemRemovesSingleItem() { + @PlaywrightTest + void localStorageRemoveItemRemovesSingleItem(Page page, Server server) { page.navigate(server.EMPTY_PAGE); page.localStorage().setItem("a", "1"); page.localStorage().setItem("b", "2"); @@ -76,8 +76,8 @@ void localStorageRemoveItemRemovesSingleItem() { assertEquals(mapOf("b", "2"), asMap(page.localStorage().items())); } - @Test - void localStorageClearEmptiesStorage() { + @PlaywrightTest + void localStorageClearEmptiesStorage(Page page, Server server) { page.navigate(server.EMPTY_PAGE); page.localStorage().setItem("a", "1"); page.localStorage().setItem("b", "2"); @@ -86,8 +86,8 @@ void localStorageClearEmptiesStorage() { assertEquals(0, page.localStorage().items().size()); } - @Test - void sessionStorageRoundTrip() { + @PlaywrightTest + void sessionStorageRoundTrip(Page page, Server server) { page.navigate(server.EMPTY_PAGE); assertEquals(0, page.sessionStorage().items().size()); @@ -103,8 +103,8 @@ void sessionStorageRoundTrip() { assertEquals(0, page.sessionStorage().items().size()); } - @Test - void localStorageAndSessionStorageAreIndependent() { + @PlaywrightTest + void localStorageAndSessionStorageAreIndependent(Page page, Server server) { page.navigate(server.EMPTY_PAGE); page.localStorage().setItem("shared", "local"); page.sessionStorage().setItem("shared", "session"); @@ -117,8 +117,8 @@ void localStorageAndSessionStorageAreIndependent() { assertEquals("session", page.sessionStorage().getItem("shared")); } - @Test - void storageMethodsAreScopedToTheCurrentOrigin() { + @PlaywrightTest + void storageMethodsAreScopedToTheCurrentOrigin(Page page, Server server) { page.navigate(server.PREFIX + "/empty.html"); page.localStorage().setItem("k", "origin-1"); diff --git a/playwright/src/test/java/com/microsoft/playwright/TestWheel.java b/playwright/src/test/java/com/microsoft/playwright/TestWheel.java index bfe83f1e9..46ef2017b 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestWheel.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestWheel.java @@ -16,22 +16,25 @@ package com.microsoft.playwright; -import org.junit.jupiter.api.Test; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.PlaywrightTest; import java.util.Map; import static com.microsoft.playwright.Utils.mapOf; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; +import static com.microsoft.playwright.fixtures.Env.isMac; import static org.junit.jupiter.api.Assertions.assertEquals; -public class TestWheel extends TestBase { - private void expectEvent(Map expected) { +public class TestWheel { + private void expectEvent(Page page, BrowserName browserName, Map expected) { // Chromium reports deltaX/deltaY scaled by host device scale factor. // https://bugs.chromium.org/p/chromium/issues/detail?id=1324819 // https://github.com/microsoft/playwright/issues/7362 // Different bots have different scale factors (usually 1 or 2), so we just ignore the values // instead of guessing the host scale factor. // This first appeared in Chromium 102. - boolean ignoreDelta = isChromium() && isMac; + boolean ignoreDelta = browserName == CHROMIUM && isMac; Map received = (Map) page.evaluate("window.lastEvent"); if (ignoreDelta) { expected.remove("deltaX"); @@ -42,8 +45,8 @@ private void expectEvent(Map expected) { assertEquals(expected, received); } - @Test - void shouldDispatchWheelEvents() { + @PlaywrightTest + void shouldDispatchWheelEvents(Page page, BrowserName browserName) { page.setContent("
"); page.mouse().move(50, 60); listenForWheelEvents(page, "div"); @@ -60,11 +63,11 @@ void shouldDispatchWheelEvents() { "altKey", false, "metaKey", false); page.waitForFunction("window.scrollY === 100"); - expectEvent(expected); + expectEvent(page, browserName, expected); } - @Test - void shouldScrollWhenNobodyIsListening() { + @PlaywrightTest + void shouldScrollWhenNobodyIsListening(Page page, Server server) { page.navigate(server.PREFIX + "/input/scrollable.html"); page.mouse().move(50, 60); page.mouse().wheel(0, 100); @@ -72,8 +75,8 @@ void shouldScrollWhenNobodyIsListening() { } - @Test - void shouldSetTheModifiers() { + @PlaywrightTest + void shouldSetTheModifiers(Page page, BrowserName browserName) { page.setContent("
"); page.mouse().move(50, 60); listenForWheelEvents(page, "div"); @@ -89,11 +92,11 @@ void shouldSetTheModifiers() { "shiftKey", true, "altKey", false, "metaKey", false); - expectEvent(expected); + expectEvent(page, browserName, expected); } - @Test - void shouldScrollHorizontally() { + @PlaywrightTest + void shouldScrollHorizontally(Page page, BrowserName browserName) { page.setContent("
"); page.mouse().move(50, 60); listenForWheelEvents(page, "div"); @@ -109,11 +112,11 @@ void shouldScrollHorizontally() { "altKey", false, "metaKey", false); page.waitForFunction("window.scrollX === 100"); - expectEvent(expected); + expectEvent(page, browserName, expected); } - @Test - void shouldWorkWhenTheEventIsCanceled() { + @PlaywrightTest + void shouldWorkWhenTheEventIsCanceled(Page page, BrowserName browserName) { page.setContent("
"); page.mouse().move(50, 60); listenForWheelEvents(page, "div"); @@ -133,7 +136,7 @@ void shouldWorkWhenTheEventIsCanceled() { "shiftKey", false, "altKey", false, "metaKey", false); - expectEvent(expected); + expectEvent(page, browserName, expected); // ensure that it did not. assertEquals(0, page.evaluate("window.scrollY")); } diff --git a/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java b/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java index 379b7d116..02a9ec88a 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java +++ b/playwright/src/test/java/com/microsoft/playwright/TestWorkers.java @@ -16,27 +16,31 @@ package com.microsoft.playwright; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.DisabledOnBrowser; +import com.microsoft.playwright.fixtures.EnabledOnBrowser; +import com.microsoft.playwright.fixtures.PlaywrightTest; import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; -import org.junit.jupiter.api.condition.EnabledIf; import java.time.Duration; import java.time.Instant; import static com.microsoft.playwright.Utils.attachFrame; +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; +import static com.microsoft.playwright.fixtures.BrowserName.WEBKIT; import static org.junit.jupiter.api.Assertions.*; -public class TestWorkers extends TestBase { +public class TestWorkers { - private int browserMajorVersion() { + private int browserMajorVersion(Browser browser) { String version = browser.version(); return Integer.parseInt(version.split("\\.")[0]); } - @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Started failing since last driver roll, flaky upstream") - void pageWorkers() { + @PlaywrightTest + @DisabledOnBrowser(value = CHROMIUM, reason = "Started failing since last driver roll, flaky upstream") + void pageWorkers(Page page, Server server) { Worker worker = page.waitForWorker(() -> page.navigate(server.PREFIX + "/worker/worker.html")); assertTrue(worker.url().contains("worker.js")); assertEquals("worker function result", worker.evaluate("() => self['workerFunction']()")); @@ -44,8 +48,8 @@ void pageWorkers() { assertEquals(0, page.workers().size()); } - @Test - void shouldEmitCreatedAndDestroyedEvents() { + @PlaywrightTest + void shouldEmitCreatedAndDestroyedEvents(Page page) { JSHandle[] workerObj = {null}; Worker worker = page.waitForWorker(() -> { workerObj[0] = page.evaluateHandle( @@ -62,18 +66,18 @@ void shouldEmitCreatedAndDestroyedEvents() { } - @Test - void shouldReportConsoleLogs() { + @PlaywrightTest + void shouldReportConsoleLogs(Page page) { ConsoleMessage message = page.waitForConsoleMessage(() -> page.evaluate( "() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))")); assertEquals("1", message.text()); } - @Test - void shouldHaveJSHandlesForConsoleLogs() { + @PlaywrightTest + void shouldHaveJSHandlesForConsoleLogs(Page page, BrowserName browserName) { ConsoleMessage log = page.waitForConsoleMessage(() -> page.evaluate( "() => new Worker(URL.createObjectURL(new Blob(['console.log(1,2,3,this)'], {type: 'application/javascript'})))")); - if (isFirefox()) { + if (browserName == FIREFOX) { assertEquals("1 2 3 JSHandle@object", log.text()); } else { assertEquals("1 2 3 DedicatedWorkerGlobalScope", log.text()); @@ -82,15 +86,15 @@ void shouldHaveJSHandlesForConsoleLogs() { assertEquals("null", (log.args().get(3).getProperty("origin")).jsonValue()); } - @Test - void shouldEvaluate() { + @PlaywrightTest + void shouldEvaluate(Page page) { Worker worker = page.waitForWorker(() -> page.evaluate( "() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))")); assertEquals(2, worker.evaluate("1+1")); } - @Test - void shouldReportErrors() { + @PlaywrightTest + void shouldReportErrors(Page page) { String[] errorLog = {null}; page.onPageError(e -> errorLog[0] = e); page.evaluate("() => new Worker(URL.createObjectURL(new Blob([`\n" + @@ -108,9 +112,9 @@ void shouldReportErrors() { assertTrue(errorLog[0].contains("this is my error")); } - @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="flaky upstream") - void shouldClearUponNavigation() { + @PlaywrightTest + @DisabledOnBrowser(value = FIREFOX, reason = "flaky upstream") + void shouldClearUponNavigation(Page page, Server server) { page.navigate(server.EMPTY_PAGE); Worker worker = page.waitForWorker(() -> page.evaluate( "() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))")); @@ -120,8 +124,8 @@ void shouldClearUponNavigation() { assertEquals(0, page.workers().size()); } - @Test - void shouldClearUponCrossProcessNavigation() { + @PlaywrightTest + void shouldClearUponCrossProcessNavigation(Page page, Server server) { page.navigate(server.EMPTY_PAGE); Worker worker = page.waitForWorker(() -> page.evaluate( "() => new Worker(URL.createObjectURL(new Blob(['console.log(1)'], {type: 'application/javascript'})))")); @@ -133,9 +137,9 @@ void shouldClearUponCrossProcessNavigation() { assertEquals(0, page.workers().size()); } - @Test - @EnabledIf(value="com.microsoft.playwright.TestBase#isWebKit", disabledReason="fixme") - void shouldAttributeNetworkActivityForWorkerInsideIframeToTheIframe() { + @PlaywrightTest + @EnabledOnBrowser(value = WEBKIT, reason = "fixme") + void shouldAttributeNetworkActivityForWorkerInsideIframeToTheIframe(Page page, Server server) { page.navigate(server.PREFIX + "/empty.html"); Frame[] frame = {null}; Worker worker = page.waitForWorker(() -> { @@ -150,10 +154,10 @@ void shouldAttributeNetworkActivityForWorkerInsideIframeToTheIframe() { assertEquals(frame[0], request.frame()); } - @Test - @DisabledIf(value="com.microsoft.playwright.TestBase#isChromium", disabledReason="Started failing since last driver roll, flaky upstream") - void shouldReportNetworkActivity() { - Assumptions.assumeFalse(isFirefox() && browserMajorVersion() < 114); + @PlaywrightTest + @DisabledOnBrowser(value = CHROMIUM, reason = "Started failing since last driver roll, flaky upstream") + void shouldReportNetworkActivity(Page page, Browser browser, Server server, BrowserName browserName) { + Assumptions.assumeFalse(browserName == FIREFOX && browserMajorVersion(browser) < 114); Worker worker = page.waitForWorker(() -> page.navigate(server.PREFIX + "/worker/worker.html")); String url = server.PREFIX + "/one-style.css"; Request[] request = {null}; @@ -167,9 +171,9 @@ void shouldReportNetworkActivity() { assertTrue(response.ok()); } - @Test - void shouldReportNetworkActivityOnWorkerCreation() { - Assumptions.assumeFalse(isFirefox() && browserMajorVersion() < 114); + @PlaywrightTest + void shouldReportNetworkActivityOnWorkerCreation(Page page, Browser browser, Server server, BrowserName browserName) { + Assumptions.assumeFalse(browserName == FIREFOX && browserMajorVersion(browser) < 114); // Chromium needs waitForDebugger enabled for this one. page.navigate(server.EMPTY_PAGE); String url = server.PREFIX + "/one-style.css"; @@ -186,8 +190,8 @@ void shouldReportNetworkActivityOnWorkerCreation() { assertTrue(response.ok()); } - @Test - void shouldFormatNumberUsingContextLocale() { + @PlaywrightTest + void shouldFormatNumberUsingContextLocale(Browser browser, Server server) { BrowserContext context = browser.newContext(new Browser.NewContextOptions().setLocale("ru-RU")); Page page = context.newPage(); page.navigate(server.EMPTY_PAGE); @@ -197,8 +201,8 @@ void shouldFormatNumberUsingContextLocale() { context.close(); } - @Test - void shouldReportConsoleEventOnTheWorker() { + @PlaywrightTest + void shouldReportConsoleEventOnTheWorker(Page page) { Worker worker = page.waitForWorker(() -> page.evaluate( "() => { window.worker = new Worker(URL.createObjectURL(new Blob(['42'], {type: 'application/javascript'}))); }" )); diff --git a/playwright/src/test/java/com/microsoft/playwright/TraceViewerPage.java b/playwright/src/test/java/com/microsoft/playwright/TraceViewerPage.java index 21a94d64f..5e3fed3fd 100644 --- a/playwright/src/test/java/com/microsoft/playwright/TraceViewerPage.java +++ b/playwright/src/test/java/com/microsoft/playwright/TraceViewerPage.java @@ -20,6 +20,7 @@ import java.nio.file.Files; import java.nio.file.Path; +import com.microsoft.playwright.fixtures.Env; import com.microsoft.playwright.impl.driver.Driver; import com.microsoft.playwright.options.AriaRole; @@ -100,7 +101,7 @@ static void showTraceViewer(BrowserType browserType, Path tracePath, TraceViewer exchange.getResponseBody().close(); }); - try (Browser browser = browserType.launch(TestBase.createLaunchOptions()); + try (Browser browser = browserType.launch(Env.launchOptions()); BrowserContext context = browser.newContext()) { Page page = context.newPage(); page.navigate(traceServer.PREFIX + "/index.html?trace=" + traceServer.PREFIX + "/trace.zip"); diff --git a/playwright/src/test/java/com/microsoft/playwright/Utils.java b/playwright/src/test/java/com/microsoft/playwright/Utils.java index 6ab8933a6..e5caf1b13 100644 --- a/playwright/src/test/java/com/microsoft/playwright/Utils.java +++ b/playwright/src/test/java/com/microsoft/playwright/Utils.java @@ -19,6 +19,9 @@ import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonParser; +import com.microsoft.playwright.fixtures.BrowserName; +import com.microsoft.playwright.fixtures.Env; +import com.microsoft.playwright.options.SameSiteAttribute; import org.junit.jupiter.api.Assumptions; import java.io.*; @@ -31,6 +34,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BooleanSupplier; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -184,6 +188,34 @@ public static BrowserType getBrowserTypeFromEnv(Playwright playwright) { } } + static SameSiteAttribute defaultSameSiteCookieValue(BrowserName browserName) { + if (browserName == BrowserName.CHROMIUM) return SameSiteAttribute.LAX; + if (browserName == BrowserName.WEBKIT && Env.isLinux) return SameSiteAttribute.LAX; + if (browserName == BrowserName.WEBKIT && !Env.isLinux) return SameSiteAttribute.NONE; + // for firefox version >= 103 'None' is used. + return SameSiteAttribute.NONE; + } + + static void waitForCondition(Page page, BooleanSupplier predicate) { + waitForCondition(page, predicate, 5_000); + } + + static void waitForCondition(Page page, BooleanSupplier predicate, int timeoutMs) { + page.waitForCondition(predicate, new Page.WaitForConditionOptions().setTimeout(timeoutMs)); + } + + static boolean chromiumVersionLessThan(String a, String b) { + String[] aParts = a.split("\\."); + String[] bParts = b.split("\\."); + for (int i = 0; i < 4; i++) { + int aPart = Integer.parseInt(aParts[i]); + int bPart = Integer.parseInt(bParts[i]); + if (aPart > bPart) return false; + if (aPart < bPart) return true; + } + return false; + } + static void verifyViewport(Page page, int width, int height) { assertEquals(width, page.viewportSize().width); assertEquals(height, page.viewportSize().height); diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserInvocation.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserInvocation.java new file mode 100644 index 000000000..6f84335b9 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserInvocation.java @@ -0,0 +1,124 @@ +/* + * 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.fixtures; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.junit.jupiter.api.extension.ParameterResolver; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.io.TempDir; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * One invocation of a {@link PlaywrightTest}: binds the browser, resolves fixture parameters and + * applies the {@link EnabledOnBrowser}/{@link DisabledOnBrowser} conditions. + */ +final class BrowserInvocation implements TestTemplateInvocationContext, ParameterResolver, ExecutionCondition { + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(BrowserInvocation.class); + + private final BrowserName browserName; + + BrowserInvocation(BrowserName browserName) { + this.browserName = browserName; + } + + @Override + public String getDisplayName(int invocationIndex) { + return "[" + browserName + "]"; + } + + @Override + public List getAdditionalExtensions() { + return Collections.singletonList(this); + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + List elements = new ArrayList<>(); + context.getTestMethod().ifPresent(elements::add); + for (Class clazz = context.getRequiredTestClass(); clazz != null; clazz = clazz.getEnclosingClass()) { + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + elements.add(c); + } + } + for (AnnotatedElement element : elements) { + for (DisabledOnBrowser disabled : element.getAnnotationsByType(DisabledOnBrowser.class)) { + if (Arrays.asList(disabled.value()).contains(browserName) && matchesOs(disabled.os()) && (!disabled.headed() || Env.headed())) { + return ConditionEvaluationResult.disabled("Disabled on " + browserName + reason(disabled.reason())); + } + } + EnabledOnBrowser enabled = element.getAnnotation(EnabledOnBrowser.class); + if (enabled != null && !Arrays.asList(enabled.value()).contains(browserName)) { + return ConditionEvaluationResult.disabled("Enabled only on " + Arrays.toString(enabled.value()) + reason(enabled.reason())); + } + } + return ConditionEvaluationResult.enabled("Running in " + browserName); + } + + private static boolean matchesOs(OS[] os) { + if (os.length == 0) { + return true; + } + for (OS candidate : os) { + if (candidate.isCurrentOs()) { + return true; + } + } + return false; + } + + private static String reason(String reason) { + return reason.isEmpty() ? "" : ": " + reason; + } + + @Override + public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { + // Constructors are resolved against the class context, which cannot hold per-invocation fixtures. + if (parameterContext.getDeclaringExecutable() instanceof Constructor) { + return false; + } + if (parameterContext.isAnnotated(TempDir.class)) { + return false; + } + return registry(extensionContext).supports(parameterContext.getParameter().getType()); + } + + @Override + public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException { + return scope(extensionContext).get(parameterContext.getParameter().getType()); + } + + private static FixtureRegistry registry(ExtensionContext context) { + return FixtureRegistry.forTestClass(context.getRequiredTestClass()); + } + + private TestScope scope(ExtensionContext context) { + return context.getStore(NAMESPACE).getOrComputeIfAbsent(TestScope.class, + key -> new TestScope(context, registry(context), browserName), TestScope.class); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserName.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserName.java new file mode 100644 index 000000000..1a1670590 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/BrowserName.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.fixtures; + +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.Playwright; + +/** + * The browser a test invocation runs in. Available as a fixture parameter. + */ +public enum BrowserName { + CHROMIUM("chromium"), + FIREFOX("firefox"), + WEBKIT("webkit"); + + private final String name; + + BrowserName(String name) { + this.name = name; + } + + public static BrowserName fromString(String name) { + for (BrowserName value : values()) { + if (value.name.equals(name)) { + return value; + } + } + throw new IllegalArgumentException("Unknown browser: " + name); + } + + public BrowserType browserType(Playwright playwright) { + switch (this) { + case CHROMIUM: return playwright.chromium(); + case FIREFOX: return playwright.firefox(); + case WEBKIT: return playwright.webkit(); + default: throw new IllegalStateException(); + } + } + + @Override + public String toString() { + return name; + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/BuiltinFixture.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/BuiltinFixture.java new file mode 100644 index 000000000..07ad961b4 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/BuiltinFixture.java @@ -0,0 +1,127 @@ +/* + * 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.fixtures; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.HttpsServer; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.Server; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * The fixtures every test can use without declaring anything, the counterpart of the + * {@code browserTest} fixtures upstream. + */ +final class BuiltinFixture implements FixtureDefinition { + interface Factory { + T create(FixtureResolver resolver) throws Exception; + } + + interface Closer { + void close(T value) throws Exception; + } + + /** Bound by the worker to the browser the test invocation runs in. */ + static final BuiltinFixture BROWSER_NAME = worker(BrowserName.class, resolver -> { + throw new IllegalStateException("BrowserName is bound by the worker"); + }, null); + + static final BuiltinFixture BROWSER = worker(Browser.class, + resolver -> resolver.get(BrowserType.class).launch(resolver.get(BrowserType.LaunchOptions.class)), + Browser::close); + + static final List ALL = Collections.unmodifiableList(Arrays.asList( + BROWSER_NAME, + worker(Playwright.CreateOptions.class, resolver -> new Playwright.CreateOptions(), null), + worker(Playwright.class, resolver -> Playwright.create(resolver.get(Playwright.CreateOptions.class)), Playwright::close), + worker(BrowserType.class, resolver -> resolver.get(BrowserName.class).browserType(resolver.get(Playwright.class)), null), + worker(BrowserType.LaunchOptions.class, resolver -> Env.launchOptions(), null), + BROWSER, + worker(Server.class, resolver -> Server.createHttp(), Server::stop), + worker(HttpsServer.class, resolver -> Server.createHttps(), Server::stop), + // The test-scoped servers are the worker ones, reset before every test. + test(Server.class, resolver -> reset(resolver.get(Server.class)), null), + test(HttpsServer.class, resolver -> reset(resolver.get(HttpsServer.class)), null), + test(Browser.NewContextOptions.class, resolver -> new Browser.NewContextOptions(), null), + test(BrowserContext.class, + resolver -> resolver.get(Browser.class).newContext(resolver.get(Browser.NewContextOptions.class)), + BrowserContext::close), + test(Page.class, resolver -> resolver.get(BrowserContext.class).newPage(), null))); + + private final Class type; + private final Scope scope; + private final Factory factory; + private final Closer closer; + + private static S reset(S server) { + server.reset(); + return server; + } + + private static BuiltinFixture worker(Class type, Factory factory, Closer closer) { + return new BuiltinFixture<>(type, Scope.WORKER, factory, closer); + } + + private static BuiltinFixture test(Class type, Factory factory, Closer closer) { + return new BuiltinFixture<>(type, Scope.TEST, factory, closer); + } + + private BuiltinFixture(Class type, Scope scope, Factory factory, Closer closer) { + this.type = type; + this.scope = scope; + this.factory = factory; + this.closer = closer; + } + + @Override + public Class type() { + return type; + } + + @Override + public Scope scope() { + return scope; + } + + @Override + public Object identity() { + return this; + } + + @Override + public Object create(FixtureResolver resolver) throws Exception { + return factory.create(resolver); + } + + @Override + public void close(Object value) throws Exception { + if (closer != null) { + closer.close(type.cast(value)); + } + } + + @Override + public String describe() { + return "built-in " + scope.name().toLowerCase() + " fixture " + type.getSimpleName(); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/DisabledOnBrowser.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/DisabledOnBrowser.java new file mode 100644 index 000000000..d666115ad --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/DisabledOnBrowser.java @@ -0,0 +1,55 @@ +/* + * 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.fixtures; + +import org.junit.jupiter.api.condition.OS; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Skips the annotated {@link PlaywrightTest} (or every test of the annotated class) in the listed + * browsers, the counterpart of {@code test.skip(({ browserName }) => browserName === 'webkit', reason)}. + * The condition can be narrowed to some operating systems or to headed mode, as in + * {@code test.skip(({ browserName, isMac }) => browserName === 'webkit' && isMac)}. + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(DisabledOnBrowser.List.class) +public @interface DisabledOnBrowser { + BrowserName[] value(); + + /** Only skip on these operating systems; empty means any. */ + OS[] os() default {}; + + /** Only skip when the browser runs headed, see {@link Env#headed()}. */ + boolean headed() default false; + + String reason() default ""; + + @Documented + @Target({ElementType.METHOD, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + @interface List { + DisabledOnBrowser[] value(); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/EnabledOnBrowser.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/EnabledOnBrowser.java new file mode 100644 index 000000000..8f1e9bcbd --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/EnabledOnBrowser.java @@ -0,0 +1,35 @@ +/* + * 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.fixtures; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Runs the annotated {@link PlaywrightTest} (or every test of the annotated class) only in the + * listed browsers; the other invocations are reported as skipped. + */ +@Documented +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface EnabledOnBrowser { + BrowserName[] value(); + String reason() default ""; +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/Env.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/Env.java new file mode 100644 index 000000000..05aecd58b --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/Env.java @@ -0,0 +1,82 @@ +/* + * 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.fixtures; + +import com.microsoft.playwright.BrowserType; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Test configuration taken from the environment, the counterpart of the project options and + * platform fixtures upstream. + */ +public final class Env { + public static final boolean isMac; + public static final boolean isLinux; + public static final boolean isWindows; + + static { + String os = System.getProperty("os.name").toLowerCase(); + isWindows = os.contains("win"); + isLinux = os.contains("linux"); + isMac = os.contains("mac os x"); + } + + private Env() { + } + + /** + * Browsers every {@link PlaywrightTest} runs in: the comma-separated {@code BROWSER} environment + * variable, or all three browsers when it is not set. + */ + public static List browserNames() { + String env = System.getenv("BROWSER"); + if (env == null || env.trim().isEmpty()) { + return Collections.unmodifiableList(Arrays.asList(BrowserName.values())); + } + List result = new ArrayList<>(); + for (String name : env.split(",")) { + BrowserName browserName = BrowserName.fromString(name.trim()); + if (!result.contains(browserName)) { + result.add(browserName); + } + } + return Collections.unmodifiableList(result); + } + + /** {@code HEADED=1} launches the browsers with a window. */ + public static boolean headed() { + String value = System.getenv("HEADED"); + return value != null && !"0".equals(value) && !"false".equals(value); + } + + /** {@code BROWSER_CHANNEL} picks a browser distribution such as {@code chrome} or {@code msedge}. */ + public static String channel() { + return System.getenv("BROWSER_CHANNEL"); + } + + /** Fresh launch options honoring {@link #headed()} and {@link #channel()}. */ + public static BrowserType.LaunchOptions launchOptions() { + BrowserType.LaunchOptions options = new BrowserType.LaunchOptions(); + options.headless = !headed(); + options.channel = channel(); + return options; + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/Fixture.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/Fixture.java new file mode 100644 index 000000000..855c6b202 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/Fixture.java @@ -0,0 +1,55 @@ +/* + * 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.fixtures; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Declares a fixture factory on a test class, a superclass, an implemented interface (default + * method) or an enclosing class of a {@code @Nested} test. The Java counterpart of + * {@code test.extend({ name: async ({ deps }, use) => ... })} upstream. + * + *

The method's return type is the fixture type that test parameters are resolved by. Its + * parameters are the fixtures it depends on. A parameter of the method's own return type resolves + * to the fixture being overridden, so defaults can be adjusted rather than rebuilt: + *

{@code
+ * @Fixture
+ * Browser.NewContextOptions contextOptions() {
+ *   return new Browser.NewContextOptions().setHasTouch(true);
+ * }
+ *
+ * @Fixture(scope = Scope.WORKER)
+ * static BrowserType.LaunchOptions launchOptions(BrowserType.LaunchOptions defaults) {
+ *   return defaults.setArgs(asList("--enable-features=WebMCP"));
+ * }
+ * }
+ * + *

A value that implements {@link AutoCloseable} is closed when its scope ends, in reverse + * creation order. Worker-scoped fixture methods must be static and may only depend on other + * worker-scoped fixtures; test-scoped ones may be instance methods and so can use fields injected + * by other extensions (Spring, Micronaut, {@code @TempDir}, {@code @Parameter}). + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Fixture { + Scope scope() default Scope.TEST; +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureDefinition.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureDefinition.java new file mode 100644 index 000000000..f5e0cc16f --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureDefinition.java @@ -0,0 +1,35 @@ +/* + * 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.fixtures; + +/** + * A fixture factory: either one of the built-in fixtures or a {@link Fixture} method. + */ +interface FixtureDefinition { + Class type(); + + Scope scope(); + + /** Stable key for cached values; equal for the same definition seen through different test classes. */ + Object identity(); + + Object create(FixtureResolver resolver) throws Exception; + + void close(Object value) throws Exception; + + String describe(); +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureRegistry.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureRegistry.java new file mode 100644 index 000000000..fdd0ced29 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureRegistry.java @@ -0,0 +1,119 @@ +/* + * 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.fixtures; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The fixtures visible to a test class: the built-in ones followed by the {@link Fixture} methods + * found on its enclosing classes, supertypes and the class itself. For every fixture type the + * registry keeps the chain of definitions in that order, the last one being the most specific + * and the one a test parameter resolves to. + */ +final class FixtureRegistry { + private static final Map, FixtureRegistry> CACHE = new ConcurrentHashMap<>(); + + private final Map, List> chains = new HashMap<>(); + private final List workerMethods = new ArrayList<>(); + + static FixtureRegistry forTestClass(Class testClass) { + return CACHE.computeIfAbsent(testClass, FixtureRegistry::new); + } + + private FixtureRegistry(Class testClass) { + for (FixtureDefinition definition : BuiltinFixture.ALL) { + add(definition); + } + for (Class clazz : lookupOrder(testClass)) { + List methods = new ArrayList<>(); + for (Method method : clazz.getDeclaredMethods()) { + if (method.isAnnotationPresent(Fixture.class)) { + methods.add(method); + } + } + // Declaration order is not guaranteed by reflection. + methods.sort(Comparator.comparing(Method::getName)); + List> typesInClass = new ArrayList<>(); + for (Method method : methods) { + MethodFixture definition = new MethodFixture(method); + if (typesInClass.contains(definition.type())) { + throw new IllegalStateException(clazz.getName() + " declares more than one @Fixture of type " + definition.type().getName()); + } + typesInClass.add(definition.type()); + add(definition); + } + } + } + + /** Outermost enclosing class first, each preceded by its supertypes; the test class last. */ + private static List> lookupOrder(Class testClass) { + List> nesting = new ArrayList<>(); + for (Class clazz = testClass; clazz != null; clazz = clazz.getEnclosingClass()) { + nesting.add(0, clazz); + } + List> result = new ArrayList<>(); + for (Class clazz : nesting) { + addHierarchy(clazz, result); + } + return result; + } + + private static void addHierarchy(Class clazz, List> out) { + if (clazz == null || clazz == Object.class || out.contains(clazz)) { + return; + } + addHierarchy(clazz.getSuperclass(), out); + for (Class iface : clazz.getInterfaces()) { + addHierarchy(iface, out); + } + out.add(clazz); + } + + private void add(FixtureDefinition definition) { + chains.computeIfAbsent(definition.type(), k -> new ArrayList<>()).add(definition); + if (definition.scope() == Scope.WORKER && definition instanceof MethodFixture) { + workerMethods.add(((MethodFixture) definition).method()); + } + } + + boolean supports(Class type) { + return chains.containsKey(type); + } + + List chain(Class type) { + List chain = chains.get(type); + return chain == null ? Collections.emptyList() : chain; + } + + /** The worker-scoped overrides; tests with equal lists can share a worker. */ + List workerMethods() { + return Collections.unmodifiableList(workerMethods); + } + + @Override + public String toString() { + return "FixtureRegistry" + Arrays.toString(chains.keySet().toArray()); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureResolver.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureResolver.java new file mode 100644 index 000000000..8841a2c54 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureResolver.java @@ -0,0 +1,32 @@ +/* + * 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.fixtures; + +/** + * Resolves the dependencies of a fixture while it is being created. + */ +interface FixtureResolver { + /** + * Returns the fixture of the given type. Asking for the type of the fixture being created + * returns the definition it overrides, the way an upstream fixture can depend on the fixture + * of the same name from the base test. + */ + T get(Class type); + + /** The test instance a non-static {@link Fixture} method is invoked on. */ + Object testInstance(Class declaringClass); +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/MethodFixture.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/MethodFixture.java new file mode 100644 index 000000000..c89eff966 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/MethodFixture.java @@ -0,0 +1,102 @@ +/* + * 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.fixtures; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; + +/** + * A fixture declared with a {@link Fixture} method on a test class. + */ +final class MethodFixture implements FixtureDefinition { + private final Method method; + private final Scope scope; + + MethodFixture(Method method) { + this.method = method; + this.scope = method.getAnnotation(Fixture.class).scope(); + if (method.getReturnType() == void.class) { + throw new IllegalStateException(describe() + " must return the fixture value"); + } + if (scope == Scope.WORKER && !Modifier.isStatic(method.getModifiers())) { + throw new IllegalStateException(describe() + " is worker-scoped and must be static"); + } + } + + Method method() { + return method; + } + + @Override + public Class type() { + return method.getReturnType(); + } + + @Override + public Scope scope() { + return scope; + } + + @Override + public Object identity() { + return method; + } + + @Override + public Object create(FixtureResolver resolver) throws Exception { + Parameter[] parameters = method.getParameters(); + Object[] args = new Object[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + args[i] = resolver.get(parameters[i].getType()); + } + Object target = Modifier.isStatic(method.getModifiers()) ? null : resolver.testInstance(method.getDeclaringClass()); + method.setAccessible(true); + try { + return method.invoke(target, args); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw (Error) cause; + } + } + + @Override + public void close(Object value) throws Exception { + if (value instanceof AutoCloseable) { + ((AutoCloseable) value).close(); + } + } + + @Override + public String describe() { + return "@Fixture " + method.getDeclaringClass().getSimpleName() + "." + method.getName() + "()"; + } + + @Override + public boolean equals(Object o) { + return o instanceof MethodFixture && method.equals(((MethodFixture) o).method); + } + + @Override + public int hashCode() { + return method.hashCode(); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java new file mode 100644 index 000000000..20a21e2d9 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java @@ -0,0 +1,51 @@ +/* + * 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.fixtures; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider; +import org.junit.platform.commons.support.AnnotationSupport; + +import java.util.stream.Stream; + +/** + * Runs every {@link PlaywrightTest} once per browser from {@link Env#browserNames()}. + */ +public final class PlaywrightExtension implements TestTemplateInvocationContextProvider { + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create(PlaywrightExtension.class); + + /** Closes the idle workers when the whole test run is over. */ + @SuppressWarnings("deprecation") + private static final class PoolCloser implements AutoCloseable, ExtensionContext.Store.CloseableResource { + @Override + public void close() { + WorkerPool.INSTANCE.closeAll(); + } + } + + @Override + public boolean supportsTestTemplate(ExtensionContext context) { + return context.getTestMethod().filter(method -> AnnotationSupport.isAnnotated(method, PlaywrightTest.class)).isPresent(); + } + + @Override + public Stream provideTestTemplateInvocationContexts(ExtensionContext context) { + context.getRoot().getStore(NAMESPACE).getOrComputeIfAbsent(PoolCloser.class, key -> new PoolCloser(), PoolCloser.class); + return Env.browserNames().stream().map(BrowserInvocation::new); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightTest.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightTest.java new file mode 100644 index 000000000..610e93195 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightTest.java @@ -0,0 +1,59 @@ +/* + * 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.fixtures; + +import org.junit.jupiter.api.TestTemplate; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a test method that runs once per browser selected by the {@code BROWSER} environment + * variable (all three browsers by default), the Java counterpart of a Playwright Test project. + * Invocations are named after the browser ({@code [chromium]}, {@code [firefox]}, {@code [webkit]}); + * reports that only show the invocation index number them in the order of {@link Env#browserNames()}. + * + *

Fixtures are injected as method parameters, mirroring the destructured fixtures of + * {@code test('...', async ({ page, server }) => ...)} upstream: + *

{@code
+ * @PlaywrightTest
+ * void shouldWork(Page page, Server server) {
+ *   page.navigate(server.EMPTY_PAGE);
+ * }
+ * }
+ * + *

Built-in fixtures: {@link com.microsoft.playwright.Playwright}, {@link BrowserName}, + * {@link com.microsoft.playwright.BrowserType}, {@link com.microsoft.playwright.BrowserType.LaunchOptions}, + * {@link com.microsoft.playwright.Browser}, {@link com.microsoft.playwright.Browser.NewContextOptions}, + * {@link com.microsoft.playwright.BrowserContext}, {@link com.microsoft.playwright.Page}, + * {@link com.microsoft.playwright.Server} and {@link com.microsoft.playwright.HttpsServer}. + * Worker-scoped fixtures (Playwright, Browser, servers) are reused by the tests that run + * sequentially on the same thread; test-scoped ones (context, page) are created for every test + * that asks for them and closed afterwards. A test class overrides or adds fixtures with + * {@link Fixture} methods. + */ +@Documented +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@TestTemplate +@ExtendWith(PlaywrightExtension.class) +public @interface PlaywrightTest { +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/Scope.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/Scope.java new file mode 100644 index 000000000..6e3671acb --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/Scope.java @@ -0,0 +1,31 @@ +/* + * 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.fixtures; + +/** + * Lifetime of a {@link Fixture}. + */ +public enum Scope { + /** Created for every test that uses it and torn down when the test finishes. */ + TEST, + /** + * Shared by all the tests that run on the same worker, like a Playwright Test worker fixture. + * A worker is a thread-bound bundle of Playwright, Browser and test servers; it is kept alive + * across tests and classes as long as they request the same worker-scoped fixtures. + */ + WORKER +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestPlaywrightFixtures.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestPlaywrightFixtures.java new file mode 100644 index 000000000..e38aedd52 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestPlaywrightFixtures.java @@ -0,0 +1,180 @@ +/* + * 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.fixtures; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.BrowserType; +import com.microsoft.playwright.HttpsServer; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Playwright; +import com.microsoft.playwright.Server; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.concurrent.atomic.AtomicInteger; + +import static com.microsoft.playwright.fixtures.BrowserName.CHROMIUM; +import static com.microsoft.playwright.fixtures.BrowserName.FIREFOX; +import static com.microsoft.playwright.fixtures.BrowserName.WEBKIT; +import static org.junit.jupiter.api.Assertions.*; + +public class TestPlaywrightFixtures { + private static final AtomicInteger createdResources = new AtomicInteger(); + private static final AtomicInteger closedResources = new AtomicInteger(); + + static final class Resource implements AutoCloseable { + final Page page; + + Resource(Page page) { + this.page = page; + createdResources.incrementAndGet(); + } + + @Override + public void close() { + closedResources.incrementAndGet(); + } + } + + private BrowserName browserNameFromBeforeEach; + + @Fixture + Resource resource(Page page) { + return new Resource(page); + } + + @BeforeEach + void rememberBrowser(BrowserName browserName) { + browserNameFromBeforeEach = browserName; + } + + @AfterAll + static void allResourcesClosed() { + assertTrue(createdResources.get() > 0); + assertEquals(createdResources.get(), closedResources.get()); + } + + @PlaywrightTest + void shouldInjectBuiltInFixtures(Page page, BrowserContext context, Browser browser, BrowserType browserType, + Playwright playwright, Server server, HttpsServer httpsServer, BrowserName browserName) { + assertSame(context, page.context()); + assertSame(browser, context.browser()); + assertSame(browserType, browser.browserType()); + assertEquals(browserName.toString(), browserType.name()); + assertSame(browserType, browserName.browserType(playwright)); + page.navigate(server.EMPTY_PAGE); + assertEquals(server.EMPTY_PAGE, page.url()); + assertNotEquals(server.PORT, httpsServer.PORT); + assertTrue(httpsServer.PREFIX.startsWith("https://")); + } + + @PlaywrightTest + void shouldCreateNewContextPerTest(BrowserContext context) { + assertEquals(0, context.pages().size()); + } + + @PlaywrightTest + void shouldResetServerBetweenTests(Page page, Server server) { + // Whichever of the two tests runs first on a worker, the route set by the other one is gone. + assertEquals(404, page.navigate(server.PREFIX + "/reset").status()); + server.setRoute("/reset", exchange -> exchange.sendResponseHeaders(200, -1)); + assertEquals(200, page.navigate(server.PREFIX + "/reset").status()); + } + + @PlaywrightTest + void shouldResetServerBetweenTests2(Page page, Server server) { + assertEquals(404, page.navigate(server.PREFIX + "/reset").status()); + server.setRoute("/reset", exchange -> exchange.sendResponseHeaders(200, -1)); + assertEquals(200, page.navigate(server.PREFIX + "/reset").status()); + } + + @PlaywrightTest + void shouldInjectCustomFixture(Resource resource, Page page) { + assertSame(page, resource.page); + } + + @PlaywrightTest + void shouldInjectIntoBeforeEach(BrowserName browserName) { + assertEquals(browserName, browserNameFromBeforeEach); + } + + @PlaywrightTest + @DisabledOnBrowser(value = {FIREFOX, WEBKIT}, reason = "checks the condition") + void shouldSkipDisabledBrowsers(BrowserName browserName) { + assertEquals(CHROMIUM, browserName); + } + + @PlaywrightTest + @DisabledOnBrowser(FIREFOX) + @DisabledOnBrowser(value = WEBKIT, reason = "repeatable") + void shouldSkipRepeatedDisabledBrowsers(BrowserName browserName) { + assertEquals(CHROMIUM, browserName); + } + + @PlaywrightTest + @EnabledOnBrowser(WEBKIT) + void shouldRunOnlyOnEnabledBrowsers(BrowserName browserName) { + assertEquals(WEBKIT, browserName); + } + + @Nested + class WithTouch { + @Fixture + Browser.NewContextOptions contextOptions() { + return new Browser.NewContextOptions().setHasTouch(true); + } + + @PlaywrightTest + void shouldUseOverriddenContextOptions(Page page, Server server, Resource resource) { + page.navigate(server.PREFIX + "/mobile.html"); + assertEquals(true, page.evaluate("'ontouchstart' in window")); + assertSame(page, resource.page); + } + } + + @Nested + @EnabledOnBrowser(CHROMIUM) + class ChromiumOnly { + @PlaywrightTest + void shouldHonorClassLevelCondition(BrowserName browserName) { + assertEquals(CHROMIUM, browserName); + } + } + + @Nested + @ParameterizedClass + @ValueSource(ints = {400, 600}) + class WithViewportWidth { + @Parameter + int width; + + @Fixture + Browser.NewContextOptions contextOptions() { + return new Browser.NewContextOptions().setViewportSize(width, 300); + } + + @PlaywrightTest + void shouldUseClassParameter(Page page) { + assertEquals(width, page.evaluate("window.innerWidth")); + } + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java new file mode 100644 index 000000000..b539a2847 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java @@ -0,0 +1,167 @@ +/* + * 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.fixtures; + +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ParameterResolutionException; +import org.opentest4j.TestAbortedException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The fixtures of one test invocation: resolves and caches test-scoped values, borrows a + * {@link Worker} for the worker-scoped ones, and tears everything down when the test is over. + */ +final class TestScope implements AutoCloseable { + private static final class Entry { + final FixtureDefinition definition; + final Object value; + + Entry(FixtureDefinition definition, Object value) { + this.definition = definition; + this.value = value; + } + } + + private final ExtensionContext context; + private final FixtureRegistry registry; + private final BrowserName browserName; + private final Map values = new HashMap<>(); + private final List created = new ArrayList<>(); + private final Set resolving = new LinkedHashSet<>(); + private Worker worker; + + TestScope(ExtensionContext context, FixtureRegistry registry, BrowserName browserName) { + this.context = context; + this.registry = registry; + this.browserName = browserName; + } + + T get(Class type) { + List chain = registry.chain(type); + if (chain.isEmpty()) { + throw new ParameterResolutionException("No fixture of type " + type.getName()); + } + return type.cast(resolve(chain.get(chain.size() - 1))); + } + + private Object resolve(FixtureDefinition definition) { + try { + if (definition.scope() == Scope.WORKER) { + return worker().get(definition, new Resolver(definition)); + } + Object value = values.get(definition.identity()); + if (value == null) { + value = create(definition); + values.put(definition.identity(), value); + created.add(new Entry(definition, value)); + } + return value; + } catch (RuntimeException | Error e) { + throw e; + } catch (Exception e) { + throw new IllegalStateException(definition.describe() + " failed", e); + } + } + + private Object create(FixtureDefinition definition) throws Exception { + if (!resolving.add(definition)) { + throw new IllegalStateException("Fixture dependency cycle: " + resolving + " -> " + definition.describe()); + } + try { + Object value = definition.create(new Resolver(definition)); + if (value == null) { + throw new IllegalStateException(definition.describe() + " returned null"); + } + return value; + } finally { + resolving.remove(definition); + } + } + + private Worker worker() { + if (worker == null) { + worker = WorkerPool.INSTANCE.acquire(new Worker.Key(browserName, registry.workerMethods())); + } + return worker; + } + + @Override + public void close() { + for (int i = created.size() - 1; i >= 0; i--) { + Entry entry = created.get(i); + try { + entry.definition.close(entry.value); + } catch (Throwable e) { + System.err.println("Failed to close " + entry.definition.describe() + ": " + e); + } + } + created.clear(); + values.clear(); + if (worker != null) { + // Like Playwright Test, do not reuse a worker after a failure: the test may have left the + // browser or the Playwright instance in an unknown state. + boolean failed = context.getExecutionException().filter(e -> !(e instanceof TestAbortedException)).isPresent(); + WorkerPool.INSTANCE.release(worker, failed); + worker = null; + } + } + + private final class Resolver implements FixtureResolver { + private final FixtureDefinition from; + + Resolver(FixtureDefinition from) { + this.from = from; + } + + @Override + public T get(Class type) { + List chain = registry.chain(type); + FixtureDefinition target; + if (type.equals(from.type())) { + int index = chain.indexOf(from); + if (index <= 0) { + throw new IllegalStateException(from.describe() + " does not override another fixture of type " + type.getName()); + } + target = chain.get(index - 1); + } else { + if (chain.isEmpty()) { + throw new IllegalStateException(from.describe() + " depends on unknown fixture type " + type.getName()); + } + target = chain.get(chain.size() - 1); + } + if (from.scope() == Scope.WORKER && target.scope() == Scope.TEST) { + throw new IllegalStateException("Worker-scoped " + from.describe() + " cannot depend on test-scoped " + target.describe()); + } + return type.cast(resolve(target)); + } + + @Override + public Object testInstance(Class declaringClass) { + if (from.scope() == Scope.WORKER) { + throw new IllegalStateException("Worker-scoped " + from.describe() + " must be static"); + } + return context.getRequiredTestInstances().findInstance(declaringClass) + .orElseThrow(() -> new IllegalStateException("No test instance of " + declaringClass.getName() + " to invoke " + from.describe())); + } + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java new file mode 100644 index 000000000..04a34bc76 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java @@ -0,0 +1,75 @@ +/* + * 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.fixtures; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserType; +import org.junit.jupiter.api.AfterAll; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + +public class TestWorkerFixtures { + private static final AtomicInteger created = new AtomicInteger(); + private static final AtomicInteger closed = new AtomicInteger(); + + static final class WorkerResource implements AutoCloseable { + final Browser browser; + + WorkerResource(Browser browser) { + this.browser = browser; + created.incrementAndGet(); + } + + @Override + public void close() { + closed.incrementAndGet(); + } + } + + @Fixture(scope = Scope.WORKER) + static BrowserType.LaunchOptions launchOptions(BrowserType.LaunchOptions defaults) { + assertEquals(!Env.headed(), defaults.headless); + return defaults.setTimeout(123456); + } + + @Fixture(scope = Scope.WORKER) + static WorkerResource workerResource(Browser browser) { + return new WorkerResource(browser); + } + + @AfterAll + static void workerResourcesOutliveTests() { + // Worker fixtures are closed with their worker, which the pool may evict while other classes + // run, never once per test. + assertTrue(created.get() > 0); + assertTrue(closed.get() <= created.get(), "created " + created.get() + ", closed " + closed.get()); + } + + @PlaywrightTest + void shouldLaunchWithOverriddenOptions(BrowserType.LaunchOptions launchOptions, Browser browser, WorkerResource resource) { + assertEquals(123456, launchOptions.timeout); + assertTrue(browser.isConnected()); + assertSame(browser, resource.browser); + } + + @PlaywrightTest + void shouldReuseWorkerFixtures(Browser browser, WorkerResource resource) { + assertSame(browser, resource.browser); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java new file mode 100644 index 000000000..85b361ba4 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java @@ -0,0 +1,119 @@ +/* + * 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.fixtures; + +import com.microsoft.playwright.Browser; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A bundle of worker-scoped fixture values, the counterpart of a Playwright Test worker process. + * Playwright Java objects are not thread-safe, so a worker is only ever used by one test at a + * time; tests that run one after another on a thread reuse it through the {@link WorkerPool}. + */ +final class Worker implements AutoCloseable { + /** Workers with equal keys are interchangeable. */ + static final class Key { + final BrowserName browserName; + final List workerMethods; + + Key(BrowserName browserName, List workerMethods) { + this.browserName = browserName; + this.workerMethods = workerMethods; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Key)) { + return false; + } + Key other = (Key) o; + return browserName == other.browserName && workerMethods.equals(other.workerMethods); + } + + @Override + public int hashCode() { + return 31 * browserName.hashCode() + workerMethods.hashCode(); + } + + @Override + public String toString() { + return browserName + (workerMethods.isEmpty() ? "" : " " + workerMethods); + } + } + + private static final class Entry { + final FixtureDefinition definition; + final Object value; + + Entry(FixtureDefinition definition, Object value) { + this.definition = definition; + this.value = value; + } + } + + final Key key; + private final Map values = new HashMap<>(); + private final List created = new ArrayList<>(); + + Worker(Key key) { + this.key = key; + values.put(BuiltinFixture.BROWSER_NAME.identity(), key.browserName); + } + + Object get(FixtureDefinition definition, FixtureResolver resolver) throws Exception { + Object value = values.get(definition.identity()); + if (value == null) { + value = definition.create(resolver); + if (value == null) { + throw new IllegalStateException(definition.describe() + " returned null"); + } + values.put(definition.identity(), value); + created.add(new Entry(definition, value)); + } + return value; + } + + /** A worker whose browser got disconnected cannot serve another test. */ + boolean isHealthy() { + Object browser = values.get(BuiltinFixture.BROWSER.identity()); + return browser == null || ((Browser) browser).isConnected(); + } + + @Override + public void close() { + for (int i = created.size() - 1; i >= 0; i--) { + Entry entry = created.get(i); + try { + entry.definition.close(entry.value); + } catch (Throwable e) { + System.err.println("Failed to close " + entry.definition.describe() + ": " + e); + } + } + created.clear(); + values.clear(); + } + + @Override + public String toString() { + return "Worker[" + key + "]"; + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java new file mode 100644 index 000000000..4a4bf3a04 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java @@ -0,0 +1,92 @@ +/* + * 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.fixtures; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ForkJoinWorkerThread; + +/** + * Hands out {@link Worker}s to tests. Idle workers are kept for the next test with the same key; + * when the pool is full, the least recently used idle worker with a different key is closed to + * make room, as Playwright Test tears a worker down when the next test needs different worker + * fixtures. Workers are not bound to threads, so the pool is safe with JUnit's fork-join + * executor which may add compensation threads or retire idle ones. + */ +final class WorkerPool { + static final WorkerPool INSTANCE = new WorkerPool(); + + // Least recently released first. + private final List idle = new ArrayList<>(); + private int busy; + + private WorkerPool() { + } + + Worker acquire(Worker.Key key) { + Worker evicted = null; + synchronized (this) { + for (int i = idle.size() - 1; i >= 0; i--) { + if (idle.get(i).key.equals(key)) { + busy++; + return idle.remove(i); + } + } + if (!idle.isEmpty() && idle.size() + busy >= maxWorkers()) { + evicted = idle.remove(0); + } + busy++; + } + if (evicted != null) { + evicted.close(); + } + return new Worker(key); + } + + void release(Worker worker, boolean discard) { + boolean close = discard || !worker.isHealthy(); + synchronized (this) { + busy--; + if (!close) { + idle.add(worker); + } + } + if (close) { + worker.close(); + } + } + + void closeAll() { + List workers; + synchronized (this) { + workers = new ArrayList<>(idle); + idle.clear(); + } + for (Worker worker : workers) { + worker.close(); + } + } + + /** As many workers as there are test threads; one when tests do not run in parallel. */ + private static int maxWorkers() { + Thread thread = Thread.currentThread(); + if (thread instanceof ForkJoinWorkerThread) { + return ((ForkJoinWorkerThread) thread).getPool().getParallelism(); + } + return 1; + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java new file mode 100644 index 000000000..76faa445d --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java @@ -0,0 +1,49 @@ +/* + * 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. + */ + +/** + * JUnit 5 fixtures for the Playwright tests, a port of the upstream Playwright Test fixtures in + * {@code tests/config/browserTest.ts}. + * + *

Mapping to Playwright Test

+ *
    + *
  • Projects (one per browser) are the invocations of a {@link com.microsoft.playwright.fixtures.PlaywrightTest}, + * selected with the {@code BROWSER} environment variable, see {@link com.microsoft.playwright.fixtures.Env}.
  • + *
  • Destructured fixtures {@code ({ page, server })} are method parameters resolved by type.
  • + *
  • {@code test.extend({...})} and {@code test.use({...})} are {@link com.microsoft.playwright.fixtures.Fixture} + * methods on the test class, its supertypes, interfaces or enclosing classes; a method may depend on + * the fixture it overrides by declaring a parameter of its own return type.
  • + *
  • Worker fixtures ({@code scope: 'worker'}) are {@link com.microsoft.playwright.fixtures.Scope#WORKER} + * fixtures. A worker is a bundle of such values that one test uses at a time; the pool hands it to + * the next test with the same worker fixtures and closes it after a failure, as Playwright Test + * restarts a worker process.
  • + *
  • {@code test.skip(({ browserName }) => ...)} is {@link com.microsoft.playwright.fixtures.DisabledOnBrowser} + * or {@link com.microsoft.playwright.fixtures.EnabledOnBrowser}; dynamic conditions use JUnit assumptions.
  • + *
  • Tests generated in a {@code for} loop are a {@code @Nested @ParameterizedClass} whose + * {@code @Parameter} field is read by a {@code @Fixture} method.
  • + *
+ * + *

Threading

+ * Playwright objects are not thread-safe, so every worker is used by a single test at a time and tests + * only touch the fixtures they were given. Workers are pooled rather than bound to threads because + * JUnit's fork-join executor may add or retire threads; sequential use from different threads is safe. + * + *

Other extensions

+ * The resolver only claims parameters whose type is a known fixture, so it coexists with the parameter + * resolvers of Spring, Quarkus, Micronaut or {@code @TempDir}. Test-scoped fixture methods may be + * instance methods and read fields those extensions injected. + */ +package com.microsoft.playwright.fixtures; From ed346c3f3083adad43158cd6cb0555b00cb1bf15 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Fri, 23 Oct 2026 17:15:16 -0700 Subject: [PATCH 2/2] test(fixtures): close leaked contexts and report teardown failures - close contexts a test leaves open in a worker's browsers before the worker is reused, keeping the ones created by worker fixtures - fail the test when closing its fixtures fails and discard the worker; failures to close idle or evicted workers fail the run - keep a worker per browser for every test thread, since each thread runs a test in all browsers in turn, and close idle workers in parallel at the end of the run - check worker reuse with pool unit tests and an isolated integration test Claude-Session: https://claude.ai/code/session_018p7xPf3r5VjAM7V46nzFTM --- .../playwright/fixtures/FixtureValues.java | 69 +++++++ .../fixtures/PlaywrightExtension.java | 2 +- .../playwright/fixtures/Teardown.java | 59 ++++++ .../playwright/fixtures/TestScope.java | 42 ++--- .../fixtures/TestWorkerFixtures.java | 43 ++++- .../playwright/fixtures/TestWorkerPool.java | 175 ++++++++++++++++++ .../microsoft/playwright/fixtures/Worker.java | 72 +++---- .../playwright/fixtures/WorkerPool.java | 68 +++++-- .../playwright/fixtures/package-info.java | 6 +- 9 files changed, 443 insertions(+), 93 deletions(-) create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureValues.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/Teardown.java create mode 100644 playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerPool.java diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureValues.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureValues.java new file mode 100644 index 000000000..369272ebd --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/FixtureValues.java @@ -0,0 +1,69 @@ +/* + * 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.fixtures; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The fixture values of one test or worker, keyed by fixture identity and closed newest first. + */ +final class FixtureValues { + private static final class Entry { + final FixtureDefinition definition; + final Object value; + + Entry(FixtureDefinition definition, Object value) { + this.definition = definition; + this.value = value; + } + } + + private final Map values = new HashMap<>(); + private final List created = new ArrayList<>(); + + Object get(FixtureDefinition definition) { + return values.get(definition.identity()); + } + + void add(FixtureDefinition definition, Object value) { + values.put(definition.identity(), value); + created.add(new Entry(definition, value)); + } + + /** The created values of the given type, e.g. the browsers of a worker. */ + List ofType(Class type) { + List result = new ArrayList<>(); + for (Entry entry : created) { + if (type.isInstance(entry.value)) { + result.add(type.cast(entry.value)); + } + } + return result; + } + + void close(Teardown teardown) { + for (int i = created.size() - 1; i >= 0; i--) { + Entry entry = created.get(i); + teardown.run(() -> entry.definition.close(entry.value)); + } + created.clear(); + values.clear(); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java index 20a21e2d9..c4dd7adf9 100644 --- a/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/PlaywrightExtension.java @@ -33,7 +33,7 @@ public final class PlaywrightExtension implements TestTemplateInvocationContextP @SuppressWarnings("deprecation") private static final class PoolCloser implements AutoCloseable, ExtensionContext.Store.CloseableResource { @Override - public void close() { + public void close() throws Exception { WorkerPool.INSTANCE.closeAll(); } } diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/Teardown.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/Teardown.java new file mode 100644 index 000000000..d9965401b --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/Teardown.java @@ -0,0 +1,59 @@ +/* + * 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.fixtures; + +/** + * Runs every cleanup step even when earlier ones fail, then reports the first failure with the + * others attached as suppressed exceptions, so a broken teardown fails the test instead of being lost. + * Steps may run on several threads at once. + */ +final class Teardown { + interface Step { + void run() throws Exception; + } + + private Throwable failure; + + void run(Step step) { + try { + step.run(); + } catch (Throwable e) { + record(e); + } + } + + private synchronized void record(Throwable e) { + if (failure == null) { + failure = e; + } else if (failure != e) { + failure.addSuppressed(e); + } + } + + synchronized boolean failed() { + return failure != null; + } + + synchronized void rethrow() throws Exception { + if (failure instanceof Error) { + throw (Error) failure; + } + if (failure != null) { + throw (Exception) failure; + } + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java index b539a2847..af4e011f8 100644 --- a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestScope.java @@ -20,11 +20,8 @@ import org.junit.jupiter.api.extension.ParameterResolutionException; import org.opentest4j.TestAbortedException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; /** @@ -32,21 +29,10 @@ * {@link Worker} for the worker-scoped ones, and tears everything down when the test is over. */ final class TestScope implements AutoCloseable { - private static final class Entry { - final FixtureDefinition definition; - final Object value; - - Entry(FixtureDefinition definition, Object value) { - this.definition = definition; - this.value = value; - } - } - private final ExtensionContext context; private final FixtureRegistry registry; private final BrowserName browserName; - private final Map values = new HashMap<>(); - private final List created = new ArrayList<>(); + private final FixtureValues values = new FixtureValues(); private final Set resolving = new LinkedHashSet<>(); private Worker worker; @@ -69,11 +55,10 @@ private Object resolve(FixtureDefinition definition) { if (definition.scope() == Scope.WORKER) { return worker().get(definition, new Resolver(definition)); } - Object value = values.get(definition.identity()); + Object value = values.get(definition); if (value == null) { value = create(definition); - values.put(definition.identity(), value); - created.add(new Entry(definition, value)); + values.add(definition, value); } return value; } catch (RuntimeException | Error e) { @@ -105,25 +90,20 @@ private Worker worker() { return worker; } + /** Closes the test fixtures and returns the worker; a failure to close any of them fails the test. */ @Override - public void close() { - for (int i = created.size() - 1; i >= 0; i--) { - Entry entry = created.get(i); - try { - entry.definition.close(entry.value); - } catch (Throwable e) { - System.err.println("Failed to close " + entry.definition.describe() + ": " + e); - } - } - created.clear(); - values.clear(); + public void close() throws Exception { + Teardown teardown = new Teardown(); + values.close(teardown); if (worker != null) { // Like Playwright Test, do not reuse a worker after a failure: the test may have left the // browser or the Playwright instance in an unknown state. - boolean failed = context.getExecutionException().filter(e -> !(e instanceof TestAbortedException)).isPresent(); - WorkerPool.INSTANCE.release(worker, failed); + boolean discard = teardown.failed() + || context.getExecutionException().filter(e -> !(e instanceof TestAbortedException)).isPresent(); + teardown.run(() -> WorkerPool.INSTANCE.release(worker, discard)); worker = null; } + teardown.rethrow(); } private final class Resolver implements FixtureResolver { diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java index 04a34bc76..dc60ee55a 100644 --- a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerFixtures.java @@ -17,23 +17,38 @@ package com.microsoft.playwright.fixtures; import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserContext; import com.microsoft.playwright.BrowserType; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.parallel.Isolated; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.*; +// Runs alone so that no other test class can evict the workers whose reuse it checks. +@Isolated +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class TestWorkerFixtures { - private static final AtomicInteger created = new AtomicInteger(); + private static final Map> resourcesByBrowser = new ConcurrentHashMap<>(); private static final AtomicInteger closed = new AtomicInteger(); static final class WorkerResource implements AutoCloseable { final Browser browser; + // Created by a worker fixture, so it is kept open from one test to the next. + final BrowserContext context; WorkerResource(Browser browser) { this.browser = browser; - created.incrementAndGet(); + this.context = browser.newContext(); } @Override @@ -53,23 +68,33 @@ static WorkerResource workerResource(Browser browser) { return new WorkerResource(browser); } + private static void record(BrowserName browserName, WorkerResource resource) { + resourcesByBrowser.computeIfAbsent(browserName, k -> new HashSet<>()).add(resource); + } + @AfterAll - static void workerResourcesOutliveTests() { - // Worker fixtures are closed with their worker, which the pool may evict while other classes - // run, never once per test. - assertTrue(created.get() > 0); - assertTrue(closed.get() <= created.get(), "created " + created.get() + ", closed " + closed.get()); + static void workerFixturesAreSharedByTests() { + resourcesByBrowser.forEach((browserName, resources) -> + assertEquals(1, resources.size(), browserName + " tests did not share the worker: " + resources)); + assertEquals(0, closed.get(), "Worker fixtures were closed while their worker was idle"); } @PlaywrightTest - void shouldLaunchWithOverriddenOptions(BrowserType.LaunchOptions launchOptions, Browser browser, WorkerResource resource) { + @Order(1) + void shouldLaunchWithOverriddenOptions(BrowserType.LaunchOptions launchOptions, Browser browser, WorkerResource resource, BrowserName browserName) { + record(browserName, resource); assertEquals(123456, launchOptions.timeout); assertTrue(browser.isConnected()); assertSame(browser, resource.browser); + // Left open on purpose: it must be closed before the next test gets this worker. + browser.newContext(); } @PlaywrightTest - void shouldReuseWorkerFixtures(Browser browser, WorkerResource resource) { + @Order(2) + void shouldReuseWorkerFixtures(Browser browser, WorkerResource resource, BrowserName browserName) { + record(browserName, resource); assertSame(browser, resource.browser); + assertEquals(singletonList(resource.context), browser.contexts()); } } diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerPool.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerPool.java new file mode 100644 index 000000000..6fab1bcd7 --- /dev/null +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/TestWorkerPool.java @@ -0,0 +1,175 @@ +/* + * 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.fixtures; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +public class TestWorkerPool { + private static final Worker.Key CHROMIUM = new Worker.Key(BrowserName.CHROMIUM, Collections.emptyList()); + private static final Worker.Key FIREFOX = new Worker.Key(BrowserName.FIREFOX, Collections.emptyList()); + private static final Worker.Key WEBKIT = new Worker.Key(BrowserName.WEBKIT, Collections.emptyList()); + + // Idle workers are closed concurrently at the end of the run. + private final List closed = Collections.synchronizedList(new ArrayList<>()); + private final FixtureDefinition resource = new WorkerFixture("resource", null); + + /** A worker fixture whose values record that they were closed, or fail to close. */ + private final class WorkerFixture implements FixtureDefinition { + private final String name; + private final String closeError; + + WorkerFixture(String name, String closeError) { + this.name = name; + this.closeError = closeError; + } + + @Override + public Class type() { + return Object.class; + } + + @Override + public Scope scope() { + return Scope.WORKER; + } + + @Override + public Object identity() { + return this; + } + + @Override + public Object create(FixtureResolver resolver) { + return new Object(); + } + + @Override + public void close(Object value) { + if (closeError != null) { + throw new IllegalStateException(closeError); + } + closed.add(value); + } + + @Override + public String describe() { + return name; + } + } + + @Test + void shouldKeepWorkerPerBrowserWhenEachTestRunsInAllBrowsers() throws Exception { + // One test thread, every test runs in three browsers one after another. + WorkerPool pool = new WorkerPool(() -> 3); + Map firstWorkers = new HashMap<>(); + for (int test = 0; test < 3; test++) { + for (Worker.Key key : asList(CHROMIUM, FIREFOX, WEBKIT)) { + Worker worker = pool.acquire(key); + worker.get(resource, null); + firstWorkers.putIfAbsent(key, worker); + assertSame(firstWorkers.get(key), worker, "test " + test + " in " + key); + pool.release(worker, false); + } + } + assertEquals(asList(), closed); + } + + @Test + void shouldEvictLeastRecentlyReleasedWorkerWhenFull() throws Exception { + WorkerPool pool = new WorkerPool(() -> 2); + Worker chromium = pool.acquire(CHROMIUM); + Object chromiumValue = chromium.get(resource, null); + pool.release(chromium, false); + Worker firefox = pool.acquire(FIREFOX); + pool.release(firefox, false); + + Worker webkit = pool.acquire(WEBKIT); + assertEquals(asList(chromiumValue), closed); + pool.release(webkit, false); + assertSame(firefox, pool.acquire(FIREFOX)); + } + + @Test + void shouldReportFailureToCloseEvictedWorkerAtEndOfRun() throws Exception { + WorkerPool pool = new WorkerPool(() -> 1); + Worker chromium = pool.acquire(CHROMIUM); + chromium.get(new WorkerFixture("broken", "broken failed"), null); + pool.release(chromium, false); + + // No test owns the evicted worker, so the test that needs its room still gets a worker. + Worker firefox = pool.acquire(FIREFOX); + pool.release(firefox, false); + + IllegalStateException e = assertThrows(IllegalStateException.class, pool::closeAll); + assertEquals("Failed to close " + chromium, e.getMessage()); + assertEquals("broken failed", e.getCause().getMessage()); + } + + @Test + void shouldCloseWorkerAfterFailedTest() throws Exception { + WorkerPool pool = new WorkerPool(() -> 1); + Worker worker = pool.acquire(CHROMIUM); + Object value = worker.get(resource, null); + pool.release(worker, true); + + assertEquals(asList(value), closed); + assertNotSame(worker, pool.acquire(CHROMIUM)); + } + + @Test + void shouldCloseEverythingAndReportAllTeardownFailures() throws Exception { + WorkerPool pool = new WorkerPool(() -> 1); + Worker worker = pool.acquire(CHROMIUM); + Object value = worker.get(resource, null); + worker.get(new WorkerFixture("first", "first failed"), null); + worker.get(new WorkerFixture("second", "second failed"), null); + + // Closed newest first: both failures are reported and the oldest value is still closed. + IllegalStateException e = assertThrows(IllegalStateException.class, () -> pool.release(worker, true)); + assertEquals("second failed", e.getMessage()); + assertEquals(1, e.getSuppressed().length); + assertEquals("first failed", e.getSuppressed()[0].getMessage()); + assertEquals(asList(value), closed); + } + + @Test + void shouldCloseIdleWorkersAtEndOfRun() throws Exception { + WorkerPool pool = new WorkerPool(() -> 3); + Worker chromium = pool.acquire(CHROMIUM); + Object chromiumValue = chromium.get(resource, null); + Worker firefox = pool.acquire(FIREFOX); + Object firefoxValue = firefox.get(resource, null); + firefox.get(new WorkerFixture("broken", "broken failed"), null); + pool.release(chromium, false); + pool.release(firefox, false); + + IllegalStateException e = assertThrows(IllegalStateException.class, pool::closeAll); + assertEquals("Failed to close " + firefox, e.getMessage()); + assertEquals("broken failed", e.getCause().getMessage()); + assertEquals(new HashSet<>(asList(chromiumValue, firefoxValue)), new HashSet<>(closed)); + } +} diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java index 85b361ba4..7c8b6de9e 100644 --- a/playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/Worker.java @@ -17,12 +17,12 @@ package com.microsoft.playwright.fixtures; import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserContext; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.HashMap; +import java.util.HashSet; import java.util.List; -import java.util.Map; +import java.util.Set; /** * A bundle of worker-scoped fixture values, the counterpart of a Playwright Test worker process. @@ -60,56 +60,64 @@ public String toString() { } } - private static final class Entry { - final FixtureDefinition definition; - final Object value; - - Entry(FixtureDefinition definition, Object value) { - this.definition = definition; - this.value = value; - } - } - final Key key; - private final Map values = new HashMap<>(); - private final List created = new ArrayList<>(); + private final FixtureValues values = new FixtureValues(); + // Contexts created by worker fixtures. They belong to the worker and outlive tests. + private final Set workerContexts = new HashSet<>(); Worker(Key key) { this.key = key; - values.put(BuiltinFixture.BROWSER_NAME.identity(), key.browserName); + values.add(BuiltinFixture.BROWSER_NAME, key.browserName); } Object get(FixtureDefinition definition, FixtureResolver resolver) throws Exception { - Object value = values.get(definition.identity()); + Object value = values.get(definition); if (value == null) { + Set before = contexts(); value = definition.create(resolver); if (value == null) { throw new IllegalStateException(definition.describe() + " returned null"); } - values.put(definition.identity(), value); - created.add(new Entry(definition, value)); + values.add(definition, value); + Set created = contexts(); + created.removeAll(before); + workerContexts.addAll(created); } return value; } + /** + * Closes the contexts that the last test opened in this worker's browsers and left behind, so + * that the next test starts with only the contexts of the worker fixtures. + */ + void closeTestContexts(Teardown teardown) { + Set leaked = contexts(); + leaked.removeAll(workerContexts); + for (BrowserContext context : leaked) { + teardown.run(context::close); + } + } + + private Set contexts() { + Set result = new HashSet<>(); + for (Browser browser : values.ofType(Browser.class)) { + if (browser.isConnected()) { + result.addAll(browser.contexts()); + } + } + return result; + } + /** A worker whose browser got disconnected cannot serve another test. */ boolean isHealthy() { - Object browser = values.get(BuiltinFixture.BROWSER.identity()); - return browser == null || ((Browser) browser).isConnected(); + return values.ofType(Browser.class).stream().allMatch(Browser::isConnected); } @Override - public void close() { - for (int i = created.size() - 1; i >= 0; i--) { - Entry entry = created.get(i); - try { - entry.definition.close(entry.value); - } catch (Throwable e) { - System.err.println("Failed to close " + entry.definition.describe() + ": " + e); - } - } - created.clear(); - values.clear(); + public void close() throws Exception { + Teardown teardown = new Teardown(); + values.close(teardown); + teardown.rethrow(); } @Override diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java index 4a4bf3a04..9e7f0c5f8 100644 --- a/playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/WorkerPool.java @@ -19,22 +19,32 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ForkJoinWorkerThread; +import java.util.function.IntSupplier; /** * Hands out {@link Worker}s to tests. Idle workers are kept for the next test with the same key; - * when the pool is full, the least recently used idle worker with a different key is closed to - * make room, as Playwright Test tears a worker down when the next test needs different worker - * fixtures. Workers are not bound to threads, so the pool is safe with JUnit's fork-join - * executor which may add compensation threads or retire idle ones. + * when the pool is full, the least recently used idle worker is closed to make room, as Playwright + * Test tears a worker down when the next test needs different worker fixtures. Workers are not + * bound to threads, so the pool is safe with JUnit's fork-join executor which may add compensation + * threads or retire idle ones. */ final class WorkerPool { - static final WorkerPool INSTANCE = new WorkerPool(); + /** + * Every test thread runs each test once per browser, one invocation after another, so it needs + * a worker per browser to reuse them from one test to the next. + */ + static final WorkerPool INSTANCE = new WorkerPool(() -> testThreads() * Env.browserNames().size()); + private final IntSupplier capacity; // Least recently released first. private final List idle = new ArrayList<>(); private int busy; + // Failures to close workers that no test owns: evicted ones and the idle ones at the end of the run. + // Like Playwright Test's worker teardown errors, they are reported for the whole run. + private final Teardown unownedTeardown = new Teardown(); - private WorkerPool() { + WorkerPool(IntSupplier capacity) { + this.capacity = capacity; } Worker acquire(Worker.Key key) { @@ -46,43 +56,63 @@ Worker acquire(Worker.Key key) { return idle.remove(i); } } - if (!idle.isEmpty() && idle.size() + busy >= maxWorkers()) { + if (!idle.isEmpty() && idle.size() + busy >= capacity.getAsInt()) { evicted = idle.remove(0); } busy++; } if (evicted != null) { - evicted.close(); + closeUnowned(evicted); } return new Worker(key); } - void release(Worker worker, boolean discard) { - boolean close = discard || !worker.isHealthy(); + /** + * Returns the worker to the pool once it is ready for the next test, or closes it when it must + * not serve another one; failures to clean it up are reported to the test that used it. + */ + void release(Worker worker, boolean discard) throws Exception { + Teardown teardown = new Teardown(); + boolean reuse = !discard && worker.isHealthy(); + if (reuse) { + worker.closeTestContexts(teardown); + reuse = !teardown.failed(); + } synchronized (this) { busy--; - if (!close) { + if (reuse) { idle.add(worker); } } - if (close) { - worker.close(); + if (!reuse) { + teardown.run(worker::close); } + teardown.rethrow(); } - void closeAll() { + void closeAll() throws Exception { List workers; synchronized (this) { workers = new ArrayList<>(idle); idle.clear(); } - for (Worker worker : workers) { - worker.close(); - } + // Workers share nothing, so they are closed concurrently. + workers.parallelStream().forEach(this::closeUnowned); + unownedTeardown.rethrow(); + } + + private void closeUnowned(Worker worker) { + unownedTeardown.run(() -> { + try { + worker.close(); + } catch (Exception e) { + throw new IllegalStateException("Failed to close " + worker, e); + } + }); } - /** As many workers as there are test threads; one when tests do not run in parallel. */ - private static int maxWorkers() { + /** As many as JUnit runs tests in parallel; one when tests do not run in parallel. */ + private static int testThreads() { Thread thread = Thread.currentThread(); if (thread instanceof ForkJoinWorkerThread) { return ((ForkJoinWorkerThread) thread).getPool().getParallelism(); diff --git a/playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java b/playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java index 76faa445d..e14932dde 100644 --- a/playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java +++ b/playwright/src/test/java/com/microsoft/playwright/fixtures/package-info.java @@ -29,7 +29,8 @@ *
  • Worker fixtures ({@code scope: 'worker'}) are {@link com.microsoft.playwright.fixtures.Scope#WORKER} * fixtures. A worker is a bundle of such values that one test uses at a time; the pool hands it to * the next test with the same worker fixtures and closes it after a failure, as Playwright Test - * restarts a worker process.
  • + * restarts a worker process. Contexts that a test leaves open in a worker's browsers are closed + * when the test ends; contexts created by worker fixtures stay open. *
  • {@code test.skip(({ browserName }) => ...)} is {@link com.microsoft.playwright.fixtures.DisabledOnBrowser} * or {@link com.microsoft.playwright.fixtures.EnabledOnBrowser}; dynamic conditions use JUnit assumptions.
  • *
  • Tests generated in a {@code for} loop are a {@code @Nested @ParameterizedClass} whose @@ -40,6 +41,9 @@ * Playwright objects are not thread-safe, so every worker is used by a single test at a time and tests * only touch the fixtures they were given. Workers are pooled rather than bound to threads because * JUnit's fork-join executor may add or retire threads; sequential use from different threads is safe. + * Each test thread runs a test in every browser one after another, so the pool keeps a worker per + * browser for every thread. Errors while closing fixtures fail the test that used them, or the whole + * run for idle workers that no test owns. * *

    Other extensions

    * The resolver only claims parameters whose type is a known fixture, so it coexists with the parameter