Tutorials

Tutorials

Dropping Down to PuppeteerCommunicator: Raw Chrome DevTools Commands

Underneath GPAL.Browser and PuppeteerClient sits PuppeteerCommunicator, a thin wrapper that sends raw Chrome DevTools Protocol (CDP) commands over a debug port. This tutorial shows how to reach it directly: launching a debug session, capturing screenshots, querying elements, and dispatching low-level mouse and keyboard events when the higher layers don't expose what you need.

Complete Program

Here's the whole workflow, start to finish. Each piece is broken down and explained below.

using System;

using GenerallyPositive;

using GenerallyPositive.Browser;

using static GenerallyPositive.Enums;

GPAL.WithPublishToConsole();

string puppeteerUrl = $"http://localhost:{0xdead}";

IBrowser browser = GPAL.Browser.ToGPALObject();

System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 900);

browser

.WithUseStealth(StealthType.GoogleReferrer)

.WithWaitOnIdleConnection(true)

.WithWindowSize(windowSize)

.WithUseDebugPort(0xdead)

.GoTo("example.com");

var puppeteerClient = ((Browser)browser).PuppeteerClient;

var communicator = ((Browser)browser).PuppeteerCommunicator;

puppeteerClient.GoTo("https://example.com/page").Execute();

while (false == puppeteerClient.CheckNetworkIdle().Execute<bool>()) ;

string sessionId = communicator.GetEffectiveSessionId();

var screenshotResponse = communicator.SendCommand(DevToolsMethods.PageCaptureScreenshot, new

{

format = "jpeg",

}, sessionId).GetAwaiter().GetResult();

string screenShotData = (screenshotResponse as Newtonsoft.Json.Linq.JObject)?["data"]?.ToString();

GPAL.Base64Helper.WithInput(screenShotData).SaveTo("page.jpg");

var selector = "#someButton";

var queryResponse = puppeteerClient.QuerySelector(selector).Execute();

puppeteerClient.Focus(selector).Execute();

puppeteerClient.LeftClick(selector).Execute();

while (false == puppeteerClient.CheckNetworkIdle().Execute<bool>()) ;

browser.WaitFor(5_000).Close(true);

Launching with a Debug Port

WithUseDebugPort opens a Chrome DevTools Protocol port that PuppeteerCommunicator talks to directly. Combine it with WithUseStealth to apply referrer-spoofing tricks, and WithWaitOnIdleConnection so GPAL waits for the network to settle before handing control back to your code.

IBrowser browser = GPAL.Browser.ToGPALObject();

System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 900);

browser

.WithUseStealth(StealthType.GoogleReferrer)

.WithWaitOnIdleConnection(true)

.WithWindowSize(windowSize)

.WithUseDebugPort(0xdead)

.GoTo("example.com");

Reaching the Communicator and Client

Casting the IBrowser to its concrete Browser type exposes two properties: PuppeteerClient, the fluent layer most workflows should use, and PuppeteerCommunicator, the raw CDP transport underneath it. GetEffectiveSessionId returns the session id that scopes every command you send from here on.

var puppeteerClient = ((Browser)browser).PuppeteerClient;

var communicator = ((Browser)browser).PuppeteerCommunicator;

puppeteerClient.GoTo("https://example.com/page").Execute();

while (false == puppeteerClient.CheckNetworkIdle().Execute<bool>()) ;

string sessionId = communicator.GetEffectiveSessionId();

TIP

PuppeteerClient methods return values via Execute<T>(). Looping on CheckNetworkIdle().Execute<bool>() until it returns true is a simple way to block until a page settles before issuing the next command.

Sending a Raw CDP Command

SendCommand takes a DevToolsMethods value, an anonymous object of parameters matching the CDP spec, and the session id. PageCaptureScreenshot returns a JSON object with a base64 'data' field, which GPAL.Base64Helper can decode straight to a file.

var screenshotResponse = communicator.SendCommand(DevToolsMethods.PageCaptureScreenshot, new

{

format = "jpeg",

}, sessionId).GetAwaiter().GetResult();

string screenShotData = (screenshotResponse as Newtonsoft.Json.Linq.JObject)?["data"]?.ToString();

GPAL.Base64Helper.WithInput(screenShotData).SaveTo("page.jpg");

WARNING

SendCommand returns a Task. GetAwaiter().GetResult() blocks synchronously, which is fine in a console Main, but in an async context prefer awaiting it directly to avoid deadlocks.

Querying and Clicking Through PuppeteerClient

Even while you have the raw communicator available, the higher-level PuppeteerClient calls (QuerySelector, Focus, LeftClick) remain the easiest way to interact with elements - they wrap the same CDP commands with sensible defaults.

var queryResponse = puppeteerClient.QuerySelector(selector).Execute();

puppeteerClient.Focus(selector).Execute();

puppeteerClient.LeftClick(selector).Execute();

while (false == puppeteerClient.CheckNetworkIdle().Execute<bool>()) ;

Dispatching Raw Mouse and Keyboard Events

When PuppeteerClient doesn't have a matching method, you can dispatch InputDispatchMouseEvent and InputDispatchKeyEvent commands directly through the communicator - down then up for a click, or one keyDown/keyUp pair per character for typing.

await communicator.SendCommand<object>(

DevToolsMethods.InputDispatchMouseEvent,

new

{

type = "mousePressed",

button = "left",

clickCount = 1,

x = 100.0,

y = 200.0,

modifiers = 0,

timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()

}, sessionId);

await communicator.SendCommand<object>(

DevToolsMethods.InputDispatchMouseEvent,

new

{

type = "mouseReleased",

button = "left",

clickCount = 1,

x = 100.0,

y = 200.0,

modifiers = 0,

timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()

}, sessionId);

foreach (char c in "Hello")

{

await communicator.SendCommand<object>(DevToolsMethods.InputDispatchKeyEvent, new { type = "keyDown", text = c.ToString() }, sessionId);

await communicator.SendCommand<object>(DevToolsMethods.InputDispatchKeyEvent, new { type = "keyUp", text = c.ToString() }, sessionId);

}

TIP

This level of detail exists for edge cases - sites that defeat normal click/fill simulation, or CDP features GPAL hasn't wrapped yet. For everyday automation, stay on PuppeteerClient or the GPAL.Browser fluent chain.