Misc

Proving a Workflow on Every Engine

The Same Workflow, Every Combination

Browser type and automation engine are settings, not structure, so running a workflow on every combination is a loop rather than a rewrite. Put the workflow in a method that takes the browser type, the engine, and whether to run headless, and the harness around it is a few lines. Skip the combinations that do not exist, such as Puppeteer driving Firefox, and skip the engine variants that do not change what the workflow is testing, such as the hardware and javascript click variants when nothing in the run clicks anything. Time each pass with a Stopwatch and count what the pass actually produced, because a run that finishes without throwing is not the same as a run that did the job.

foreach (BrowserType browserType in Enum.GetValues(typeof(BrowserType)))

{

if (BrowserType.Chrome != browserType

&& BrowserType.Edge != browserType

&& BrowserType.FireFox != browserType) continue;


foreach (AutomationEngine engine in Enum.GetValues(typeof(AutomationEngine)))

{

// puppeteer does not drive firefox

if (AutomationEngine.PuppeteerPort == engine && BrowserType.FireFox == browserType) continue;


for (int headlessMode = 0; 2 > headlessMode; headlessMode++)

{

bool headless = (1 == headlessMode);

Stopwatch runStopwatch = Stopwatch.StartNew();


priced = 0;

PriceTheFestival(browserType, engine, headless, nights);


runStopwatch.Stop();


(headless ? headlessRuntimes : windowRuntimes)[(browserType, engine)] = runStopwatch.Elapsed;

(headless ? headlessPriced : windowPriced)[(browserType, engine)] = priced;

}

}

}

EmitTable and EmitRuntimeTable

A dictionary keyed by a (row, column) tuple is already a table, so EmitTable prints it as one: row keys down the side, column keys across the top, one cell each, and a dash where a combination was never run. Rows and columns come out in the natural order of the key type, which for an enum is the order it is declared in, so the table reads the same way every time. The whole table is published as a single INFO event, which means it lands wherever the run's events already go: the console, a log file, the log tab of a form. EmitRuntimeTable is the same call with the formatting already chosen for elapsed times, since timing a matrix is what the pattern is mostly used for.

GPAL.EmitRuntimeTable("Window", windowRuntimes);

GPAL.EmitRuntimeTable("Headless", headlessRuntimes);


// Any two-axis dictionary, with the cell formatted however it reads best

GPAL.EmitTable("Nights priced, window", windowPriced, "Browser", count => count.ToString());


// Window runtime (min:sec.ms)

// Browser | OttoMagic | PuppeteerPort | Selenium

// Chrome | 1:04.221 | 0:41.902 | 0:52.664

// Edge | - | 0:44.117 | 0:55.301

// FireFox | - | - | 1:12.884

NOTE

Emit two tables when the run has something to count: one for how long each combination took, one for how much each combination actually got. A combination that finishes fast and returns nothing is a failure that looks like a win in a timing table on its own.

What the Matrix Is Actually For

Every feature in GPAL is expected to work on all three engines. The matrix is how that claim gets checked rather than assumed, and it is worth running whenever a workflow is finished, not just when something looks wrong. Differences it surfaces are usually real: an engine that cannot reach a closed shadow root, a headless run the site treats differently, a browser whose driver is behind its own version. Those are all better found by a table than by a support ticket.

WARNING

Every combination repeats the whole workflow against the real site. A three-browser, three-engine, two-mode matrix is eighteen full runs, which is enough to get rate limited on a site that would never notice a single pass. Run the full matrix deliberately, and keep a single combination as the default for everyday work.

💬 Ask GPAL