Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion docs/docs/examples/playwright.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public class LoginPageTests : PageTest

Override the `BrowserName` property to control which browser is launched. The possible values are:

- `chromium` (default)
- `chromium` (default, unless the `BROWSER` environment variable names another; see [Headed Runs and Browser Selection](#headed-runs-and-browser-selection))
- `firefox`
- `webkit`

Expand Down Expand Up @@ -92,6 +92,30 @@ public class HeadlessChromeTests : PageTest
}
```

## Headed Runs and Browser Selection

Switch to a visible browser or a different browser for a whole run with environment variables, without changing code. Like the Microsoft Playwright test adapters, TUnit reads:

- `HEADED=1` (or `true`): launch visible browsers instead of headless ones.
- `BROWSER=chromium|firefox|webkit`: the default `BrowserName`. An unknown value fails the test with an error naming the variable.

```bash
HEADED=1 dotnet run --project Tests
BROWSER=firefox dotnet run --project Tests
```

Both apply to every `BrowserTest`, `ContextTest`, `PageTest`, `BrowserFixture`, `ContextFixture` and `PageFixture` consumer. CI runs without the variables stay headless on Chromium. Make sure the selected browser is installed, for example with `pwsh bin/Debug/netX/playwright.ps1 install firefox`, replacing `netX` with your test project's target framework.

`HEADED` only affects browsers launched locally. When `PLAYWRIGHT_SERVICE_ACCESS_TOKEN` and `PLAYWRIGHT_SERVICE_URL` are set, TUnit connects to the remote Playwright service instead, which ignores launch options, so `HEADED=1` has no effect. `BROWSER` still selects the remote browser.

Headless mode is decided in this order:

1. An explicit `BrowserTypeLaunchOptions.Headless`, whether passed to the base constructor, returned from `BrowserFixture.GetLaunchOptions()`, or set in `PlaywrightSettings.DefaultBrowserTypeLaunchOptions`.
2. `HEADED=1`.
3. Playwright's default: headless.

`PWDEBUG=1` is applied by Playwright itself and always opens a visible browser, even when `Headless` is `true`. Leave `Headless` unset to let `HEADED` decide. Overriding `BrowserName` likewise pins the browser regardless of `BROWSER`. The variables are read when a browser launches, so a `[Before(TestSession)]` hook can still set them.

## Limiting Parallel Browser Tests

Browser tests can be resource-intensive. Use `[ParallelLimiter<T>]` to control how many run concurrently:
Expand Down
18 changes: 14 additions & 4 deletions src/TUnit.Playwright/BrowserFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,27 @@ public class BrowserFixture : IAsyncInitializer, IAsyncDisposable
/// </remarks>
public DateTimeOffset? DisconnectedAt => Browser is { } browser ? PlaywrightBrowserMonitor.For(browser).DisconnectedAt : null;

public virtual string BrowserName => Microsoft.Playwright.BrowserType.Chromium;
/// <summary>
/// The browser to launch. Defaults to the <c>BROWSER</c> environment variable
/// (<c>chromium</c>, <c>firefox</c> or <c>webkit</c>), or <c>chromium</c> when it is unset.
/// Override to pin a browser regardless of the environment.
/// </summary>
public virtual string BrowserName => PlaywrightEnvironment.DefaultBrowserName;

/// <summary>
/// Options for launching <see cref="Browser"/>. When the returned options leave
/// <see cref="BrowserTypeLaunchOptions.Headless"/> unset, <c>HEADED=1</c> launches a visible browser.
/// </summary>
protected virtual BrowserTypeLaunchOptions GetLaunchOptions() =>
TUnitPlaywrightSettings.Default.DefaultBrowserTypeLaunchOptions ?? new BrowserTypeLaunchOptions();

public virtual async Task InitializeAsync()
{
var browserType = PlaywrightFixture.Playwright[BrowserName]
?? throw new InvalidOperationException($"Unknown BrowserName '{BrowserName}'.");
var browserName = BrowserName;
var browserType = PlaywrightFixture.Playwright[browserName]
?? throw new InvalidOperationException($"Unknown BrowserName '{browserName}'.");

Browser = await PlaywrightServiceConnector.LaunchAsync(browserType, GetLaunchOptions()).ConfigureAwait(false);
Browser = await PlaywrightServiceConnector.LaunchAsync(browserType, PlaywrightEnvironment.ApplyHeaded(GetLaunchOptions())).ConfigureAwait(false);
// Watch from launch, so a crash before the first context is created is still reported.
PlaywrightBrowserMonitor.For(Browser);
}
Expand Down
8 changes: 7 additions & 1 deletion src/TUnit.Playwright/BrowserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ public async Task<IBrowserContext> NewContext(BrowserNewContextOptions options)
return (context, recording, capture);
}

/// <summary>
/// The options <see cref="BrowserSetup"/> launches with: the constructor's options with
/// the <c>HEADED</c> environment variable applied, read at the time of the call.
/// </summary>
internal BrowserTypeLaunchOptions ResolveLaunchOptions() => PlaywrightEnvironment.ApplyHeaded(_options);

[Before(HookType.Test, "", 0)]
public async Task BrowserSetup()
{
Expand All @@ -111,7 +117,7 @@ public async Task BrowserSetup()
throw new InvalidOperationException($"BrowserType is not initialized. This may indicate that {nameof(PlaywrightTest)}.{nameof(Playwright)} is not initialized or {nameof(PlaywrightTest)}.{nameof(PlaywrightSetup)} did not execute properly.");
}

var service = await BrowserService.Register(this, BrowserType, _options).ConfigureAwait(false);
var service = await BrowserService.Register(this, BrowserType, ResolveLaunchOptions()).ConfigureAwait(false);
lock (_contextsLock)
{
Browser = service.Browser;
Expand Down
78 changes: 78 additions & 0 deletions src/TUnit.Playwright/PlaywrightEnvironment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using Microsoft.Playwright;

namespace TUnit.Playwright;

/// <summary>
/// Run-level switches read from environment variables, matching the Microsoft Playwright
/// test adapters: <c>HEADED=1</c> launches visible browsers and <c>BROWSER</c> picks the browser.
/// </summary>
/// <remarks>
/// Values are read when a browser launches, not cached, so a <c>[Before(TestSession)]</c> hook
/// can still set them.
/// </remarks>
internal static class PlaywrightEnvironment
{
internal const string HeadedVariable = "HEADED";
internal const string BrowserVariable = "BROWSER";

/// <summary>
/// The browser named by the <c>BROWSER</c> environment variable, or Chromium when it is unset.
/// </summary>
internal static string DefaultBrowserName => ResolveBrowserName(Environment.GetEnvironmentVariable(BrowserVariable));

internal static string ResolveBrowserName(string? value)
{
if (string.IsNullOrWhiteSpace(value))
{
return BrowserType.Chromium;
}

var name = value!.Trim();
if (string.Equals(name, BrowserType.Chromium, StringComparison.OrdinalIgnoreCase))
{
return BrowserType.Chromium;
}

if (string.Equals(name, BrowserType.Firefox, StringComparison.OrdinalIgnoreCase))
{
return BrowserType.Firefox;
}

if (string.Equals(name, BrowserType.Webkit, StringComparison.OrdinalIgnoreCase))
{
return BrowserType.Webkit;
}

throw new InvalidOperationException(
$"The {BrowserVariable} environment variable is '{value}'. Expected '{BrowserType.Chromium}', '{BrowserType.Firefox}' or '{BrowserType.Webkit}'.");
}

/// <summary>
/// Applies the <c>HEADED</c> environment variable to <paramref name="options"/>.
/// </summary>
internal static BrowserTypeLaunchOptions ApplyHeaded(BrowserTypeLaunchOptions options) =>
ApplyHeaded(options, Environment.GetEnvironmentVariable(HeadedVariable));

/// <summary>
/// Returns a copy of <paramref name="options"/> with <c>Headless = false</c> when
/// <paramref name="headed"/> is <c>1</c> or <c>true</c> and the options leave
/// <see cref="BrowserTypeLaunchOptions.Headless"/> unset; otherwise returns
/// <paramref name="options"/> unchanged. An explicit <c>Headless</c> wins over <c>HEADED</c>, and the
/// options are copied so shared settings objects are never mutated.
/// </summary>
internal static BrowserTypeLaunchOptions ApplyHeaded(BrowserTypeLaunchOptions options, string? headed)
{
if (options.Headless is not null || !IsEnabled(headed))
{
return options;
}

return new BrowserTypeLaunchOptions(options) { Headless = false };
}

private static bool IsEnabled(string? value)
{
var trimmed = value?.Trim();
return trimmed == "1" || string.Equals(trimmed, "true", StringComparison.OrdinalIgnoreCase);
}
}
2 changes: 1 addition & 1 deletion src/TUnit.Playwright/PlaywrightFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace TUnit.Playwright;

public class PlaywrightFixture : IAsyncInitializer, IAsyncDisposable
{
public IPlaywright Playwright { get; private set; } = null!;
public IPlaywright Playwright { get; internal set; } = null!;

protected virtual string TestIdAttribute => "data-testid";

Expand Down
6 changes: 6 additions & 0 deletions src/TUnit.Playwright/PlaywrightSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ internal TUnitPlaywrightSettings()
/// Options used when launching the browser for tests inheriting <see cref="BrowserTest"/>
/// or <see cref="BrowserFixture"/>. When non-null, this fully replaces the hardcoded defaults.
/// </summary>
/// <remarks>
/// Leave <see cref="BrowserTypeLaunchOptions.Headless"/> unset so the <c>HEADED=1</c>
/// environment variable can open visible browsers; an explicit value wins over <c>HEADED</c>.
/// Playwright's <c>PWDEBUG=1</c> opens a visible browser even when <c>Headless</c> is
/// <c>true</c>.
/// </remarks>
public BrowserTypeLaunchOptions? DefaultBrowserTypeLaunchOptions { get; set; } = null;

/// <summary>
Expand Down
7 changes: 6 additions & 1 deletion src/TUnit.Playwright/PlaywrightTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ namespace TUnit.Playwright;

public class PlaywrightTest : WorkerAwareTest
{
public virtual string BrowserName { get; } = Microsoft.Playwright.BrowserType.Chromium;
/// <summary>
/// The browser to launch. Defaults to the <c>BROWSER</c> environment variable
/// (<c>chromium</c>, <c>firefox</c> or <c>webkit</c>), or <c>chromium</c> when it is unset.
/// Override to pin a browser regardless of the environment.
/// </summary>
public virtual string BrowserName => PlaywrightEnvironment.DefaultBrowserName;
public IBrowserType BrowserType => Playwright[BrowserName];

private static readonly Task<IPlaywright> PlaywrightTask = Microsoft.Playwright.Playwright.CreateAsync();
Expand Down
Loading
Loading