How do Playwright's Java and .NET bindings hand a test a ready-made Page without the JavaScript runner?
answer
- Java annotates, .NET inherits
- One annotation on the test class
- Four base classes, one per layer
- Browser reused, context per test
- OptionsFactory in Java, run settings in .NET
basics
~20 sThrough small per-language integration layers. In Java, @UsePlaywright on a JUnit 5 class injects Page, BrowserContext and Browser as test-method parameters. In .NET, inheriting the PageTest base class from Microsoft.Playwright.NUnit or MSTest exposes Page, Context and Expect().
solid answer
~40 sJava uses an annotation: `@UsePlaywright` on a JUnit 5 class registers an extension that resolves `Page`, `BrowserContext`, `Browser`, `Playwright` and `APIRequestContext` as test-method parameters, with `Playwright` and `Browser` reused across tests and a fresh context and page per test. Configuration is a class -- an `OptionsFactory` returning `Options`, which sets browser name, channel, headless mode, base URL and `Options.Trace`. .NET uses inheritance: `Microsoft.Playwright.NUnit` and `Microsoft.Playwright.MSTest` ship `PlaywrightTest`, `BrowserTest`, `ContextTest` and `PageTest`, and deriving from `PageTest` gives you `Page`, `Context` and `Expect()` with no setup code. Its options come from run settings, for example `dotnet test -- Playwright.BrowserName=firefox`. Both are adapters, not runners: no projects, no retries, no sharding, no reporter of their own.
code
java · 32 linesimport com.microsoft.playwright.Page;
import com.microsoft.playwright.junit.Options;
import com.microsoft.playwright.junit.OptionsFactory;
import com.microsoft.playwright.junit.UsePlaywright;
import com.microsoft.playwright.options.AriaRole;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
@UsePlaywright(QuoteTest.QuoteOptions.class)
public class QuoteTest {
public static class QuoteOptions implements OptionsFactory {
@Override
public Options getOptions() {
return new Options()
.setBrowserName("firefox")
.setHeadless(true)
.setBaseUrl("https://quotes.example.com")
.setTrace(Options.Trace.RETAIN_ON_FAILURE);
}
}
@Test
void annualPremiumIsShown(Page page) {
page.navigate("/motor");
page.getByLabel("Vehicle value").fill("18500");
page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Get quote")).click();
assertThat(page.getByTestId("annual-premium")).hasText("412.60");
}
}go deeper
Learn the two entry points by name: @UsePlaywright on a JUnit 5 class in Java, and inheriting PageTest in .NET. Both hand your test a ready page so you never create or close a browser yourself.
Explain the lifetimes and the configuration route: browser reused across tests, context and page per test, options from an OptionsFactory in Java and from run settings on the dotnet test command line in .NET.
Show how you make these suites diagnosable in CI. Set Options.Trace.RETAIN_ON_FAILURE in Java, wire Context.Tracing start and stop into setup and teardown in .NET, and agree where the artefacts land.
Recognise these as deliberately thin adapters. Decide how much shared harness your organisation builds on top of them, and keep that harness small enough that upgrading the binding never becomes a project of its own.
Neither the JVM nor .NET has anything like `@playwright/test`, so both bindings ship a small integration layer whose only job is to create and dispose browser objects around a test written for the host runner. Knowing the exact entry point is what separates someone who has run these suites from someone who has read about them. ## Java: @UsePlaywright as a JUnit 5 extension `com.microsoft.playwright.junit.UsePlaywright` is an annotation you put on a JUnit 5 test class. It registers an extension that resolves Playwright types declared as test-method parameters: - `Page` -- a fresh page for this test - `BrowserContext` -- the context that page lives in - `Browser` and `Playwright` -- for the rare test that needs to build its own objects - `APIRequestContext` -- for direct HTTP calls alongside the browser The lifetime rule mirrors the Python plugin: `Playwright` and `Browser` are created once and reused across tests, while a new `BrowserContext` and `Page` are created for each test method and closed afterwards. You never call `Playwright.create()` or `browser.close()` yourself, and forgetting that is the usual source of leaked processes in hand-rolled JVM suites. Assertions come from `PlaywrightAssertions.assertThat(locator).hasText(...)`, statically imported; the Java binding is synchronous throughout, so there is no future or callback to unwrap. ## Configuring it with an OptionsFactory Configuration is a class, not a file. Implement `OptionsFactory`, return an `Options` object, and name the factory in the annotation: ```java @UsePlaywright(QuoteOptions.class) class QuoteTest { // test methods take Page as a parameter } class QuoteOptions implements OptionsFactory { @Override public Options getOptions() { return new Options() .setBrowserName("firefox") .setHeadless(true) .setTrace(Options.Trace.RETAIN_ON_FAILURE); } } ``` `Options` covers browser name, channel, headless mode, base URL, launch options, context options and the trace mode. That last one matters: `Options.Trace.RETAIN_ON_FAILURE` is the Java equivalent of the artefact policy a JavaScript suite would set in its config. ## .NET: the PageTest base-class chain The .NET integration is inheritance rather than annotation. Add `Microsoft.Playwright.NUnit` or `Microsoft.Playwright.MSTest` and derive from one of four base classes, each adding one layer: | Base class | What it provides | | --- | --- | | `PlaywrightTest` | the `Playwright` object and the `Expect()` assertion helper | | `BrowserTest` | adds a shared `Browser` | | `ContextTest` | adds a fresh `BrowserContext` per test | | `PageTest` | adds a `Page` in that context -- the class almost everyone uses | Deriving from `PageTest` gives a test a `Page` property and a `Context` property with no setup code, and `Expect(Page.GetByTestId("annual-premium")).ToHaveTextAsync(...)` for web-first assertions. The .NET binding is asynchronous only, so every call is awaited and every test method returns `Task`. ## Configuring a .NET run Options come from run settings rather than a factory class. Pass them on the command line, for example `dotnet test -- Playwright.BrowserName=firefox` or `dotnet test -- Playwright.LaunchOptions.Headless=false`, or put the same values in a `.runsettings` file under a `Playwright` section so CI and developers share them. ## What both shims deliberately leave out Neither one is a runner, and both stop at the same line: - no `projects`, so a browser matrix is parametrisation or a second CI job - no retries, no worker model, no `--shard` - no reporter of their own -- reports come from the host runner - no component mounting - neither reads `playwright.config.ts`; putting one in a Java or .NET repository does nothing Tracing is the interesting asymmetry. Java exposes it declaratively through `Options.setTrace(...)`, while a .NET suite starts and stops `Context.Tracing` by hand in setup and teardown. Both end up with the same trace zip; only the amount of code differs.
- In the Java integration, which Playwright objects survive between test methods and which do not?`Playwright` and `Browser` are created once and reused, because launching a browser per test is expensive. A new `BrowserContext` and `Page` are built for each test method and closed when it ends, which is what gives per-test isolation of cookies and storage without restarting the engine.
- Why does a .NET suite start tracing by hand when a Java suite can set it in options?The .NET packages expose only the base classes and run settings; they have no trace-mode setting, so you call `Context.Tracing.StartAsync` in setup and `StopAsync` with a path in teardown. Java's `OptionsFactory` carries `Options.Trace`, which does the same thing declaratively. The trace file itself is identical.
saying these in an interview costs you the question
- Thinks @UsePlaywright makes the Java suite read playwright.config.ts
- Says a browser is launched and closed for every test method
- Inherits PlaywrightTest and expects a Page property to exist
- Calls Playwright.create() manually inside an annotated class
- Assumes the .NET base classes bring retries and a reporter