OttoRecording captures a full search-click-scroll sequence as a chain of pre-built Selectors, then replays it against a live browser over a debug port. Each Selector carries multiple lookup strategies (CSS, XPath, value/text matches) recorded ahead of time, so the replay can fall back gracefully if one strategy stops matching.
Here's the whole workflow, start to finish. Each piece is broken down and explained below.
using GenerallyPositive;
using GenerallyPositive.Browser;
using static GenerallyPositive.Enums;
static Selector searchInput = (Selector)GPAL.Selector
.WithCSS("#gh-ac")
.WithXPath("//*[@id='gh-ac']")
.MatchPlaceholder("Search for anything");
static Selector searchButton = (Selector)GPAL.Selector
.WithCSS("#gh-btn")
.WithXPath("//*[@id='gh-btn']")
.WithValue("Search")
.MatchValue("Search");
static Selector resultLink = (Selector)GPAL.Selector
.WithCSS("#srp-river-results > ul > li:nth-of-type(2) > div > div.s-item__info > a > div > span")
.WithXPath("//*[@id='srp-river-results']/ul/li[2]/div/div[2]/a/div/span");
static Selector backToResults = (Selector)GPAL.Selector
.WithCSS("#bc > li > div > a > span")
.WithXPath("//*[@id='bc']/li[1]/div/a/span[2]")
.WithText("Back to search results")
.MatchText("Back to search results");
GPAL
.WithSimulateMouseMovement(true)
.WithPublishToConsole()
.WithExceptionHandler(ExceptionEventHandler)
.WithInformationHandler(InformationHandler);
Browser browser = (Browser)GPAL.Browser
.WithBrowserType(BrowserType.Chrome)
.WithDriverLocation(@"C:drivers")
.WithUseAutomationEngine(AutomationEngine.OttoMagic)
.WithUseDebugPort(6565)
.ToGPALObject();
browser.GoTo("https://example.com");
browser
.WaitFor(5)
.WithSelector(searchInput)
.LeftClick()
.FillInFrom("vintage vinyl records")
.WithSelector(searchButton)
.LeftClick()
.WaitFor(5)
.WithSelector(resultLink)
.LeftClick()
.WithSelector(backToResults)
.LeftClick()
.WaitFor(10);
browser.Close(true);
public static void ExceptionEventHandler(object sender, EventArgs e)
{
var args = (GPAL.GPALEventArgs)e;
Debug.WriteLine(args.Message);
Debug.WriteLine(args.ExceptionRaised?.Message);
}
public static void InformationHandler(object sender, EventArgs e)
{
Debug.WriteLine(((GPAL.GPALEventArgs)e).Message);
}
Each Selector here is built once at class scope using GPAL.Selector, stacking up multiple WithCSS and WithXPath calls plus value/text/placeholder matches. This mirrors how a recording tool would capture an element: grab every identifying detail available at the moment of recording, so replay has several ways to find the same element later even if the page shifts slightly.
static Selector searchInput = (Selector)GPAL.Selector
.WithCSS("#gh-ac")
.WithXPath("//*[@id='gh-ac']")
.MatchPlaceholder("Search for anything");
static Selector searchButton = (Selector)GPAL.Selector
.WithCSS("#gh-btn")
.WithXPath("//*[@id='gh-btn']")
.WithValue("Search")
.MatchValue("Search");
GPAL.Selector returns a builder; the (Selector) cast at the front converts the finished chain into a reusable Selector value you can assign to a field and pass around. See The Selector System.
WithUseDebugPort(6565) attaches GPAL to a browser already running with remote debugging enabled on that port, rather than launching a fresh instance. This is useful for replaying a recorded sequence against a browser session you've already set up and signed into.
Browser browser = (Browser)GPAL.Browser
.WithBrowserType(BrowserType.Chrome)
.WithDriverLocation(@"C:drivers")
.WithUseAutomationEngine(AutomationEngine.OttoMagic)
.WithUseDebugPort(6565)
.ToGPALObject();
browser.GoTo("https://example.com");
The replay is one continuous chain: click the search input, fill it in, click the search button, wait for results, click into a result, then click back to the results list. Each WithSelector/LeftClick pair is one recorded step - GPAL works through the chain in order, the same shape a recording tool would generate.
browser
.WaitFor(5)
.WithSelector(searchInput)
.LeftClick()
.FillInFrom("vintage vinyl records")
.WithSelector(searchButton)
.LeftClick()
.WaitFor(5)
.WithSelector(resultLink)
.LeftClick()
.WithSelector(backToResults)
.LeftClick()
.WaitFor(10);
browser.Close(true);
Because this browser was attached over a debug port, Close(true) still stops the underlying driver process. If other workflows are sharing that debug port session, use Close(false) instead.
WithExceptionHandler and WithInformationHandler hook GPAL's event system so every exception and informational event during replay gets written to the debug output - handy for diagnosing which recorded step stopped matching if the page changes.
GPAL
.WithSimulateMouseMovement(true)
.WithPublishToConsole()
.WithExceptionHandler(ExceptionEventHandler)
.WithInformationHandler(InformationHandler);
public static void ExceptionEventHandler(object sender, EventArgs e)
{
var args = (GPAL.GPALEventArgs)e;
Debug.WriteLine(args.Message);
Debug.WriteLine(args.ExceptionRaised?.Message);
}
public static void InformationHandler(object sender, EventArgs e)
{
Debug.WriteLine(((GPAL.GPALEventArgs)e).Message);
}
The actual OttoRecording test program loops over every BrowserType, AutomationEngine, and headless/headful combination to regression-test the recorded sequence across all of them. A real workflow only needs the single pass shown here. See Automation Engines.
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();