WithWorkflow records a chain of AndThen calls as a repeatable block; While and Until run that block under a condition you control, and GetExecutionResults keeps every pass's named results so you can read them all back afterward - or feed one pass's result into the next pass's condition.
Here's the whole workflow, start to finish. Each piece is broken down and explained below.
using GenerallyPositive;
using static GenerallyPositive.Enums;
using System;
GPAL.WithUseOttoMagic(@"C:OttoMagic").WithPublishToConsole();
IRESTClient client = GPAL.OttoMagicClient.WithName("Pager").ToGPALObject();
int page = 0;
const int maxPages = 5;
client
.WithWorkflow(c => c
.GoTo($"https://example.com/search?q=widgets&page={++page}")
.AndThen()
.CheckNetworkIdle()
.AndThen()
.WithResultName("pageUrl")
.GetCurrentUrl()
.Execute<string>())
.Until(() => page >= maxPages);
foreach (var url in client.GetExecutionResults().GetAllResults("pageUrl"))
Console.WriteLine(url);
WithWorkflow(c => ...) doesn't run anything by itself - it records the lambda, plus a snapshot of the client's current endpoint, parameters, HTTP method, and pending result name at the moment WithWorkflow was called. Every pass through the loop restores that snapshot first, then runs the lambda against it, so each pass starts from the same clean configuration rather than continuing the chain from wherever the previous pass left off. Inside the lambda, c is the same client - AndThen/AndThen<T>, WithResultName, and the rest of the fluent surface all work exactly as they do outside a workflow. The lambda's last call is the one that produces the pass's named result, and since nothing follows it in the chain, Execute<T>() is the cleaner choice over AndThen<T>() - the <T> is the type of the result being retrieved.
client
.WithWorkflow(c => c
// one pass: navigate, wait for the network, then capture the URL
.GoTo($"https://example.com/search?q=widgets&page={++page}")
.AndThen()
.CheckNetworkIdle()
.AndThen()
.WithResultName("pageUrl")
.GetCurrentUrl()
.Execute<string>());
// nothing has executed yet - While or Until is what actually runs it
Until(condition) repeats the recorded workflow until condition() returns true; While(condition) repeats it for as long as condition() returns true. Both check the condition before every pass, including the first - so a workflow can run zero times if the condition is already satisfied. Capturing a plain C# variable (page) in the lambda is the simplest condition: ++page increments before the comparison, so starting page at 0 means the first pass sees page == 1, the second sees page == 2, and so on through maxPages.
// Until: keep going while the condition is false
int page = 0;
client
.WithWorkflow(c => c
.GoTo($"https://example.com/search?q=widgets&page={++page}")
.AndThen()
.CheckNetworkIdle()
.AndThen())
.Until(() => page >= maxPages);
// While: keep going while the condition is true - same five passes
page = 0;
client
.WithWorkflow(c => c
.GoTo($"https://example.com/search?q=widgets&page={++page}")
.AndThen()
.CheckNetworkIdle()
.AndThen())
.While(() => page < maxPages);
Both While and Until stop after 1000 passes even if the condition never resolves, and publish a WARNING event when that cap is hit. Treat the cap as a safety net for a runaway condition, not as a loop-count you should rely on.
Each pass through the workflow adds another entry under the same result name, so GetExecutionResults() ends up holding one "pageUrl" per pass rather than overwriting the last one. results["pageUrl"] returns the most recent pass's value; results.GetAllResults("pageUrl") returns every pass's value as a list, in the order the passes ran; and results["pageUrl", n] returns the value from a specific pass by its zero-based iteration number.
var results = client.GetExecutionResults();
string lastUrl = (string)results["pageUrl"];
string firstUrl = (string)results["pageUrl", 0];
foreach (var url in results.GetAllResults("pageUrl"))
Console.WriteLine(url);
IsEndOfPage() reports whether the page is scrolled to its bottom - the right condition for infinite-scroll content, not for the URL-based ?page=N pagination used above. Because While/Until check their condition before every pass - including the first - a condition built around IsEndOfPage has nothing to read on that first check. The fix is to prime the result with one chain before the workflow exists, using the same WithResultName/Execute<T> pair the workflow will use on every later pass. Since results["name"] always returns the most recent value with that name, the primed value covers the first check, and each pass's own result covers every check after that. Both chains end with Execute<bool>() rather than AndThen<bool>() - AndThen<bool>() would also execute and store the result, but Execute<T>() is the cleaner final call when nothing follows it, and the <bool> is the type of the result being retrieved.
// Prime "atBottom" so the first While check has something to read
client
.ScrollWindowByVertical(800)
.AndThen()
.CheckNetworkIdle()
.AndThen()
.WithResultName("atBottom")
.IsEndOfPage()
.Execute<bool>();
client
.WithWorkflow(c => c
.ScrollWindowByVertical(800)
.AndThen()
.CheckNetworkIdle()
.AndThen()
.WithResultName("atBottom")
.IsEndOfPage()
.Execute<bool>())
.While(() => false == (bool)client.GetExecutionResults()["atBottom"]);
int scrollPasses = client.GetExecutionResults().GetAllResults("atBottom").Count;
Console.WriteLine($"Reached the bottom after {scrollPasses} scroll passes.");
If you skip the priming chain, the first While/Until check reads a result that doesn't exist yet. results["name"] returns null in that case and publishes a WARNING event - casting that null to a value type like bool throws. Always run one chain that produces the result under the same name before handing the workflow to While or Until.
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();