Hosting the server
editStart the server
DevToolsServer listens on a TCP port of its own. StartAsync starts it. The clients connect to:
- HttpEndpoint (e.g.
http://127.0.0.1:9222/): the clients read the WebSocket endpoint from/json/version, as with Chromium. This is the addressConnectOverCDPAsynctakes. - WebSocketEndpoint (e.g.
ws://127.0.0.1:9222/devtools/browser/<id>): the endpoint of the browser, for the clients that take a WebSocket address (puppeteer.connect({ browserWSEndpoint }), orConnectOverCDPAsynctoo).
await using var server = new DevToolsServer(); // no DocumentLoader: the pages use page.SetContentAsync() and the routes
await server.StartAsync();
using var playwright = await Microsoft.Playwright.Playwright.CreateAsync();
await using var browser = await playwright.Chromium.ConnectOverCDPAsync(server.HttpEndpoint.ToString());
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.SetContentAsync("""
<label for=name>Name</label><input id=name>
<button onclick="document.querySelector('output').textContent = 'Hello ' + document.querySelector('input').value">Greet</button>
<output></output>
""");
await page.GetByLabel("Name").FillAsync("World");
await page.GetByRole(AriaRole.Button, new() { Name = "Greet" }).ClickAsync();
await Assertions.Expect(page.Locator("output")).ToHaveTextAsync("Hello World");
var screenshot = await page.ScreenshotAsync();| DevToolsServerOptions | Default | Effect |
|---|---|---|
| BindAddress, Port | loopback, a free port | Where the server listens |
| DocumentLoader | none | The network requests of the pages (see below) |
| ScriptEngineOptions | the default limits | The limits of the scripts |
| UseSystemFonts | false | The pages can use the fonts installed on the machine |
| MaxConnections | none | The number of clients connected at the same time |
Let the pages use the network
By default, nothing is fetched: the pages only use the content they are given (SetContentAsync()) and the responses of the interception of the requests (page.RouteAsync()). Set a DocumentLoader to let them load documents and resources:
await using var server = new DevToolsServer(new DevToolsServerOptions
{
DocumentLoader = DocumentLoader.Create(), // the pages can load any URL
});
await server.StartAsync();
using var playwright = await Microsoft.Playwright.Playwright.CreateAsync();
await using var browser = await playwright.Chromium.ConnectOverCDPAsync(server.HttpEndpoint.ToString());
var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync("https://example.com/");
await Assertions.Expect(page.GetByRole(AriaRole.Heading, new() { Level = 1 })).ToHaveTextAsync("Example Domain");
await page.ScreenshotAsync(new() { Path = "example.png", FullPage = true });The requests of the pages go through the loader: filter them as for any page (see Network requests). The data: and blob: URLs are not reported to the clients, and the bodies of the responses are buffered.
Host the server in ASP.NET Core
An application can give the server the WebSockets it accepts, instead of a port of its own. The clients then connect to the WebSocket address of the application:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
await using var server = new DevToolsServer(new DevToolsServerOptions { DocumentLoader = DocumentLoader.Create() });
// The clients connect to the WebSocket endpoint: playwright.Chromium.ConnectOverCDPAsync("ws://host/devtools")
app.UseWebSockets();
app.Map("/devtools", async context =>
{
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = StatusCodes.Status400BadRequest;
return;
}
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
await server.HandleConnectionAsync(webSocket, context.RequestAborted);
});
await app.RunAsync();Security
The scripts of the pages run in the process of the server, and a client can make the server request any URL that the DocumentLoader allows. Expose the server to trusted clients only:
- The default BindAddress is the loopback address.
- The path of WebSocketEndpoint is random: a client that does not know it cannot connect.
- As Chromium, the server rejects the HTTP requests whose
Hostheader is not an IP address orlocalhost, so that a web page cannot reach it with a DNS rebinding attack. An application that hosts the server checks its own requests.