diff --git a/docs/src/test-runners-java.md b/docs/src/test-runners-java.md new file mode 100644 index 0000000000..6e47f9a0af --- /dev/null +++ b/docs/src/test-runners-java.md @@ -0,0 +1,188 @@ +--- +id: test-runners +title: "Test Runners" +--- + +With a few lines of code, you can hook up Playwright to your favorite Java test runner. + +Playwright and Browser instances can be reused between tests for better performance. We +recommend running each test case in a new BrowserContext, this way browser state will be +isolated between the tests. + + + +## JUnit + +In JUnit you can initialize [Playwright] and [Browser] in [@BeforeAll](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/BeforeAll.html) method and +destroy them in [@AfterAll](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/AfterAll.html). In the example below all three test methods use the same +[Browser]. Each test uses its own [BrowserContext] and [Page]. + +```java +package org.example; + +import com.microsoft.playwright.Browser; +import com.microsoft.playwright.BrowserContext; +import com.microsoft.playwright.Page; +import com.microsoft.playwright.Playwright; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestExample { + // Shared between all tests in this class. + static Playwright playwright; + static Browser browser; + + // New instance for each test method. + BrowserContext context; + Page page; + + @BeforeAll + static void launchBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch(); + } + + @AfterAll + static void closeBrowser() { + playwright.close(); + } + + @BeforeEach + void createContextAndPage() { + context = browser.newContext(); + page = context.newPage(); + } + + @AfterEach + void closeContext() { + context.close(); + } + + @Test + void shouldClickButton() { + page.navigate("data:text/html,"); + page.click("button"); + assertEquals("Clicked", page.evaluate("result")); + } + + @Test + void shouldCheckTheBox() { + page.setContent(""); + page.check("input"); + assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); + } + + @Test + void shouldSearchWiki() { + page.navigate("https://www.wikipedia.org/"); + page.click("input[name=\"search\"]"); + page.fill("input[name=\"search\"]", "playwright"); + page.press("input[name=\"search\"]", "Enter"); + assertEquals("https://en.wikipedia.org/wiki/Playwright", page.url()); + } +} +``` + +### Running Tests in Parallel + +By default JUnit will run all tests sequentially on a single thread. Since JUnit 5.3 you can change this behavior to run tests in parallel +to speed up execution (see [this page](https://junit.org/junit5/docs/snapshot/user-guide/index.html#writing-tests-parallel-execution)). +Since it is not safe to use same Playwright objects from multiple threads without extra synchronization we recommend you create Playwright +instance per thread and use it on that thread exclusively. Here is an example how to run multiple test classes in parallel. + +Use [`@TestInstance(TestInstance.Lifecycle.PER_CLASS)`](https://junit.org/junit5/docs/current/api/org.junit.jupiter.api/org/junit/jupiter/api/TestInstance.html) +annotation to make JUnit create one instance of a class for all test methods within that class (by default each JUnit will create a new instance of the class +for each test method). Store [Playwright] and [Browser] objects in instance fields. They will be shared between tests. Each instace of the class will use its +own copy of Playwright. + + +```java +// Subclasses will inherit PER_CLASS behavior. +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class TestFixtures { + // Shared between all tests in the class. + Playwright playwright; + Browser browser; + + @BeforeAll + void launchBrowser() { + playwright = Playwright.create(); + browser = playwright.chromium().launch(); + } + + @AfterAll + void closeBrowser() { + playwright.close(); + } + + // New instance for each test method. + BrowserContext context; + Page page; + + @BeforeEach + void createContextAndPage() { + context = browser.newContext(); + page = context.newPage(); + } + + @AfterEach + void closeContext() { + context.close(); + } +} + +class Test1 extends TestFixtures { + @Test + void shouldClickButton() { + page.navigate("data:text/html,"); + page.click("button"); + assertEquals("Clicked", page.evaluate("result")); + } + + @Test + void shouldCheckTheBox() { + page.setContent(""); + page.check("input"); + assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); + } + + @Test + void shouldSearchWiki() { + page.navigate("https://www.wikipedia.org/"); + page.click("input[name=\"search\"]"); + page.fill("input[name=\"search\"]", "playwright"); + page.press("input[name=\"search\"]", "Enter"); + assertEquals("https://en.wikipedia.org/wiki/Playwright", page.url()); + } +} + +class Test2 extends TestFixtures { + @Test + void shouldReturnInnerHTML() { + page.setContent("
hello
"); + assertEquals("hello", page.innerHTML("css=div")); + } + + @Test + void shouldClickButton() { + Page popup = page.waitForPopup(() -> { + page.evaluate("window.open('about:blank');"); + }); + assertEquals("about:blank", popup.url()); + } +} +``` + + +Configure JUnit to run tests in each class sequentially and run multiple classes on parallel threads (with max +number of thread equal to 1/2 of the number of CPU cores): + +```sh +junit.jupiter.execution.parallel.enabled = true +junit.jupiter.execution.parallel.mode.default = same_thread +junit.jupiter.execution.parallel.mode.classes.default = concurrent +junit.jupiter.execution.parallel.config.strategy=dynamic +junit.jupiter.execution.parallel.config.dynamic.factor=0.5 +```