Browser Testing
server_testing layers an ergonomic browser automation API on top of WebDriver.
The flow is:
- Bootstrap the environment with
testBootstrap(installs binaries, sets up logging, registers global teardown). - Start your application under test using
TestClient.ephemeralServer(or any other real server). - Use
browserTestorbrowserGroupto orchestrate WebDriver sessions and run assertions through theBrowserinterface.
Minimal Example
import 'package:shelf/shelf.dart' as shelf;
import 'package:server_testing/server_testing.dart';
import 'package:server_testing_shelf/server_testing_shelf.dart';
import 'package:test/test.dart';
void main() async {
await testBootstrap(BrowserConfig(browserName: 'firefox', headless: true));
final app = (shelf.Request req) async => shelf.Response.ok(
'<html><head><title>Home</title></head><body>Hi</body></html>',
headers: {'content-type': 'text/html'},
);
final handler = ShelfRequestHandler(app);
final client = TestClient.ephemeralServer(handler); // real HTTP server
final baseUrl = await client.baseUrlFuture;
browserGroup('real browser', baseUrl: baseUrl, define: (getBrowser) {
test('home loads', () async {
final browser = getBrowser();
await browser.visit('/');
await browser.assertTitle('Home');
});
tearDownAll(() async {
await client.close(); // release the HttpServer
});
});
}
Key pieces:
testBootstrapis asynchronous: call it at the top of the test file (before defining groups/tests). It readsbrowsers.json, ensures configured browser bundles are present, and wires up teardown to stop WebDriver drivers.BrowserConfigcontrols the default browser, headless mode, logging, proxy, and device overrides. See BrowserConfig & Devices.TestClient.ephemeralServerprovides an isolated base URL for the suite; reuse the same handler across HTTP and browser tests when possible.browserGroupis analogous togroupbut passes agetBrowser()getter for one shared browser session. Call it inside each test body, after group setup has run. UsebrowserTestfor single tests outside a group.
Mixing HTTP and Browser Assertions
Because both HTTP and browser tests can share the same handler, you can prepare state through the TestClient before navigating in the UI:
late TestClient client;
late String baseUrl;
setUpAll(() async {
client = TestClient.ephemeralServer(handler);
baseUrl = await client.baseUrlFuture;
await client.postJson('/seed', {'users': [{'id': 1, 'name': 'Ada'}]});
});
browserTest('user list renders', (browser) async {
await browser.visit('/users');
await browser.assertSee('Ada');
}, baseUrl: baseUrl);
tearDownAll(() async {
await client.close();
});
Controlling the Browser
The Browser wrapper exposes high-level helpers:
- Navigation:
visit,reload,back,forward - Interactions:
click,type,selectOption,check,uncheck - Assertions:
assertTitle,assertSee,assertSeeIn,assertSeeNothingIn,waiter.waitFor - Screenshot/logging:
captureScreenshot, automatic screenshots on failure whenBrowserConfig.autoScreenshotsistrue - Window/device APIs:
setViewportSize,emulateDevice, device presets viadevicesJsonData
Under the hood it keeps a WebDriver instance alive for the duration of the test and disposes it automatically when the test completes.
Handling Drivers and Logs
testBootstrapregisters a globaltearDownAllto stop any drivers that were started during the run.- Logs and screenshots are written under
BrowserConfig.logDir(defaults to.server_testing_logs). - Use
BrowserManagement.overrideConfigto temporarily run a test with a different browser or headless setting:
browserTest(
'run in headed chrome',
(browser) async {
await browser.visit('/');
},
browserType: ChromiumType(),
headless: false,
baseUrl: baseUrl,
);
For more granular configuration options, continue to the BrowserConfig & Devices guide.