Tutorials

Tutorials

Layer-by-Layer: Click, Fill In, and Read

The same everyday operations - click an element, fill in a field, read a value - look different at each of GPAL's five layers. This tutorial walks Click and FillIn side by side across all five, then digs into Read: GPAL's GetGrid convenience, the UseAttribute selector setting that controls what GetGrid returns, and how reading a single attribute looks at each lower layer.

Clicking an Element

At the GPAL layer, WithSelector plus LeftClick is one step - GPAL finds the element and clicks it. At the other layers, the elementId argument can simply be the CSS selector itself (an element's elem.Css is just the selector that found it), so RESTClient, MagicHelper, and PuppeteerClient can target an element with the same selector string you'd pass to WithSelector - no separate QuerySelector/ElementHandle step needed for a single, specific element. PuppeteerCommunicator's LeftClickByCss takes a CSS selector directly too, collapsing both steps into one the way GPAL does. QuerySelector + ElementHandle only becomes necessary when a generic selector matches many elements and you need to act on one specific match from that set.

// GPAL

browser.WithSelector("#submit-btn").LeftClick();

// RESTClient

GPAL.OttoMagicClient.LeftClick("#submit-btn").Execute();

// MagicHelper

GPAL.MagicHelper.LeftClick("#submit-btn");

// PuppeteerClient

GPAL.PuppeteerClient.LeftClick("#submit-btn").Execute();

// PuppeteerCommunicator

await communicator.LeftClickByCss("#submit-btn");

Filling In a Text Field

GPAL's FillInFrom takes the text directly. At RESTClient, MagicHelper, and PuppeteerClient, FillInOverwrite (or FillInAppend / FillInInsert for the non-destructive variants) accepts the CSS selector as its element identifier, just like LeftClick - elem.Css is the same string you'd pass to WithSelector. PuppeteerCommunicator sits one level lower and works with a resolved object handle rather than a selector, so it needs QuerySelector first to obtain elem.ElementHandle.

// GPAL

browser.WithSelector("#email").FillInFrom("user@example.com");

// RESTClient

GPAL.OttoMagicClient.FillInOverwrite("#email").WithText("user@example.com").Execute();

// MagicHelper

GPAL.MagicHelper.FillInOverwrite("#email", "user@example.com");

// PuppeteerClient

GPAL.PuppeteerClient.FillInOverwrite("#email").WithText("user@example.com").Execute();

// PuppeteerCommunicator

var elem = await communicator.QuerySelector("#email");

await communicator.FillInOverwrite(elem.ElementHandle, "user@example.com");

Reading Values: GPAL's Built-In Convenience

Reading is where GPAL does the most for you, so it's the wrong place to start a layer-by-layer comparison - there isn't a one-line GetGrid equivalent at the lower layers. Instead, GetGrid is itself the convenience: for each matched element, GPAL inspects the GPALElement it already built while finding that element and picks a sensible default value for the grid - href for an <a>, src for an <img>, and Text for everything else. You never call GetAttribute yourself for the common cases.

browser

.WithSelector(linkColumn)

.WithSelector(imageColumn)

.WithSelector(descriptionColumn)

.WithAllThatMatch(10)

.GetGrid(out var grid);

// grid column 0 -> each <a>'s href

// grid column 1 -> each <img>'s src

// grid column 2 -> each element's Text

TIP

These per-tag defaults come from the same GPALElement that LeftClick and FillInOverwrite use to find the element - GetGrid doesn't make any extra round trips per element to read these values.

UseAttribute: Choosing What Feeds the Grid

Sometimes the tag-based default isn't the value you want - you need an <img>'s alt text instead of its src, or an <input>'s value instead of its (empty) Text. Selector.UseAttribute(attributeName) overrides the default for that selector's column: instead of href/src/Text, GetGrid reads the named attribute from each matched element for that column.

Selector imageColumn = "//img[contains(@class, 's-card__image')]";

imageColumn.WithSelectorName("ImageAlt").UseAttribute("alt");

Selector priceInput = "#price-input";

priceInput.WithSelectorName("PriceValue").UseAttribute("value");

browser

.WithSelector(imageColumn)

.WithSelector(priceInput)

.WithAllThatMatch(10)

.GetGrid(out var grid);

// grid column 0 -> each <img>'s alt text (not its src)

// grid column 1 -> each <input>'s value attribute

TIP

UseAttribute is set on the Selector itself, so different columns in the same GetGrid call can each read a different attribute - or fall back to the tag-based default by simply not calling UseAttribute on that selector.

Reading One Attribute at the Lower Layers

Below GPAL, there is no grid - you read one attribute at a time with GetAttribute. RESTClient, MagicHelper, and PuppeteerClient accept the CSS selector as the element identifier directly (elem.Css, the same string used with WithSelector), then chain WithAttribute before Execute<string> (RESTClient/PuppeteerClient) or pass the attribute name directly (MagicHelper). PuppeteerCommunicator's GetAttribute works at the CDP level and takes the element's backend node ID as an int, so it needs QuerySelector first to obtain elem.ElementBackendNodeId.

// RESTClient

string alt = GPAL.OttoMagicClient.GetAttribute("img.logo").WithAttribute("alt").Execute<string>();

// MagicHelper

string alt = GPAL.MagicHelper.GetAttribute("img.logo", "alt");

// PuppeteerClient

string alt = GPAL.PuppeteerClient.GetAttribute("img.logo").WithAttribute("alt").Execute<string>();

// PuppeteerCommunicator

var elem = await communicator.QuerySelector("img.logo");

var alt = await communicator.GetAttribute(elem.ElementBackendNodeId, "alt");

WARNING

GetAttribute means the same thing at every layer below GPAL - only how you obtain the element handle and how the result comes back changes. This is the pattern Under the Hood describes: pick the altitude that matches how much you want GPAL to manage for you. At the GPAL layer, GetGrid plus UseAttribute covers the vast majority of cases without ever calling GetAttribute directly.