GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
RESTClient.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.IO;
20using System.Linq;
21using System.Net.Http;
22using System.Threading.Tasks;
23using DocumentFormat.OpenXml.Office2019.Drawing.Diagram11;
25using static GenerallyPositive.Enums;
26
27namespace GenerallyPositive
28{
55 public class RESTClient : IRESTClient
56 {
57 private readonly List<Action<RESTClient>> _workflows = new List<Action<RESTClient>>();
58 private readonly List<(string Endpoint, object Parameters, string HttpMethod, string NextResultName)?> _workflowStates = new List<(string, object, string, string)?>();
59 private bool _customEndpoint = false;
60 private string _endpoint;
61 private object _parameters;
62 private string _httpMethod = "GET";
63 internal ContentEncoding _contentEncoding = ContentEncoding.Json; // Default to JSON
64 private string _name;
65 private string _saveFile { get; set; }
66 private string _nextResultName;
67 private readonly List<(string Name, object Result, int Iteration)> _executionResults = new List<(string Name, object Result, int Iteration)>();
68 private RESTHelper _helper;
69 private int _currentIteration;
70 internal Dictionary<string, string> _headers = new Dictionary<string, string>();
71 public int StatusCode
72 {
73 get => _helper.StatusCode;
74 }
75
77 {
78 get => _helper.RetryAfterSeconds;
79 }
80
81 internal Dictionary<string, (string[] Mandatory, string[] Optional, Func<IDictionary<string, object>, bool> CustomValidation)>
82 openApiParamRules = new Dictionary<string, (string[] Mandatory, string[] Optional, Func<IDictionary<string, object>, bool> CustomValidation)>(StringComparer.OrdinalIgnoreCase);
83
84 Dictionary<ApiEndpoint, (string[] Mandatory, string[] Optional, Func<IDictionary<string, object>, bool> CustomValidation)> paramRules = new Dictionary<ApiEndpoint, (string[] Mandatory, string[] Optional, Func<IDictionary<string, object>, bool> CustomValidation)>
85 {
86 [ApiEndpoint.Back] = (new string[] { }, new string[] { }, null),
87 [ApiEndpoint.CaptureVisibleTab] = (new string[] { }, new string[] { "windowId", "imageFormat" }, null),
88 [ApiEndpoint.CastDesktop] = (new[] { "sinkName" }, new string[] { }, null),
89 [ApiEndpoint.CastTab] = (new[] { "sinkName" }, new string[] { }, null),
90 [ApiEndpoint.CheckNetworkIdle] = (
91 Mandatory: new string[] { },
92 Optional: new[] { "maxConnections", "timeoutMs", "pruneMs", "sessionToken" },
93 CustomValidation: null
94 ),
95 [ApiEndpoint.CheckStatus] = (new string[] { }, new string[] { }, null),
96 [ApiEndpoint.ClearReferrer] = (new string[] { }, new string[] { }, null),
97 [ApiEndpoint.CloseBrowser] = (
98 Mandatory: new string[] { },
99 Optional: new string[] { },
100 CustomValidation: null
101 ),
102 [ApiEndpoint.CloseTab] = (
103 Mandatory: new string[] { },
104 Optional: new[] { "tabId", "url" },
105 CustomValidation: null
106 ),
107 [ApiEndpoint.CloseWindow] = (
108 Mandatory: new string[] { },
109 Optional: new[] { "windowId", "url" },
110 CustomValidation: null
111 ),
112 // NOTE: magic allows no storageType to delete all storage by not specifying storageType
113 // NOTE: at this point, we don't want to accidentally delete all, so we enforce storageType as mandatory, but all of a storageType can be deleted
114 // BROWSER.browsingData.remove({ origins }, {
115 // cache: true,
116 // cookies: true,
117 // localStorage: true,
118 // serviceWorkers: true
119 // });
120 [ApiEndpoint.DeleteStorage] = (new[] { "storageType" }, new[] { "domain", "path", "key", "storeName" }, null),
121 [ApiEndpoint.DragAndDrop] = (new[] { "deltaX", "deltaY" }, new[] { "css", "xpath", "elementId", "offsetX", "offsetY" }, null),
122 [ApiEndpoint.Evaluate] = (new string[] { }, new string[] { "xpath", "elementId" }, dict => true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
123 [ApiEndpoint.EvaluateAll] = (new string[] { }, new string[] { "xpath", "elementId" }, dict => true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
124 [ApiEndpoint.EvaluatePersistent] = (new string[] { }, new string[] { "xpath", "elementId" }, dict => true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
125 [ApiEndpoint.EvaluateAllPersistent] = (new string[] { }, new string[] { "xpath", "elementId" }, dict => true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
126 // the extension reads script and puppeteer reads expression, and both clients are checked against this
127 // one rule, so either names the code. parameters optional
128 [ApiEndpoint.ExecuteJavaScript] = (new string[] { }, new[] { "script", "expression", "parameters" }, dict => true == dict.ContainsKey("script") || true == dict.ContainsKey("expression")),
129 [ApiEndpoint.Fetch] = (new[] { "url" }, new[] { "method", "body", "contentType", "headers", "bytes" }, null), // GET with no body when unsaid, text unless bytes were asked for
130 [ApiEndpoint.FillInAppend] = (new[] { "text" }, new string[] { "css", "xpath", "elementId", "gpalElements", "typingDelay" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
131 [ApiEndpoint.FillInInsert] = (new[] { "text" }, new string[] { "css", "xpath", "elementId", "gpalElements", "typingDelay" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
132 [ApiEndpoint.FillInOverwrite] = (new[] { "text" }, new string[] { "css", "xpath", "elementId", "gpalElements", "typingDelay" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
133 [ApiEndpoint.FireChangeEvent] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
134 [ApiEndpoint.Focus] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
135 [ApiEndpoint.Forward] = (new string[] { }, new string[] { }, null),
136 [ApiEndpoint.FullScreen] = (new string[] { }, new string[] { }, null),
137 [ApiEndpoint.GetAttribute] = (new[] { "attribute" }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
138 [ApiEndpoint.GetBoundingClientRect] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
139 [ApiEndpoint.ElementFromPoint] = (new[] { "x", "y" }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
140 [ApiEndpoint.GetBrowserSettings] = (new string[] { }, new string[] { }, null),
141 [ApiEndpoint.GetCssAttributes] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, null),
142 [ApiEndpoint.GetContentAndCss] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, null),
143 [ApiEndpoint.GetCurrentUrl] = (new string[] { }, new string[] { }, null),
144 [ApiEndpoint.GetCurrentWindow] = (new string[] { }, new string[] { }, null),
145 [ApiEndpoint.GetDocumentStamp] = (new string[] { }, new string[] { }, null),
146 [ApiEndpoint.GetHydrationScripts] = (new string[] { }, new string[] { }, null),
147 [ApiEndpoint.GetDomAttributes] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, null),
148 [ApiEndpoint.GetDomProperties] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, null),
149 [ApiEndpoint.GetElementAttributeHash] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, null),
150 [ApiEndpoint.GetGpalSettings] = (new string[] { }, new string[] { }, null),
151 [ApiEndpoint.GetPageSource] = (new string[] { }, new string[] { }, null),
152 [ApiEndpoint.GetParentNode] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, null),
153 [ApiEndpoint.GetReadyStatus] = (new string[] { }, new string[] { "sessionToken" }, null),
154 [ApiEndpoint.GetSettings] = (new string[] { }, new string[] { }, null),
155 [ApiEndpoint.GetShadowRoot] = (new string[] { }, new string[] { "css", "xpath", "elementId" }, null),
156 [ApiEndpoint.GetStorage] = (new[] { "storageType" }, new[] { "domain", "path", "key", "storeName" }, null),
157 [ApiEndpoint.GetLanguages] = (new string[] { }, new string[] { }, null),
158 [ApiEndpoint.CaptureCalls] = (new string[] { }, new[] { "capture", "filter", "clear" }, null), // no filter records everything
159 [ApiEndpoint.GetCapturedCalls] = (new string[] { }, new string[] { }, null),
160 [ApiEndpoint.GetUserAgent] = (new string[] { }, new string[] { }, null),
161 [ApiEndpoint.GetWorkflow] = (new string[] { }, new string[] { }, null),
162 [ApiEndpoint.GoTo] = (new[] { "url" }, new string[] { }, null),
163 [ApiEndpoint.GoToTab] = (
164 Mandatory: new string[] { },
165 Optional: new[] { "tabId", "url" },
166 CustomValidation: dict => true == dict.ContainsKey("tabId") || true == dict.ContainsKey("url")
167 ),
168 [ApiEndpoint.GoToWindow] = (
169 Mandatory: new string[] { },
170 Optional: new[] { "url", "windowId" },
171 CustomValidation: dict => dict.ContainsKey("url") != dict.ContainsKey("windowId") // Exactly one must be present
172 ),
173 [ApiEndpoint.GetWindowRectangle] = (new string[] { }, new string[] { }, null),
174 [ApiEndpoint.HideElement] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
175 [ApiEndpoint.Hover] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
176 [ApiEndpoint.InjectScript] = (new[] { "script" }, new string[] { }, null),
177 [ApiEndpoint.ClearInjectedScripts] = (new string[] { }, new string[] { }, null),
178 [ApiEndpoint.IsClickable] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
179 [ApiEndpoint.IsDisplayed] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
180 [ApiEndpoint.IsEnabled] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
181 [ApiEndpoint.IsEndOfPage] = (new string[] { }, new string[] { }, null),
182 [ApiEndpoint.IsVisibleInViewport] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
183 [ApiEndpoint.LeftClick] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements", "modifiers" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
184 [ApiEndpoint.LeftClickAndDownload] = (new[] { "downloadPath" }, new string[] { "css", "xpath", "elementId", "gpalElements", "modifiers" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
185 [ApiEndpoint.LeftDoubleClick] = (new string [] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
186 [ApiEndpoint.Maximize] = (new string[] { }, new string[] { }, null),
187 [ApiEndpoint.MiddleClick] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements", "modifiers" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
188 [ApiEndpoint.Minimize] = (new string[] { }, new string[] { }, null),
189 [ApiEndpoint.MoveTo] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
190 [ApiEndpoint.NewTab] = (new string[] { }, new[] { "url" }, null),
191 [ApiEndpoint.NextTab] = (new string[] { }, new string[] { }, null),
192 [ApiEndpoint.NextWindow] = (new string[] { }, new string[] { }, null),
193 [ApiEndpoint.Normal] = (new string[] { }, new string[] { }, null),
194 [ApiEndpoint.OpenWindow] = (new string[] { }, new[] { "url" }, null),
195 [ApiEndpoint.OverrideReferrer] = (new[] { "url" }, new string[] { }, null),
196 [ApiEndpoint.PageDown] = (new string[] { }, new string[] { "pagesToScroll" }, null),
197 [ApiEndpoint.PageEnd] = (new string[] { }, new string[] { }, null),
198 [ApiEndpoint.PageTop] = (new string[] { }, new string[] { }, null),
199 [ApiEndpoint.PageUp] = (new string[] { }, new string[] { "pagesToScroll" }, null),
200 [ApiEndpoint.PressModifierKey] = (new[] { "modifierKeys" }, new string[] { }, null),
201 [ApiEndpoint.PreviousTab] = (new string[] { }, new string[] { }, null),
202 [ApiEndpoint.PreviousWindow] = (new string[] { }, new string[] { }, null),
203 [ApiEndpoint.QuerySelector] = (new string[] { }, new string[] { "css", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("elementId")),
204 [ApiEndpoint.QuerySelectors] = (new string[] { }, new string[] { "css", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("elementId")),
205 [ApiEndpoint.QueryPersistentSelector] = (new string[] { }, new string[] { "css", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("elementId")),
206 [ApiEndpoint.QueryPersistentSelectors] = (new string[] { }, new string[] { "css", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("elementId")),
207 [ApiEndpoint.Refresh] = (new string[] { }, new string[] { }, null),
208 [ApiEndpoint.ReleaseModifierKey] = (new[] { "modifierKeys" }, new string[] { }, null),
209 [ApiEndpoint.Restore] = (new string[] { }, new string[] { }, null),
210 [ApiEndpoint.RightClick] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements", "modifiers" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
211 [ApiEndpoint.ScrollWindowByHorizontal] = (new[] { "hPixels" }, new string[] { "vPixels" }, null),
212 [ApiEndpoint.ScrollWindowByVertical] = (new[] { "vPixels" }, new string[] { "hPixels" }, null),
213 [ApiEndpoint.ScrollElement] = (
214 Mandatory: new string[] { },
215 Optional: new string[] { "css", "xpath", "elementId", "gpalElements", "hPixels", "vPixels" },
216 CustomValidation: null
217 ),
218 [ApiEndpoint.ScrollIntoView] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
219 [ApiEndpoint.ScrollWindow] = (
220 Mandatory: new string[] { },
221 Optional: new[] { "hPixels", "vPixels" },
222 CustomValidation: null
223 ),
224 [ApiEndpoint.SelectClick] = ( new string[] { }, new[] { "css", "xpath", "elementId", "gpalElements", "selectIndex", "selectValue" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements") || true == dict.ContainsKey("selectIndex") || true == dict.ContainsKey("selectValue")),
225 [ApiEndpoint.SendKey] = (new [] { "key", "code", "vk" }, new string[] { }, null),
226 [ApiEndpoint.SendString] = (new[] { "text" }, new[] { "typingDelay" }, null),
227 [ApiEndpoint.SetAttribute] = (new[] { "attribute", "value" }, new string[] { "css", "xpath", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
228 [ApiEndpoint.SetDownloadFilename] = (new[] { "downloadPath" }, new string[] { }, null),
229 [ApiEndpoint.SetRange] = (new[] { "rangeValue" }, new string[] { "css", "xpath", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
230 [ApiEndpoint.SetStorage] = (new[] { "storageType", "key", "data" }, new[] { "domain", "path", "storeName" }, null), // local/sessionStorage are simple key/value pairs, so that is the only ones we can make mandatory
231 [ApiEndpoint.SetStorageCache] = (new[] { "storageType", "key", "data", "storeName" }, new[] { "domain", "path" }, null),
232 [ApiEndpoint.SetStorageIndexedDb] = (new[] { "storageType", "key", "data", "path", "storeName" }, new[] { "domain" }, null),
233 [ApiEndpoint.SetUserAgent] = (new[] { "userAgent" }, new string[] { }, null),
234 [ApiEndpoint.SetValueFromElement] = (new[] { "destSelector" }, new string[] { "css", "xpath", "elementId", "gpalElements" }, null),
235 [ApiEndpoint.SetGoogleReferrer] = (new string[] { }, new string[] { }, null),
236 [ApiEndpoint.StopCasting] = (new string[] { }, new string[] { }, null),
237 [ApiEndpoint.SwitchToDefaultContent] = (new string[] { }, new string[] { }, null),
238 [ApiEndpoint.SwitchToElement] = (new string[] { }, new string[] { "css", "xpath", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
239 [ApiEndpoint.SwitchToFrame] = (new string[] { }, new string[] { "css", "xpath", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
240 [ApiEndpoint.SwitchToShadowRoot] = (new string[] { }, new string[] { "css", "xpath", "elementId" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId")),
241 [ApiEndpoint.TabCount] = (new string[] { }, new string[] { }, null),
242 [ApiEndpoint.TopBrowser] = (new string[] { }, new string[] { }, null),
243 [ApiEndpoint.MoveToPoint] = (new[] { "x", "y" }, new string[] { }, null),
244 // modifiers too: the point click always sends them, and without it here every puppeteer click at a point,
245 // an image match's, was refused before it was sent
246 [ApiEndpoint.ClickPoint] = (new[] { "x", "y" }, new string[] { "clickType", "modifiers" }, null),
247 [ApiEndpoint.TrustedLeftClick] = (new[] { "x", "y" }, new string[] { "holdMs" }, null), // the extension clicks a viewport point and holds for holdMs; it reads no element
248 [ApiEndpoint.TrustedMiddleClick] = (new[] { "x", "y" }, new string[] { "holdMs" }, null), // the same, with the middle button
249 [ApiEndpoint.Upload] = (new string[] { }, new string[] { "css", "xpath", "elementId", "gpalElements", "uploadPath", "uploadPaths" }, dict => true == dict.ContainsKey("css") || true == dict.ContainsKey("xpath") || true == dict.ContainsKey("elementId") || true == dict.ContainsKey("gpalElements")),
250 [ApiEndpoint.WindowInnerHeight] = (new string[] { }, new string[] { }, null),
251 [ApiEndpoint.WindowInnerWidth] = (new string[] { }, new string[] { }, null),
252 [ApiEndpoint.WindowOuterHeight] = (new string[] { }, new string[] { }, null),
253 [ApiEndpoint.WindowOuterWidth] = (new string[] { }, new string[] { }, null),
254 [ApiEndpoint.WindowPageOffsetX] = (new string[] { }, new string[] { }, null),
255 [ApiEndpoint.WindowPageOffsetY] = (new string[] { }, new string[] { }, null),
256 [ApiEndpoint.WindowScreenLeft] = (new string[] { }, new string[] { }, null),
257 [ApiEndpoint.WindowScreenTop] = (new string[] { }, new string[] { }, null),
258 };
259
260
261 internal RESTClient()
262 {
263 }
264
268 public string Name
269 {
270 get => _name;
271 internal set => _name = value;
272 }
273
280 {
281 _helper = new RESTHelper(url, this);
282 return this;
283 }
284
293 public IAllowRESTEndpointDetails WithAPIBase(string url, TimeSpan httpTimeout)
294 {
295 _helper = new RESTHelper(url, this, httpTimeout);
296 return this;
297 }
298
305 {
306 _endpoint = endpoint;
307 _customEndpoint = true;
308 return this;
309 }
310
317 {
318 if (null == _helper)
319 {
320 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
321 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}RESTHelper not initialized. Call WithAPIBase first.",
322 null, GPALObjectType.None);
323 _endpoint = "";
324 }
325 else if (false == RESTHelper.Endpoints.ContainsKey(endpoint))
326 {
327 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
328 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}API endpoint [{endpoint}] not found in Endpoints dictionary. Using empty string.",
329 null, GPALObjectType.None);
330 _endpoint = "";
331 }
332 else
333 {
334 _endpoint = RESTHelper.Endpoints[endpoint];
335 }
336 return this;
337 }
338
339 public IAllowRESTEndpointDetails LoadOpenAPIMap(string yamlOrUrl)
340 {
341 if (string.IsNullOrWhiteSpace(yamlOrUrl))
342 {
343 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "LoadOpenAPIMap: yamlOrUrl is empty", null, GPALObjectType.Other);
344 return this;
345 }
346
347 string yamlContent;
348 string chosenBaseUrl = null;
349
350 if (yamlOrUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
351 {
352 // It's already a direct URL to the YAML
353 try
354 {
355 using var http = new HttpClient();
356 yamlContent = http.GetStringAsync(yamlOrUrl).GetAwaiter().GetResult();
357 chosenBaseUrl = ExtractBestServerUrl(yamlContent);
358 }
359 catch (Exception ex)
360 {
361 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
362 $"Failed to download OpenAPI spec from [{yamlOrUrl}]", ex.Message, GPALObjectType.Other, ex);
363 return this;
364 }
365 }
366 else
367 {
368 // Assume it's raw YAML content
369 yamlContent = yamlOrUrl;
370 chosenBaseUrl = ExtractBestServerUrl(yamlContent);
371 }
372
373 // Set the RESTHelper with the best base URL (just like WithAPIBase does)
374 if (!string.IsNullOrWhiteSpace(chosenBaseUrl))
375 {
376 _helper = new RESTHelper(chosenBaseUrl, this);
377 GPAL.PublishSimpleEvent(GPALEventType.INFO,
378 $"LoadOpenAPIMap set API base to: [{chosenBaseUrl}]", null, GPALObjectType.Other);
379 }
380 else
381 {
382 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
383 "LoadOpenAPIMap could not determine API base from servers section", null, GPALObjectType.Other);
384 }
385
386 // Now load the rules
387 LoadOpenApiRulesInternal(yamlContent);
388
389 return this;
390 }
391
392 public IAllowRESTEndpointDetails LoadOpenAPIMap(GPALFile openApiMapFile)
393 {
394
395 try
396 {
397 string yamlContent = File.ReadAllText(openApiMapFile.Filename);
398 return LoadOpenAPIMap(yamlContent); // reuse the string overload
399 }
400 catch (Exception ex)
401 {
402 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
403 $"Failed to read OpenAPI file [{openApiMapFile.Filename}]", ex.Message, GPALObjectType.Other, ex);
404 return this;
405 }
406 }
407
408 public void LoadOpenApiRulesInternal(string yamlContent)
409 {
410 try
411 {
412 var reader = new Microsoft.OpenApi.Readers.OpenApiStreamReader();
413 using var stream = new System.IO.MemoryStream(System.Text.Encoding.UTF8.GetBytes(yamlContent));
414 var document = reader.Read(stream, out var diagnostic);
415
416 if (diagnostic.Errors.Any())
417 {
418 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
419 $"OpenAPI parse issues: [{diagnostic.Errors.Count}] [{string.Join(",",diagnostic.Errors)}]", diagnostic, GPALObjectType.Other);
420 }
421
422 openApiParamRules.Clear();
423
424 foreach (var pathItem in document.Paths)
425 {
426 foreach (var opKv in pathItem.Value.Operations)
427 {
428 var method = opKv.Key.ToString().ToUpperInvariant();
429 var path = pathItem.Key.TrimStart('/');
430 var ruleKey = $"{method} {path}"; // e.g. "GET /api/basicstore/products"
431
432 var mandatory = new List<string>();
433 var optional = new List<string>();
434
435 foreach (var param in opKv.Value.Parameters)
436 {
437 if (param.Required)
438 mandatory.Add(param.Name);
439 else
440 optional.Add(param.Name);
441 }
442
443 if (opKv.Value.RequestBody?.Required == true)
444 mandatory.Add("body"); // simple body marker
445
446 openApiParamRules[ruleKey] = (
447 Mandatory: mandatory.ToArray(),
448 Optional: optional.ToArray(),
449 CustomValidation: null
450 );
451 }
452 }
453
454 GPAL.PublishSimpleEvent(GPALEventType.INFO,
455 $"Loaded OpenAPI rules for [{openApiParamRules.Count}] operations from spec",
456 null, GPALObjectType.Other);
457 }
458 catch (Exception ex)
459 {
460 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
461 "Failed to load OpenAPI parameter rules", yamlContent?.Length ?? 0, GPALObjectType.Other, ex);
462 }
463 }
464
465 public IAllowRESTEndpointDetails ListOpenAPIEndPoints()
466 {
467 foreach (var key in openApiParamRules.Keys)
468 GPAL.PublishSimpleEvent(GPALEventType.INFO, key);
469
470 return this;
471 }
472
479 {
480 _nextResultName = name;
481 return this;
482 }
483
490 {
491 _parameters = new { url };
492 WithHttpMethod("POST");
493 return this;
494 }
495
502 {
503 if (0 > resultIndex || resultIndex >= _executionResults.Count)
504 {
505 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
506 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithUrlFromResult. Using empty string.",
507 null, GPALObjectType.None);
508 return WithUrl("");
509 }
510 var previousResult = _executionResults[resultIndex].Result;
511 var url = null == previousResult ? "" : previousResult.ToString();
512 return WithUrl(url);
513 }
514
521 {
522 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
523 if (null == resultEntry.Result)
524 {
525 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
526 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithUrlFromResult. Using empty string.",
527 null, GPALObjectType.None);
528 return WithUrl("");
529 }
530 var previousResult = resultEntry.Result;
531 var url = null == previousResult ? "" : previousResult.ToString();
532 return WithUrl(url);
533 }
534
541 {
542 _parameters = new { tabId };
543 WithHttpMethod("POST");
544 return this;
545 }
546
553 {
554 if (0 > resultIndex || resultIndex >= _executionResults.Count)
555 {
556 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
557 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithTabIdFromResult. Using 0.",
558 null, GPALObjectType.None);
559 return WithTabId(0);
560 }
561 var previousResult = _executionResults[resultIndex].Result;
562 var tabId = previousResult is int i ? i : 0;
563 return WithTabId(tabId);
564 }
565
572 {
573 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
574 if (null == resultEntry.Result)
575 {
576 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
577 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithTabIdFromResult. Using 0.",
578 null, GPALObjectType.None);
579 return WithTabId(0);
580 }
581 var previousResult = resultEntry.Result;
582 var tabId = previousResult is int i ? i : 0;
583 return WithTabId(tabId);
584 }
585
592 {
593 _parameters = new { script, parameters = _parameters };
594 WithHttpMethod("POST");
595 return this;
596 }
597
604 {
605 _parameters = null == _parameters
606 ? new { key }
607 : MergeParameters(_parameters, new { key });
608
609 WithHttpMethod("POST");
610 return this;
611 }
612
619 {
620 if (0 > resultIndex || resultIndex >= _executionResults.Count)
621 {
622 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
623 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithKeyFromResult. Using empty string.",
624 null, GPALObjectType.None);
625 return WithKey("");
626 }
627 var previousResult = _executionResults[resultIndex].Result;
628 var key = null == previousResult ? "" : previousResult.ToString();
629 return WithKey(key);
630 }
631
638 {
639 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
640 if (null == resultEntry.Result)
641 {
642 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
643 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithKeyFromResult. Using empty string.",
644 null, GPALObjectType.None);
645 return WithKey("");
646 }
647 var previousResult = resultEntry.Result;
648 var key = null == previousResult ? "" : previousResult.ToString();
649 return WithKey(key);
650 }
651
658 {
659 _name = name;
660 return this;
661 }
662
669 {
670 _parameters = null == _parameters
671 ? new { css }
672 : MergeParameters(_parameters, new { css });
673 WithHttpMethod("POST");
674 return this;
675 }
676
683 {
684 if (0 > resultIndex || resultIndex >= _executionResults.Count)
685 {
686 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
687 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithCssFromResult. Using empty string.",
688 null, GPALObjectType.None);
689 return WithCss("");
690 }
691 var previousResult = _executionResults[resultIndex].Result;
692 var css = null == previousResult ? "" : previousResult.ToString();
693 return WithCss(css);
694 }
695
702 {
703 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
704 if (null == resultEntry.Result)
705 {
706 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
707 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithCssFromResult. Using empty string.",
708 null, GPALObjectType.None);
709 return WithCss("");
710 }
711 var previousResult = resultEntry.Result;
712 var css = null == previousResult ? "" : previousResult.ToString();
713 return WithCss(css);
714 }
715
722 {
723 List<GPALElement> gpalElements = new List<GPALElement>() { (GPALElement)gPALElement };
724
725 _parameters = null == _parameters
726 ? new { gpalElements }
727 : MergeParameters(_parameters, new { gpalElements });
728 WithHttpMethod("POST");
729 return this;
730 }
731
738 {
739 if (0 > resultIndex || resultIndex >= _executionResults.Count)
740 {
741 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
742 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithElementFromResult. Using empty string.",
743 null, GPALObjectType.None);
744 return WithElementId("");
745 }
746 var previousResult = _executionResults[resultIndex].Result;
747 string elementId;
748
749 // NOTE: what kind of enumerable, what is envisioned? - grok generated...
750 if (previousResult is IEnumerable<object> enumerable && false == (previousResult is string))
751 {
752 elementId = null == enumerable.FirstOrDefault() ? "" : enumerable.FirstOrDefault().ToString();
753 }
754 else
755 {
756 elementId = null == previousResult ? "" : previousResult.ToString();
757 }
758 return WithElementId(elementId);
759 }
760
767 {
768 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
769 if (null == resultEntry.Result)
770 {
771 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
772 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithElementFromResult. Using empty string.",
773 null, GPALObjectType.None);
774 return WithElementId("");
775 }
776 var previousResult = resultEntry.Result;
777 string elementId;
778
779 // NOTE: what kind of enumerable, what is envisioned? - grok generated...
780 if (previousResult is IEnumerable<object> enumerable && false == (previousResult is string))
781 {
782 elementId = null == enumerable.FirstOrDefault() ? "" : enumerable.FirstOrDefault().ToString();
783 }
784 else
785 {
786 elementId = null == previousResult ? "" : previousResult.ToString();
787 }
788 return WithElementId(elementId);
789 }
790
797 {
798 _parameters = null == _parameters
799 ? new { timeoutMs }
800 : MergeParameters(_parameters, new { timeoutMs });
801 WithHttpMethod("POST");
802 return this;
803 }
804
811 {
812 _parameters = null == _parameters
813 ? new { pruneMs }
814 : MergeParameters(_parameters, new { pruneMs });
815 WithHttpMethod("POST");
816 return this;
817 }
818
825 {
826 _parameters = null == _parameters
827 ? new { maxConnections }
828 : MergeParameters(_parameters, new { maxConnections });
829 WithHttpMethod("POST");
830 return this;
831 }
832
838 public IAllowRESTExecution WithUploadFile(string uploadPath)
839 {
840 _parameters = null == _parameters
841 ? new { uploadPath }
842 : MergeParameters(_parameters, new { uploadPath });
843 WithHttpMethod("POST");
844 return this;
845 }
846
853 {
854 _parameters = null == _parameters
855 ? new { uploadPaths }
856 : MergeParameters(_parameters, new { uploadPaths });
857 WithHttpMethod("POST");
858 return this;
859 }
860
867 {
868 _parameters = null == _parameters
869 ? new { xpath }
870 : MergeParameters(_parameters, new { xpath });
871 WithHttpMethod("POST");
872 return this;
873 }
874
881 {
882 if (0 > resultIndex || resultIndex >= _executionResults.Count)
883 {
884 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
885 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithXPathFromResult. Using empty string.",
886 null, GPALObjectType.None);
887 return WithXPath("");
888 }
889 var previousResult = _executionResults[resultIndex].Result;
890 var xpath = null == previousResult ? "" : previousResult.ToString();
891 return WithXPath(xpath);
892 }
893
900 {
901 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
902 if (null == resultEntry.Result)
903 {
904 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
905 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithXPathFromResult. Using empty string.",
906 null, GPALObjectType.None);
907 return WithXPath("");
908 }
909 var previousResult = resultEntry.Result;
910 var xpath = null == previousResult ? "" : previousResult.ToString();
911 return WithXPath(xpath);
912 }
913
919 public IAllowRESTParametersOrExecution WithParameters(params object[] parameters)
920 {
921 _parameters = null == _parameters
922 ? parameters
923 : MergeParameters(_parameters, parameters);
924 WithHttpMethod("POST");
925 return this;
926 }
927
934 {
935 _parameters = null == _parameters
936 ? parameters
937 : MergeParameters(_parameters, parameters);
938 WithHttpMethod("POST");
939 return this;
940 }
941
948 {
949 if (0 > resultIndex || resultIndex >= _executionResults.Count)
950 {
951 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
952 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithParametersFromResult. Using null.",
953 null, GPALObjectType.None);
954 _parameters = null;
955 }
956 else
957 {
958 _parameters = _executionResults[resultIndex].Result;
959 }
960 WithHttpMethod("POST");
961 return this;
962 }
963
970 {
971 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
972 if (null == resultEntry.Result)
973 {
974 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
975 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithParametersFromResult. Using null.",
976 null, GPALObjectType.None);
977 _parameters = null;
978 }
979 else
980 {
981 _parameters = resultEntry.Result;
982 }
983 WithHttpMethod("POST");
984 return this;
985 }
986
993 {
994 _parameters = null == _parameters
995 ? new { data }
996 : MergeParameters(_parameters, new { data });
997
998 WithHttpMethod("POST");
999 return this;
1000 }
1001
1008 {
1009 _parameters = null == _parameters
1010 ? new { storeName }
1011 : MergeParameters(_parameters, new { storeName });
1012
1013 WithHttpMethod("POST");
1014 return this;
1015 }
1016
1023 {
1024 _parameters = null == _parameters
1025 ? new { domain }
1026 : MergeParameters(_parameters, new { domain });
1027
1028 WithHttpMethod("POST");
1029 return this;
1030 }
1031
1038 {
1039 _parameters = null == _parameters
1040 ? new { path }
1041 : MergeParameters(_parameters, new { path });
1042
1043 WithHttpMethod("POST");
1044 return this;
1045 }
1046
1052 public IAllowRESTStorageOptions WithStorageType(WebsiteStorageType storageType)
1053 {
1054 _parameters = null == _parameters
1055 ? new { storageType = storageType.ToString() }
1056 : MergeParameters(_parameters, new { storageType = storageType.ToString() });
1057
1058 WithHttpMethod("POST");
1059 return this;
1060 }
1061
1068 {
1069 _parameters = null == _parameters
1070 ? new { key }
1071 : MergeParameters(_parameters, new { key });
1072
1073 WithHttpMethod("POST");
1074 return this;
1075 }
1076
1077 public IAllowRESTStorageOptions WithDeleteAcrossOrigins(bool crossOrigin)
1078 {
1079 _parameters = null == _parameters
1080 ? new { crossOrigin }
1081 : MergeParameters(_parameters, new { crossOrigin });
1082
1083 WithHttpMethod("POST");
1084 return this;
1085 }
1086
1093 {
1094 _parameters = null == _parameters
1095 ? new { pixels }
1096 : MergeParameters(_parameters, new { pixels });
1097 WithHttpMethod("POST");
1098 return this;
1099 }
1100
1107 {
1108 if (0 > resultIndex || resultIndex >= _executionResults.Count)
1109 {
1110 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1111 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithPixelsFromResult. Using 0.",
1112 null, GPALObjectType.None);
1113 return WithPixels(0);
1114 }
1115 var previousResult = _executionResults[resultIndex].Result;
1116 var pixels = previousResult is int i ? i : 0;
1117 return WithPixels(pixels);
1118 }
1119
1126 {
1127 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
1128 if (null == resultEntry.Result)
1129 {
1130 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1131 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithPixelsFromResult. Using 0.",
1132 null, GPALObjectType.None);
1133 return WithPixels(0);
1134 }
1135 var previousResult = resultEntry.Result;
1136 var pixels = previousResult is int i ? i : 0;
1137 return WithPixels(pixels);
1138 }
1139
1146 {
1147 _parameters = null == _parameters
1148 ? new { url }
1149 : MergeParameters(_parameters, new { url });
1150 WithHttpMethod("POST");
1151 return this;
1152 }
1153
1160 {
1161 if (0 > resultIndex || resultIndex >= _executionResults.Count)
1162 {
1163 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1164 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range. Using empty string.",
1165 null, GPALObjectType.None);
1166 return WithReferrer("");
1167 }
1168 var previousResult = _executionResults[resultIndex].Result;
1169 var referrer = null == previousResult ? "" : previousResult.ToString();
1170 return WithReferrer(referrer);
1171 }
1172
1179 {
1180 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
1181 if (null == resultEntry.Result)
1182 {
1183 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1184 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found. Using empty string.",
1185 null, GPALObjectType.None);
1186 return WithReferrer("");
1187 }
1188 var previousResult = resultEntry.Result;
1189 var referrer = null == previousResult ? "" : previousResult.ToString();
1190 return WithReferrer(referrer);
1191 }
1192
1198 public IAllowRESTExecution WithText(string text)
1199 {
1200 _parameters = null == _parameters
1201 ? new { text }
1202 : MergeParameters(_parameters, new { text });
1203 WithHttpMethod("POST");
1204 return this;
1205 }
1206
1213 {
1214 if (0 > resultIndex || resultIndex >= _executionResults.Count)
1215 {
1216 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1217 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithTextFromResult. Using empty string.",
1218 null, GPALObjectType.None);
1219 return WithText("");
1220 }
1221 var previousResult = _executionResults[resultIndex].Result;
1222 var text = null == previousResult ? "" : previousResult.ToString();
1223 return WithText(text);
1224 }
1225
1232 {
1233 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
1234 if (null == resultEntry.Result)
1235 {
1236 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1237 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithTextFromResult. Using empty string.",
1238 null, GPALObjectType.None);
1239 return WithText("");
1240 }
1241 var previousResult = resultEntry.Result;
1242 var text = null == previousResult ? "" : previousResult.ToString();
1243 return WithText(text);
1244 }
1245
1256 {
1257 if (null == downloadPath || null == downloadPath.Filenames || 0 == downloadPath.Filenames.Count)
1258 {
1259 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "DownloadTo requires a valid GPALFile with at least one filename.", null, GPALObjectType.None);
1260 return this;
1261 }
1262
1263 _parameters = null == _parameters
1264 ? new { downloadPath }
1265 : MergeParameters(_parameters, new { downloadPath });
1266 WithHttpMethod("POST");
1267
1268 return this;
1269 }
1270
1277 {
1278 if (null == file || null == file.Filenames || 0 == file.Filenames.Count)
1279 {
1280 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "SaveResultsTo requires a valid GPALFile with at least one filename.", null, GPALObjectType.None);
1281 return this;
1282 }
1283 _saveFile = file.Filename; // Store filename for Execute
1284
1285 return this;
1286 }
1287
1293 {
1294 _httpMethod = null == method ? "GET" : method;
1295 return this;
1296 }
1297
1303 internal IAllowRESTParametersOrExecution WithImageFormat(ImageFormat format)
1304 {
1305 string imageFormat = format.ToString();
1306 _parameters = null == _parameters
1307 ? new { imageFormat }
1308 : MergeParameters(_parameters, new { imageFormat });
1309 WithHttpMethod("POST");
1310 return this;
1311 }
1319 {
1320 _parameters = null == _parameters
1321 ? new { index }
1322 : MergeParameters(_parameters, new { index });
1323 WithHttpMethod("POST");
1324 return this;
1325 }
1326
1332 public IAllowRESTExecution WithDeviceName(string sinkName)
1333 {
1334 _parameters = null == _parameters
1335 ? new { sinkName }
1336 : MergeParameters(_parameters, new { sinkName });
1337 WithHttpMethod("POST");
1338 return this;
1339 }
1340
1345 public string Execute(bool publishEvent = true)
1346 {
1347 // no api base means nothing to call. for a browser's own client that is a call made before the
1348 // browser was launched, and saying so beats a null reference from three frames down
1349 if (null == _helper)
1350 {
1351 string noBase = $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No API base is set for endpoint [{_endpoint}]. A browser's own client has one once the browser is launched on an engine that runs one, which OttoMagicClient only is on the OttoMagic engines.";
1352
1353 GPAL.PublishSimpleEvent(GPALEventType.ERROR, noBase, this, GPALObjectType.RestClient);
1354
1355 throw new GPALException($"{GPAL.MyMethodName()}: {noBase}");
1356 }
1357
1358 GPALEventType publishToConsole = GPAL.GPALSettings.ConsoleEvents;
1359 GPALEventType publishToDebug = GPAL.GPALSettings.DebugEvents;
1360
1361 try
1362 {
1363 bool customApiPassedValidation = _customEndpoint;
1364
1365 if (true == _customEndpoint && 0 < openApiParamRules.Count())
1366 {
1367 string ruleKey = GetOpenApiRuleKey(_httpMethod, _endpoint); // e.g. "GET /api/basicstore/products"
1368 customApiPassedValidation = (false == string.IsNullOrEmpty(ruleKey) && true == openApiParamRules.ContainsKey(ruleKey) && true == ValidateOpenApiParameters(ruleKey));
1369 }
1370
1371 if (true == customApiPassedValidation || true == ValidateParameters(RESTHelper.Endpoints.FirstOrDefault(kvp => kvp.Value == _endpoint).Key))
1372 {
1373 if (false == publishEvent)
1374 {
1375 GPAL.GPALSettings.ConsoleEvents = GPALEventType.NONE;
1376 GPAL.GPALSettings.DebugEvents = GPALEventType.NONE;
1377 }
1378
1379 var result = _helper.Call(_endpoint, _parameters, _httpMethod, _contentEncoding).Result;
1380
1381 _executionResults.Add((_nextResultName, result, _currentIteration));
1382 _nextResultName = null;
1383
1384 // the chain is cleared once it is spent rather than when the endpoint is named, so a parameter
1385 // set before the endpoint still reaches the call
1386 Reset();
1387 if (null != _saveFile)
1388 // Assume the response is a stream URL or base64 content for now
1389 SaveResponseToFile(result, _saveFile);
1390
1391 return result;
1392 }
1393 else
1394 {
1395 // a call whose parameters were refused never went out, and leaving them behind sent them on
1396 // with the next call, which was then refused for parameters it never set. the chain is spent
1397 // either way
1398 Reset();
1399
1400 return null;
1401 }
1402 }
1403 catch (AggregateException ex)
1404 {
1405 if (true == publishEvent)
1406 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
1407 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Execution failed synchronously for endpoint [{_endpoint}].",
1408 null, GPALObjectType.None, ex.InnerException);
1409 // a browser that cannot be reached is not a call that came back empty. the reason is
1410 // wrapped by the Result that surfaced it, and it leaves here as itself
1411 if (ex.InnerException is GPALException)
1412 throw ex.InnerException;
1413
1414 return "";
1415 }
1416 finally
1417 {
1418 GPAL.GPALSettings.ConsoleEvents = publishToConsole;
1419 GPAL.GPALSettings.DebugEvents = publishToDebug;
1420 }
1421 }
1422
1428 public T Execute<T>(bool publishEvent = true)
1429 {
1430 // no api base means nothing to call. for a browser's own client that is a call made before the
1431 // browser was launched, and saying so beats a null reference from three frames down
1432 if (null == _helper)
1433 {
1434 string noBase = $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No API base is set for endpoint [{_endpoint}]. A browser's own client has one once the browser is launched on an engine that runs one, which OttoMagicClient only is on the OttoMagic engines.";
1435
1436 GPAL.PublishSimpleEvent(GPALEventType.ERROR, noBase, this, GPALObjectType.RestClient);
1437
1438 throw new GPALException($"{GPAL.MyMethodName()}: {noBase}");
1439 }
1440
1441 GPALEventType publishToConsole = GPAL.GPALSettings.ConsoleEvents;
1442 GPALEventType publishToDebug = GPAL.GPALSettings.DebugEvents;
1443 T result = default;
1444
1445 try
1446 {
1447 bool customApiPassedValidation = _customEndpoint;
1448
1449 if (true == _customEndpoint && 0 < openApiParamRules.Count())
1450 {
1451 string ruleKey = GetOpenApiRuleKey(_httpMethod, _endpoint); // e.g. "GET /api/basicstore/products"
1452 customApiPassedValidation = (false == string.IsNullOrEmpty(ruleKey) && true == openApiParamRules.ContainsKey(ruleKey) && true == ValidateOpenApiParameters(ruleKey));
1453 }
1454
1455 if (true == customApiPassedValidation || true == ValidateParameters(RESTHelper.Endpoints.FirstOrDefault(kvp => kvp.Value == _endpoint).Key))
1456 {
1457 if (false == publishEvent)
1458 {
1459 GPAL.GPALSettings.ConsoleEvents = GPALEventType.NONE;
1460 GPAL.GPALSettings.DebugEvents = GPALEventType.NONE;
1461 }
1462
1463 int retry = 1;
1464
1465 do
1466 {
1467 try
1468 {
1469 result = _helper.CallAndDeserialize<T>(_endpoint, _parameters, _httpMethod, _contentEncoding).Result;
1470 }
1471 catch (AggregateException ex)
1472 {
1473 if (true == publishEvent)
1474 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
1475 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Execution failed synchronously for endpoint [{_endpoint}].",
1476 null, GPALObjectType.None, ex.InnerException);
1477 // a browser that cannot be reached is not a call that came back empty. the reason is
1478 // wrapped by the Result that surfaced it, and it leaves here as itself
1479 if (ex.InnerException is GPALException)
1480 throw ex.InnerException;
1481
1482 return default;
1483 }
1484 }
1485 while (null == result && 0 < --retry);
1486
1487 _executionResults.Add((_nextResultName, result, _currentIteration));
1488 _nextResultName = null;
1489
1490 // the chain is cleared once it is spent rather than when the endpoint is named, so a parameter
1491 // set before the endpoint still reaches the call
1492 Reset();
1493
1494 if (_saveFile != null)
1495 GPAL.Converter.WithInput(result).SaveTo(_saveFile);
1496
1497 return result;
1498 }
1499
1500 // a call whose parameters were refused never went out, and leaving them behind sent them on
1501 // with the next call, which was then refused for parameters it never set. the chain is spent
1502 // either way
1503 Reset();
1504
1505 return default;
1506 }
1507 finally
1508 {
1509 GPAL.GPALSettings.ConsoleEvents = publishToConsole;
1510 GPAL.GPALSettings.DebugEvents = publishToDebug;
1511 }
1512 }
1513
1518 public IRESTClient AndThen(bool publishEvent = true)
1519 {
1520 var result = Execute(publishEvent);
1521 _executionResults.Add((_nextResultName, result, _currentIteration));
1522 Reset();
1523 return this;
1524 }
1525
1531 public IRESTClient AndThen<T>(bool publishEvent = true)
1532 {
1533 var result = Execute<T>(publishEvent);
1534 _executionResults.Add((_nextResultName, result, _currentIteration));
1535 Reset();
1536 return this;
1537 }
1538
1544 public async Task<string> ExecuteAsync(bool publishEvent = true)
1545 {
1546 // no api base means nothing to call. for a browser's own client that is a call made before the
1547 // browser was launched, and saying so beats a null reference from three frames down
1548 if (null == _helper)
1549 {
1550 string noBase = $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No API base is set for endpoint [{_endpoint}]. A browser's own client has one once the browser is launched on an engine that runs one, which OttoMagicClient only is on the OttoMagic engines.";
1551
1552 GPAL.PublishSimpleEvent(GPALEventType.ERROR, noBase, this, GPALObjectType.RestClient);
1553
1554 throw new GPALException($"{GPAL.MyMethodName()}: {noBase}");
1555 }
1556
1557 GPALEventType publishToConsole = GPAL.GPALSettings.ConsoleEvents;
1558 GPALEventType publishToDebug = GPAL.GPALSettings.DebugEvents;
1559
1560 try
1561 {
1562 bool customApiPassedValidation = _customEndpoint;
1563
1564 if (true == _customEndpoint && 0 < openApiParamRules.Count())
1565 {
1566 string ruleKey = GetOpenApiRuleKey(_httpMethod, _endpoint); // e.g. "GET /api/basicstore/products"
1567 customApiPassedValidation = (false == string.IsNullOrEmpty(ruleKey) && true == openApiParamRules.ContainsKey(ruleKey) && true == ValidateOpenApiParameters(ruleKey));
1568 }
1569
1570 if (true == customApiPassedValidation || true == ValidateParameters(RESTHelper.Endpoints.FirstOrDefault(kvp => kvp.Value == _endpoint).Key))
1571 {
1572 if (false == publishEvent)
1573 {
1574 GPAL.GPALSettings.ConsoleEvents = GPALEventType.NONE;
1575 GPAL.GPALSettings.DebugEvents = GPALEventType.NONE;
1576 }
1577
1578 var result = await _helper.Call(_endpoint, _parameters, _httpMethod, _contentEncoding);
1579 _executionResults.Add((_nextResultName, result, _currentIteration));
1580 _nextResultName = null;
1581
1582 // the chain is cleared once it is spent rather than when the endpoint is named, so a parameter
1583 // set before the endpoint still reaches the call
1584 Reset();
1585 return result;
1586 }
1587 else
1588 {
1589 // a call whose parameters were refused never went out, and leaving them behind sent them on
1590 // with the next call, which was then refused for parameters it never set. the chain is spent
1591 // either way
1592 Reset();
1593
1594 return null;
1595 }
1596 }
1597 catch (AggregateException ex)
1598 {
1599 if (true == publishEvent)
1600 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
1601 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Execution failed asynchronously for endpoint [{_endpoint}].",
1602 null, GPALObjectType.None, ex.InnerException);
1603 // a browser that cannot be reached is not a call that came back empty. the reason is
1604 // wrapped by the Result that surfaced it, and it leaves here as itself
1605 if (ex.InnerException is GPALException)
1606 throw ex.InnerException;
1607
1608 return null;
1609 }
1610 finally
1611 {
1612 GPAL.GPALSettings.ConsoleEvents = publishToConsole;
1613 GPAL.GPALSettings.DebugEvents = publishToDebug;
1614 }
1615 }
1616
1622 public async Task<T> ExecuteAsync<T>(bool publishEvent = true)
1623 {
1624 // no api base means nothing to call. for a browser's own client that is a call made before the
1625 // browser was launched, and saying so beats a null reference from three frames down
1626 if (null == _helper)
1627 {
1628 string noBase = $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No API base is set for endpoint [{_endpoint}]. A browser's own client has one once the browser is launched on an engine that runs one, which OttoMagicClient only is on the OttoMagic engines.";
1629
1630 GPAL.PublishSimpleEvent(GPALEventType.ERROR, noBase, this, GPALObjectType.RestClient);
1631
1632 throw new GPALException($"{GPAL.MyMethodName()}: {noBase}");
1633 }
1634
1635 GPALEventType publishToConsole = GPAL.GPALSettings.ConsoleEvents;
1636 GPALEventType publishToDebug = GPAL.GPALSettings.DebugEvents;
1637
1638 try
1639 {
1640 bool customApiPassedValidation = _customEndpoint;
1641
1642 if (true == _customEndpoint && 0 < openApiParamRules.Count())
1643 {
1644 string ruleKey = GetOpenApiRuleKey(_httpMethod, _endpoint); // e.g. "GET /api/basicstore/products"
1645 customApiPassedValidation = (false == string.IsNullOrEmpty(ruleKey) && true == openApiParamRules.ContainsKey(ruleKey) && true == ValidateOpenApiParameters(ruleKey));
1646 }
1647
1648 if (true == customApiPassedValidation || true == ValidateParameters(RESTHelper.Endpoints.FirstOrDefault(kvp => kvp.Value == _endpoint).Key))
1649 {
1650 if (false == publishEvent)
1651 {
1652 GPAL.GPALSettings.ConsoleEvents = GPALEventType.NONE;
1653 GPAL.GPALSettings.DebugEvents = GPALEventType.NONE;
1654 }
1655
1656 T result = await _helper.CallAndDeserialize<T>(_endpoint, _parameters, _httpMethod, _contentEncoding);
1657 _executionResults.Add((_nextResultName, result, _currentIteration));
1658 _nextResultName = null;
1659
1660 // the chain is cleared once it is spent rather than when the endpoint is named, so a parameter
1661 // set before the endpoint still reaches the call
1662 Reset();
1663
1664 if (_saveFile != null)
1665 GPAL.Converter.WithInput(result).SaveTo(_saveFile);
1666
1667 return result;
1668 }
1669
1670 // a call whose parameters were refused never went out, and leaving them behind sent them on
1671 // with the next call, which was then refused for parameters it never set. the chain is spent
1672 // either way
1673 Reset();
1674
1675 return default;
1676 }
1677 catch (Exception ex) // Changed to catch base Exception instead of AggregateException for async
1678 {
1679 if (true == publishEvent)
1680 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
1681 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Execution failed asynchronously for endpoint [{_endpoint}].",
1682 null, GPALObjectType.None, ex);
1683 return default;
1684 }
1685 finally
1686 {
1687 GPAL.GPALSettings.ConsoleEvents = publishToConsole;
1688 GPAL.GPALSettings.DebugEvents = publishToDebug;
1689 }
1690 }
1691
1697 {
1698 return this;
1699 }
1700
1713 {
1714 // the two lists are index aligned, so one is never emptied without the other
1715 _workflows.Clear();
1716 _workflowStates.Clear();
1717
1718 return this;
1719 }
1720
1728 {
1729 _executionResults.Clear();
1730
1731 return this;
1732 }
1733 public ResultCollection GetExecutionResults()
1734 {
1735 return new ResultCollection(_executionResults, _name);
1736 }
1737
1746 public IReadOnlyList<object> GetExecutionResults(string name)
1747 {
1748 return GetExecutionResults().GetAllResults(name);
1749 }
1750
1757 public IAllowRESTWorkflow WithWorkflow(Action<RESTClient> workflow)
1758 {
1759 if (null == workflow)
1760 {
1761 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1762 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Workflow cannot be null.",
1763 null, GPALObjectType.None);
1764 return this;
1765 }
1766
1767 _workflowStates.Add((_endpoint, _parameters, _httpMethod, _nextResultName));
1768 _workflows.Add(workflow);
1769 return this;
1770 }
1771
1778 public IAllowRESTParametersOrExecution While(Func<bool> condition)
1779 {
1780 if (0 == _workflows.Count)
1781 {
1782 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1783 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No workflows defined for While loop.",
1784 null, GPALObjectType.None);
1785 Reset();
1786 return this;
1787 }
1788
1789 int iteration = 0;
1790 const int maxIterations = 1000;
1791
1792 while (condition() && iteration < maxIterations)
1793 {
1794 _currentIteration = iteration;
1795 for (int i = 0; i < _workflows.Count; i++)
1796 {
1797 var initialState = _workflowStates[i];
1798 if (initialState.HasValue)
1799 {
1800 (_endpoint, _parameters, _httpMethod, _nextResultName) = initialState.Value;
1801 }
1802 else
1803 {
1804 Reset();
1805 }
1806 _workflows[i](this);
1807 }
1808 iteration++;
1809 }
1810
1811 if (maxIterations <= iteration)
1812 {
1813 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1814 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}While loop reached maximum iterations [{maxIterations}].",
1815 null, GPALObjectType.None);
1816 }
1817
1818 Reset();
1819 _currentIteration = 0;
1820 return this;
1821 }
1822
1828 public IAllowRESTParametersOrExecution Until(Func<bool> condition)
1829 {
1830 if (0 == _workflows.Count)
1831 {
1832 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1833 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No workflows defined for Until loop.",
1834 null, GPALObjectType.None);
1835 Reset();
1836 return this;
1837 }
1838
1839 int iteration = 0;
1840 const int maxIterations = 1000;
1841
1842 while (false == condition() && iteration < maxIterations)
1843 {
1844 _currentIteration = iteration;
1845 for (int i = 0; i < _workflows.Count; i++)
1846 {
1847 var initialState = _workflowStates[i];
1848 if (initialState.HasValue)
1849 {
1850 (_endpoint, _parameters, _httpMethod, _nextResultName) = initialState.Value;
1851 }
1852 else
1853 {
1854 Reset();
1855 }
1856 _workflows[i](this);
1857 }
1858 iteration++;
1859 }
1860
1861 if (maxIterations <= iteration)
1862 {
1863 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1864 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Until loop reached maximum iterations [{maxIterations}].",
1865 null, GPALObjectType.None);
1866 }
1867
1868 Reset();
1869 _currentIteration = 0;
1870 return this;
1871 }
1872
1879 {
1880 _parameters = null == _parameters
1881 ? new { elementId }
1882 : MergeParameters(_parameters, new { elementId });
1883 WithHttpMethod("POST");
1884 return this;
1885 }
1886
1892 public IAllowRESTParametersOrExecution WithEncoding(ContentEncoding encoding)
1893 {
1894 _contentEncoding = encoding;
1895 return this;
1896 }
1897 internal static string GetContentType(ContentEncoding encoding)
1898 {
1899 return encoding switch
1900 {
1901 ContentEncoding.UrlEncoded => "application/x-www-form-urlencoded",
1902 ContentEncoding.Json => "application/json",
1903 _ => "application/json" // Default for unknown encodings
1904 };
1905 }
1913 public IAllowRESTExecution WithHeader(string name, string value)
1914 {
1915 if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value))
1916 {
1917 _headers[name] = value;
1918 }
1919 else
1920 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid name [{name}] or value [{value}], nothing added to the header.",null, GPALObjectType.None);
1921
1922 return this;
1923 }
1924
1947 public IAllowRESTExecution WithTimeout(TimeSpan httpTimeout)
1948 {
1949 if (null == _helper)
1950 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1951 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No API base to set a timeout on. Call WithAPIBase first, or take a client from ContinueAsRESTClient",
1952 null, GPALObjectType.None);
1953 else
1954 _helper.HttpTimeout = httpTimeout;
1955
1956 return this;
1957 }
1958
1965 {
1966 if (0 > resultIndex || resultIndex >= _executionResults.Count)
1967 {
1968 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1969 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] out of range. Using empty values.",
1970 null, GPALObjectType.None);
1971 return WithHeader("", "");
1972 }
1973 var result = _executionResults[resultIndex].Result?.ToString().Split(new[] { ':' }, 2);
1974 var name = result?.Length > 0 ? result[0] : "";
1975 var value = result?.Length > 1 ? result[1] : "";
1976 return WithHeader(name, value);
1977 }
1978
1985 {
1986 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
1987 if (null == resultEntry.Result)
1988 {
1989 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
1990 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found. Using empty values.",
1991 null, GPALObjectType.None);
1992 return WithHeader("", "");
1993 }
1994 var result = resultEntry.Result?.ToString().Split(new[] { ':' }, 2);
1995 var headerName = result?.Length > 0 ? result[0] : "";
1996 var headerValue = result?.Length > 1 ? result[1] : "";
1997 return WithHeader(headerName, headerValue);
1998 }
1999
2006 {
2007 _parameters = null == _parameters
2008 ? new { deltaX }
2009 : MergeParameters(_parameters, new { deltaX });
2010 WithHttpMethod("POST");
2011 return this;
2012 }
2013
2020 {
2021 _parameters = null == _parameters
2022 ? new { deltaY }
2023 : MergeParameters(_parameters, new { deltaY });
2024 WithHttpMethod("POST");
2025 return this;
2026 }
2027
2034 {
2035 _parameters = null == _parameters
2036 ? new { offsetX }
2037 : MergeParameters(_parameters, new { offsetX });
2038 WithHttpMethod("POST");
2039 return this;
2040 }
2041
2048 {
2049 _parameters = null == _parameters
2050 ? new { offsetY }
2051 : MergeParameters(_parameters, new { offsetY });
2052 WithHttpMethod("POST");
2053 return this;
2054 }
2055
2062 {
2063 _parameters = null == _parameters
2064 ? new { hPixels }
2065 : MergeParameters(_parameters, new { hPixels });
2066 WithHttpMethod("POST");
2067 return this;
2068 }
2069
2076 {
2077 if (0 > resultIndex || resultIndex >= _executionResults.Count)
2078 {
2079 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2080 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithHPixelsFromResult. Using 0.",
2081 null, GPALObjectType.None);
2082 return WithHPixels(0);
2083 }
2084 var previousResult = _executionResults[resultIndex].Result;
2085 var hPixels = previousResult is int i ? i : 0;
2086 return WithHPixels(hPixels);
2087 }
2088
2095 {
2096 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
2097 if (null == resultEntry.Result)
2098 {
2099 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2100 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithHPixelsFromResult. Using 0.",
2101 null, GPALObjectType.None);
2102 return WithHPixels(0);
2103 }
2104 var previousResult = resultEntry.Result;
2105 var hPixels = previousResult is int i ? i : 0;
2106 return WithHPixels(hPixels);
2107 }
2108
2115 {
2116 _parameters = null == _parameters
2117 ? new { vPixels }
2118 : MergeParameters(_parameters, new { vPixels });
2119 WithHttpMethod("POST");
2120 return this;
2121 }
2122
2129 {
2130 if (0 > resultIndex || resultIndex >= _executionResults.Count)
2131 {
2132 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2133 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithVPixelsFromResult. Using 0.",
2134 null, GPALObjectType.None);
2135 return WithVPixels(0);
2136 }
2137 var previousResult = _executionResults[resultIndex].Result;
2138 var vPixels = previousResult is int i ? i : 0;
2139 return WithVPixels(vPixels);
2140 }
2141
2148 {
2149 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
2150 if (null == resultEntry.Result)
2151 {
2152 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2153 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithVPixelsFromResult. Using 0.",
2154 null, GPALObjectType.None);
2155 return WithVPixels(0);
2156 }
2157 var previousResult = resultEntry.Result;
2158 var vPixels = previousResult is int i ? i : 0;
2159 return WithVPixels(vPixels);
2160 }
2161
2165 public IAllowRESTExecution CaptureVisibleTab(ImageFormat imageFormat = ImageFormat.JPEG)
2166 {
2167 WithImageFormat(imageFormat);
2168 WithEndpoint(ApiEndpoint.CaptureVisibleTab);
2169 return this;
2170 }
2171
2183 public IAllowRESTExecution SetDownloadFilename(string downloadPath)
2184 {
2185 WithEndpoint(ApiEndpoint.SetDownloadFilename);
2186 return WithDownloadFile(downloadPath);
2187 }
2188 public IAllowRESTExecution WithDownloadFile(string downloadPath)
2189 {
2190 // the parameter only. setting the endpoint here overwrote whatever it was chained onto, so
2191 // LeftClickAndDownload(id).WithDownloadFile(path) called set-download-filename and never clicked
2192 _parameters = null == _parameters
2193 ? new { downloadPath }
2194 : MergeParameters(_parameters, new { downloadPath });
2195 WithHttpMethod("POST");
2196 return this;
2197 }
2198
2205 public IAllowRESTExecution Evaluate(string xpath)
2206 {
2207 WithEndpoint(ApiEndpoint.Evaluate).WithXPath(xpath);
2208 return this;
2209 }
2210
2218 {
2219 WithEndpoint(ApiEndpoint.EvaluateAll).WithXPath(xpath);
2220 return this;
2221 }
2222
2230 {
2231 WithEndpoint(ApiEndpoint.EvaluatePersistent).WithXPath(xpath);
2232 return this;
2233 }
2234
2242 {
2243 WithEndpoint(ApiEndpoint.EvaluateAllPersistent).WithXPath(xpath);
2244 return this;
2245 }
2246
2253 {
2254 WithEndpoint(ApiEndpoint.ClearReferrer);
2255 return this;
2256 }
2257
2264 public IAllowRESTExecution FireChangeEvent(string elementId)
2265 {
2266 WithEndpoint(ApiEndpoint.FireChangeEvent).WithElementId(elementId);
2267 return this;
2268 }
2269
2276 public IAllowRESTExecution Focus(string elementId)
2277 {
2278 WithEndpoint(ApiEndpoint.Focus).WithElementId(elementId);
2279 return this;
2280 }
2281
2289 {
2290 WithEndpoint(ApiEndpoint.GetBoundingClientRect).WithElementId(elementId);
2291 return this;
2292 }
2293
2302 public IAllowRESTExecution ElementFromPoint(string elementId, int x, int y)
2303 {
2304 WithEndpoint(ApiEndpoint.ElementFromPoint).WithElementId(elementId);
2305
2306 _parameters = null == _parameters
2307 ? new { x, y }
2308 : MergeParameters(_parameters, new { x, y });
2309
2310 return this;
2311 }
2312
2322 public IAllowRESTExecution TrustedLeftClick(int x, int y, int holdMs = 0)
2323 {
2324 WithEndpoint(ApiEndpoint.TrustedLeftClick);
2325
2326 // holdMs is optional on the endpoint, so it only goes out when a hold was asked for
2327 object point = 0 < holdMs ? (object)new { x, y, holdMs } : new { x, y };
2328
2329 _parameters = null == _parameters
2330 ? point
2331 : MergeParameters(_parameters, point);
2332
2333 // posted, because GPALRestAPI hands the extension no parameters from a GET and the point never arrived
2334 WithHttpMethod("POST");
2335
2336 return this;
2337 }
2338
2348 public IAllowRESTExecution TrustedMiddleClick(int x, int y, int holdMs = 0)
2349 {
2350 WithEndpoint(ApiEndpoint.TrustedMiddleClick);
2351
2352 // holdMs is optional on the endpoint, so it only goes out when a hold was asked for
2353 object point = 0 < holdMs ? (object)new { x, y, holdMs } : new { x, y };
2354
2355 _parameters = null == _parameters
2356 ? point
2357 : MergeParameters(_parameters, point);
2358
2359 // posted, because GPALRestAPI hands the extension no parameters from a GET and the point never arrived
2360 WithHttpMethod("POST");
2361
2362 return this;
2363 }
2364 public IAllowRESTExecution GetWindowRectangle()
2365 {
2366 WithEndpoint(ApiEndpoint.GetWindowRectangle);
2367 return this;
2368 }
2369 public IAllowRESTExecution GetContentAndCss(string elementId)
2370 {
2371 WithEndpoint(ApiEndpoint.GetContentAndCss);
2372 _parameters = null == _parameters
2373 ? new { elementId }
2374 : MergeParameters(_parameters, new { elementId });
2375 WithHttpMethod("POST");
2376 return this;
2377 }
2378 public IAllowRESTExecution GetCssAttributes(string elementId)
2379 {
2380 WithEndpoint(ApiEndpoint.GetCssAttributes);
2381 _parameters = null == _parameters
2382 ? new { elementId }
2383 : MergeParameters(_parameters, new { elementId });
2384 WithHttpMethod("POST");
2385 return this;
2386 }
2387 public IAllowRESTExecution GetDomAttributes(string elementId)
2388 {
2389 WithEndpoint(ApiEndpoint.GetDomAttributes);
2390 _parameters = null == _parameters
2391 ? new { elementId }
2392 : MergeParameters(_parameters, new { elementId });
2393 WithHttpMethod("POST");
2394 return this;
2395 }
2396 public IAllowRESTExecution GetDomProperties(string elementId)
2397 {
2398 WithEndpoint(ApiEndpoint.GetDomProperties);
2399 _parameters = null == _parameters
2400 ? new { elementId }
2401 : MergeParameters(_parameters, new { elementId });
2402 WithHttpMethod("POST");
2403 return this;
2404 }
2411 {
2412 WithEndpoint(ApiEndpoint.GetElementAttributeHash).WithElementId(elementId);
2413 return this;
2414 }
2415
2421 public IAllowRESTExecution GetOptions(string elementId)
2422 {
2423 WithEndpoint(ApiEndpoint.SwitchToElement).WithElementId(elementId).AndThen().WithCss("option");
2424 return this;
2425 }
2426 public IAllowRESTExecution GetPageSource()
2427 {
2428 WithEndpoint(ApiEndpoint.GetPageSource);
2429 return this;
2430 }
2437 public IAllowRESTExecution GetParentNode(string elementId)
2438 {
2439 WithEndpoint(ApiEndpoint.GetParentNode).WithElementId(elementId);
2440 return this;
2441 }
2442
2449 public IAllowRESTExecution HideElement(string elementId)
2450 {
2451 WithEndpoint(ApiEndpoint.HideElement).WithElementId(elementId);
2452 return this;
2453 }
2454
2461 public IAllowRESTExecution IsClickable(string elementId)
2462 {
2463 WithEndpoint(ApiEndpoint.IsClickable).WithElementId(elementId);
2464 return this;
2465 }
2466
2473 public IAllowRESTExecution IsDisplayed(string elementId)
2474 {
2475 WithEndpoint(ApiEndpoint.IsDisplayed).WithElementId(elementId);
2476 return this;
2477 }
2478
2485 public IAllowRESTExecution IsEnabled(string elementId)
2486 {
2487 WithEndpoint(ApiEndpoint.IsEnabled).WithElementId(elementId);
2488 return this;
2489 }
2490
2497 {
2498 WithEndpoint(ApiEndpoint.IsEndOfPage);
2499 return this;
2500 }
2501
2509 {
2510 WithEndpoint(ApiEndpoint.IsVisibleInViewport).WithElementId(elementId);
2511 return this;
2512 }
2513
2520 public IAllowRESTExecution LeftClick(string elementId)
2521 {
2522 WithEndpoint(ApiEndpoint.LeftClick).WithElementId(elementId);
2523 return this;
2524 }
2525
2532 public IAllowRESTExecution LeftDoubleClick(string elementId)
2533 {
2534 WithEndpoint(ApiEndpoint.LeftDoubleClick).WithElementId(elementId);
2535 return this;
2536 }
2537
2543 public IAllowRESTExecution MiddleClick(string elementId)
2544 {
2545 WithEndpoint(ApiEndpoint.MiddleClick).WithElementId(elementId);
2546 return this;
2547 }
2548
2556 {
2557 WithEndpoint(ApiEndpoint.QuerySelector).WithCss(css);
2558 return this;
2559 }
2560
2568 {
2569 WithEndpoint(ApiEndpoint.QuerySelectors).WithCss(css);
2570 return this;
2571 }
2572
2580 {
2581 WithEndpoint(ApiEndpoint.QueryPersistentSelector).WithCss(css);
2582 return this;
2583 }
2584
2592 {
2593 WithEndpoint(ApiEndpoint.QueryPersistentSelectors).WithCss(css);
2594 return this;
2595 }
2596
2603 public IAllowRESTExecution RightClick(string elementId)
2604 {
2605 WithEndpoint(ApiEndpoint.RightClick).WithElementId(elementId);
2606 return this;
2607 }
2608
2615 public IAllowRESTExecution ScrollIntoView(string elementId)
2616 {
2617 WithEndpoint(ApiEndpoint.ScrollIntoView).WithElementId(elementId);
2618 return this;
2619 }
2620
2628 public IAllowRESTExecution ScrollElement(int hPixels, int vPixels)
2629 {
2630 WithEndpoint(ApiEndpoint.ScrollElement).WithHPixels(hPixels).WithVPixels(vPixels);
2631 return this;
2632 }
2633
2640 public IAllowRESTAttribute GetAttribute(string elementId)
2641 {
2642 WithEndpoint(ApiEndpoint.GetAttribute).WithElementId(elementId);
2643 return this;
2644 }
2645
2652 public IAllowRESTAttribute SetAttribute(string elementId)
2653 {
2654 WithEndpoint(ApiEndpoint.SetAttribute).WithElementId(elementId);
2655 return this;
2656 }
2657
2663 {
2664 WithEndpoint(ApiEndpoint.TopBrowser);
2665 return this;
2666 }
2667
2674 public IAllowRESTAttribute WithAttribute(string attribute)
2675 {
2676 _parameters = null == _parameters
2677 ? new { attribute }
2678 : MergeParameters(_parameters, new { attribute });
2679 WithHttpMethod("POST");
2680 return this;
2681 }
2682
2689 public IAllowRangeValue SetRange(string elementId)
2690 {
2691 WithEndpoint(ApiEndpoint.SetRange).WithElementId(elementId);
2692 return this;
2693 }
2694
2702 {
2703 _parameters = null == _parameters
2704 ? new { rangeValue = rangeValue.ToString() }
2705 : MergeParameters(_parameters, new { rangeValue = rangeValue.ToString() });
2706 WithHttpMethod("POST");
2707 return this;
2708 }
2709
2716 public IAllowRESTAttribute WithValue(string value)
2717 {
2718 _parameters = null == _parameters
2719 ? new { value }
2720 : MergeParameters(_parameters, new { value });
2721 WithHttpMethod("POST");
2722 return this;
2723 }
2724
2731 {
2732 WithEndpoint(ApiEndpoint.WindowInnerHeight);
2733 return this;
2734 }
2735
2742 {
2743 WithEndpoint(ApiEndpoint.WindowInnerWidth);
2744 return this;
2745 }
2746
2753 {
2754 WithEndpoint(ApiEndpoint.WindowOuterHeight);
2755 return this;
2756 }
2757
2764 {
2765 WithEndpoint(ApiEndpoint.WindowOuterWidth);
2766 return this;
2767 }
2768
2778 {
2779 WithEndpoint(ApiEndpoint.Fetch);
2780 _parameters = null == _parameters
2781 ? new { url }
2782 : MergeParameters(_parameters, new { url });
2783 WithHttpMethod("POST");
2784 return this;
2785 }
2786
2795 {
2796 _parameters = null == _parameters
2797 ? new { method }
2798 : MergeParameters(_parameters, new { method });
2799 WithHttpMethod("POST");
2800 return this;
2801 }
2802
2810 {
2811 _parameters = null == _parameters
2812 ? new { body }
2813 : MergeParameters(_parameters, new { body });
2814 WithHttpMethod("POST");
2815 return this;
2816 }
2817
2825 {
2826 _parameters = null == _parameters
2827 ? new { contentType }
2828 : MergeParameters(_parameters, new { contentType });
2829 WithHttpMethod("POST");
2830 return this;
2831 }
2832
2840 {
2841 _parameters = null == _parameters
2842 ? new { headers }
2843 : MergeParameters(_parameters, new { headers });
2844 WithHttpMethod("POST");
2845 return this;
2846 }
2847
2856 {
2857 _parameters = null == _parameters
2858 ? new { bytes }
2859 : MergeParameters(_parameters, new { bytes });
2860 WithHttpMethod("POST");
2861 return this;
2862 }
2863
2870 {
2871 WithEndpoint(ApiEndpoint.WindowPageOffsetX);
2872 return this;
2873 }
2874
2881 {
2882 WithEndpoint(ApiEndpoint.WindowPageOffsetY);
2883 return this;
2884 }
2885
2892 {
2893 WithEndpoint(ApiEndpoint.WindowScreenLeft);
2894 return this;
2895 }
2896
2903 {
2904 WithEndpoint(ApiEndpoint.WindowScreenTop);
2905 return this;
2906 }
2907
2914 public IAllowRESTExecution Hover(string elementId)
2915 {
2916 WithEndpoint(ApiEndpoint.Hover).WithElementId(elementId);
2917 return this;
2918 }
2919
2923 private void Reset()
2924 {
2925 _customEndpoint = false; ;
2926 _parameters = null;
2927 _httpMethod = "GET";
2928 _endpoint = null;
2929 // Called at the end of Execute and Execute<T>, so a parameter named before the endpoint survives
2930 // long enough to be sent. Headers are the client's rather than the call's: Authorization, Cookie
2931 // and User-Agent describe who is asking and stay true across every endpoint, so they are not here.
2932 // _nextResultName is not here either, since Execute clears it once it has consumed it
2933 }
2934
2935 // grok left out a lot, so these are not in order
2942 {
2943 WithEndpoint(ApiEndpoint.Back);
2944 return this;
2945 }
2946
2953 public IAllowRESTExecution CheckNetworkIdle(int maxConnections = 0)
2954 {
2955 WithEndpoint(ApiEndpoint.CheckNetworkIdle).WithMaxConnections(maxConnections);
2956 return this;
2957 }
2958
2966 {
2967 WithEndpoint(ApiEndpoint.CloseTab).WithTabId(tabId);
2968 return this;
2969 }
2970
2978 {
2979 if (null != url)
2980 WithEndpoint(ApiEndpoint.CloseTab).WithUrl(url.Url);
2981 else
2982 WithEndpoint(ApiEndpoint.CloseTab);
2983 return this;
2984 }
2985
2993 {
2994 WithEndpoint(ApiEndpoint.ExecuteJavaScript);
2995 _parameters = null == _parameters
2996 ? new { script }
2997 : MergeParameters(_parameters, new { script });
2998 WithHttpMethod("POST");
2999 return this;
3000 }
3001
3008 {
3009 WithEndpoint(ApiEndpoint.InjectScript);
3010 _parameters = null == _parameters
3011 ? new { script }
3012 : MergeParameters(_parameters, new { script });
3013 WithHttpMethod("POST");
3014 return this;
3015 }
3016
3022 {
3023 WithEndpoint(ApiEndpoint.ClearInjectedScripts);
3024 WithHttpMethod("POST");
3025 return this;
3026 }
3027
3034 {
3035 WithEndpoint(ApiEndpoint.Forward);
3036 return this;
3037 }
3038
3045 {
3046 WithEndpoint(ApiEndpoint.FullScreen);
3047 return this;
3048 }
3049
3056 {
3057 WithEndpoint(ApiEndpoint.GetBrowserSettings);
3058 return this;
3059 }
3060
3067 {
3068 WithEndpoint(ApiEndpoint.GetCurrentUrl);
3069 return this;
3070 }
3071
3080 {
3081 WithEndpoint(ApiEndpoint.GetDocumentStamp);
3082 return this;
3083 }
3084
3092 {
3093 WithEndpoint(ApiEndpoint.GetHydrationScripts);
3094 return this;
3095 }
3096
3103 {
3104 WithEndpoint(ApiEndpoint.GetGpalSettings);
3105 return this;
3106 }
3107
3114 {
3115 WithEndpoint(ApiEndpoint.GetReadyStatus);
3116 return this;
3117 }
3118
3125 {
3126 WithEndpoint(ApiEndpoint.GetSettings);
3127 return this;
3128 }
3129
3137 {
3138 WithEndpoint(ApiEndpoint.GetShadowRoot).WithElementId(css);
3139 return this;
3140 }
3141
3148 {
3149 WithEndpoint(ApiEndpoint.GetLanguages);
3150 return this;
3151 }
3152
3160 public IAllowRESTCaptureOptions CaptureCalls(bool capture = true)
3161 {
3162 WithEndpoint(ApiEndpoint.CaptureCalls);
3163
3164 _parameters = null == _parameters
3165 ? new { capture }
3166 : MergeParameters(_parameters, new { capture });
3167
3168 WithHttpMethod("POST");
3169 return this;
3170 }
3171
3178 public IAllowRESTCaptureOptions WithCallFilter(string urlFragment)
3179 {
3180 _parameters = null == _parameters
3181 ? new { filter = urlFragment }
3182 : MergeParameters(_parameters, new { filter = urlFragment });
3183
3184 WithHttpMethod("POST");
3185 return this;
3186 }
3187
3194 {
3195 _parameters = null == _parameters
3196 ? new { clear }
3197 : MergeParameters(_parameters, new { clear });
3198
3199 WithHttpMethod("POST");
3200 return this;
3201 }
3202
3209 {
3210 WithEndpoint(ApiEndpoint.GetCapturedCalls);
3211 return this;
3212 }
3213
3220 {
3221 WithEndpoint(ApiEndpoint.GetUserAgent);
3222 return this;
3223 }
3224
3231 {
3232 WithEndpoint(ApiEndpoint.GetWorkflow);
3233 return this;
3234 }
3235
3243 {
3244 if (true == string.IsNullOrEmpty(url))
3245 url = new GPALUrl("google.com");
3246
3247 WithEndpoint(ApiEndpoint.GoTo).WithUrl(url.Url);
3248 WithHttpMethod("POST");
3249 return this;
3250 }
3251
3259 {
3260 WithEndpoint(ApiEndpoint.GoToTab).WithTabId(tabId);
3261 return this;
3262 }
3263
3271 {
3272 WithEndpoint(ApiEndpoint.GoToTab).WithUrl(url?.Url);
3273 return this;
3274 }
3275
3282 public IAllowRESTInputText FillInAppend(string elementId)
3283 {
3284 WithEndpoint(ApiEndpoint.FillInAppend).WithElementId(elementId);
3285 return this;
3286 }
3287
3294 public IAllowRESTInputText FillInInsert(string elementId)
3295 {
3296 WithEndpoint(ApiEndpoint.FillInInsert).WithElementId(elementId);
3297 return this;
3298 }
3299
3306 public IAllowRESTInputText FillInOverwrite(string elementId)
3307 {
3308 WithEndpoint(ApiEndpoint.FillInOverwrite).WithElementId(elementId);
3309 return this;
3310 }
3311
3319 {
3320 WithEndpoint(ApiEndpoint.LeftClickAndDownload).WithElementId(elementId);
3321 return this;
3322 }
3323
3331 {
3332 WithEndpoint(ApiEndpoint.Upload).WithElementId(elementId);
3333 return this;
3334 }
3335
3342 {
3343 WithEndpoint(ApiEndpoint.Maximize);
3344 return this;
3345 }
3346
3353 {
3354 WithEndpoint(ApiEndpoint.Minimize);
3355 return this;
3356 }
3357
3364 public IAllowRESTExecution MoveTo(string elementId)
3365 {
3366 WithEndpoint(ApiEndpoint.MoveTo).WithElementId(elementId);
3367 return this;
3368 }
3369
3377 {
3378
3379 WithEndpoint(ApiEndpoint.NewTab);
3380
3381 if (null != url)
3382 WithUrl(url?.Url);
3383
3384 return this;
3385 }
3386
3393 {
3394 WithEndpoint(ApiEndpoint.NextTab);
3395 return this;
3396 }
3397
3405 {
3406 WithEndpoint(ApiEndpoint.TabCount);
3407 return this;
3408 }
3409
3416 {
3417 WithEndpoint(ApiEndpoint.NextWindow);
3418 return this;
3419 }
3420
3427 {
3428 WithEndpoint(ApiEndpoint.Normal);
3429 return this;
3430 }
3431
3439 {
3440 WithEndpoint(ApiEndpoint.OverrideReferrer).WithReferrer(referrer);
3441 return this;
3442 }
3443
3450 public IAllowRESTExecution SetUserAgent(string userAgent)
3451 {
3452 WithEndpoint(ApiEndpoint.SetUserAgent);
3453 _parameters = null == _parameters
3454 ? new { userAgent }
3455 : MergeParameters(_parameters, new { userAgent });
3456 WithHttpMethod("POST");
3457 return this;
3458 }
3459
3466 public IAllowRESTExecution PageDown(int pagesToScroll = 1)
3467 {
3468 WithEndpoint(ApiEndpoint.PageDown);
3469 _parameters = null == _parameters
3470 ? new { pagesToScroll }
3471 : MergeParameters(_parameters, new { pagesToScroll });
3472 WithHttpMethod("POST");
3473 return this;
3474 }
3475
3482 {
3483 WithEndpoint(ApiEndpoint.PageEnd);
3484 return this;
3485 }
3486
3493 {
3494 WithEndpoint(ApiEndpoint.PageTop);
3495 return this;
3496 }
3497
3504 public IAllowRESTExecution PageUp(int pagesToScroll = 1)
3505 {
3506 WithEndpoint(ApiEndpoint.PageUp);
3507 _parameters = null == _parameters
3508 ? new { pagesToScroll }
3509 : MergeParameters(_parameters, new { pagesToScroll });
3510 WithHttpMethod("POST");
3511 return this;
3512 }
3513
3519 public IAllowRESTExecution PressModifierKey(ModifierKeys modifierKeys)
3520 {
3521 WithEndpoint(ApiEndpoint.PressModifierKey);
3522 _parameters = null == _parameters
3523 ? new { modifierKeys }
3524 : MergeParameters(_parameters, new { modifierKeys });
3525 WithHttpMethod("POST");
3526 return this;
3527 }
3528
3534 {
3535 WithEndpoint(ApiEndpoint.PreviousTab);
3536 return this;
3537 }
3538
3545 {
3546 WithEndpoint(ApiEndpoint.PreviousWindow);
3547 return this;
3548 }
3549
3556 {
3557 WithEndpoint(ApiEndpoint.Refresh);
3558 return this;
3559 }
3560
3567 public IAllowRESTExecution ReleaseModifierKey(ModifierKeys modifierKeys)
3568 {
3569 WithEndpoint(ApiEndpoint.ReleaseModifierKey);
3570 _parameters = null == _parameters
3571 ? new { modifierKeys }
3572 : MergeParameters(_parameters, new { modifierKeys });
3573 WithHttpMethod("POST");
3574 return this;
3575 }
3576
3583 {
3584 WithEndpoint(ApiEndpoint.Restore);
3585 return this;
3586 }
3587
3594 public IAllowRESTDragAndDrop DragAndDrop(string elementId)
3595 {
3596 WithEndpoint(ApiEndpoint.DragAndDrop).WithElementId(elementId);
3597 return this;
3598 }
3599
3607 {
3608 WithEndpoint(ApiEndpoint.ScrollElement).WithElementId(elementId);
3609 return this;
3610 }
3611
3612 public IAllowRESTExecution ScrollWindow(int hPixels, int vPixels)
3613 {
3614 WithEndpoint(ApiEndpoint.ScrollWindow).WithHPixels(hPixels).WithVPixels(vPixels);
3615 return this;
3616 }
3624 {
3625 int vPixels = 0;
3626 WithEndpoint(ApiEndpoint.ScrollWindow).WithHPixels(hPixels).WithVPixels(vPixels);
3627 WithHttpMethod("POST");
3628 return this;
3629 }
3630
3638 {
3639 int hPixels = 0;
3640 WithEndpoint(ApiEndpoint.ScrollWindow).WithHPixels(hPixels).WithVPixels(vPixels);
3641 WithHttpMethod("POST");
3642 return this;
3643 }
3644
3650 public IAllowSelectOptions SelectClick(string elementId)
3651 {
3652 WithEndpoint(ApiEndpoint.SelectClick).WithElementId(elementId);
3653 WithHttpMethod("POST");
3654 return this;
3655 }
3656
3662 public IAllowRESTExecution WithSelectValue(string selectValue)
3663 {
3664 _parameters = null == _parameters
3665 ? new { selectValue }
3666 : MergeParameters(_parameters, new { selectValue });
3667 WithHttpMethod("POST");
3668 return this;
3669 }
3670 public IAllowRESTExecution WithSelectIndex(int selectIndex)
3671 {
3672 _parameters = null == _parameters
3673 ? new { selectIndex }
3674 : MergeParameters(_parameters, new { selectIndex });
3675 WithHttpMethod("POST");
3676 return this;
3677 }
3684 public IAllowRESTExecution SendKey(byte vkcode)
3685 {
3686 string key = null;
3687 string code = null;
3688
3689 if (true == VkCodeToDomKeyConverter.TryConvertVkCodeToDomKey(vkcode, out key, out code))
3690 {
3691 WithEndpoint(ApiEndpoint.SendKey);
3692 _parameters = null == _parameters
3693 ? new { key }
3694 : MergeParameters(_parameters, new { key });
3695 _parameters = MergeParameters(_parameters, new { code });
3696 _parameters = MergeParameters(_parameters, new { vk = code });
3697 WithHttpMethod("POST");
3698 }
3699 return this;
3700 }
3701
3709 public IAllowRESTExecution SendString(string text, int delayMs = 0)
3710 {
3711 WithEndpoint(ApiEndpoint.SendString);
3712 _parameters = null == _parameters
3713 ? new { text, typingDelay = delayMs }
3714 : MergeParameters(_parameters, new { text, typingDelay = delayMs });
3715 WithHttpMethod("POST");
3716 return this;
3717 }
3718
3728 {
3729 WithEndpoint(ApiEndpoint.SetValueFromElement);
3730 _parameters = null == _parameters
3731 ? new { destSelector }
3732 : MergeParameters(_parameters, new { destSelector });
3733 WithHttpMethod("POST");
3734 return this;
3735 }
3736
3743 {
3744 WithEndpoint(ApiEndpoint.SetGoogleReferrer);
3745 return this;
3746 }
3747
3754 {
3755 WithEndpoint(ApiEndpoint.SwitchToDefaultContent);
3756 return this;
3757 }
3758 #region Window Handling
3766 {
3767 _parameters = new { windowId };
3768 WithHttpMethod("POST");
3769 return this;
3770 }
3771
3778 {
3779 if (resultIndex < 0 || resultIndex >= _executionResults.Count)
3780 {
3781 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
3782 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result index [{resultIndex}] is out of range for WithWindowIdFromResult. Using 0.",
3783 null, GPALObjectType.None);
3784 return WithWindowId(0);
3785 }
3786 var previousResult = _executionResults[resultIndex].Result;
3787 var windowId = previousResult is int i ? i : (int.TryParse(previousResult?.ToString(), out int parsed) ? parsed : 0);
3788 return WithWindowId(windowId);
3789 }
3790
3797 {
3798 var resultEntry = _executionResults.LastOrDefault(entry => entry.Name == name);
3799 if (resultEntry.Result == null)
3800 {
3801 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
3802 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Result name [{name}] not found for WithWindowIdFromResult. Using 0.",
3803 null, GPALObjectType.None);
3804 return WithWindowId(0);
3805 }
3806 var previousResult = resultEntry.Result;
3807 var windowId = previousResult is int i ? i : (int.TryParse(previousResult?.ToString(), out int parsed) ? parsed : 0);
3808 return WithWindowId(windowId);
3809 }
3810
3817 {
3818 if (true == string.IsNullOrEmpty(url))
3819 url = new GPALUrl("google.com");
3820
3821 WithEndpoint(ApiEndpoint.OpenWindow).WithUrl(url.Url);
3822 return this;
3823 }
3824
3832 {
3833 WithEndpoint(ApiEndpoint.GoToWindow).WithUrl(url?.Url);
3834 return this;
3835 }
3836
3843 public IAllowRESTExecution GoToWindow(int windowId)
3844 {
3845 WithEndpoint(ApiEndpoint.GoToWindow).WithWindowId(windowId);
3846 return this;
3847 }
3848
3855 {
3856 WithEndpoint(ApiEndpoint.CloseBrowser);
3857 return this;
3858 }
3859
3867 {
3868 WithEndpoint(ApiEndpoint.CloseWindow);
3869
3870 if (null != url)
3871 WithUrl(url?.Url);
3872
3873 return this;
3874 }
3875
3883 {
3884 WithEndpoint(ApiEndpoint.CloseWindow).WithWindowId(windowId);
3885 return this;
3886 }
3887
3893 public IAllowRESTStorageOptions DeleteStorage(WebsiteStorageType storageType)
3894 {
3895 WithEndpoint(ApiEndpoint.DeleteStorage).WithStorageType(storageType);
3896
3897 return this;
3898 }
3899
3905 public IAllowRESTStorageOptions GetStorage(WebsiteStorageType storageType)
3906 {
3907 WithEndpoint(ApiEndpoint.GetStorage).WithStorageType(storageType);
3908
3909 return this;
3910 }
3911
3917 public IAllowRESTStorageOptions SetStorage(WebsiteStorageType storageType)
3918 {
3919 WithEndpoint(ApiEndpoint.SetStorage).WithStorageType(storageType);
3920
3921 return this;
3922 }
3923
3929 {
3930 WithEndpoint(ApiEndpoint.GetCurrentWindow);
3931 return this;
3932 }
3933 #endregion Window Handling
3940 public IAllowRESTExecution SwitchToElement(string elementId)
3941 {
3942 WithEndpoint(ApiEndpoint.SwitchToElement);
3943 _parameters = null == _parameters
3944 ? new { elementId }
3945 : MergeParameters(_parameters, new { elementId });
3946 WithHttpMethod("POST");
3947 return this;
3948 }
3949
3956 public IAllowRESTExecution SwitchToFrame(string elementId)
3957 {
3958 WithEndpoint(ApiEndpoint.SwitchToElement);
3959 _parameters = null == _parameters
3960 ? new { elementId }
3961 : MergeParameters(_parameters, new { elementId });
3962 WithHttpMethod("POST");
3963 return this;
3964 }
3965
3973 {
3974 WithEndpoint(ApiEndpoint.SwitchToShadowRoot);
3975 _parameters = null == _parameters
3976 ? new { elementId }
3977 : MergeParameters(_parameters, new { elementId });
3978 WithHttpMethod("POST");
3979 return this;
3980 }
3981
3988 private object MergeParameters(object existing, object additional)
3989 {
3990 var dict = existing is IDictionary<string, object> existingDict
3991 ? new Dictionary<string, object>(existingDict)
3992 : existing.GetType().GetProperties()
3993 .ToDictionary(p => p.Name, p => p.GetValue(existing));
3994
3995 if (null != additional)
3996 {
3997 var additionalDict = additional is IDictionary<string, object> addDict
3998 ? addDict
3999 : additional?.GetType().GetProperties()
4000 .ToDictionary(p => p.Name, p => p.GetValue(additional));
4001
4002 foreach (var kvp in additionalDict)
4003 {
4004 dict[kvp.Key] = kvp.Value;
4005 }
4006 }
4007 return dict;
4008 }
4009
4010
4011 internal bool ValidateParameters(ApiEndpoint endpoint, IDictionary<string, object> parmsToValidate = null)
4012 {
4013 try
4014 {
4015 var internalParms = _parameters as IDictionary<string, object>
4016 ?? (null != _parameters ? _parameters.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(_parameters))
4017 : new Dictionary<string, object>());
4018
4019 var paramsDict = parmsToValidate ?? internalParms;
4020
4021 if (false == paramRules.ContainsKey(endpoint))
4022 {
4023 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4024 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No validation rules defined for endpoint [{endpoint}].",
4025 null, GPALObjectType.None);
4026 return true;
4027 }
4028
4029 var (mandatory, optional, customValidation) = paramRules[endpoint];
4030
4031 if (ApiEndpoint.SetStorage == endpoint)
4032 {
4033 var storageType = paramsDict.ContainsKey("storageType") ? paramsDict["storageType"]?.ToString() : "";
4034 if (true == "cache".Equals(storageType))
4035 (mandatory, optional, customValidation) = paramRules[ApiEndpoint.SetStorageCache];
4036 else if (true == "indexedDb".Equals(storageType))
4037 (mandatory, optional, customValidation) = paramRules[ApiEndpoint.SetStorageIndexedDb];
4038 }
4039
4040 var missingMandatory = mandatory.Where(p => false == paramsDict.ContainsKey(p)).ToArray();
4041
4042 if (true == missingMandatory.Any())
4043 {
4044 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4045 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Missing required parameters for [{endpoint}]: [{string.Join(", ", missingMandatory)}].",
4046 null, GPALObjectType.None);
4047 return false;
4048 }
4049
4050 if (null != customValidation && false == customValidation(paramsDict))
4051 {
4052 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4053 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Custom validation failed for [{endpoint}].",
4054 null, GPALObjectType.None);
4055 return false;
4056 }
4057
4058 // Ensure only allowed parameters (mandatory + optional) are present
4059 var allowedParams = mandatory.Concat(optional).ToHashSet();
4060 var invalidParams = paramsDict.Keys.Where(k => false == allowedParams.Contains(k)).ToArray();
4061 if (true == invalidParams.Any())
4062 {
4063 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4064 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Invalid parameters for [{endpoint}]: [{string.Join(", ", invalidParams)}].",
4065 null, GPALObjectType.None);
4066 return false;
4067 }
4068 }
4069 catch (Exception ex)
4070 {
4071 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to validate [{endpoint}]", this, GPALObjectType.RestClient, ex);
4072 }
4073
4074 return true;
4075 }
4076
4077 internal bool ValidateOpenApiParameters(string ruleKey, IDictionary<string, object> parmsToValidate = null)
4078 {
4079 try
4080 {
4081 var internalParms = _parameters as IDictionary<string, object>
4082 ?? (null != _parameters ? _parameters.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(_parameters))
4083 : new Dictionary<string, object>());
4084
4085 var paramsDict = parmsToValidate ?? internalParms;
4086
4087 if (false == openApiParamRules.ContainsKey(ruleKey))
4088 {
4089 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4090 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No OpenAPI validation rules defined for [{ruleKey}].",
4091 null, GPALObjectType.None);
4092 return true;
4093 }
4094
4095 var (mandatory, optional, customValidation) = openApiParamRules[ruleKey];
4096
4097 var missingMandatory = mandatory.Where(p => false == paramsDict.ContainsKey(p)).ToArray();
4098
4099 if (true == missingMandatory.Any())
4100 {
4101 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4102 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Missing required parameters for [{ruleKey}]: [{string.Join(", ", missingMandatory)}].",
4103 null, GPALObjectType.None);
4104 return false;
4105 }
4106
4107 if (null != customValidation && false == customValidation(paramsDict))
4108 {
4109 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4110 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Custom validation failed for OpenAPI endpoint [{ruleKey}].",
4111 null, GPALObjectType.None);
4112 return false;
4113 }
4114
4115 // Ensure only allowed parameters are present
4116 var allowedParams = mandatory.Concat(optional).ToHashSet(StringComparer.OrdinalIgnoreCase);
4117 var invalidParams = paramsDict.Keys.Where(k => false == allowedParams.Contains(k)).ToArray();
4118
4119 if (true == invalidParams.Any())
4120 {
4121 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
4122 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Invalid parameters for [{ruleKey}]: [{string.Join(", ", invalidParams)}].",
4123 null, GPALObjectType.None);
4124 return false;
4125 }
4126 }
4127 catch (Exception ex)
4128 {
4129 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION,
4130 $"Unable to validate OpenAPI endpoint [{ruleKey}]", this, GPALObjectType.RestClient, ex);
4131 }
4132
4133 return true;
4134 }
4141 private string GetOpenApiRuleKey(string httpMethod, string endpoint)
4142 {
4143 if (string.IsNullOrWhiteSpace(httpMethod) || string.IsNullOrWhiteSpace(endpoint))
4144 return null;
4145
4146 var path = endpoint.TrimStart('/');
4147 return $"{httpMethod.ToUpperInvariant()} {path}";
4148 }
4149
4155 private string ExtractBestServerUrl(string yamlContent)
4156 {
4157 try
4158 {
4159 var reader = new Microsoft.OpenApi.Readers.OpenApiStreamReader();
4160 using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yamlContent));
4161 var document = reader.Read(stream, out _);
4162
4163 if (document.Servers == null || document.Servers.Count == 0)
4164 return null;
4165
4166 // 1. Prefer remote (https) server
4167 var remote = document.Servers.FirstOrDefault(s =>
4168 s.Url.StartsWith("https://", StringComparison.OrdinalIgnoreCase));
4169
4170 if (remote != null)
4171 return remote.Url.TrimEnd('/');
4172
4173 // 2. Otherwise take the first one
4174 return document.Servers[0].Url.TrimEnd('/');
4175 }
4176 catch
4177 {
4178 return null;
4179 }
4180 }
4181 internal static void SaveResponseToFile(string response, string filePath)
4182 {
4183 try
4184 {
4185 // Handle Data URI (e.g., base64-encoded image) or direct HTTP URL
4186 if (response.StartsWith("data:"))
4187 {
4188 // Handle base64-encoded data URI
4189 var base64Part = response.Substring(response.IndexOf(",") + 1);
4190 var bytes = Convert.FromBase64String(base64Part);
4191 System.IO.File.WriteAllBytes(filePath, bytes);
4192 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Saved base64 response to [{filePath}]", null, GPALObjectType.None);
4193 }
4194 else if (Uri.TryCreate(response, UriKind.Absolute, out var uri))
4195 {
4196 // Handle direct HTTP(S) URL
4197 using (var client = new HttpClient())
4198 using (var stream = client.GetStreamAsync(uri).Result)
4199 using (var fileStream = System.IO.File.Create(filePath))
4200 {
4201 stream.CopyTo(fileStream);
4202 }
4203 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Downloaded response to [{filePath}]", null, GPALObjectType.None);
4204 }
4205 else
4206 {
4207 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Unsupported response format for saving to [{filePath}]", null, GPALObjectType.None);
4208 }
4209 }
4210 catch (Exception ex)
4211 {
4212 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to save response to [{filePath}]", null, GPALObjectType.None, ex);
4213 }
4214 }
4215 }
4216}
Represents a URL with optional pre-navigation storage cleanup / inspection actions....
Definition GPALUrl.cs:53
string Url
Gets the target URL string.
Definition GPALUrl.cs:69
Pseudo element used in Applications and Browser workflows for image matching and unified automation....
Thrown where GPAL deliberately ends the workflow, such as a CallIf handler returning CallIfStatus....
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
Definition GPALFile.cs:36
List< string > Filenames
Get the list of filenames.
Definition GPALFile.cs:540
string Filename
We have only one file, accessing it.
Definition GPALFile.cs:468
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static IAllowConverterInput Converter
New GPAL Convertor.
Definition GPAL.cs:571
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2568
IAllowRESTParametersOrExecution WithWindowIdFromResult(string name)
Sets the window ID from a result by name client.GoToWindow().WithWindowIdFromResult("windowResult")....
IAllowRESTExecution WindowScreenLeft()
Gets the window screen left position client.WindowScreenLeft().Execute<int>();.
IAllowRESTParametersOrExecution WithUrl(string url)
Sets the URL parameter.
IAllowRESTStorageOptions WithStoragePath(string path)
Sets the storage path parameter.
IAllowRESTParametersOrExecution WithKeyFromResult(int resultIndex)
Sets the key from a result by index.
IAllowRESTStorageOptions DeleteStorage(WebsiteStorageType storageType)
Delete the specified storage type.
IAllowRESTExecution ScrollIntoView(string elementId)
Scrolls an element into view by ID client.ScrollIntoView("element123").Execute();.
IAllowRESTParametersOrExecution WithResultName(string name)
Sets the result name for the next execution.
IAllowRESTParametersOrExecution WithElementIdFromResult(int resultIndex)
Sets the element from a result by index.
IAllowRESTParametersOrExecution WithCss(string css)
Sets the CSS parameter.
IAllowRESTParametersOrExecution WithXPath(string xpath)
Sets the XPath parameter.
IAllowRESTExecution WindowInnerWidth()
Gets the window inner width client.WindowInnerWidth().Execute<int>();.
IAllowRESTExecution Back()
Navigates browser history back client.Back().Execute();.
IAllowRESTExecution HideElement(string elementId)
Hides an element by ID client.HideElement("element123").Execute();.
IAllowRESTCaptureOptions WithClearCalls(bool clear=true)
Forgets what was already recorded before starting, so a second capture is not read as the first.
IAllowRESTParametersOrExecution WithCssFromResult(string name)
Sets the CSS from a result by name.
IAllowRESTParametersOrExecution WithKeyFromResult(string name)
Sets the key from a result by name.
IAllowRESTExecution WithTextFromResult(string name)
Sets the text from a result by name.
IAllowRESTParametersOrExecution WithUrlFromResult(string name)
Sets the URL from a result by name.
int StatusCode
The HTTP status code the last call came back with.
Definition RESTClient.cs:72
int RetryAfterSeconds
The seconds the last call was asked to wait before asking again, zero when it was not asked.
Definition RESTClient.cs:77
IAllowRESTExecution SwitchToElement(string elementId)
Switches to an element to search for selectors contained client.SwitchToElement("div123")....
IAllowRESTExecution GetBrowserSettings()
Gets browser settings client.GetBrowserSettings().Execute();.
IAllowRESTExecution Maximize()
Maximizes the browser window client.Maximize().Execute();.
IAllowRESTExecution CloseTab(int tabId)
Closes a tab by tab ID client.CloseTab(1).Execute();.
IAllowRESTParametersOrExecution WithEncoding(ContentEncoding encoding)
Specifiy the encoiding type for posted parameters: json or urlencoded.
IAllowRESTParametersOrExecution WithBytes(bool bytes)
True to bring the fetched response body back base64 encoded, which is what a file needs....
IAllowRESTParametersOrExecution WithCssFromResult(int resultIndex)
Sets the CSS from a result by index.
IAllowRESTExecution WithRangeValue(int rangeValue)
Specifies the value for SetRange operation client.SetRange("element123").WithRangeValue(63)....
IAllowRESTExecution PreviousWindow()
Switches to the next window client.NextTab().Execute();.
IAllowRESTScrollElement ScrollElement(string elementId)
Starts scrolling an element by ID client.ScrollElement("element123").WithHPixels(100)....
IAllowRESTExecution IsVisibleInViewport(string elementId)
Checks if an element is visible in viewport by ID client.IsVisibleInViewport("element123")....
IAllowRESTExecution ScrollElement(int hPixels, int vPixels)
Scrolls an element by horizontal and vertical pixels client.ScrollElement(100, 200)....
IAllowRESTExecution CloseWindow(int windowId)
Closes a window by window ID, optionally switching to the main window client.CloseWindow(123)....
IAllowRESTExecution GetLanguages()
Gets the browser's preferred languages, most preferred first client.GetLanguages()....
IAllowRESTParametersOrExecution WithTabIdFromResult(string name)
Sets the tab ID from a result by name.
IAllowRESTParametersOrExecution WithParametersFromResult(int resultIndex)
Sets parameters from a result by index.
IAllowRESTStorageOptions WithData(string data)
Sets the set storage data parameter.
IAllowRESTParametersOrExecution WithTabIdFromResult(int resultIndex)
Sets the tab ID from a result by index.
IAllowRESTExecution RightClick(string elementId)
Performs a right click on an element by ID client.RightClick("element123").Execute();.
IAllowRESTStorageOptions SetStorage(WebsiteStorageType storageType)
Set data for the specified storage type.
IAllowRESTExecution NextTab()
Switches to the next tab client.NextTab().Execute();.
IAllowCheckNetworkIdleOrExecution WithPruneMs(int pruneMs)
Sets the prune interval in milliseconds.
IAllowRESTStorageOptions WithStorageDomain(string domain)
Sets the storage domain parameter.
IAllowRESTExecution WithHttpMethod(string method)
Sets the HTTP method.
string Execute(bool publishEvent=true)
Executes the request synchronously.
IAllowRESTExecution LeftDoubleClick(string elementId)
Performs a left double click on an element by ID client.LeftDoubleClick("element123")....
IAllowRESTExecution GetUserAgent()
Gets the user agent client.GetUserAgent().Execute<string>();.
IAllowRESTParametersOrExecution WithReferrerFromResult(int resultIndex)
Sets the referrer from a result by index.
IAllowRESTInputText FillInInsert(string elementId)
Appends text to an element client.FillInAppend("input123").WithText("Hello").Execute();.
IAllowRESTDragAndDrop WithOffsetX(int offsetX)
Sets the horizontal grab offset within the element for a drag-and-drop operation.
IAllowRESTExecution SwitchToDefaultContent()
Switches to default content client.SwitchToDefaultContent().Execute();.
IAllowRESTScrollElement WithVPixelsFromResult(string name)
Sets the vertical pixels from a result by name.
IAllowRESTExecution SwitchToFrame(string elementId)
Switches to a frame client.SwitchToFrame("frame123").Execute();.
IAllowRESTExecution GoTo(GPALUrl url)
Navigates to a URL client.Goto("https://example.com").Execute();.
IAllowRESTExecution CloseTab(GPALUrl url=null)
Closes a tab by URL client.CloseTab("https://example.com").Execute();.
IAllowRESTExecution WindowOuterHeight()
Gets the window outer height client.WindowOuterHeight().Execute<int>();.
IAllowRESTParametersOrExecution WithXPathFromResult(string name)
Sets the XPath from a result by name.
async Task< string > ExecuteAsync(bool publishEvent=true)
Executes the request asynchronously and returns the raw response await client.ExecuteAsync();.
IAllowRESTExecution WindowInnerHeight()
Gets the window inner height client.WindowInnerHeight().Execute<int>();.
IAllowRESTExecution QuerySelectors(string css)
Queries multiple elements using a CSS selector client.QuerySelectors(".class").Execute<string[]>();.
IAllowRESTStorageOptions WithStorageType(WebsiteStorageType storageType)
Sets the storage path parameter.
IAllowRESTParametersOrExecution SetValueFrom(string destSelector)
Sets the value of the destination element identified by destSelector from the source element identif...
IAllowRESTExecution IsEndOfPage()
Checks if the page is at the end client.IsEndOfPage().Execute<bool>();.
IAllowRESTScrollElement WithVPixelsFromResult(int resultIndex)
Sets the vertical pixels from a result by index.
IAllowRESTExecution IsClickable(string elementId)
Checks if an element is clickable by ID client.IsClickAble("element123").Execute<bool>();.
IAllowRESTParametersOrExecution WithParametersFromResult(string name)
Sets parameters from a result by name.
IRESTClient AndThen< T >(bool publishEvent=true)
Executes and chains the request with type.
IAllowRESTExecution ScrollWindowByHorizontal(int hPixels)
Scrolls the window horizontally by pixels client.ScrollWindowByHorizontal(100).Execute();.
IAllowRESTExecution EvaluatePersistent(string xpath)
Evaluates an XPath expression client.Evaluate("//div").Execute();.
IAllowRESTExecution OpenWindow(GPALUrl url)
Opens a new window with the specified URL client.OpenWindow("https://example.com")....
IAllowRESTWithUploadFilename LeftClickAndUpload(string elementId)
Purely headless mode, attaches the file to the input[type=file] client.LeftClickAndUpload("element123...
IAllowRESTExecution GetBoundingClientRect(string elementId)
Gets the bounding client rect of an element by ID client.GetBoundingClientRect("element123")....
T Execute< T >(bool publishEvent=true)
Executes the request synchronously with type.
IAllowRESTExecution GetElementAttributeHash(string elementId)
Gets the attribute hash of an element client.GetElementAttributeHash().Execute<string>();.
IAllowRESTParametersOrExecution WithBody(string body)
Sets the body of the request being fetched. client.Fetch(url).WithVerb("POST").WithBody(json)....
IAllowRESTExecution WithHeaderFromResult(string name)
Sets the header from a result by name.
IAllowRESTParametersOrExecution WithParameters(params object[] parameters)
Sets parameters as a variable argument list.
IAllowRESTAttribute WithValue(string value)
Specifies the value for set operation client.SetAttribute("element123").WithAttribute("disabled")....
IAllowRESTExecution TrustedLeftClick(int x, int y, int holdMs=0)
Clicks a viewport point through the browser's own input pipeline, so the page reads the event as trus...
IAllowRESTExecution PreviousTab()
Switches to the previous tab client.PreviousTab().Execute();.
IAllowRESTExecution WithUploadFiles(GPALFile uploadPaths)
Sets the timeout in milliseconds.
IAllowRESTParametersOrExecution While(Func< bool > condition)
Executes workflows while condition holds client.While(() => someCondition).Execute();.
IAllowRESTParametersOrExecution Fetch(string url)
Issue an API request from inside the page, so it carries the session the browser has already earned: ...
IAllowRESTParametersOrExecution WithWindowIdFromResult(int resultIndex)
Sets the window ID from a result by index client.GoToWindow().WithWindowIdFromResult(0)....
IAllowRESTParametersOrExecution WithScript(string script)
Sets the script parameter.
IAllowRESTExecution GoToWindow(int windowId)
Switches to a window by window ID client.GoToWindow(123).Execute<WindowTuple>();.
IAllowRESTExecution GetCurrentWindow()
Gets the current window's ID and URL client.GetCurrentWindow().Execute<WindowTuple>();.
IAllowRESTExecution Focus(string elementId)
Focuses an element by ID client.Focus("element123").Execute();.
IAllowCheckNetworkIdleOrExecution WithTimeoutMs(int timeoutMs)
Sets the timeout in milliseconds.
IAllowRESTParametersOrExecution WithHeaders(string[] headers)
Sets extra headers for the request being fetched, flattened to name, value, name, value....
IAllowRESTParametersOrExecution WithUrlFromResult(int resultIndex)
Sets the URL from a result by index.
IAllowRESTExecution ReleaseModifierKey(ModifierKeys modifierKeys)
Press the modifier keys specified. Keep pressed until a release is sent.
IAllowRESTParameters ClearInjectedScripts()
Removes all scripts previously registered via InjectScript(string).
IAllowRESTExecution PageUp(int pagesToScroll=1)
Scrolls the page up by pages client.PageUp(1).Execute();.
IAllowRESTExecution NewTab(GPALUrl url)
Opens a new tab with optional URL client.NewTab("https://example.com").Execute();.
string Name
Gets or sets the client name.
IAllowRESTExecution GetCapturedCalls()
Everything recorded so far, oldest first, as the json the extension keeps. string json = client....
IAllowRESTAttribute SetAttribute(string elementId)
Starts setting an attribute of an element by ID client.SetAttribute("element123")....
IAllowRESTExecution WithHeaderFromResult(int resultIndex)
Sets the header from a result by index.
IAllowRESTExecution ClearReferrer()
Clears the referrer client.ClearReferrer().Execute();.
IAllowRESTExecution IsEnabled(string elementId)
Checks if an element is enabled by ID client.IsEnabled("element123").Execute<bool>();.
IAllowRESTExecution PageEnd()
Scrolls to the page end client.PageEnd().Execute();.
IAllowRESTExecution Normal()
Restores the browser window to normal client.Normal().Execute();.
IAllowRESTExecution Restore()
Restores the browser window client.Restore().Execute();.
IAllowRESTExecution QueryPersistentSelectors(string css)
Queries multiple elements using a CSS selector client.QuerySelectors(".class").Execute<string[]>();.
IAllowRESTExecution TabCount()
How many tabs the browser actually has open, which is not the same as how many GPAL opened....
IAllowRESTExecution SetGoogleReferrer()
Overrides referrer stealthily - sets to google.com client.SetGoogleReferrer().Execute();.
IAllowRESTEndpointDetails WithName(string name)
Sets the client name.
IAllowRESTExecution GetOptions(string elementId)
Get the options in a Select manue.
IAllowRESTExecution GetReadyStatus()
Gets the ready status client.GetReadyStatus().Execute<string>();.
async Task< T > ExecuteAsync< T >(bool publishEvent=true)
Executes the request asynchronously with type.
IAllowRESTExecution GoToTab(int tabId)
Navigates to a tab by tab ID client.GotoTab(1).Execute();.
IAllowRESTExecution GetParentNode(string elementId)
Gets the parent node of an element by ID client.GetParentNode("element123").Execute<string>();.
IAllowRESTAttribute WithAttribute(string attribute)
Specifies the attribute name for get or set operation client.GetAttribute("element123")....
IAllowRESTExecution SwitchToShadowRoot(string elementId)
Switches to a shadow root client.SwitchToShadowRoot("shadow123").Execute();.
IAllowRESTExecution MoveTo(string elementId)
Moves to an element client.MoveTo("element123").Execute();.
IAllowRESTInputText FillInAppend(string elementId)
Appends text to an element client.FillInAppend("input123").WithText("Hello").Execute();.
IAllowRESTParametersOrExecution DownloadTo(GPALFile downloadPath)
Where a file the browser downloads should end up. Not SaveTo, which is GPAL writing a file it alread...
IAllowRESTParametersOrExecution WithEndpoint(ApiEndpoint endpoint)
Sets the endpoint from an ApiEndpoint enum.
IAllowRESTExecution PressModifierKey(ModifierKeys modifierKeys)
Press the modifier keys specified. Keep pressed until a release is sent.
IAllowRESTParametersOrExecution WithReferrerFromResult(string name)
Sets the referrer from a result by name.
IAllowRESTParameters ExecuteJavaScript(string script)
Executes JavaScript code client.ExecuteJavaScript("return document.title").Execute<string>();.
IAllowRESTParametersOrExecution WithElementId(string elementId)
Sets the element ID parameter.
IAllowRESTStorageOptions WithStorageStoreName(string storeName)
Sets the storage domain parameter.
IAllowRESTExecution GetSettings()
Gets settings client.GetSettings().Execute();.
IAllowRESTExecution WithHeader(string name, string value)
Adds an HTTP header to the request client.WithHeader("Authorization", "Bearer YOUR_ACCESS_TOKEN")....
IAllowRESTExecution TrustedMiddleClick(int x, int y, int holdMs=0)
Middle clicks a viewport point through the browser's own input pipeline, so the page reads the event ...
IAllowRESTParametersOrExecution WithPixelsFromResult(int resultIndex)
Sets the pixels from a result by index.
IAllowRESTParametersOrExecution WithKey(string key)
Sets the key parameter.
IAllowRESTParametersOrExecution WithElementIdFromResult(string name)
Sets the element from a result by name.
IAllowRESTParametersOrExecution WithElement(IGPALElement gPALElement)
Sets the element parameter.
IAllowRESTExecution EvaluateAll(string xpath)
Evaluates an XPath expression for multiple elements client.EvaluateAll("//div").Execute<string[]>();.
IAllowRESTCaptureOptions WithCallFilter(string urlFragment)
Records only the calls whose url holds this, so a page that talks to a dozen places narrows to the on...
IAllowRESTExecution GetHydrationScripts()
Gets the page title, the NEXT_DATA payload and any React Flight chunks, read from the document rather...
IAllowRESTExecution WithTextFromResult(int resultIndex)
Sets the text from a result by index.
IAllowSelectOptions SelectClick(string elementId)
Click a Select menu option.
IAllowRESTExecution CaptureVisibleTab(ImageFormat imageFormat=ImageFormat.JPEG)
Get a screenshot of the current tab visble contents.
IAllowRESTAttribute GetAttribute(string elementId)
Starts getting an attribute of an element by ID client.GetAttribute("element123")....
IAllowRESTExecution CloseWindow(GPALUrl url)
Closes a window by URL, optionally switching to the main window client.CloseWindow("https://example....
IAllowRESTParametersOrExecution WithXPathFromResult(int resultIndex)
Sets the XPath from a result by index.
IAllowRESTExecution ElementFromPoint(string elementId, int x, int y)
Asks whether a click at this viewport point would reach the element, rather than land on whatever is ...
IRESTClient AndThen(bool publishEvent=true)
Executes and chains the request.
IAllowRangeValue SetRange(string elementId)
Starts setting an attribute of an element by ID client.SetAttribute("element123")....
IAllowRESTExecution WithUploadFile(string uploadPath)
Sets the timeout in milliseconds.
IAllowRESTEndpointDetails WithAPIBase(string url)
Sets the API base URL with download path.
IReadOnlyList< object > GetExecutionResults(string name)
Every result stored under name , in the order it was stored, so a loop that ran five times answers wi...
IAllowRESTExecution WithText(string text)
Sets the text parameter.
IAllowRESTExecution Refresh()
Refreshes the current page client.Refresh().Execute();.
IAllowRESTExecution FullScreen()
Sets browser to full screen client.FullScreen().Execute();.
IAllowRESTExecution CheckNetworkIdle(int maxConnections=0)
Checks if network is idle with max connections client.CheckNetworkIdle(5).Execute<bool>();.
IAllowRESTExecution SendString(string text, int delayMs=0)
Sends a string client.SendString("Hello").Execute();.
IAllowRESTParametersOrExecution WithPixels(int pixels)
Sets the pixels parameter.
IAllowRESTExecution NextWindow()
Switches to the next window client.NextTab().Execute();.
IAllowRESTExecution WithTimeout(TimeSpan httpTimeout)
How long one attempt is given before it is abandoned. Said after the base url, so a client handed a s...
IAllowRESTExecution MiddleClick(string elementId)
Performs a left click on an element by ID client.LeftClick("element123").Execute();.
IAllowRESTExecution ClearResults()
Empties what WithResultName has stored, so GetExecutionResults answers about what has happened since ...
IAllowRESTExecution PageDown(int pagesToScroll=1)
Scrolls the page down by pages client.PageDown(1).Execute();.
IRESTClient ToGPALObject()
Converts the client to a GPAL object.
IAllowRESTExecution GetShadowRoot(string css)
Gets the shadow root for a CSS selector client.GetShadowRoot(".class").Execute();.
IAllowRESTExecution EvaluateAllPersistent(string xpath)
Evaluates an XPath expression for multiple elements client.EvaluateAll("//div").Execute<string[]>();.
IAllowRESTExecution TopBrowser()
Top the browser, bring it into focus. Used before performing any actions on the browser.
IAllowRESTWorkflow ClearWorkflows()
Gets the execution results.
IAllowRESTExecution GetWorkflow()
Gets the workflow client.GetWorkflow().Execute();.
IAllowRESTScrollElement WithVPixels(int vPixels)
Sets the vertical pixels parameter.
IAllowRESTExecution WindowOuterWidth()
Gets the window outer width client.WindowOuterWidth().Execute<int>();.
IAllowRESTExecution GetGpalSettings()
Gets GPAL settings client.GetGpalSettings().Execute();.
IAllowRESTExecution Minimize()
Minimizes the browser window client.Minimize().Execute();.
IAllowRESTParametersOrExecution SaveResultsTo(GPALFile file)
Saves the response to a file if the endpoint returns a downloadable content client....
IAllowRESTParametersOrExecution WithReferrer(string url)
Sets the referrer parameter.
IAllowRESTParametersOrExecution WithParameters(object parameters)
Sets parameters as a single object.
IAllowRESTEndpointDetails WithAPIBase(string url, TimeSpan httpTimeout)
Sets the API base URL and how long one attempt is given before it is abandoned.
IAllowRESTParametersOrExecution Until(Func< bool > condition)
Executes workflows until condition holds.
IAllowRESTExecution Evaluate(string xpath)
Evaluates an XPath expression client.Evaluate("//div").Execute();.
IAllowRESTStorageOptions WithStorageKey(string key)
Sets the storage key parameter.
IAllowRESTDragAndDrop WithOffsetY(int offsetY)
Sets the vertical grab offset within the element for a drag-and-drop operation.
IAllowRESTDragAndDrop WithDeltaX(int deltaX)
Sets the horizontal drop offset for a drag-and-drop operation.
IAllowRESTScrollElement WithHPixels(int hPixels)
Sets the horizontal pixels parameter.
IAllowRESTExecution PageTop()
Scrolls to the page top client.PageTop().Execute();.
IAllowRESTParametersOrExecution WithTabId(int tabId)
Sets the tab ID parameter.
IAllowRESTExecution CloseBrowser()
Closes every window the browser has, which is how it is asked to quit client.CloseBrowser()....
IAllowRESTExecution SetUserAgent(string userAgent)
Overrides the browser's user agent string client.SetUserAgent("Mozilla/5.0 ...").Execute();.
IAllowRESTExecution Hover(string elementId)
Hovers over an element by ID client.Hover("element123").Execute();.
IAllowRESTExecution WindowScreenTop()
Gets the window screen top position client.WindowScreenTop().Execute<int>();.
IAllowRESTExecution GetCurrentUrl()
Gets the current URL client.GetCurrentUrl().Execute<string>();.
IAllowRESTDragAndDrop DragAndDrop(string elementId)
Drags an element from its current position by the given offset and drops it client....
IAllowRESTParametersOrExecution WithVerb(string method)
Sets the verb of the request being fetched. Not .WithHttpMethod, which is the verb of the REST call i...
IAllowRESTExecution FireChangeEvent(string elementId)
Fires a change event on an element by ID client.FireChangeEvent("element123").Execute();.
IAllowRESTExecution WindowPageOffsetY()
Gets the window page offset Y client.WindowPageOffsetY().Execute<int>();.
IAllowRESTExecution WithSelectValue(string selectValue)
Set the value to choose from the Select menu.
IAllowRESTParametersOrExecution WithWindowId(int windowId)
Sets the window ID parameter client.CloseWindow(123).Execute<WindowTuple>();.
IAllowRESTExecution Forward()
Navigates browser history forward client.Forward().Execute();.
IAllowRESTExecution WithDeviceName(string sinkName)
Sets the CSS parameter.
IAllowRESTExecution SendKey(byte vkcode)
Sends a key client.SendKey("Enter").Execute();.
IAllowRESTExecution ScrollWindowByVertical(int vPixels)
Scrolls the window vertically by pixels client.ScrollWindowByVertical(100).Execute();.
IAllowRESTScrollElement WithHPixelsFromResult(int resultIndex)
Sets the horizontal pixels from a result by index.
IAllowRESTExecution QuerySelector(string css)
Queries a single element using a CSS selector client.QuerySelector(".class").Execute();.
IAllowRESTExecution GoToTab(GPALUrl url)
Navigates to a tab by URL client.GotoTab("https://example.com").Execute();.
IAllowRESTInputText FillInOverwrite(string elementId)
Appends text to an element client.FillInAppend("input123").WithText("Hello").Execute();.
IAllowRESTExecution GoToWindow(GPALUrl url)
Switches to a window by URL client.GoToWindow("https://example.com").Execute<WindowTuple>();.
IAllowRESTStorageOptions GetStorage(WebsiteStorageType storageType)
Get the specified storage type.
IAllowRESTParametersOrExecution WithContentType(string contentType)
Sets the content type of the body of the request being fetched. client.Fetch(url)....
IAllowRESTExecution LeftClick(string elementId)
Performs a left click on an element by ID client.LeftClick("element123").Execute();.
IAllowRESTParameters InjectScript(string script)
Registers a script to run on every new document load. Persists until ClearInjectedScripts is called.
IAllowRESTWithDownloadFilename LeftClickAndDownload(string elementId)
Performs a left click and download on an element client.LeftClickAndDownload("element123")....
IAllowRESTCaptureOptions CaptureCalls(bool capture=true)
Starts or stops recording what the page asks for. Narrow it with WithCallFilter and start from nothin...
IAllowRESTExecution OverrideReferrer(string referrer)
Overrides the referrer URL client.OverrideReferrr("https://google.com").Execute();.
IAllowCheckNetworkIdleOrExecution WithMaxConnections(int maxConnections)
Sets the maximum connections.
IAllowRESTExecution GetDocumentStamp()
Gets the current URL and the document identity in one call. A form posting to itself and a route rew...
IAllowRESTParametersOrExecution WithPixelsFromResult(string name)
Sets the pixels from a result by name.
IAllowRESTWorkflow WithWorkflow(Action< RESTClient > workflow)
Adds a workflow definition client.WithWorkflow(c => c.WithEndpoint(...).Execute())....
IAllowRESTExecution WindowPageOffsetX()
Gets the window page offset X client.WindowPageOffsetX().Execute<int>();.
IAllowRESTExecution IsDisplayed(string elementId)
Checks if an element is displayed by ID client.IsDisplayed("element123").Execute<bool>();.
IAllowRESTExecution SetDownloadFilename(string downloadPath)
Sets the download filename for the current session client.SetDownloadFilename("/path/to/file")....
IAllowRESTScrollElement WithHPixelsFromResult(string name)
Sets the horizontal pixels from a result by name.
IAllowRESTParametersOrExecution WithEndpoint(string endpoint)
Sets the endpoint as a string.
IAllowRESTExecution QueryPersistentSelector(string css)
Queries a single element using a CSS selector client.QuerySelector(".class").Execute();.
IAllowRESTDragAndDrop WithDeltaY(int deltaY)
Sets the vertical drop offset for a drag-and-drop operation.
IAllowRESTAttribute WithIndex(int index)
Specifies the option index for select menu operations client.SetAttribute("element123")....
static bool TryConvertVkCodeToDomKey(byte vkCode, out string key, out string code)
Converts a Windows VK code to DOM KeyboardEvent key and code values.