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.
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);
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");
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();
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.
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");
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.
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>()) ;
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);
}
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.
Showing off some plain text in these paragraphs eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Here you can find different accents and emphasis sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
This is a link and how it could look like bestlinkinthebeautifulworld. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Here's just some classic bold text adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam notBoldSecondbestlinkinthebeautifulworld illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at adipisicing elit libero vel voluptas? Expedita, adipisicing facere dolores voluptatem ad ab rem assumenda soluta!
Other cuple of colors in case we want to emphasize several ways adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at voluptatem libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae quas consequuntur illo numquam assumenda autem exercitationem distinctio perspiciatis in natus. Eius dicta similique ipsam ipsa minima, nemo quae enim tempore.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithPublishToConsole();
//System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 1024);
// NOTE: we have to set browser = before we execute any steps
// this is due to the 'GenericCallIfNotFound' which might throw an exception, and BankScraper will not have the browser set when it calls scraper.Close()
// until the complete fluent line gets executed (meaning every step, meaning browser is not set until everything else succeeds)
browser = GPAL.Browser
.WithBrowserType(Enums.BrowserType.Chrome)
.WithProfileDataDirectory(ChromeProfileLocation)
.WithUseAutomationEngine(AutomationEngine.Selenium)
.WithWindowSize(new System.Drawing.Rectangle(0,0,1920,1080))
.ToGPALObject();