GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
PupeteerClient.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.Drawing;
20using System.IO;
21using System.Linq;
22using System.Security.Policy;
23using System.Threading.Tasks;
24using System.Windows.Controls.Primitives;
25using Newtonsoft.Json;
26using Newtonsoft.Json.Linq;
27using OpenQA.Selenium;
28using static GenerallyPositive.Enums;
29
31{
32 public class PuppeteerClient : IPuppeteerClient
33 {
37 private readonly List<Action<PuppeteerClient>> _workflows = new List<Action<PuppeteerClient>>();
41 private List<(string Endpoint, object Parameters, string NextResultName)?> _workflowStates { get; set; } = new List<(string, object, string)?>();
45 private bool _customEndpoint { get; set; } = false;
49 private ApiEndpoint _apiEndpoint;
53 private string _endpoint { get; set; }
57 private object _parameters { get; set; }
61 internal ContentEncoding _contentEncoding = ContentEncoding.Json;
65 private string _name { get; set; }
69 private string _nextResultName { get; set; } // = "results"; .. do not save results unless asked to, this is a potential memory leak, it just keeps accumulating
73 private string _saveFile { get; set; }
74
78 private readonly List<(string Name, object Result, int Iteration, string TargetId)> _executionResults = new List<(string Name, object Result, int Iteration, string TargetId)>();
82 private PuppeteerCommunicator _puppeteerCommunicator { get; set; }
86 private int _currentIteration { get; set; }
90 internal Dictionary<string, string> _headers = new Dictionary<string, string>();
94 private int _timeoutMs = 30000;
98 private int _pruneMs = 3_000;
102 private int _maxConnections { get; set; }
106 private int _hPixels { get; set; }
110 private int _vPixels { get; set; }
114 private string _attribute { get; set; }
118 private string _value { get; set; }
122 private string _text { get; set; }
126 private RESTClient restClient { get; set; } // for validation
130 internal string _currentTargetId { get; set; }
134 private Dictionary<string, Func<PuppeteerCommunicator, IDictionary<string, object>, string, Task<object>>> endpointMap { get; set; }
135
145 private int _whileLoopTimeoutMs { get; set; } // loop timeout
149 private int _whileLoopMaxIterations { get; set; } // loop max iterations
150
156 internal PuppeteerClient()
157 {
158 restClient = new RESTClient();
159 init();
160 }
161
165 private void init()
166 {
167 endpointMap = new Dictionary<string, Func<PuppeteerCommunicator, IDictionary<string, object>, string, Task<object>>>
168 {
169 [RESTHelper.Endpoints[ApiEndpoint.Back]] = async (c, p, s) =>
170 {
171 return await c.Back(s).ConfigureAwait(false);
172 },
173 [RESTHelper.Endpoints[ApiEndpoint.CaptureVisibleTab]] = async (c, p, s) =>
174 {
175 var parameters = new Dictionary<string, object> { { "format", "jpeg" } };
176 if (p.ContainsKey("clip"))
177 {
178 dynamic format = p.ContainsKey("imageFormat") ? p["imageFormat"] : "JPEG";
179 var clip = p["clip"] as IDictionary<string, object>;
180 if (clip != null)
181 {
182 double x = clip.ContainsKey("x") ? Convert.ToDouble(clip["x"]) : 0;
183 double y = clip.ContainsKey("y") ? Convert.ToDouble(clip["y"]) : 0;
184 double width = clip.ContainsKey("width") ? Convert.ToDouble(clip["width"]) : Puppeteer.GetScreenWidthInt();
185 double height = clip.ContainsKey("height") ? Convert.ToDouble(clip["height"]) : Puppeteer.GetScreenHeightInt();
186 double scale = clip.ContainsKey("scale") ? Convert.ToDouble(clip["scale"]) : 1.0;
187
188 if (width > 0 && height > 0 && x >= 0 && y >= 0)
189 {
190 parameters = new Dictionary<string, object>
191 {
192 { "format", format },
193 {
194 "clip", new Dictionary<string, object>
195 {
196 { "x", x },
197 { "y", y },
198 { "width", width },
199 { "height", height },
200 { "scale", scale }
201 }
202 }
203 };
204 }
205 else
206 {
207 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid clip parameters: x=[{x}], y=[{y}], width=[{width}], height=[{height}]. Using viewport.", this, GPALObjectType.PuppeteerClient);
208 }
209 }
210 }
211 JObject retData = await c.SendCommand(DevToolsMethods.PageCaptureScreenshot, parameters, s).ConfigureAwait(false);
212 string base64 = retData["data"]?.ToString();
213
214 return base64;
215 },
216 [RESTHelper.Endpoints[ApiEndpoint.CastDesktop]] = async (c, p, s) =>
217 {
218 string sinkName = p.ContainsKey("sinkName") ? p["sinkName"]?.ToString() : null;
219 await c.CastDesktop(sinkName, s).ConfigureAwait(false);
220 return true;
221 },
222 [RESTHelper.Endpoints[ApiEndpoint.CastTab]] = async (c, p, s) =>
223 {
224 string sinkName = p.ContainsKey("sinkName") ? p["sinkName"]?.ToString() : null;
225 await c.CastTab(sinkName, s).ConfigureAwait(false);
226 return true;
227 },
228 [RESTHelper.Endpoints[ApiEndpoint.CheckNetworkIdle]] = async (c, p, s) =>
229 {
230 string sessionToken = Guid.NewGuid().ToString();
231 return await c.CheckNetworkIdle(s, _maxConnections, _timeoutMs, _pruneMs, sessionToken).ConfigureAwait(false);
232 },
233 [RESTHelper.Endpoints[ApiEndpoint.ClearReferrer]] = async (c, p, s) =>
234 {
235 return await c.SendCommand(DevToolsMethods.NetworkSetExtraHTTPHeaders, new Dictionary<string, object> { { "headers", new Dictionary<string, object> { { "Referer", "" } } } }, s).ConfigureAwait(false);
236 },
237 [RESTHelper.Endpoints[ApiEndpoint.CloseTab]] = async (c, p, s) =>
238 {
239 string targetId = null;
240 if (p.ContainsKey("tabId"))
241 {
242 int tabId = int.Parse(p["tabId"]?.ToString());
243 var queueArray = c.CurrentSessions.ToArray();
244 if (tabId >= 0 && tabId < queueArray.Length)
245 {
246 targetId = queueArray[tabId].Key;
247 }
248 }
249 else
250 {
251 targetId = c.GetCurrentTargetId(); // Default to active
252 }
253
254 if (targetId != null)
255 {
256 await c.CloseTab(targetId, s).ConfigureAwait(false);
257 return true;
258 }
259 return false;
260 },
261 [RESTHelper.Endpoints[ApiEndpoint.CloseWindow]] = async (c, p, s) =>
262 {
263 string targetId = null;
264 string windowId = p.ContainsKey("windowId") ? p["windowId"]?.ToString() : null;
265 string url = p.ContainsKey("url") ? p["url"]?.ToString() : null;
266
267 if (!string.IsNullOrEmpty(windowId))
268 {
269 targetId = windowId; // Direct browserContextId
270 }
271 else if (!string.IsNullOrEmpty(url))
272 {
273 targetId = await c.GetTargetWindowIdByUrl(url).ConfigureAwait(false);
274 if (targetId != null)
275 {
276 // Set the window as active to ensure correct context
277 int windowIndex = c.GetCurrentWindowIndex(targetId);
278 if (windowIndex >= 0)
279 {
280 c.SetActiveWindowIndex(windowIndex);
281 }
282 }
283 }
284 else
285 {
286 targetId = c.GetCurrentWindowTargetId(); // Current window's BrowserContextId
287 }
288
289 if (!string.IsNullOrEmpty(targetId))
290 {
291 await c.CloseWindow(targetId, s).ConfigureAwait(false);
292 return true;
293 }
294
295 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"Unable to find window for targetId [{targetId}] or URL [{url}]", c, GPALObjectType.Puppeteer);
296 return false;
297 },
298 [RESTHelper.Endpoints[ApiEndpoint.DeleteStorage]] = async (c, p, s) =>
299 {
300 var storageType = p.ContainsKey("storageType") ? p["storageType"]?.ToString() : "";
301 var domain = p.ContainsKey("domain") ? p["domain"]?.ToString() : "";
302 var path = p.ContainsKey("path") ? p["path"]?.ToString() : "";
303 var key = p.ContainsKey("key") ? p["key"]?.ToString() : "";
304 var storeName = p.ContainsKey("storeName") ? p["storeName"]?.ToString() : "";
305
306 return await c.DeleteStorage(storageType, s, domain, storeName, path, key).ConfigureAwait(false);
307 },
308 [RESTHelper.Endpoints[ApiEndpoint.DragAndDrop]] = async (c, p, s) =>
309 {
310 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
311 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
312 var deltaX = p.ContainsKey("deltaX") ? Convert.ToInt32(p["deltaX"]) : 0;
313 var deltaY = p.ContainsKey("deltaY") ? Convert.ToInt32(p["deltaY"]) : 0;
314 var offsetX = p.ContainsKey("offsetX") ? Convert.ToInt32(p["offsetX"]) : 0;
315 var offsetY = p.ContainsKey("offsetY") ? Convert.ToInt32(p["offsetY"]) : 0;
316 List<dynamic> responses = new List<dynamic>();
317
318 foreach (GPALElement elem in elems)
319 {
320 await c.ScrollIntoView(elem, s).ConfigureAwait(false);
321 await c.DragAndDrop(elem, s, responses, deltaX, deltaY, offsetX, offsetY).ConfigureAwait(false);
322 }
323
324 return (responses, elems);
325 },
326 [RESTHelper.Endpoints[ApiEndpoint.Evaluate]] = async (c, p, s) =>
327 {
328 var xpath = p.ContainsKey("xpath") ? p["xpath"]?.ToString() : "";
329 xpath = xpath.Replace("'", "\\'");
330 List<GPALElement> elems = await _puppeteerCommunicator.EvaluateSelector(xpath, s).ConfigureAwait(false);
331
332 if (0 < elems.Count)
333 return elems[0];
334 else
335 return null;
336 // return await c.Evaluate(xpath, s).ConfigureAwait(false);
337 },
338 [RESTHelper.Endpoints[ApiEndpoint.EvaluateAll]] = async (c, p, s) =>
339 {
340 var xpath = p.ContainsKey("xpath") ? p["xpath"]?.ToString() : "";
341
342 xpath = xpath.Replace("'", "\\'");
343 return await _puppeteerCommunicator.EvaluateSelector(xpath, s).ConfigureAwait(false);
344 /*
345 var expression = $@"(function() {{
346 const result = document.evaluate('{xpath}', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
347 const elementsArray = [];
348 for (let i = 0; i < result.snapshotLength; i++) {{
349 elementsArray.push(result.snapshotItem(i));
350 }}
351 console.log('Nodes matched:', result.snapshotLength);
352 return elementsArray;
353 }})()";
354
355 // Step 1: Evaluate to get the array of nodes
356 var evalResponse = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
357 {
358 expression,
359 returnByValue = false
360 }, s).ConfigureAwait(false);
361
362 var arrayObjectId = evalResponse["result"]?["objectId"]?.ToString();
363 if (string.IsNullOrEmpty(arrayObjectId))
364 {
365 throw new InvalidOperationException("EvaluateAll failed to return an array objectId.");
366 }
367
368 // Step 2: Fetch array elements' properties
369 var propsResponse = await c.SendCommand<object>(DevToolsMethods.RuntimeGetProperties, new
370 {
371 objectId = arrayObjectId,
372 ownProperties = true
373 }, s).ConfigureAwait(false);
374
375 // Step 3: Extract node references and serialize to JSON strings
376 var nodeReferences = new List<string>();
377 foreach (var prop in propsResponse["result"])
378 {
379 int dummy;
380 var name = prop["name"]?.ToString();
381 if (int.TryParse(name, out dummy)) // Numeric indices
382 {
383 var value = prop["value"];
384 if (value != null)
385 {
386 nodeReferences.Add(value.ToString()); // Serialize to JSON string
387 }
388 }
389 }
390
391 // Step 4: Return as a value array
392 var response = new JObject
393 {
394 ["result"] = new JObject
395 {
396 ["type"] = "object",
397 ["subtype"] = "array",
398 ["className"] = "Array",
399 ["value"] = new JArray(nodeReferences)
400 }
401 };
402
403 return response;
404 */
405 },
406 [RESTHelper.Endpoints[ApiEndpoint.Fetch]] = async (c, p, s) =>
407 {
408 return await c.Fetch(
409 p.ContainsKey("url") ? p["url"]?.ToString() : null,
410 p.ContainsKey("method") ? p["method"]?.ToString() : null,
411 p.ContainsKey("body") ? p["body"]?.ToString() : null,
412 p.ContainsKey("contentType") ? p["contentType"]?.ToString() : null,
413 p.ContainsKey("headers") ? p["headers"] as string[] : null,
414 p.ContainsKey("bytes") && true == Convert.ToBoolean(p["bytes"]),
415 s).ConfigureAwait(false);
416 },
417 [RESTHelper.Endpoints[ApiEndpoint.ExecuteJavaScript]] = async (c, p, s) =>
418 {
419 var expression = p.ContainsKey("expression") ? p["expression"]?.ToString() : p["script"]?.ToString();
420 return await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
421 {
422 expression,
423 returnByValue = true,
424 awaitPromise = true
425 }, s).ConfigureAwait(false);
426 },
427 [RESTHelper.Endpoints[ApiEndpoint.FillInAppend]] = async (c, p, s) =>
428 {
429 List<dynamic> responses = new List<dynamic>();
430
431 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
432 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
433 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
434
435 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
436
437 if (null == elems && null != parm)
438 elems = await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
439
440 foreach (GPALElement element in elems)
441 {
442 await c.ScrollIntoView(element, s).ConfigureAwait(false);
443
444 // focus, not a click. Input.insertText goes to whatever has focus, and a click only ever
445 // got us that as a side effect while costing a coordinate the element may not be at
446 await c.FocusElement(element.ElementHandle, s).ConfigureAwait(false);
447 responses.Add(await c.FillIn(element.ElementHandle, element.Value + text, typingDelay, s).ConfigureAwait(false));
448 /*
449 string selector = !string.IsNullOrEmpty(element.Css) ? element.Css : element.Xpath; ;
450 string expression = $@"
451 (function() {{
452 var el = null;
453 try {{
454 el = document.querySelector('{selector.Replace("'", "\\'")}');
455 }} catch(e) {{}}
456 if (!el) {{
457 try {{
458 el = document.evaluate(
459 '{selector.Replace("'", "\\'")}',
460 document,
461 null,
462 XPathResult.FIRST_ORDERED_NODE_TYPE,
463 null
464 ).singleNodeValue;
465 }} catch(e) {{}}
466 }}
467 if (el) {{
468 el.value += '{text.Replace("'", "\\'")}';
469 }}
470 }})();
471 ";
472
473 responses.Add(await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
474 {
475 expression,
476 returnByValue = true
477 }, s));
478 */
479 }
480 return new { responses, elems };
481 },
482 [RESTHelper.Endpoints[ApiEndpoint.FillInInsert]] = async (c, p, s) =>
483 {
484 List<dynamic> responses = new List<dynamic>();
485 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
486 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
487 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
488
489 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
490
491 if (null == elems && null != parm)
492 elems = await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
493
494 foreach (GPALElement element in elems)
495 {
496 await c.ScrollIntoView(element, s).ConfigureAwait(false);
497
498 // focus, not a click. Input.insertText goes to whatever has focus, and a click only ever
499 // got us that as a side effect while costing a coordinate the element may not be at
500 await c.FocusElement(element.ElementHandle, s).ConfigureAwait(false);
501 responses.Add(await c.FillIn(element.ElementHandle, text + element.Value, typingDelay, s).ConfigureAwait(false));
502 /*
503 string selector = !string.IsNullOrEmpty(element.Css) ? element.Css : element.Xpath; ;
504 string expression = $@"
505 (function() {{
506 var el = null;
507 try {{
508 el = document.querySelector('{selector.Replace("'", "\\'")}');
509 }} catch(e) {{}}
510 if (!el) {{
511 try {{
512 el = document.evaluate(
513 '{selector.Replace("'", "\\'")}',
514 document,
515 null,
516 XPathResult.FIRST_ORDERED_NODE_TYPE,
517 null
518 ).singleNodeValue;
519 }} catch(e) {{}}
520 }}
521 if (el) {{
522 el.value = '{text.Replace("'", "\\'")}' + el.value;
523 }}
524 }})();
525 ";
526
527 await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
528 {
529 expression,
530 returnByValue = true
531 }, s);
532 */
533 }
534 return new { responses, elems };
535 },
536 [RESTHelper.Endpoints[ApiEndpoint.FillInOverwrite]] = async (c, p, s) =>
537 {
538 List<dynamic> responses = new List<dynamic>();
539 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
540 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
541 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
542
543 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
544
545 if (null == elems && null != parm)
546 elems = await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
547
548 foreach (GPALElement element in elems)
549 {
550 await c.ScrollIntoView(element, s).ConfigureAwait(false);
551
552 // focus, not a click. Input.insertText goes to whatever has focus, and a click only ever
553 // got us that as a side effect while costing a coordinate the element may not be at
554 await c.FocusElement(element.ElementHandle, s).ConfigureAwait(false);
555 responses.Add(await c.FillIn(element.ElementHandle, text, typingDelay, s).ConfigureAwait(false));
556 }
557 return new { responses, elems };
558 },
559 [RESTHelper.Endpoints[ApiEndpoint.FireChangeEvent]] = async (c, p, s) =>
560 {
561 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
562 return await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
563 {
564 expression = $"document.querySelector('{selector.Replace("'", "\\'")}').dispatchEvent(new Event('change'))",
565 returnByValue = true
566 }, s).ConfigureAwait(false);
567 },
568 [RESTHelper.Endpoints[ApiEndpoint.Focus]] = async (c, p, s) =>
569 {
570 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
571 //var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
572 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
573
574 foreach (GPALElement elem in elems)
575 {
576 var requestNodeResult = await c.SendCommand<object>(DevToolsMethods.DOMRequestNode, new { objectId = elem.ElementHandle }, s).ConfigureAwait(false);
577
578 if (requestNodeResult?.nodeId != null)
579 {
580 return await c.SendCommand<object>(DevToolsMethods.DOMFocus, new { nodeId = (int)requestNodeResult.nodeId }, s).ConfigureAwait(false);
581 }
582
583 //var doc = await c.SendCommand<object>(DevToolsMethods.DOMGetDocument, new { depth = 0 }, s);
584 //var node = await c.SendCommand<object>(DevToolsMethods.DOMQuerySelector, new { nodeId = (int)doc.root.nodeId, selector }, s);
585 //if (node?.nodeId != null)
586 //{
587 // return await c.SendCommand<object>(DevToolsMethods.DOMFocus, new { nodeId = (int)node.nodeId }, s);
588 //}
589 }
590 return null;
591 },
592 [RESTHelper.Endpoints[ApiEndpoint.Forward]] = async (c, p, s) =>
593 {
594 return await c.Forward(s).ConfigureAwait(false);
595 },
596 [RESTHelper.Endpoints[ApiEndpoint.FullScreen]] = async (c, p, s) =>
597 {
598 var windowId = await c.GetCurrentWindow(s).ConfigureAwait(false);
599 return await c.SendCommand(DevToolsMethods.BrowserSetWindowBounds, new
600 {
601 windowId = windowId,
602 bounds = new { windowState = "fullscreen" }
603 }, s).ConfigureAwait(false);
604 },
605 [RESTHelper.Endpoints[ApiEndpoint.GetAttribute]] = async (c, p, s) =>
606 {
607 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
608 var attribute = p.ContainsKey("attribute") ? p["attribute"]?.ToString() : "";
609 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
610
611 foreach (GPALElement elem in elems)
612 return elem.GetAttribute(attribute);
613
614 return null;
615 },
616 [RESTHelper.Endpoints[ApiEndpoint.GetBoundingClientRect]] = async (c, p, s) =>
617 {
618 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null; // (string) backendnodeid
619
620 return await c.GetBoundingClientRect(Int32.Parse(parm)).ConfigureAwait(false);
621 },
622 [RESTHelper.Endpoints[ApiEndpoint.GetContentAndCss]] = async (c, p, s) =>
623 {
624 var selector = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : "";
625 return await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
626 {
627 expression = $"(() => {{ const el = document.querySelector('{selector.Replace("'", "\\'")}'); return {{ text: el.textContent, css: window.getComputedStyle(el).cssText }}; }})()",
628 returnByValue = true
629 }, s).ConfigureAwait(false);
630 },
631 [RESTHelper.Endpoints[ApiEndpoint.GetCssAttributes]] = async (c, p, s) =>
632 {
633 string backendNodeId = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : null;
634
635 return await c.GetCssAttributes(Int32.Parse(backendNodeId), s).ConfigureAwait(false);
636 },
637 [RESTHelper.Endpoints[ApiEndpoint.GetCurrentUrl]] = async (c, p, s) =>
638 {
639 return await c.GetCurrentUrl(s).ConfigureAwait(false);
640 },
641 [RESTHelper.Endpoints[ApiEndpoint.GetCurrentWindow]] = async (c, p, s) =>
642 {
643 return await c.GetCurrentWindow(s).ConfigureAwait(false);
644 },
645 [RESTHelper.Endpoints[ApiEndpoint.GetDomAttributes]] = async (c, p, s) =>
646 {
647 string backendNodeId = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : null;
648
649 return await c.GetDomAttributes(Int32.Parse(backendNodeId), s).ConfigureAwait(false);
650 },
651 [RESTHelper.Endpoints[ApiEndpoint.GetDomProperties]] = async (c, p, s) =>
652 {
653 string backendNodeId = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : null;
654
655 return await c.GetDomProperties(Int32.Parse(backendNodeId), s).ConfigureAwait(false);
656 },
657 [RESTHelper.Endpoints[ApiEndpoint.GetElementAttributeHash]] = async (c, p, s) =>
658 {
659 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
660 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
661 var attribute = p.ContainsKey("attribute") ? p["attribute"]?.ToString() : "";
662 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
663
664 foreach (GPALElement elem in elems)
666
667 return null;
668 },
669 [RESTHelper.Endpoints[ApiEndpoint.GetPageSource]] = async (c, p, s) =>
670 {
671 return await c.GetPageSource(s).ConfigureAwait(false);
672 },
673 [RESTHelper.Endpoints[ApiEndpoint.GetParentNode]] = async (c, p, s) =>
674 {
675 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
676 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
677
678 foreach (GPALElement elem in elems)
679 {
680 // elem.ElementHandle is the Runtime.objectId of the <option>
681
682 if (!string.IsNullOrEmpty(elem.ElementHandle))
683 {
684 // Call parentElement on the remote object
685 var parentResult = await c.SendCommand<object>(
686 DevToolsMethods.RuntimeCallFunctionOn,
687 new
688 {
689 objectId = elem.ElementHandle,
690 functionDeclaration = "function() { return this.parentElement; }",
691 returnByValue = false, // We want a RemoteObject, not a value
692 awaitPromise = false
693 },
694 s).ConfigureAwait(false);
695
696 if (parentResult?.result?.objectId != null)
697 {
698 string parentObjectId = parentResult.result.objectId;
699
700 // Now use your existing EvaluateSelector method (or whatever you use to turn objectId > element)
701 var parentElems = await c.EvaluateSelector(parentObjectId).ConfigureAwait(false);
702
703 return parentElems[0]; // This is now your parent element (e.g., the <select>)
704 }
705 }
706 }
707 return null;
708 },
709 [RESTHelper.Endpoints[ApiEndpoint.GetReadyStatus]] = async (c, p, s) =>
710 {
711 dynamic sessionToken = p.ContainsKey("sessionToken") ? p["sessionToken"] : null;
712 return await c.GetReadyStatus(s, sessionToken).ConfigureAwait(false);
713 },
714 [RESTHelper.Endpoints[ApiEndpoint.GetShadowRoot]] = async (c, p, s) =>
715 {
716 // NOTE: CAVEAT: this may not make any sense
717 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
718 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
719
720 foreach (GPALElement element in elems)
721 return element.GetShadowRoot();
722 return null;
723 },
724 [RESTHelper.Endpoints[ApiEndpoint.GetStorage]] = async (c, p, s) =>
725 {
726 var storageType = p.ContainsKey("storageType") ? p["storageType"]?.ToString() : null;
727 var domain = p.ContainsKey("domain") ? p["domain"]?.ToString() : null;
728 var path = p.ContainsKey("path") ? p["path"]?.ToString() : null;
729 var key = p.ContainsKey("key") ? p["key"]?.ToString() : null;
730 var storeName = p.ContainsKey("storeName") ? p["storeName"]?.ToString() : null;
731
732 return await c.GetStorage(storageType, s, domain, storeName, path, key).ConfigureAwait(false);
733 },
734 [RESTHelper.Endpoints[ApiEndpoint.GetLanguages]] = async (c, p, s) =>
735 {
736 var languages = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
737 {
738 expression = "navigator.languages.join(',')",
739 returnByValue = true
740 }, s).ConfigureAwait(false);
741 return (string)languages.result.value;
742 },
743 [RESTHelper.Endpoints[ApiEndpoint.GetUserAgent]] = async (c, p, s) =>
744 {
745 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
746 {
747 expression = "navigator.userAgent",
748 returnByValue = true
749 }, s).ConfigureAwait(false);
750 return (string)result.result.value;
751 },
752 [RESTHelper.Endpoints[ApiEndpoint.GoTo]] = async (c, p, s) =>
753 {
754 /*
755 * public class PageNavigateResponse
756 {
757 public string frameId { get; set; }
758 public string loaderId { get; set; }
759 public string errorText { get; set; }
760 }
761 */
762 var url = p.ContainsKey("url") ? p["url"]?.ToString() : "about:blank";
763 return await c.GoTo(url, s).ConfigureAwait(false);
764 },
765 [RESTHelper.Endpoints[ApiEndpoint.GoToTab]] = async (c, p, s) =>
766 {
767 string tabId = p.ContainsKey("tabId") ? p["tabId"]?.ToString() : null;
768 string url = p.ContainsKey("url") ? p["url"]?.ToString() : null;
769 int? index = p.TryGetValue("index", out object value) && int.TryParse(value?.ToString(), out int parsed) ? (int?)parsed : null;
770
771 object tabIdOrUrlOrIndex = tabId ?? url ?? (object)index;
772 if (tabIdOrUrlOrIndex == null)
773 {
774 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "No valid input provided for GoToTab (tabId, url, or index required)", c, GPALObjectType.PuppeteerCommunicator);
775 return null;
776 }
777
778 string targetId = await c.GoToTab(tabIdOrUrlOrIndex, s).ConfigureAwait(false);
779 if (targetId == null)
780 {
781 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Failed to switch to tab for input [{tabIdOrUrlOrIndex}]", c, GPALObjectType.PuppeteerCommunicator);
782 return null;
783 }
784
785 return new { targetId };
786 },
787 [RESTHelper.Endpoints[ApiEndpoint.GoToWindow]] = async (c, p, s) =>
788 {
789 var tabIdOrUrl = p.ContainsKey("tabId") ? (object)Convert.ToInt32(p["tabId"])
790 : (object)(p.ContainsKey("url") ? p["url"]?.ToString() : null);
791
792 if (tabIdOrUrl == null)
793 return null;
794
795 var targetId = await c.GoToWindow(tabIdOrUrl, s).ConfigureAwait(false);
796
797 if (!string.IsNullOrEmpty(targetId))
798 {
799 _currentTargetId = targetId;
800 return new { targetId };
801 }
802
803 return new { targetId };
804 },
805 [RESTHelper.Endpoints[ApiEndpoint.HideElement]] = async (c, p, s) =>
806 {
807 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] :
808 p.ContainsKey("css") ? p["css"] :
809 p.ContainsKey("xpath") ? p["xpath"] : null;
810 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] as List<GPALElement> :
811 await c.EvaluateSelector(parm).ConfigureAwait(false);
812 if (elems == null || !elems.Any())
813 {
814 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "No elements resolved for HideElement", c, GPALObjectType.Puppeteer);
815 return new { };
816 }
817
818 List<dynamic> responses = new List<dynamic>();
819 foreach (GPALElement elem in elems)
820 {
821 responses.Add(await c.HideElement(elem.ElementBackendNodeId, s).ConfigureAwait(false));
822 }
823
824 return (responses, elems);
825 },
826 [RESTHelper.Endpoints[ApiEndpoint.Hover]] = async (c, p, s) =>
827 {
828 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
829 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
830 List<dynamic> responses = new List<dynamic>();
831
832 await c.MoveTo(elems, s, responses).ConfigureAwait(false);
833
834 return (responses, elems);
835 },
836 [RESTHelper.Endpoints[ApiEndpoint.ClickPoint]] = async (c, p, s) =>
837 {
838 var x = p.ContainsKey("x") ? Convert.ToInt32(p["x"]) : 0;
839 var y = p.ContainsKey("y") ? Convert.ToInt32(p["y"]) : 0;
840 var clickType = p.ContainsKey("clickType") ? (ClickType)Convert.ToInt32(p["clickType"]) : ClickType.LeftClick;
841 var modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
842 List<dynamic> responses = new List<dynamic>();
843
844 await c.ClickElement(x, y, s, responses, clickType, modifiers).ConfigureAwait(false);
845 return responses;
846 },
847 [RESTHelper.Endpoints[ApiEndpoint.MoveToPoint]] = async (c, p, s) =>
848 {
849 var x = p.ContainsKey("x") ? Convert.ToInt32(p["x"]) : 0;
850 var y = p.ContainsKey("y") ? Convert.ToInt32(p["y"]) : 0;
851
852 await c.MoveTo(x, y, s).ConfigureAwait(false);
853 return true;
854 },
855 [RESTHelper.Endpoints[ApiEndpoint.InjectScript]] = async (c, p, s) =>
856 {
857 var script = p.ContainsKey("script") ? p["script"]?.ToString() : "";
858 await c.InjectScript(script, s).ConfigureAwait(false);
859 return true;
860 },
861 [RESTHelper.Endpoints[ApiEndpoint.ClearInjectedScripts]] = async (c, p, s) =>
862 {
863 await c.ClearInjectedScripts(s).ConfigureAwait(false);
864 return true;
865 },
866 [RESTHelper.Endpoints[ApiEndpoint.SwitchToDefaultContent]] = async (c, p, s) =>
867 {
868 // one implementation, so the bring-to-front only happens when there was a frame to leave
869 await c.SwitchToDefaultContent(s).ConfigureAwait(false);
870 return null;
871 },
872 [RESTHelper.Endpoints[ApiEndpoint.SwitchToShadowRoot]] = (c, p, s) =>
873 {
874 // this means nothing in terms of puppeteer, only for gpal tracking
875 return null;
876 },
877 [RESTHelper.Endpoints[ApiEndpoint.IsClickable]] = async (c, p, s) =>
878 {
879 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
880 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
881 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
882 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
883 List<bool> isClickables = new List<bool>();
884
885 foreach (GPALElement element in elems)
886 {
887 isClickables.Add(element.IsClickable());
888 }
889 return isClickables.All(isClickAble => true == isClickAble);
890 },
891 [RESTHelper.Endpoints[ApiEndpoint.IsDisplayed]] = async (c, p, s) =>
892 {
893 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
894 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
895 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
896 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
897
898 bool isDisplayed = true;
899 foreach (GPALElement element in elems)
900 {
901 isDisplayed &= element.Displayed;
902 }
903 return isDisplayed;
904 },
905 [RESTHelper.Endpoints[ApiEndpoint.IsEnabled]] = async (c, p, s) =>
906 {
907 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
908 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
909 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
910 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
911
912 bool isDisabled = false;
913 foreach (GPALElement element in elems)
914 {
915 isDisabled |= element.IsDisabled();
916 }
917 return !isDisabled;
918 },
919 [RESTHelper.Endpoints[ApiEndpoint.IsEndOfPage]] = async (c, p, s) =>
920 {
921 return await c.IsEndOfPage(s).ConfigureAwait(false);
922 },
923 [RESTHelper.Endpoints[ApiEndpoint.IsVisibleInViewport]] = async (c, p, s) =>
924 {
925 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
926 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
927 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
928 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
929
930 if (null != elems)
931 return await c.IsVisibleInViewport(elems, s).ConfigureAwait(false);
932 else
933 return await c.IsVisibleInViewport(parm, s).ConfigureAwait(false);
934 },
935 [RESTHelper.Endpoints[ApiEndpoint.LeftClick]] = async (c, p, s) =>
936 {
937 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
938 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
939 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
940 List<dynamic> responses = new List<dynamic>();
941
942 foreach (GPALElement element in elems)
943 {
944 await c.ScrollIntoView(element, s).ConfigureAwait(false);
945 await c.ClickElement(element, s, responses, ClickType.LeftClick, modifiers).ConfigureAwait(false);
946 }
947
948 return (responses, elems);
949 },
950 [RESTHelper.Endpoints[ApiEndpoint.LeftDoubleClick]] = async (c, p, s) =>
951 {
952 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
953 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
954 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
955 List<dynamic> responses = new List<dynamic>();
956
957 foreach (GPALElement element in elems)
958 {
959 await c.ScrollIntoView(element, s).ConfigureAwait(false);
960 await c.ClickElement(element, s, responses, ClickType.LeftDoubleClick, modifiers).ConfigureAwait(false);
961 }
962
963 return (responses, elems);
964 },
965 [RESTHelper.Endpoints[ApiEndpoint.LeftClickAndDownload]] = async (c, p, s) =>
966 {
967 string downloadPath = p.ContainsKey("downloadPath") ? p["downloadPath"]?.ToString() : "";
968 dynamic elementId = p.ContainsKey("elementId") ? p["elementId"] : null;
969 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
970
971 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(elementId).ConfigureAwait(false);
972 List<dynamic> responses = new List<dynamic>();
973
974 // we can set the download location, but not the filename
975 // se we still require a filewatcher to rename to the users filename
976 await c.LeftClickAndDownload(elems, downloadPath, modifiers, s, responses).ConfigureAwait(false);
977
978 // we turn it off too quickly before it can even download, and do not want a sleep, so leave it on, it's all good
979 //await c.SendCommand<object>(DevToolsMethods.BrowserSetDownloadBehavior, new { behavior = "deny" }, s);
980
981 return (responses, elems);
982 },
983 [RESTHelper.Endpoints[ApiEndpoint.Upload]] = async (c, p, s) =>
984 {
985 GPALFile uploadPaths = p.ContainsKey("uploadPaths") ? (GPALFile)p["uploadPaths"] : null;
986 string uploadPath = p.ContainsKey("uploadPath") ? p["uploadPath"]?.ToString() : "";
987 dynamic elementId = p.ContainsKey("elementId") ? p["elementId"] : null;
988 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0; // ???
989
990 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(elementId).ConfigureAwait(false);
991 List<dynamic> responses = new List<dynamic>();
992
993 // we can set the download location, but not the filename
994 // se we still require a filewatcher to rename to the users filename
995 if (0 < uploadPaths?.Count)
996 await c.LeftClickAndUpload(elems, uploadPaths, modifiers, s, responses).ConfigureAwait(false);
997 else
998 await c.LeftClickAndUpload(elems, uploadPath, modifiers, s, responses).ConfigureAwait(false);
999
1000 // we turn it off too quickly before it can even download, and do not want a sleep, so leave it on, it's all good
1001 //await c.SendCommand<object>(DevToolsMethods.BrowserSetDownloadBehavior, new { behavior = "deny" }, s);
1002
1003 return (responses, elems);
1004 },
1005 [RESTHelper.Endpoints[ApiEndpoint.LeftDoubleClick]] = async (c, p, s) =>
1006 {
1007 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1008 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1009 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
1010
1011 List<dynamic> responses = new List<dynamic>();
1012
1013 foreach (GPALElement element in elems)
1014 {
1015 await c.ScrollIntoView(element, s).ConfigureAwait(false);
1016 await c.ClickElement(element, s, responses, ClickType.LeftDoubleClick, modifiers).ConfigureAwait(false);
1017 }
1018 return (responses, elems);
1019 },
1020 [RESTHelper.Endpoints[ApiEndpoint.MiddleClick]] = async (c, p, s) =>
1021 {
1022 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1023 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1024 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
1025 List<dynamic> responses = new List<dynamic>();
1026
1027 foreach (GPALElement element in elems)
1028 {
1029 await c.ScrollIntoView(element, s).ConfigureAwait(false);
1030 await c.ClickElement(element, s, responses, ClickType.MiddleClick, modifiers).ConfigureAwait(false);
1031 }
1032
1033 return (responses, elems);
1034 },
1035 [RESTHelper.Endpoints[ApiEndpoint.Maximize]] = async (c, p, s) =>
1036 {
1037 return await c.Maximize(s).ConfigureAwait(false);
1038 },
1039 [RESTHelper.Endpoints[ApiEndpoint.Minimize]] = async (c, p, s) =>
1040 {
1041 return await c.Minimize(s).ConfigureAwait(false);
1042 },
1043 [RESTHelper.Endpoints[ApiEndpoint.MoveTo]] = async (c, p, s) =>
1044 {
1045 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1046 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1047 List<dynamic> responses = new List<dynamic>();
1048
1049 return await c.MoveTo(elems, s, responses).ConfigureAwait(false);
1050 },
1051 [RESTHelper.Endpoints[ApiEndpoint.NewTab]] = async (c, p, s) =>
1052 {
1053 var url = p.ContainsKey("url") ? p["url"]?.ToString() : "https://google.com";
1054 var targetId = await c.NewTab(url, s).ConfigureAwait(false);
1055
1056 if (!string.IsNullOrEmpty(targetId))
1057 _currentTargetId = targetId;
1058 else
1059 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
1060 "NewTab: Failed to open a new tab",
1061 this, GPALObjectType.Puppeteer);
1062
1063 return _currentTargetId;
1064 },
1065 [RESTHelper.Endpoints[ApiEndpoint.NextTab]] = async (c, p, s) =>
1066 {
1067 var tmp = _currentTargetId;
1068
1069 _currentTargetId = await c.NextTab(_currentTargetId).ConfigureAwait(false);
1070
1071 return _currentTargetId;
1072 },
1073 [RESTHelper.Endpoints[ApiEndpoint.NextWindow]] = async (c, p, s) =>
1074 {
1075 _currentTargetId = await c.NextWindow(_currentTargetId).ConfigureAwait(false);
1076
1077 return _currentTargetId;
1078 },
1079 [RESTHelper.Endpoints[ApiEndpoint.Normal]] = async (c, p, s) =>
1080 {
1081 return await c.Normal(s).ConfigureAwait(false);
1082 },
1083 [RESTHelper.Endpoints[ApiEndpoint.OpenWindow]] = async (c, p, s) =>
1084 {
1085 var url = p.ContainsKey("url") ? p["url"]?.ToString() : "about:blank";
1086
1087 _currentTargetId = await c.OpenWindow(url, s).ConfigureAwait(false);
1088
1089 return _currentTargetId;
1090 },
1091 [RESTHelper.Endpoints[ApiEndpoint.OverrideReferrer]] = async (c, p, s) =>
1092 {
1093 var referrer = p.ContainsKey("referrer") ? p["referrer"]?.ToString() : "";
1094 return await c.SendCommand<object>(DevToolsMethods.NetworkSetExtraHTTPHeaders, new
1095 {
1096 headers = new Dictionary<string, object> { { "Referer", referrer } }
1097 }, s).ConfigureAwait(false);
1098 },
1099 [RESTHelper.Endpoints[ApiEndpoint.SetUserAgent]] = async (c, p, s) =>
1100 {
1101 var userAgent = p.ContainsKey("userAgent") ? p["userAgent"]?.ToString() : "";
1102 return await c.SetUserAgent(userAgent, s).ConfigureAwait(false);
1103 },
1104 [RESTHelper.Endpoints[ApiEndpoint.PageDown]] = async (c, p, s) =>
1105 {
1106 int pagesToScroll = p.ContainsKey("pagesToScroll") ? Convert.ToInt32(p["pagesToScroll"]) : 1;
1107 return await c.ScrollPageAsync("down", pagesToScroll, s).ConfigureAwait(false);
1108 },
1109 [RESTHelper.Endpoints[ApiEndpoint.PageEnd]] = async (c, p, s) =>
1110 {
1111 return await c.ScrollToPositionAsync("end", s).ConfigureAwait(false);
1112 },
1113 [RESTHelper.Endpoints[ApiEndpoint.PageTop]] = async (c, p, s) =>
1114 {
1115 return await c.ScrollToPositionAsync("top", s).ConfigureAwait(false);
1116 },
1117 [RESTHelper.Endpoints[ApiEndpoint.PageUp]] = async (c, p, s) =>
1118 {
1119 int pagesToScroll = p.ContainsKey("pagesToScroll") ? Convert.ToInt32(p["pagesToScroll"]) : 1;
1120 return await c.ScrollPageAsync("up", pagesToScroll, s).ConfigureAwait(false);
1121 },
1122 [RESTHelper.Endpoints[ApiEndpoint.PressModifierKey]] = async (c, p, s) =>
1123 {
1124 var modifier = p.ContainsKey("modifier") ? Convert.ToInt32(p["modifier"]) : 0;
1125 return await c.SendCommand<object>(DevToolsMethods.InputDispatchKeyEvent, new
1126 {
1127 type = "keyDown",
1128 modifiers = modifier
1129 }, s).ConfigureAwait(false);
1130 },
1131 [RESTHelper.Endpoints[ApiEndpoint.PreviousTab]] = async (c, p, s) =>
1132 {
1133 _currentTargetId = await c.PreviousTab(_currentTargetId).ConfigureAwait(false);
1134
1135 return _currentTargetId;
1136 },
1137 [RESTHelper.Endpoints[ApiEndpoint.PreviousWindow]] = async (c, p, s) =>
1138 {
1139 _currentTargetId = await c.PreviousWindow(_currentTargetId).ConfigureAwait(false);
1140
1141 return _currentTargetId;
1142 },
1143 [RESTHelper.Endpoints[ApiEndpoint.QuerySelector]] = async (c, p, s) =>
1144 {
1145 var selector = p.ContainsKey("css") ? p["css"]?.ToString() : "";
1146 List<GPALElement> elems = await _puppeteerCommunicator.EvaluateSelector(selector, s).ConfigureAwait(false);
1147
1148 if (0 < elems.Count)
1149 return elems[0];
1150 else
1151 return null;
1152 //var doc = await c.SendCommand<object>(DevToolsMethods.DOMGetDocument, new { depth = 0 }, s).ConfigureAwait(false);
1153 //return await c.SendCommand<object>(DevToolsMethods.DOMQuerySelector, new { nodeId = (int)doc.root.nodeId, selector }, s).ConfigureAwait(false);
1154 },
1155 [RESTHelper.Endpoints[ApiEndpoint.QuerySelectors]] = async (c, p, s) =>
1156 {
1157 var selector = p.ContainsKey("css") ? p["css"]?.ToString() : "";
1158 return await _puppeteerCommunicator.EvaluateSelector(selector, s).ConfigureAwait(false);
1159 },
1160 [RESTHelper.Endpoints[ApiEndpoint.Refresh]] = async (c, p, s) =>
1161 {
1162 return await c.SendCommand(DevToolsMethods.PageReload, new { }, s).ConfigureAwait(false);
1163 },
1164 [RESTHelper.Endpoints[ApiEndpoint.ReleaseModifierKey]] = async (c, p, s) =>
1165 {
1166 var modifier = p.ContainsKey("modifier") ? Convert.ToInt32(p["modifier"]) : 0;
1167 return await c.SendCommand<object>(DevToolsMethods.InputDispatchKeyEvent, new
1168 {
1169 type = "keyUp",
1170 modifiers = modifier
1171 }, s).ConfigureAwait(false);
1172 },
1173 [RESTHelper.Endpoints[ApiEndpoint.Restore]] = async (c, p, s) =>
1174 {
1175 return await c.Restore(s).ConfigureAwait(false);
1176 },
1177 [RESTHelper.Endpoints[ApiEndpoint.RightClick]] = async (c, p, s) =>
1178 {
1179 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1180 List<dynamic> responses = new List<dynamic>();
1181 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1182
1183 foreach (GPALElement element in elems)
1184 {
1185 await c.ScrollIntoView(element, s).ConfigureAwait(false);
1186 await c.ClickElement(element, s, responses, ClickType.RightClick).ConfigureAwait(false);
1187 }
1188 return elems;
1189 },
1190 [RESTHelper.Endpoints[ApiEndpoint.ScrollElement]] = async (c, p, s) =>
1191 {
1192 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1193 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1194 bool isObjectId = System.Text.RegularExpressions.Regex.IsMatch(parm, @"^-?\d+\.\d+\.\d+$");
1195 var hPixels = p.ContainsKey("hPixels") ? Convert.ToInt32(p["hPixels"]) : 0;
1196 var vPixels = p.ContainsKey("vPixels") ? Convert.ToInt32(p["vPixels"]) : 0;
1197 List<dynamic> responses = new List<dynamic>();
1198
1199 foreach (GPALElement elem in elems)
1200 {
1201 string jsFunc = $@"
1202 (function(selector, hPixels, vPixels) {{
1203 function evaluateSelector(selector) {{
1204 let nodes = [];
1205 let type = 'unknown';
1206 try {{
1207 nodes = Array.from(document.querySelectorAll(selector));
1208 if (nodes.length > 0) return nodes;
1209 }} catch {{}}
1210
1211 try {{
1212 const result = document.evaluate(selector, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1213 nodes = Array.from({{ length: result.snapshotLength }}, (_, i) => result.snapshotItem(i));
1214 if (nodes.length > 0) return nodes;
1215 }} catch {{}}
1216
1217 return [];
1218 }}
1219
1220 const elements = evaluateSelector(selector);
1221 elements.forEach(el => {{
1222 if (el.scrollBy) el.scrollBy(hPixels, vPixels);
1223 }});
1224 return elements.length; // optional: return number of elements scrolled
1225 }})('{(true == isObjectId ? (elem.Css ?? elem.Xpath) : parm).Replace("'", "\\'")}', {hPixels}, {vPixels})";
1226
1227 responses.Add(await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1228 {
1229 expression = jsFunc,
1230 returnByValue = true
1231 }, s).ConfigureAwait(false));
1232 }
1233 return (responses, elems);
1234 },
1235 [RESTHelper.Endpoints[ApiEndpoint.ScrollIntoView]] = async (c, p, s) =>
1236 {
1237 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1238 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1239 List<dynamic> responses = new List<dynamic>();
1240
1241 if (null != elems)
1242 foreach (GPALElement elem in elems)
1243 responses.Add(await c.ScrollIntoView(elem, s).ConfigureAwait(false));
1244
1245 return (responses, elems);
1246 },
1247 [RESTHelper.Endpoints[ApiEndpoint.ScrollWindow]] = async (c, p, s) =>
1248 {
1249 var hPixels = p.ContainsKey("hPixels") ? Convert.ToInt32(p["hPixels"]) : 0;
1250 var vPixels = p.ContainsKey("vPixels") ? Convert.ToInt32(p["vPixels"]) : 0;
1251 return await c.ScrollWindowByPixelsAsync(hPixels, vPixels, s).ConfigureAwait(false);
1252 },
1253 // SelectClick - selects an option in a <select> element
1254 // elementId = CSS selector of the <select>
1255 // value = string: option value or visible text (optional)
1256 // index = int: 0-based index of option (optional)
1257 // Exactly one of value/index must be provided
1258 [RESTHelper.Endpoints[ApiEndpoint.SelectClick]] = async (c, p, s) =>
1259 {
1260 // this is the select selector
1261 dynamic elementHandle = p.ContainsKey("elementId") ? p["elementId"] : null; // objectid
1262 string selectValue = p.ContainsKey("selectValue") ? p["selectValue"]?.ToString() : null;
1263 int? selectIndex = p.ContainsKey("selectIndex") && int.TryParse(p["selectIndex"]?.ToString(), out int idx) ? idx : (int?)null;
1264 bool result = false;
1265
1266 if (selectIndex.HasValue)
1267 {
1268 result = await c.SelectByIndex(elementHandle, selectIndex.Value, s).ConfigureAwait(false);
1269 }
1270 else // value is not null
1271 {
1272 string safeValue = selectValue.Replace("'", "\\'"); // not sure about this...
1273 result = await c.SelectByValue(elementHandle, safeValue, s).ConfigureAwait(false);
1274 }
1275
1276 return result;
1277 },
1278 [RESTHelper.Endpoints[ApiEndpoint.SendKey]] = async (c, p, s) =>
1279 {
1280 var key = p.ContainsKey("key") ? p["key"]?.ToString() : "Unidentified";
1281 var code = p.ContainsKey("code") ? p["code"]?.ToString() : "Unidentified";
1282 var vk = p.ContainsKey("vk") ? Convert.ToInt32(p["vk"]) : 0;
1283
1284 return await c.SendKey(key, code, vk).ConfigureAwait(false);
1285 },
1286
1287 [RESTHelper.Endpoints[ApiEndpoint.SendString]] = async (c, p, s) =>
1288 {
1289 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
1290 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
1291 await c.SendString(text, typingDelay, s).ConfigureAwait(false);
1292 return true;
1293 },
1294 [RESTHelper.Endpoints[ApiEndpoint.SetAttribute]] = async (c, p, s) =>
1295 {
1296 List<dynamic> responses = new List<dynamic>();
1297 List<GPALElement> elems = null;
1298
1299 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1300 if (null != parm)
1301 {
1302 var value = p.ContainsKey("value") ? p["value"]?.ToString() : string.Empty;
1303 var attribute = p.ContainsKey("attribute") ? p["attribute"] : "";
1304 bool isObjectId = System.Text.RegularExpressions.Regex.IsMatch(parm, @"^-?\d+\.\d+\.\d+$");
1305 elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1306
1307 foreach (GPALElement elem in elems)
1308 {
1309 string jsFunc = $@"
1310 (function(selector, attribute, value) {{
1311 function evaluateSelector(selector) {{
1312 let nodes = [];
1313 let type = 'unknown';
1314 try {{
1315 nodes = Array.from(document.querySelectorAll(selector));
1316 if (nodes.length > 0) return nodes;
1317 }} catch {{}}
1318
1319 try {{
1320 const result = document.evaluate(selector, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1321 nodes = Array.from({{ length: result.snapshotLength }}, (_, i) => result.snapshotItem(i));
1322 if (nodes.length > 0) return nodes;
1323 }} catch {{}}
1324
1325 return [];
1326 }}
1327
1328 const elements = evaluateSelector(selector);
1329 elements.forEach(el => {{
1330 el.setAttribute(attribute, value);
1331 }});
1332 return elements.length; // optional: return number of elements scrolled
1333 }})('{(true == isObjectId ? (elem.Css ?? elem.Xpath) : parm).Replace("'", "\\'")}', {attribute}, {value})";
1334
1335 responses.Add(await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1336 {
1337 expression = jsFunc,
1338 returnByValue = true
1339 }, s).ConfigureAwait(false));
1340 elem.Attributes[attribute.ToString()] = value;
1341 }
1342 }
1343 return (responses, elems);
1344 },
1345 [RESTHelper.Endpoints[ApiEndpoint.SetDownloadFilename]] = async (c, p, s) =>
1346 {
1347 var filename = p.ContainsKey("filename") ? p["filename"]?.ToString() : "";
1348 return await c.SendCommand<object>(DevToolsMethods.PageSetDownloadBehavior, new
1349 {
1350 behavior = "allow",
1351 downloadPath = filename
1352 }, s).ConfigureAwait(false);
1353 },
1354 [RESTHelper.Endpoints[ApiEndpoint.SetRange]] = async (c, p, s) =>
1355 {
1356 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1357 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1358 string rangeValue = (string)(p.ContainsKey("rangeValue") ? p["rangeValue"] : "0xdeadbeef"); // -1 should not be possib except it could be set by the user, but it's not valuid
1359 List<dynamic> responses = new List<dynamic>();
1360
1361 if ("0xdeadbeef" != rangeValue)
1362 foreach (GPALElement elem in elems)
1363 responses.Add(await c.SetRange(elem.Css, rangeValue, s).ConfigureAwait(false));
1364
1365 return responses;
1366 },
1367 [RESTHelper.Endpoints[ApiEndpoint.SetValueFromElement]] = async (c, p, s) =>
1368 {
1369 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1370 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1371 string destSelector = p.ContainsKey("destSelector") ? p["destSelector"]?.ToString() : null;
1372 List<dynamic> responses = new List<dynamic>();
1373
1374 if (false == string.IsNullOrEmpty(destSelector))
1375 foreach (GPALElement elem in elems)
1376 responses.Add(await c.SetValueFromElement(elem.Css, destSelector, s).ConfigureAwait(false));
1377
1378 return responses;
1379 },
1380 [RESTHelper.Endpoints[ApiEndpoint.SetStorage]] = async (c, p, s) =>
1381 {
1382 var storageType = p.ContainsKey("storageType") ? p["storageType"]?.ToString() : "";
1383 var domain = p.ContainsKey("domain") ? p["domain"]?.ToString() : "";
1384 var storeName = p.ContainsKey("storeName") ? p["storeName"]?.ToString() : null;
1385 var path = p.ContainsKey("path") ? p["path"]?.ToString() : "";
1386 var key = p.ContainsKey("key") ? p["key"]?.ToString() : "";
1387 var data = p.ContainsKey("data") ? p["data"]?.ToString() : "";
1388
1389 return await c.SetStorage(storageType, s, domain, key, data, storeName, path).ConfigureAwait(false);
1390 },
1391 [RESTHelper.Endpoints[ApiEndpoint.SetGoogleReferrer]] = async (c, p, s) =>
1392 {
1393 return await c.SendCommand<object>(DevToolsMethods.NetworkSetExtraHTTPHeaders, new
1394 {
1395 headers = new Dictionary<string, object> { { "Referer", "https://www.google.com" } }
1396 }, s).ConfigureAwait(false);
1397 },
1398 [RESTHelper.Endpoints[ApiEndpoint.StopCasting]] = async (c, p, s) =>
1399 {
1400 return await c.StopCasting(s).ConfigureAwait(false);
1401 },
1402 [RESTHelper.Endpoints[ApiEndpoint.SwitchToDefaultContent]] = async (c, p, s) =>
1403 {
1404 // doesn't mean anything, useful for gpal tracking
1405 await c.SwitchToDefaultContent(s).ConfigureAwait(false);
1406 return null;
1407 },
1408 [RESTHelper.Endpoints[ApiEndpoint.SwitchToElement]] = async (c, p, s) =>
1409 {
1410 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
1411 await c.SwitchToElement(selector).ConfigureAwait(false);
1412 return null;
1413 },
1414 [RESTHelper.Endpoints[ApiEndpoint.SwitchToFrame]] = async (c, p, s) =>
1415 {
1416 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
1417 await c.SwitchToFrame(selector).ConfigureAwait(false);
1418 return null;
1419 },
1420 [RESTHelper.Endpoints[ApiEndpoint.SwitchToShadowRoot]] = async (c, p, s) =>
1421 {
1422 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
1423 await c.SwitchToShadowDom(selector).ConfigureAwait(false);
1424 return null;
1425 },
1426 [RESTHelper.Endpoints[ApiEndpoint.TopBrowser]] = async (c, p, s) =>
1427 {
1428 await c.TopBrowser().ConfigureAwait(false);
1429 return null;
1430 },
1431 [RESTHelper.Endpoints[ApiEndpoint.WindowInnerHeight]] = async (c, p, s) =>
1432 {
1433 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1434 {
1435 expression = "window.innerHeight",
1436 returnByValue = true
1437 // always the top level window - inside an iframe this would report the frame's own viewport
1438 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1439 return (int)result.result.value;
1440 },
1441 [RESTHelper.Endpoints[ApiEndpoint.WindowInnerWidth]] = async (c, p, s) =>
1442 {
1443 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1444 {
1445 expression = "window.innerWidth",
1446 returnByValue = true
1447 // always the top level window - inside an iframe this would report the frame's own viewport
1448 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1449 return (int)result.result.value;
1450 },
1451 [RESTHelper.Endpoints[ApiEndpoint.WindowOuterHeight]] = async (c, p, s) =>
1452 {
1453 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1454 {
1455 expression = "window.outerHeight",
1456 returnByValue = true
1457 // always the top level window - the browser frame is not a property of any iframe
1458 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1459 return (int)result.result.value;
1460 },
1461 [RESTHelper.Endpoints[ApiEndpoint.WindowOuterWidth]] = async (c, p, s) =>
1462 {
1463 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1464 {
1465 expression = "window.outerWidth",
1466 returnByValue = true
1467 // always the top level window - the browser frame is not a property of any iframe
1468 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1469 return (int)result.result.value;
1470 },
1471 [RESTHelper.Endpoints[ApiEndpoint.WindowPageOffsetX]] = async (c, p, s) =>
1472 {
1473 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1474 {
1475 expression = "window.pageXOffset",
1476 returnByValue = true
1477 }, s).ConfigureAwait(false);
1478 return (int)result.result.value;
1479 },
1480 [RESTHelper.Endpoints[ApiEndpoint.WindowPageOffsetY]] = async (c, p, s) =>
1481 {
1482 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1483 {
1484 expression = "window.pageYOffset",
1485 returnByValue = true
1486 }, s).ConfigureAwait(false);
1487 return (int)result.result.value;
1488 },
1489 [RESTHelper.Endpoints[ApiEndpoint.WindowScreenLeft]] = async (c, p, s) =>
1490 {
1491 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1492 {
1493 expression = "window.screenLeft",
1494 returnByValue = true
1495 // always the top level window - this is where the browser sits on the desktop
1496 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1497 return (int)result.result.value;
1498 },
1499 [RESTHelper.Endpoints[ApiEndpoint.WindowScreenTop]] = async (c, p, s) =>
1500 {
1501 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1502 {
1503 expression = "window.screenTop",
1504 returnByValue = true
1505 // always the top level window - this is where the browser sits on the desktop
1506 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1507 return (int)result.result.value;
1508 }
1509 };
1511
1516 public IPuppeteerClient ToGPALObject()
1517 {
1518 return this;
1519 }
1520
1522 /// The display name assigned to this client via WithName.
1523 /// </summary>
1524 public string Name
1525 {
1526 get => _name;
1527 }
1528
1534 public IAllowPuppeteerEndpointDetails WithPuppeteerCommunicator(PuppeteerCommunicator communicator)
1535 {
1536 _puppeteerCommunicator = communicator;
1537 //_currentTargetId = _puppeteerCommunicator.GetInitialTargetId().Result; // Fetch initial target ID
1538 return this;
1544
1545 public IAllowPuppeteerParametersOrExecution WithEndpoint(string endpoint)
1546 {
1547 _endpoint = endpoint;
1548 _customEndpoint = true;
1549 return this;
1551
1555
1558 public IAllowPuppeteerParametersOrExecution WithEndpoint(ApiEndpoint endpoint)
1559 {
1560 _apiEndpoint = endpoint;
1561 _endpoint = RESTHelper.Endpoints.TryGetValue(endpoint, out var ep) ? ep : "";
1562 return this;
1563 }
1564
1566
1570 public IAllowPuppeteerParametersOrExecution WithResultName(string name)
1571 {
1572 _nextResultName = name;
1573 return this;
1574 }
1575
1576 public IAllowPuppeteerEndpointDetails WithSaveFile(GPALFile file)
1577 {
1578 _saveFile = file?.Filename;
1579 return this;
1580 }
1581
1583 /// Sets the session token parameter for the request.
1584 /// </summary>
1585 /// <param name="sessionToken">The session token to send</param>
1586 /// <returns>Fluent interface to continue building the request</returns>
1587 public IAllowPuppeteerParametersOrExecution WithSessionToken(string sessionToken)
1588 {
1589 SetParameter("sessionToken", sessionToken);
1590 return this;
1591 }
1592
1594 /// Sets the display name for this client.
1595 /// </summary>
1596 /// <param name="name">The name to assign</param>
1597 /// <returns>Fluent interface to continue building the request</returns>
1598 public IAllowPuppeteerEndpointDetails WithName(string name)
1599 {
1600 _name = name;
1601 return this;
1602 }
1603
1605 /// Sets the url parameter for the request.
1606 /// </summary>
1607 /// <param name="url">The URL</param>
1608 /// <returns>Fluent interface to continue building the request</returns>
1610 {
1611 SetParameter("url", url);
1612 return this;
1613 }
1614
1616 /// Sets the image format parameter for the request (e.g. for CaptureVisibleTab).
1617 /// </summary>
1618 /// <param name="imageFormat">The image format to request</param>
1619 /// <returns>Fluent interface to continue building the request</returns>
1620 internal IAllowPuppeteerParametersOrExecution WithImageFormat(ImageFormat imageFormat)
1621 {
1622 SetParameter("imageFormat", imageFormat.ToString());
1623 return this;
1624 }
1625
1627 /// Sets the url parameter from the result at the given index of a previous execution.
1628 /// </summary>
1629 /// <param name="resultIndex">Index into the execution results</param>
1630 /// <returns>Fluent interface to continue building the request</returns>
1632 {
1633 var url = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1634 return WithUrl(url);
1635 }
1636
1638 /// Sets the url parameter from the named result of a previous execution.
1639 /// </summary>
1640 /// <param name="name">Name of a previous execution result</param>
1641 /// <returns>Fluent interface to continue building the request</returns>
1643 {
1644 var url = GetResultFromName(name)?.ToString() ?? "";
1645 return WithUrl(url);
1646 }
1647
1649 /// Sets the tab id parameter for the request.
1650 /// </summary>
1651 /// <param name="tabId">The tab id</param>
1652 /// <returns>Fluent interface to continue building the request</returns>
1654 {
1655 SetParameter("tabId", tabId);
1656 return this;
1657 }
1658
1660 /// Sets the tab id parameter from the result at the given index of a previous execution.
1661 /// </summary>
1662 /// <param name="resultIndex">Index into the execution results</param>
1663 /// <returns>Fluent interface to continue building the request</returns>
1665 {
1666 var tabId = GetResultFromIndex(resultIndex) is int i ? i : 0;
1667 return WithTabId(tabId);
1668 }
1669
1671 /// Sets the tab id parameter from the named result of a previous execution.
1672 /// </summary>
1673 /// <param name="name">Name of a previous execution result</param>
1674 /// <returns>Fluent interface to continue building the request</returns>
1676 {
1677 var tabId = GetResultFromName(name) is int i ? i : 0;
1678 return WithTabId(tabId);
1679 }
1680
1681 /// <summary>
1682 /// Sets the window id parameter for the request.
1683 /// </summary>
1684 /// <param name="windowId">The window id</param>
1685 /// <returns>Fluent interface to continue building the request</returns>
1687 {
1688 SetParameter("windowId", windowId);
1689 return this;
1690 }
1696
1698 {
1699 var windowId = GetResultFromIndex(resultIndex) is int i ? i : 0;
1700 return WithWindowId(windowId);
1702
1706
1708 public IAllowPuppeteerParametersOrExecution WithWindowIdFromResult(string name)
1709 {
1710 var windowId = GetResultFromName(name) is int i ? i : 0;
1711 return WithWindowId(windowId);
1712 }
1713
1716
1719 public IAllowPuppeteerStorageOptions WithStorageDomain(string domain)
1720 {
1721 SetParameter("domain", domain);
1722 return this;
1723 }
1726
1729 public IAllowPuppeteerStorageOptions WithStoragePath(string path)
1730 {
1731 SetParameter("path", path);
1732 return this;
1733 }
1736
1739 public IAllowPuppeteerStorageOptions WithStorageKey(string key)
1740 {
1741 SetParameter("key", key);
1742 return this;
1743 }
1747
1749 public IAllowPuppeteerStorageOptions WithStorageStoreName(string storeName)
1750 {
1751 SetParameter("storeName", storeName);
1752 return this;
1758
1759 public IAllowPuppeteerStorageOptions WithStorageData(string data)
1760 {
1761 SetParameter("data", data);
1762 return this;
1763 }
1764 /// <summary>
1765 /// Sets whether storage should be deleted across all origins, not just the current one.
1766 /// </summary>
1767 /// <param name="trueOrFalse">True to delete storage across all origins</param>
1768 /// <returns>Fluent interface to continue building the request</returns>
1770 {
1771 SetParameter("crossOrigin", trueOrFalse);
1772 return this;
1773 }
1775 /// Sets the key parameter for the request.
1776 /// </summary>
1777 /// <param name="key">The key</param>
1778 /// <returns>Fluent interface to continue building the request</returns>
1780 {
1781 SetParameter("key", key);
1782 return this;
1783 }
1784
1786 /// Sets the key parameter from the result at the given index of a previous execution.
1787 /// </summary>
1788 /// <param name="resultIndex">Index into the execution results</param>
1789 /// <returns>Fluent interface to continue building the request</returns>
1791 {
1792 var key = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1793 return WithKey(key);
1794 }
1795
1797 /// Sets the key parameter from the named result of a previous execution.
1798 /// </summary>
1799 /// <param name="name">Name of a previous execution result</param>
1800 /// <returns>Fluent interface to continue building the request</returns>
1802 {
1803 var key = GetResultFromName(name)?.ToString() ?? "";
1804 return WithKey(key);
1805 }
1806
1808 /// Sets the css selector parameter for the request.
1809 /// </summary>
1810 /// <param name="css">CSS selector</param>
1811 /// <returns>Fluent interface to continue building the request</returns>
1813 {
1814 SetParameter("css", css);
1815 return this;
1816 }
1817
1819 /// Sets the css selector parameter from the result at the given index of a previous execution.
1820 /// </summary>
1821 /// <param name="resultIndex">Index into the execution results</param>
1822 /// <returns>Fluent interface to continue building the request</returns>
1824 {
1825 var css = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1826 return WithCss(css);
1827 }
1828
1829 /// <summary>
1830 /// Sets the css selector parameter from the named result of a previous execution.
1831 /// </summary>
1832 /// <param name="name">Name of a previous execution result</param>
1833 /// <returns>Fluent interface to continue building the request</returns>
1835 {
1836 var css = GetResultFromName(name)?.ToString() ?? "";
1837 return WithCss(css);
1838 }
1844
1845 public IAllowPuppeteerParametersOrExecution WithElements(List<GPALElement> gpalElements)
1846 {
1847 SetParameter("gpalElements", gpalElements);
1848 return this;
1849 }
1855
1857 {
1858 var elementId = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1859 return WithElementId(elementId);
1860 }
1866
1868 {
1869 var elementId = GetResultFromName(name)?.ToString() ?? "";
1870 return WithElementId(elementId);
1871 }
1872 /// <summary>
1873 /// Sets the iteration for an endpoint to retrieve from the results when using WithXXXFromResults
1874 /// </summary>
1875 /// <param name="iteration"></param>
1876 /// <returns></returns>
1878 {
1879 _currentIteration = iteration > 0 ? iteration : 1;
1880 return this;
1881 }
1883 /// Sets the xpath parameter for the request.
1884 /// </summary>
1885 /// <param name="xpath">XPath expression</param>
1886 /// <returns>Fluent interface to continue building the request</returns>
1888 {
1889 SetParameter("xpath", xpath);
1890 return this;
1891 }
1892
1894 /// Sets the xpath parameter from the result at the given index of a previous execution.
1895 /// </summary>
1896 /// <param name="resultIndex">Index into the execution results</param>
1897 /// <returns>Fluent interface to continue building the request</returns>
1899 {
1900 var xpath = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1901 return WithXPath(xpath);
1902 }
1903
1905 /// Sets the xpath parameter from the named result of a previous execution.
1906 /// </summary>
1907 /// <param name="name">Name of a previous execution result</param>
1908 /// <returns>Fluent interface to continue building the request</returns>
1910 {
1911 var xpath = GetResultFromName(name)?.ToString() ?? "";
1912 return WithXPath(xpath);
1913 }
1914
1916 /// Sets the pixels parameter for the request.
1917 /// </summary>
1918 /// <param name="pixels">Number of pixels</param>
1919 /// <returns>Fluent interface to continue building the request</returns>
1921 {
1922 SetParameter("pixels", pixels);
1923 return this;
1924 }
1925
1927 /// Sets the pixels parameter from the result at the given index of a previous execution.
1928 /// </summary>
1929 /// <param name="resultIndex">Index into the execution results</param>
1930 /// <returns>Fluent interface to continue building the request</returns>
1932 {
1933 var pixels = GetResultFromIndex(resultIndex) is int i ? i : 0;
1934 return WithPixels(pixels);
1935 }
1936
1938 /// Sets the pixels parameter from the named result of a previous execution.
1939 /// </summary>
1940 /// <param name="name">Name of a previous execution result</param>
1941 /// <returns>Fluent interface to continue building the request</returns>
1943 {
1944 var pixels = GetResultFromName(name) is int i ? i : 0;
1945 return WithPixels(pixels);
1946 }
1947
1950 /// </summary>
1951 /// <param name="referrer">The referrer URL</param>
1952 /// <returns>Fluent interface to continue building the request</returns>
1954 {
1955 SetParameter("referrer", referrer);
1956 return this;
1957 }
1958
1961 /// </summary>
1962 /// <param name="resultIndex">Index into the execution results</param>
1963 /// <returns>Fluent interface to continue building the request</returns>
1965 {
1966 var referrer = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1967 return WithReferrer(referrer);
1968 }
1969
1976 {
1977 var referrer = GetResultFromName(name)?.ToString() ?? "";
1978 return WithReferrer(referrer);
1979 }
1980
1986 public IAllowPuppeteerExecution WithText(string text)
1987 {
1988 _text = text;
1989 SetParameter("text", text);
1990 return this;
1991 }
1992
1994 /// Sets the text parameter from the result at the given index of a previous execution.
1995 /// </summary>
1996 /// <param name="resultIndex">Index into the execution results</param>
1997 /// <returns>Fluent interface to continue building the request</returns>
1998 public IAllowPuppeteerExecution WithTextFromResult(int resultIndex)
1999 {
2000 var text = GetResultFromIndex(resultIndex)?.ToString() ?? "";
2001 return WithText(text);
2002 }
2003
2009 public IAllowPuppeteerExecution WithTextFromResult(string name)
2010 {
2011 var text = GetResultFromName(name)?.ToString() ?? "";
2012 return WithText(text);
2013 }
2014
2015 /// <summary>
2016 /// Where a file the browser downloads should end up.<br/>
2017 /// Not SaveTo, which is GPAL writing a file it already has. Only the directory of <paramref name="file"/>
2018 /// reaches the browser, since CDP sets where a download lands and not what it is called; the download
2019 /// watcher gives it the name that was asked for.
2020 /// </summary>
2021 /// <param name="file">Where the downloaded file is to end up</param>
2022 /// <returns>Fluent interface to continue building the request</returns>
2024 {
2025 SetParameter("downloadPath", file.Filename);
2026 return this;
2027 }
2031
2033 private List<GPALElement> GetElements(string selector)
2034 {
2035 return _puppeteerCommunicator.EvaluateSelector(selector).Result;
2036 }
2037 /// <summary>
2038 /// Sets the elementId parameter for the request, resolving CSS selectors to an element handle if needed.
2039 /// </summary>
2040 /// <param name="selector">An element id/object id, or a CSS selector to resolve</param>
2041 /// <returns>Fluent interface to continue building the request</returns>
2043 {
2044 bool isObjectId = System.Text.RegularExpressions.Regex.IsMatch(selector, @"^-?\d+\.\d+\.\d+$");
2045
2046 if (true/* == isObjectId*/)
2047 {
2048 SetParameter("elementId", selector);
2050 //else // we have to switch to css when interfacing with gpal, so insert it as it was called
2051 //{
2052 // List<GPALElement> queryResponse = GetElements(selector);
2053 // SetParameter("elementId", queryResponse?[0]?.ElementHandle);
2054 //}
2055 return this;
2056 }
2057
2061 /// <param name="resultIndex">Index into the execution results</param>
2062 /// <returns>Fluent interface to continue building the request</returns>
2064 {
2065 var elementId = GetResultFromIndex(resultIndex)?.ToString() ?? "";
2066 return WithElementId(elementId);
2067 }
2068
2073 /// <returns>Fluent interface to continue building the request</returns>
2075 {
2076 var elementId = GetResultFromName(name)?.ToString() ?? "";
2077 return WithElementId(elementId);
2078 }
2079
2084 /// <returns>Fluent interface to continue building the request</returns>
2086 {
2087 _hPixels = GetResultFromIndex(resultIndex) is int i ? i : 0;
2088 SetParameter("hPixels", _hPixels);
2089 return this;
2090 }
2091
2097 public IAllowPuppeteerParametersOrExecution WithHPixelsFromResult(string name)
2098 {
2099 _hPixels = GetResultFromName(name) is int i ? i : 0;
2100 SetParameter("hPixels", _hPixels);
2101 return this;
2102 }
2103
2109 public IAllowPuppeteerParametersOrExecution WithVPixelsFromResult(int resultIndex)
2110 {
2111 _vPixels = GetResultFromIndex(resultIndex) is int i ? i : 0;
2112 SetParameter("vPixels", _vPixels);
2113 return this;
2114 }
2115
2117 /// Sets the vertical pixels parameter from the named result of a previous execution.
2118 /// </summary>
2119 /// <param name="name">Name of a previous execution result</param>
2120 /// <returns>Fluent interface to continue building the request</returns>
2122 {
2123 _vPixels = GetResultFromName(name) is int i ? i : 0;
2124 SetParameter("vPixels", _vPixels);
2125 return this;
2126 }
2127
2132 public string Execute()
2134 return Task.Run(async () => await ExecuteAsync().ConfigureAwait(false)).GetAwaiter().GetResult();
2135 }
2136
2137 public T Execute<T>()
2138 {
2139 try
2140 {
2141 Task<T> task = ExecuteAsync<T>();
2142
2143 // WinForms compatibilty
2144 // This keeps the UI thread blocked, but allows the internal awaits
2145 // to complete successfully on a background thread.
2146 return Task.Run(async () => await task.ConfigureAwait(false)).GetAwaiter().GetResult();
2147 }
2148 catch (GPALException)
2149 {
2150 // the comms layer already decided the browser is gone, and default(T) reads to the caller
2151 // as an answer rather than as the browser being unreachable
2152 throw;
2153 }
2154 catch (Exception ex)
2155 {
2156 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to execute synchronously for type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, ex);
2157 return default(T);
2158 }
2159 }
2160
2165 public IPuppeteerClient AndThen()
2166 {
2167 Execute();
2168 return this;
2169 }
2170
2171 public IPuppeteerClient AndThen<T>()
2172 {
2173 Execute<T>();
2174 return this;
2175 }
2176
2181 public async Task<string> ExecuteAsync()
2182 {
2183 var result = await ExecuteInternalAsync().ConfigureAwait(false);
2184
2185 // the chain is cleared here rather than when the endpoint is named, so a parameter set before the
2186 // endpoint still reaches the call
2187 Reset();
2188
2189 return result?.ToString() ?? "";
2190 }
2191
2192 public async Task<T> ExecuteAsync<T>()
2193 {
2194 dynamic result = await ExecuteInternalAsync().ConfigureAwait(false); // Returns Task<dynamic>
2195
2196 // the chain is cleared here rather than when the endpoint is named, so a parameter set before the
2197 // endpoint still reaches the call
2198 Reset();
2199
2200 if (result == null)
2201 {
2202 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "ExecuteInternalAsync returned null.", this, GPALObjectType.PuppeteerClient, null);
2203 return default(T);
2204 }
2205
2206 if (typeof(T) == typeof(JObject))
2207 {
2208 if (result is JObject)
2209 {
2210 return (T)(object)result;
2211 }
2212 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Expected JObject but got [{result.GetType().Name}].", this, GPALObjectType.PuppeteerClient, null);
2213 return default(T);
2214 }
2215
2216 // Try direct deserialization for non-JObject types (e.g., bool, string)
2217 try
2218 {
2219 if (result is T)
2220 {
2221 return (T)result;
2222 }
2223 if (result is JToken)
2224 {
2225 return JsonConvert.DeserializeObject<T>(result.ToString());
2226 }
2227 }
2228 catch //(Exception ex)
2229 {
2230 //GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize [{result}] to type [{typeof(T).Name}]. [{ex.Message}]", this, GPALObjectType.PuppeteerClient, ex);
2231 // Continue to try CDP structure
2232 }
2233
2234 // Try CDP structure (result.value or result)
2235 if (result is JObject jObject)
2236 {
2237 JToken resultToken = jObject["result"];
2238 if (resultToken == null)
2239 {
2240 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Result object is missing 'result' property.", this, GPALObjectType.PuppeteerClient, null);
2241 return default(T);
2243
2244 JToken valueToken = resultToken["value"];
2245 if (valueToken != null)
2246 {
2247 // Special handling for string
2248 if (typeof(T) == typeof(string) && valueToken.Type == JTokenType.String)
2249 {
2250 return (T)(object)valueToken.ToString();
2251 }
2252
2253 try
2254 {
2255 return JsonConvert.DeserializeObject<T>(valueToken.ToString());
2256 }
2257 catch (Exception ex)
2258 {
2259 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize result.value [{valueToken.ToString()}] to type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, ex);
2260 return default(T);
2261 }
2262 }
2263
2264 try
2265 {
2266 return JsonConvert.DeserializeObject<T>(resultToken.ToString());
2267 }
2268 catch (Exception ex)
2269 {
2270 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize result [{resultToken.ToString()}] to type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, ex);
2271 return default(T);
2272 }
2273 }
2274
2275 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unsupported result type [{result.GetType().Name}] for type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, null);
2276 return default(T);
2277 }
2278
2281 /// </summary>
2282 /// <returns>Collection of execution results keyed by result name</returns>
2283 /// <summary>
2284 /// Empties the workflow steps declared with <c>WithWorkflow</c>, so the next While or Until loop runs a
2285 /// new set rather than that one plus everything added before it. Steps otherwise stay on the client for
2286 /// as long as it lives, which is what lets the same set be run more than once.
2287 /// </summary>
2288 /// <example><code>client.ClearWorkflows().WithWorkflow(c =&gt; ...).While(() =&gt; ...);</code></example>
2289 /// <returns>Fluent interface to declare the next workflow</returns>
2291 {
2292 // the two lists are index aligned, so one is never emptied without the other
2293 _workflows.Clear();
2294 _workflowStates.Clear();
2295
2296 return this;
2297 }
2298
2303 /// <example><code>client.ClearResults();</code></example>
2304 /// <returns>Fluent interface to write your workflow</returns>
2306 {
2307 _executionResults.Clear();
2308
2309 return this;
2310 }
2314 /// </summary>
2315 /// <returns>The results, ready to be read by name or by position</returns>
2317 {
2318 return new ResultCollection(_executionResults.Select(entry => (entry.Name, entry.Result, entry.Iteration)).ToList(), _name);
2319 }
2320
2325 /// </summary>
2326 /// <example><code>int passes = client.GetExecutionResults("atBottom").Count;</code></example>
2327 /// <param name="name">The name given to WithResultName</param>
2328 /// <returns>What was stored under that name</returns>
2329 public IReadOnlyList<object> GetExecutionResults(string name)
2330 {
2331 return GetExecutionResults().GetAllResults(name);
2332 }
2333
2336 /// </summary>
2337 /// <param name="name">Header name</param>
2338 /// <param name="value">Header value</param>
2339 /// <returns>Fluent interface to continue building the request</returns>
2340 public IAllowPuppeteerExecution WithHeader(string name, string value)
2341 {
2342 _headers[name] = value;
2343 return this;
2344 }
2345
2347 /// Sets the request parameters as an ordinal array.
2348 /// </summary>
2349 /// <param name="parameters">Ordinal parameter values</param>
2350 /// <returns>Fluent interface to continue building the request</returns>
2351 public IAllowPuppeteerParametersOrExecution WithParameters(params object[] parameters)
2352 {
2353 SetParameter("parameters", parameters); // ordinal array
2354 return this;
2355 }
2356
2358 /// Sets the request parameters from an object with named fields.
2359 /// </summary>
2360 /// <param name="parameters">Object whose properties are the named parameters</param>
2361 /// <returns>Fluent interface to continue building the request</returns>
2363 {
2364 SetParameter("parameters", parameters); // object with named fields
2365 return this;
2366 }
2367
2374 {
2375 SetParameter("parameters", GetResultFromIndex(resultIndex));
2376 return this;
2377 }
2378
2385 {
2386 SetParameter("parameters", GetResultFromName(name));
2387 return this;
2388 }
2389
2395 public IAllowPuppeteerParametersOrExecution WithEncoding(ContentEncoding encoding)
2396 {
2397 _contentEncoding = encoding;
2398 return this;
2399 }
2400
2406 public IAllowPuppeteerWorkflow WithWorkflow(Action<PuppeteerClient> workflow)
2407 {
2408 // the endpoint and parameters standing when the step was declared, put back before every pass so a
2409 // second pass runs the same call the first one did
2410 _workflowStates.Add((_endpoint, _parameters, _nextResultName));
2411 _workflows.Add(workflow);
2412
2413 return this;
2414 }
2415
2421 public IAllowPuppeteerWorkflow WhileLoopTimeout(int timeoutMs)
2422 {
2423 _whileLoopTimeoutMs = timeoutMs;
2424 return this;
2425 }
2426
2432 public IAllowPuppeteerWorkflow WhileLoopMaxIterations(int maxIterations)
2433 {
2434 _whileLoopMaxIterations = maxIterations;
2435 return this;
2436 }
2437
2450 private void RunWorkflowLoop(Func<bool> condition, bool until)
2451 {
2452 if (0 == _workflows.Count)
2453 {
2454 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2455 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}No workflow steps declared for the {(true == until ? "Until" : "While")} loop.",
2456 this, GPALObjectType.PuppeteerClient);
2457
2458 Reset();
2459
2460 return;
2461 }
2462
2463 // read before the first Reset, which clears both
2464 int maxIterations = 0 < _whileLoopMaxIterations ? _whileLoopMaxIterations : 1000;
2465 int timeoutMs = 0 < _whileLoopTimeoutMs ? _whileLoopTimeoutMs : int.MaxValue;
2466
2467 // While runs for as long as the condition holds, Until for as long as it does not
2468 Func<bool> anotherPass = true == until ? () => false == condition() : condition;
2469
2470 int iteration = 0;
2471 DateTime started = DateTime.UtcNow;
2472
2473 while (true == anotherPass()
2474 && maxIterations > iteration
2475 && timeoutMs > (DateTime.UtcNow - started).TotalMilliseconds)
2476 {
2477 _currentIteration = iteration;
2478
2479 for (int step = 0; step < _workflows.Count; step++)
2480 {
2481 (string Endpoint, object Parameters, string NextResultName)? declaredAs = _workflowStates[step];
2482
2483 if (true == declaredAs.HasValue)
2484 (_endpoint, _parameters, _nextResultName) = declaredAs.Value;
2485 else
2486 Reset();
2487
2488 _workflows[step](this);
2489 }
2491 iteration++;
2492 }
2493
2494 if (maxIterations <= iteration)
2495 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
2496 $"{(string.IsNullOrEmpty(_name) ? "" : $"[{_name}] ")}Loop stopped at its maximum of [{maxIterations}] passes.",
2497 this, GPALObjectType.PuppeteerClient);
2498
2499 Reset();
2500
2501 _currentIteration = 1;
2502 }
2503 public IAllowPuppeteerParametersOrExecution While(Func<bool> condition)
2504 {
2505 RunWorkflowLoop(condition, false);
2506
2507 return this;
2508 }
2509
2512 /// </summary>
2513 /// <param name="condition">Predicate evaluated each iteration</param>
2514 /// <returns>Fluent interface to continue building the request</returns>
2515 public IAllowPuppeteerParametersOrExecution Until(Func<bool> condition)
2516 {
2517 RunWorkflowLoop(condition, true);
2518
2519 return this;
2520 }
2521
2523 /// Sets the timeout, in milliseconds, used by CheckNetworkIdle.
2524 /// </summary>
2525 /// <param name="timeoutMs">Timeout in milliseconds</param>
2526 /// <returns>Fluent interface to continue building the request</returns>
2528 {
2529 _timeoutMs = timeoutMs;
2530 return this;
2531 }
2532
2534 /// Sets the prune interval, in milliseconds, used by CheckNetworkIdle.
2535 /// </summary>
2536 /// <param name="pruneMs">Prune interval in milliseconds</param>
2537 /// <returns>Fluent interface to continue building the request</returns>
2539 {
2540 _pruneMs = pruneMs;
2541 return this;
2542 }
2543
2545 /// Sets the maximum number of in-flight connections allowed before the network is considered busy, used by CheckNetworkIdle.
2546 /// </summary>
2547 /// <param name="maxConnections">Maximum number of in-flight connections</param>
2548 /// <returns>Fluent interface to continue building the request</returns>
2550 {
2551 _maxConnections = maxConnections;
2552 return this;
2553 }
2554
2556 /// Sets the modifier keys parameter for the request.
2557 /// </summary>
2558 /// <param name="modifierKeys">Modifier keys to hold during the action</param>
2559 /// <returns>Fluent interface to continue building the request</returns>
2560 public IAllowPuppeteerParametersOrExecution WithModifiers(ModifierKeys modifierKeys)
2561 {
2562 SetParameter("modifiers", GetModifierBitmask(modifierKeys).ToString());
2563 return this;
2564 }
2565
2567 /// Sets the horizontal pixels parameter for the request.
2568 /// </summary>
2569 /// <param name="hPixels">Number of horizontal pixels</param>
2570 /// <returns>Fluent interface to continue building the request</returns>
2571 public IAllowPuppeteerScrollElement WithHPixels(int hPixels)
2572 {
2573 SetParameter("hPixels", hPixels);
2574 return this;
2575 }
2576
2578 /// Sets the vertical pixels parameter for the request.
2579 /// </summary>
2580 /// <param name="vPixels">Number of vertical pixels</param>
2581 /// <returns>Fluent interface to continue building the request</returns>
2582 public IAllowPuppeteerScrollElement WithVPixels(int vPixels)
2583 {
2584 SetParameter("vPixels", vPixels);
2585 return this;
2586 }
2587
2589 /// Sets the horizontal drag delta parameter for a drag and drop.
2590 /// </summary>
2591 /// <param name="deltaX">Horizontal drag distance, in pixels</param>
2592 /// <returns>Fluent interface to continue building the request</returns>
2593 public IAllowPuppeteerDragAndDrop WithDeltaX(int deltaX)
2594 {
2595 SetParameter("deltaX", deltaX);
2596 return this;
2597 }
2598
2601 /// </summary>
2602 /// <param name="deltaY">Vertical drag distance, in pixels</param>
2603 /// <returns>Fluent interface to continue building the request</returns>
2604 public IAllowPuppeteerDragAndDrop WithDeltaY(int deltaY)
2605 {
2606 SetParameter("deltaY", deltaY);
2607 return this;
2608 }
2609
2613 /// <param name="offsetX">Horizontal grab offset, in pixels, within the element</param>
2614 /// <returns>Fluent interface to continue building the request</returns>
2615 public IAllowPuppeteerDragAndDrop WithOffsetX(int offsetX)
2616 {
2617 SetParameter("offsetX", offsetX);
2618 return this;
2619 }
2620
2624 /// <param name="offsetY">Vertical grab offset, in pixels, within the element</param>
2625 /// <returns>Fluent interface to continue building the request</returns>
2626 public IAllowPuppeteerDragAndDrop WithOffsetY(int offsetY)
2627 {
2628 SetParameter("offsetY", offsetY);
2629 return this;
2630 }
2631
2634 /// </summary>
2635 /// <param name="attribute">The attribute name</param>
2636 /// <returns>Fluent interface to continue building the request</returns>
2637 public IAllowPuppeteerAttribute WithAttribute(string attribute)
2638 {
2639 _attribute = attribute;
2640 SetParameter("attribute", attribute);
2641 return this;
2642 }
2643
2645 /// Sets the attribute value parameter for SetAttribute requests.
2646 /// </summary>
2647 /// <param name="value">The value to set</param>
2648 /// <returns>Fluent interface to continue building the request</returns>
2649 public IAllowPuppeteerAttribute WithValue(string value)
2650 {
2651 _value = value;
2652 SetParameter("value", value);
2653 return this;
2654 }
2655
2657 /// Sets the destination element id parameter for a drag and drop.
2658 /// </summary>
2659 /// <param name="destElementId">Element id of the drop target</param>
2660 /// <returns>Fluent interface to continue building the request</returns>
2661 public IAllowPuppeteerExecution WithDestElementId(string destElementId)
2662 {
2663 SetParameter("destElementId", destElementId);
2664 return this;
2665 }
2666
2668 /// Sets the CSS parameter
2669 /// </summary>
2670 /// <param name="css">The CSS selector</param>
2671 /// <returns></returns>
2672 public IAllowPuppeteerExecution WithDeviceName(string sinkName)
2673 {
2674 SetParameter("sinkName", sinkName);
2675 return this;
2676 }
2677
2679 /// Configure this client to navigate the browser back one page in history.
2680 /// </summary>
2681 /// <returns>Fluent interface to continue building the request</returns>
2683 {
2684 WithEndpoint(ApiEndpoint.Back);
2685 return this;
2686 }
2687
2691 /// <param name="imageFormat">The image format to capture</param>
2692 /// <returns>Fluent interface to continue building the request</returns>
2693 public IAllowPuppeteerExecution CaptureVisibleTab(ImageFormat imageFormat = ImageFormat.JPEG)
2694 {
2695 WithImageFormat(imageFormat);
2696 WithEndpoint(ApiEndpoint.CaptureVisibleTab);
2697 return this;
2698 }
2703 /// <param name="deviceNameOrId">The Cast Device Name or ID</param>
2704 /// <returns></returns>
2705 public IAllowPuppeteerExecution CastDesktop(string deviceNameOrId)
2706 {
2707 WithEndpoint(ApiEndpoint.CastDesktop).WithDeviceName(deviceNameOrId);
2708 return this;
2709 }
2715 /// <returns></returns>
2716 public IAllowPuppeteerExecution CastTab(string deviceNameOrId)
2717 {
2718 WithEndpoint(ApiEndpoint.CastTab).WithDeviceName(deviceNameOrId);
2719 return this;
2720 }
2721
2728 {
2729 WithEndpoint(ApiEndpoint.CloseTab);
2730 if (url != null) WithUrl(url.Url);
2731 return this;
2732 }
2733
2734
2739 public IAllowPuppeteerExecution CloseTab(int tabId)
2741 WithEndpoint(ApiEndpoint.CloseTab);
2742 WithTabId(tabId);
2743 return this;
2744 }
2745
2746
2751 public IAllowPuppeteerExecution CloseWindow(GPALUrl url = null)
2752 {
2753 WithEndpoint(ApiEndpoint.CloseWindow);
2754 if (url != null) WithUrl(url.Url);
2755 return this;
2756 }
2757
2759
2763 public IAllowPuppeteerExecution CloseWindow(int tabId)
2764 {
2765 WithEndpoint(ApiEndpoint.CloseWindow);
2766 WithTabId(tabId);
2767 return this;
2768 }
2769
2770
2775 public IAllowPuppeteerStorageOptions DeleteStorage(WebsiteStorageType storageType)
2777 WithEndpoint(ApiEndpoint.DeleteStorage);
2778 SetParameter("storageType", storageType.ToString());
2779
2780 return this;
2781 }
2782
2787 /// <returns>Fluent interface to continue building the request</returns>
2788 public IAllowPuppeteerParameters ExecuteJavaScript(string script)
2789 {
2790 WithEndpoint(ApiEndpoint.ExecuteJavaScript);
2791 SetParameter("expression", script);
2792 return this;
2793 }
2794
2798 /// </summary>
2799 /// <param name="url">Url to request. A relative one resolves against wherever the browser currently is</param>
2800 /// <returns>Fluent interface to continue building the request</returns>
2802 {
2803 WithEndpoint(ApiEndpoint.Fetch);
2804 SetParameter("url", url);
2805 return this;
2806 }
2807
2810 /// </summary>
2811 /// <param name="method">HTTP method</param>
2812 /// <returns>Fluent interface to continue building the request</returns>
2814 {
2815 SetParameter("method", method);
2816 return this;
2817 }
2818
2822 /// <param name="body">Request body</param>
2823 /// <returns>Fluent interface to continue building the request</returns>
2825 {
2826 SetParameter("body", body);
2827 return this;
2828 }
2829
2835 public IAllowPuppeteerParametersOrExecution WithContentType(string contentType)
2836 {
2837 SetParameter("contentType", contentType);
2838 return this;
2839 }
2840
2843 /// </summary>
2844 /// <param name="headers">Header names and values in pairs</param>
2845 /// <returns>Fluent interface to continue building the request</returns>
2846 public IAllowPuppeteerParametersOrExecution WithHeaders(string[] headers)
2847 {
2848 SetParameter("headers", headers);
2849 return this;
2850 }
2851
2857
2858 public IAllowPuppeteerParametersOrExecution WithBytes(bool asBytes)
2859 {
2860 SetParameter("bytes", asBytes);
2861 return this;
2862 }
2863
2864 /// <summary>
2865 /// Configure this client to append text to the given input element.
2866 /// </summary>
2867 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the input</param>
2868 /// <param name="delayMs">Inter-character pacing delay in milliseconds for the per-character key-event fallback. 0 uses the fallback's default pacing.</param>
2869 /// <returns>Fluent interface to continue building the request</returns>
2870 public IAllowPuppeteerInputText FillInAppend(dynamic elementOrelementId, int delayMs = 0)
2871 {
2872 WithEndpoint(ApiEndpoint.FillInAppend);
2873
2874 if (elementOrelementId is GPALElement element)
2875 WithElements(new List<GPALElement>() { element });
2876 else if (elementOrelementId is List<GPALElement> elements)
2877 WithElements(elements);
2878 else
2879 WithElementId(elementOrelementId);
2880
2881 SetParameter("typingDelay", delayMs);
2882 return this;
2884
2888
2891 public IAllowPuppeteerInputText FillInInsert(dynamic elementOrelementId, int delayMs = 0)
2892 {
2893 WithEndpoint(ApiEndpoint.FillInInsert);
2894
2895 if (elementOrelementId is GPALElement element)
2896 WithElements(new List<GPALElement>() { element });
2897 else if (elementOrelementId is List<GPALElement> elements)
2898 WithElements(elements);
2899 else
2900 WithElementId(elementOrelementId);
2901
2902 SetParameter("typingDelay", delayMs);
2903 return this;
2905
2908
2912 public IAllowPuppeteerInputText FillInOverwrite(dynamic elementOrelementId, int delayMs = 0)
2913 {
2914 WithEndpoint(ApiEndpoint.FillInOverwrite);
2915
2916 if (elementOrelementId is GPALElement element)
2917 WithElements(new List<GPALElement>() { element });
2918 else if (elementOrelementId is List<GPALElement> elements)
2919 WithElements(elements);
2920 else
2921 WithElementId(elementOrelementId);
2922
2923 SetParameter("typingDelay", delayMs);
2924 return this;
2925 }
2926
2930 /// <returns>Fluent interface to continue building the request</returns>
2932 {
2933 WithEndpoint(ApiEndpoint.Forward);
2934 return this;
2935 }
2936
2941 public IAllowPuppeteerExecution FullScreen()
2943 WithEndpoint(ApiEndpoint.FullScreen);
2944 return this;
2945 }
2946
2948
2952 public IAllowPuppeteerExecution GetReadyStatus()
2954 return GetReadyStatus(null);
2955 }
2956
2958
2963 internal IAllowPuppeteerExecution GetReadyStatus(string sessionToken)
2964 {
2965 WithEndpoint(ApiEndpoint.GetReadyStatus);
2966
2967 if (false == string.IsNullOrEmpty(sessionToken))
2968 WithSessionToken(sessionToken);
2969
2970 return this;
2971 }
2972
2974 /// Configure this client to check whether network activity has gone idle.
2975 /// </summary>
2976 /// <param name="maxConnections">Maximum number of in-flight connections still considered idle</param>
2977 /// <returns>Fluent interface to continue building the request</returns>
2978 public IAllowPuppeteerParametersOrExecution CheckNetworkIdle(int maxConnections = 0)
2979 {
2980 WithEndpoint(ApiEndpoint.CheckNetworkIdle);
2981 WithMaxConnections(maxConnections);
2982 return this;
2983 }
2984
2986 /// Configure this client to retrieve the shadow root of the element matching the given CSS selector.
2987 /// </summary>
2988 /// <param name="css">CSS selector of the element</param>
2989 /// <returns>Fluent interface to continue building the request</returns>
2990 public IAllowPuppeteerExecution GetShadowRoot(string css)
2991 {
2992 WithEndpoint(ApiEndpoint.GetShadowRoot);
2993 WithCss(css);
2994 return this;
2995 }
3002 {
3003 WithEndpoint(ApiEndpoint.GetCurrentUrl);
3004 return this;
3005 }
3006
3007 /// <summary>
3008 /// Configure this client to retrieve information about the current browser window.
3009 /// </summary>
3010 /// <returns>Fluent interface to continue building the request</returns>
3012 {
3013 WithEndpoint(ApiEndpoint.GetCurrentWindow);
3014 return this;
3015 }
3016
3020
3022 public IAllowPuppeteerStorageOptions GetStorage(WebsiteStorageType storageType)
3023 {
3024 WithEndpoint(ApiEndpoint.GetStorage);
3025 SetParameter("storageType", storageType.ToString());
3027 return this;
3028 }
3029
3032
3034 public IAllowPuppeteerExecution GetLanguages()
3035 {
3036 WithEndpoint(ApiEndpoint.GetLanguages);
3037 return this;
3039
3044 public IAllowPuppeteerExecution GetUserAgent()
3045 {
3046 WithEndpoint(ApiEndpoint.GetUserAgent);
3047 return this;
3048 }
3049
3050 /// <summary>
3051 /// Configure this client to navigate the current tab to the given URL, resolving relative URLs and checking robots.txt restrictions.
3052 /// </summary>
3053 /// <param name="url">The URL to navigate to</param>
3054 /// <returns>Fluent interface to continue building the request</returns>
3056 {
3057 url.ForUrl(MagicHelper.GetFullUrl(url?.Url, _puppeteerCommunicator.Browser, out _puppeteerCommunicator.Browser._areRobotsAllowed));
3058
3059 if (false == _puppeteerCommunicator.Browser.AreRobotsAllowed && true == _puppeteerCommunicator.Browser.BrowserSettings.ObeyRobotsTxt)
3060 {
3061 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"[{url?.Url}] not allowed to robots and your honoring robots.txt. Not going to URL. Workflow will fail.", this, GPALObjectType.PuppeteerClient);
3062 url.ForUrl("https://google.com");
3063 }
3064 WithEndpoint(ApiEndpoint.GoTo);
3065 WithUrl(url?.Url);
3066 return this;
3067 }
3068
3075 {
3076 WithEndpoint(ApiEndpoint.GoToTab);
3077 WithUrl(url?.Url);
3078 return this;
3079 }
3080
3085 /// <returns>Fluent interface to continue building the request</returns>
3086 public IAllowPuppeteerExecution GoToTab(int tabId)
3087 {
3088 WithEndpoint(ApiEndpoint.GoToTab);
3089 WithTabId(tabId);
3090 return this;
3091 }
3092
3096 /// <param name="url">The URL of the tab whose window to switch to</param>
3097 /// <returns>Fluent interface to continue building the request</returns>
3099 {
3100 WithEndpoint(ApiEndpoint.GoToWindow);
3101 WithUrl(url?.Url);
3102 return this;
3103 }
3104
3107 /// </summary>
3108 /// <param name="tabId">The tab id whose window to switch to</param>
3109 /// <returns>Fluent interface to continue building the request</returns>
3110 public IAllowPuppeteerExecution GoToWindow(int tabId)
3111 {
3112 WithEndpoint(ApiEndpoint.GoToWindow);
3113 WithTabId(tabId);
3114 return this;
3115 }
3116
3117 /// <summary>
3118 /// Configure this client to switch context into the element matching the given selector.
3119 /// </summary>
3120 /// <param name="selector">CSS selector of the element</param>
3121 /// <returns>Fluent interface to continue building the request</returns>
3122 public IAllowPuppeteerExecution InElement(string selector)
3123 {
3124 WithEndpoint(ApiEndpoint.SwitchToElement);
3125 WithElementId(selector);
3126 return this;
3127 }
3134 {
3135 WithEndpoint(ApiEndpoint.SwitchToDefaultContent);
3136 return this;
3137 }
3138
3140 /// Configure this client to switch context into the shadow root of the element matching the given selector.
3141 /// </summary>
3142 /// <param name="selector">CSS selector of the element</param>
3143 /// <returns>Fluent interface to continue building the request</returns>
3144 public IAllowPuppeteerExecution InShadowDom(string selector)
3145 {
3146 WithEndpoint(ApiEndpoint.SwitchToShadowRoot);
3147 WithElementId(selector);
3148 return this;
3149 }
3150
3155 public IAllowPuppeteerExecution Maximize()
3156 {
3157 WithEndpoint(ApiEndpoint.Maximize);
3158 return this;
3159 }
3166 {
3167 WithEndpoint(ApiEndpoint.Minimize);
3168 return this;
3169 }
3175
3176 public IAllowPuppeteerExecution MoveTo(string selector)
3177 {
3178 WithEndpoint(ApiEndpoint.MoveTo);
3179 WithElementId(selector);
3180 return this;
3181 }
3182
3185
3188 public IAllowPuppeteerExecution NewTab(GPALUrl url = null)
3189 {
3190 WithEndpoint(ApiEndpoint.NewTab);
3192 url?.ForUrl(MagicHelper.GetFullUrl(url?.Url, _puppeteerCommunicator.Browser, out _puppeteerCommunicator.Browser._areRobotsAllowed));
3193
3194 if (false == _puppeteerCommunicator.Browser.AreRobotsAllowed && true == _puppeteerCommunicator.Browser.BrowserSettings.ObeyRobotsTxt)
3195 {
3196 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"[{url.Url}] not allowed to robots and your honoring robots.txt. Not going to URL. Workflow will fail.", this, GPALObjectType.PuppeteerClient);
3197 url.ForUrl("https://google.com");
3198 }
3199
3200 WithUrl(url?.Url);
3201 return this;
3202 }
3203
3206
3208 public IAllowPuppeteerExecution NextTab()
3209 {
3210 WithEndpoint(ApiEndpoint.NextTab);
3211 return this;
3213
3218 public IAllowPuppeteerExecution NextWindow()
3219 {
3220 WithEndpoint(ApiEndpoint.NextWindow);
3221 return this;
3222 }
3223
3224 /// <summary>
3225 /// Configure this client to restore the browser window to its normal (non-maximized, non-minimized) state.
3226 /// </summary>
3227 /// <returns>Fluent interface to continue building the request</returns>
3229 {
3230 WithEndpoint(ApiEndpoint.Normal);
3231 return this;
3232 }
3233
3236 /// </summary>
3237 /// <param name="url">The URL to navigate to in the new window</param>
3238 /// <returns>Fluent interface to continue building the request</returns>
3240 {
3241 WithEndpoint(ApiEndpoint.OpenWindow);
3242
3243 url.ForUrl(MagicHelper.GetFullUrl(url?.Url, _puppeteerCommunicator.Browser, out _puppeteerCommunicator.Browser._areRobotsAllowed));
3244
3245 if (false == _puppeteerCommunicator.Browser.AreRobotsAllowed && true == _puppeteerCommunicator.Browser.BrowserSettings.ObeyRobotsTxt)
3246 {
3247 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"[{url?.Url}] not allowed to robots and your honoring robots.txt. Not going to URL. Workflow will fail.", this, GPALObjectType.PuppeteerClient);
3248 url.ForUrl("https://google.com");
3249 }
3250
3251 WithUrl(url?.Url);
3252 return this;
3253 }
3254
3257 /// </summary>
3258 /// <param name="referrer">The referrer URL to send</param>
3259 /// <returns>Fluent interface to continue building the request</returns>
3260 public IAllowPuppeteerExecution OverrideReferrer(string referrer)
3261 {
3262 WithEndpoint(ApiEndpoint.OverrideReferrer);
3263 WithReferrer(referrer);
3264 return this;
3265 }
3266
3268 /// Configure this client to override the browser's user agent string for subsequent requests.
3269 /// </summary>
3270 /// <param name="userAgent">The user agent string to use</param>
3271 /// <returns>Fluent interface to continue building the request</returns>
3272 public IAllowPuppeteerExecution SetUserAgent(string userAgent)
3273 {
3274 WithEndpoint(ApiEndpoint.SetUserAgent);
3275 SetParameter("userAgent", userAgent);
3276 return this;
3277 }
3278
3279 /// <summary>
3280 /// Configure this client to scroll the page down by the given number of page heights.
3281 /// </summary>
3282 /// <param name="pagesToScroll">Number of page heights to scroll down</param>
3283 /// <returns>Fluent interface to continue building the request</returns>
3284 public IAllowPuppeteerExecution PageDown(int pagesToScroll = 1)
3285 {
3286 WithEndpoint(ApiEndpoint.PageDown);
3287 SetParameter("pagesToScroll", pagesToScroll);
3288 return this;
3290
3294
3295 public IAllowPuppeteerExecution PageEnd()
3296 {
3297 WithEndpoint(ApiEndpoint.PageEnd);
3298 return this;
3300
3304
3305 public IAllowPuppeteerExecution PageTop()
3306 {
3307 WithEndpoint(ApiEndpoint.PageTop);
3308 return this;
3309 }
3316 public IAllowPuppeteerExecution PageUp(int pagesToScroll = 1)
3317 {
3318 WithEndpoint(ApiEndpoint.PageUp);
3319 SetParameter("pagesToScroll", pagesToScroll);
3320 return this;
3321 }
3328 {
3329 WithEndpoint(ApiEndpoint.PreviousTab);
3330 return this;
3331 }
3332
3335 /// </summary>
3336 /// <returns>Fluent interface to continue building the request</returns>
3338 {
3339 WithEndpoint(ApiEndpoint.PreviousWindow);
3340 return this;
3341 }
3342
3347 public IAllowPuppeteerExecution Refresh()
3348 {
3349 WithEndpoint(ApiEndpoint.Refresh);
3350 return this;
3351 }
3352
3353 /// <summary>
3354 /// Configure this client to press and hold the given modifier key(s).
3355 /// </summary>
3356 /// <param name="modifierKeys">Modifier key(s) to press and hold</param>
3357 /// <returns>Fluent interface to continue building the request</returns>
3358 public IAllowPuppeteerExecution PressModifierKey(ModifierKeys modifierKeys)
3359 {
3360 WithEndpoint(ApiEndpoint.PressModifierKey);
3361 SetParameter("modifier", GetModifierBitmask(modifierKeys));
3362 return this;
3363 }
3364
3368
3370 public IAllowPuppeteerExecution ReleaseModifierKey(ModifierKeys modifierKeys)
3371 {
3372 WithEndpoint(ApiEndpoint.ReleaseModifierKey);
3373 SetParameter("modifier", GetModifierBitmask(modifierKeys));
3374 return this;
3375 }
3376
3377 // Helper method to convert ModifierKeys to CDP modifiers bitmask
3383 public int GetModifierBitmask(ModifierKeys modifierKeys)
3384 {
3385 int bitmask = 0;
3386 if (modifierKeys.HasFlag(ModifierKeys.Alt))
3387 bitmask |= 1; // CDP Alt
3388 if (modifierKeys.HasFlag(ModifierKeys.Control))
3389 bitmask |= 2; // CDP Ctrl
3390 if (modifierKeys.HasFlag(ModifierKeys.Windows)) // Maps to Meta/Command
3391 bitmask |= 4; // CDP Meta
3392 if (modifierKeys.HasFlag(ModifierKeys.Shift))
3393 bitmask |= 8; // CDP Shift
3394 return bitmask;
3401 public string GetModifiersString(int bitmask)
3402 {
3403 var modifiers = new List<string>();
3404
3405 if ((bitmask & 1) != 0) // Check for Alt
3406 modifiers.Add("Alt");
3407 if ((bitmask & 2) != 0) // Check for Control
3408 modifiers.Add("Control");
3409 if ((bitmask & 4) != 0) // Check for Windows/Meta
3410 modifiers.Add("Windows");
3411 if ((bitmask & 8) != 0) // Check for Shift
3412 modifiers.Add("Shift");
3413
3414 return modifiers.Count > 0 ? string.Join("+", modifiers) : "None";
3415 }
3421 {
3422 WithEndpoint(ApiEndpoint.Restore);
3423 return this;
3424 }
3425
3427
3431 public IAllowPuppeteerParametersOrExecution LeftClickAndDownload(string selector)
3432 {
3433 WithEndpoint(ApiEndpoint.LeftClickAndDownload);
3434 WithElementId(selector);
3435 return this;
3436 }
3437
3443 public IAllowPuppeteerExecution ScrollWindowByHorizontal(int pixels)
3444 {
3445 WithEndpoint(ApiEndpoint.ScrollWindowByHorizontal);
3446 SetParameter("hPixels", pixels);
3447 return this;
3448 }
3454
3456 {
3457 WithEndpoint(ApiEndpoint.ScrollWindowByVertical);
3458 SetParameter("vPixels", pixels);
3459 return this;
3460 }
3461
3464
3468 public IAllowPuppeteerExecution SendString(string text, int delayMs = 0)
3470 WithEndpoint(ApiEndpoint.SendString);
3471 WithText(text);
3472 SetParameter("typingDelay", delayMs);
3473 return this;
3474 }
3475
3480 /// <returns>Fluent interface to continue building the request</returns>
3481 public IAllowPuppeteerExecution SendKey(byte vkcode)
3482 {
3483 VkCodeToDomKeyConverter.TryConvertVkCodeToDomKey(vkcode, out string key, out string code);
3484
3485 WithEndpoint(ApiEndpoint.SendKey);
3486 SetParameter("key", key);
3487 SetParameter("code", code);
3488 SetParameter("vk", (int)vkcode);
3489 return this;
3490 }
3491
3498 {
3499 WithEndpoint(ApiEndpoint.OverrideReferrer).WithReferrer("https://www.google.com");
3500 return this;
3501 }
3502
3504 /// Configure this client to stop any active screen casting.
3505 /// </summary>
3506 /// <returns>Fluent interface to continue building the request</returns>
3508 {
3509 WithEndpoint(ApiEndpoint.StopCasting);
3510 return this;
3511 }
3512
3516 /// <returns>Fluent interface to continue building the request</returns>
3518 {
3519 WithEndpoint(ApiEndpoint.SwitchToDefaultContent);
3520 return this;
3521 }
3522
3527 /// <returns>Fluent interface to continue building the request</returns>
3528 public IAllowPuppeteerExecution SwitchToElement(string selector)
3529 {
3530 WithEndpoint(ApiEndpoint.SwitchToElement);
3531 WithElementId(selector);
3532 return this;
3533 }
3534
3539 /// <returns>Fluent interface to continue building the request</returns>
3540 public IAllowPuppeteerExecution SwitchToFrame(string selector)
3541 {
3542 WithEndpoint(ApiEndpoint.SwitchToFrame);
3543 WithElementId(selector);
3544 return this;
3545 }
3546
3553 {
3554 WithEndpoint(ApiEndpoint.SwitchToShadowRoot);
3555 WithElementId(selector);
3556 return this;
3557 }
3558
3565 {
3566 WithEndpoint(ApiEndpoint.InjectScript);
3567 SetParameter("script", script);
3568 return this;
3569 }
3570
3575 public IAllowPuppeteerExecution ClearInjectedScripts()
3577 WithEndpoint(ApiEndpoint.ClearInjectedScripts);
3578 return this;
3579 }
3580
3582
3587 public IAllowPuppeteerExecution DownloadTo(string downloadPath)
3589 // the parameter only. setting the endpoint here overwrote whatever it was chained onto, so
3590 // LeftClickAndDownload(handle).DownloadTo(path) called set-download-filename and never clicked
3591 SetParameter("downloadPath", downloadPath);
3592 return this;
3593 }
3594
3599 /// <returns>Fluent interface to continue building the request</returns>
3600 public IAllowPuppeteerExecution FireChangeEvent(string selector)
3601 {
3602 WithEndpoint(ApiEndpoint.FireChangeEvent);
3603 WithElementId(selector);
3604 return this;
3605 }
3606
3610 /// <param name="selector">CSS selector of the element</param>
3611 /// <returns>Fluent interface to continue building the request</returns>
3612 public IAllowPuppeteerExecution Focus(string selector)
3613 {
3614 WithEndpoint(ApiEndpoint.Focus);
3615 WithElementId(selector);
3616 return this;
3617 }
3618
3622 /// <param name="backendNodeId">Backend node id of the element</param>
3623 /// <returns>Fluent interface to continue building the request</returns>
3624 public IAllowPuppeteerExecution GetBoundingClientRect(string backendNodeId)
3625 {
3626 WithEndpoint(ApiEndpoint.GetBoundingClientRect);
3627 WithElementId(backendNodeId);
3628 return this;
3629 }
3630
3634 /// <param name="selector">CSS selector of the element</param>
3635 /// <returns>Fluent interface to continue building the request</returns>
3637 {
3638 WithEndpoint(ApiEndpoint.GetElementAttributeHash);
3639 WithElementId(selector);
3640 return this;
3641 }
3642
3646 /// <returns>Fluent interface to continue building the request</returns>
3648 {
3649 WithEndpoint(ApiEndpoint.GetPageSource);
3650 return this;
3651 }
3652
3659 {
3660 WithEndpoint(ApiEndpoint.GetParentNode);
3661 WithElementId(selector);
3662 return this;
3663 }
3664
3671 {
3672 WithEndpoint(ApiEndpoint.GetContentAndCss);
3673 WithElementId(selector);
3674 return this;
3675 }
3676
3683 {
3684 WithEndpoint(ApiEndpoint.GetCssAttributes);
3685 WithElementId(selector);
3686 return this;
3687 }
3688
3695 {
3696 WithEndpoint(ApiEndpoint.GetDomAttributes);
3697 WithElementId(selector);
3698 return this;
3699 }
3700
3707 {
3708 WithEndpoint(ApiEndpoint.GetDomProperties);
3709 WithElementId(selector);
3710 return this;
3711 }
3712
3717 /// <returns>Fluent interface to continue building the request</returns>
3718 public IAllowPuppeteerExecution HideElement(string selector)
3719 {
3720 WithEndpoint(ApiEndpoint.HideElement);
3721 WithElementId(selector);
3722 return this;
3723 }
3724
3728 /// <param name="selector">CSS selector of the element</param>
3729 /// <returns>Fluent interface to continue building the request</returns>
3730 public IAllowPuppeteerExecution IsClickable(string selector)
3731 {
3732 WithEndpoint(ApiEndpoint.IsClickable);
3733 WithElementId(selector);
3734 return this;
3735 }
3736
3740 /// <param name="selector">CSS selector of the element</param>
3741 /// <returns>Fluent interface to continue building the request</returns>
3742 public IAllowPuppeteerExecution IsDisplayed(string selector)
3743 {
3744 WithEndpoint(ApiEndpoint.IsDisplayed);
3745 WithElementId(selector);
3746 return this;
3747 }
3748
3752 /// <param name="selector">CSS selector of the element</param>
3753 /// <returns>Fluent interface to continue building the request</returns>
3754 public IAllowPuppeteerExecution IsEnabled(string selector)
3755 {
3756 WithEndpoint(ApiEndpoint.IsEnabled);
3757 WithElementId(selector);
3758 return this;
3759 }
3760
3765 public IAllowPuppeteerExecution IsEndOfPage()
3766 {
3767 WithEndpoint(ApiEndpoint.IsEndOfPage);
3768 return this;
3769 }
3770
3774 /// <param name="element">The element to check</param>
3775 /// <returns>Fluent interface to continue building the request</returns>
3777 {
3778 WithEndpoint(ApiEndpoint.IsVisibleInViewport);
3779 SetParameter("gpalElements", new List<GPALElement>() { (GPALElement)element });
3780 return this;
3781 }
3782
3788 public IAllowPuppeteerExecution IsVisibleInViewport(string backendNodeId)
3789 {
3790 WithEndpoint(ApiEndpoint.IsVisibleInViewport);
3791 WithElementId(backendNodeId);
3792 return this;
3793 }
3794
3796 /// Configure this client to left-click the given element.
3797 /// </summary>
3798 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, element id, or a viewport Point for a target with no DOM handle</param>
3799 /// <returns>Fluent interface to continue building the request</returns>
3800 public IAllowPuppeteerParametersOrExecution LeftClick(dynamic elementOrelementId)
3801 {
3802 if (elementOrelementId is System.Drawing.Point point)
3803 return WithClickPoint(point, ClickType.LeftClick);
3804
3805 WithEndpoint(ApiEndpoint.LeftClick);
3806
3807 if (elementOrelementId is GPALElement element)
3808 WithElements(new List<GPALElement>() { element });
3809 else if (elementOrelementId is List<GPALElement> elements)
3810 WithElements(elements);
3811 else
3812 WithElementId(elementOrelementId);
3813
3814 return this;
3815 }
3816
3818 /// Configure this client to middle-click the given element.
3819 /// </summary>
3820 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the target</param>
3821 /// <returns>Fluent interface to continue building the request</returns>
3822 public IAllowPuppeteerParametersOrExecution MiddleClick(dynamic elementOrelementId)
3823 {
3824 if (elementOrelementId is System.Drawing.Point point)
3825 return WithClickPoint(point, ClickType.MiddleClick);
3826
3827 WithEndpoint(ApiEndpoint.MiddleClick);
3828
3829 if (elementOrelementId is GPALElement element)
3830 WithElements(new List<GPALElement>() { element });
3831 else if (elementOrelementId is List<GPALElement> elements)
3832 WithElements(elements);
3833 else
3834 WithElementId(elementOrelementId);
3835
3836 return this;
3837 }
3838
3840 /// Configure this client to double left-click the given element.
3841 /// </summary>
3842 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the target</param>
3843 /// <returns>Fluent interface to continue building the request</returns>
3844 public IAllowPuppeteerParametersOrExecution LeftDoubleClick(dynamic elementOrelementId)
3845 {
3846 if (elementOrelementId is System.Drawing.Point point)
3847 return WithClickPoint(point, ClickType.LeftDoubleClick);
3848
3849 WithEndpoint(ApiEndpoint.LeftDoubleClick);
3850
3851 if (elementOrelementId is GPALElement element)
3852 WithElements(new List<GPALElement>() { element });
3853 else if (elementOrelementId is List<GPALElement> elements)
3854 WithElements(elements);
3855 else
3856 WithElementId(elementOrelementId);
3857
3858 return this;
3859 }
3860
3862 /// Configure this client to right-click the given element.
3863 /// </summary>
3864 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the target</param>
3865 /// <returns>Fluent interface to continue building the request</returns>
3866 public IAllowPuppeteerParametersOrExecution RightClick(dynamic elementOrelementId)
3867 {
3868 if (elementOrelementId is System.Drawing.Point point)
3869 return WithClickPoint(point, ClickType.RightClick);
3870
3871 WithEndpoint(ApiEndpoint.RightClick);
3873 if (elementOrelementId is GPALElement element)
3874 WithElements(new List<GPALElement>() { element });
3875 else if (elementOrelementId is List<GPALElement> elements)
3876 WithElements(elements);
3877 else
3878 WithElementId(elementOrelementId);
3879
3880 return this;
3881 }
3882
3885
3888 public IAllowPuppeteerSelectOptions SelectClick(string elementHandle) // objectid
3889 {
3890 WithEndpoint(ApiEndpoint.SelectClick);
3891 WithElementId(elementHandle);
3892
3893 return this;
3894 }
3895
3901 {
3902 SetParameter("selectValue", value);
3903 return this;
3904 }
3905
3911 {
3912 SetParameter("selectIndex", index);
3913 return this;
3914 }
3915
3920 public IAllowPuppeteerExecution ScrollIntoView(dynamic elementOrelementId)
3921 {
3922 WithEndpoint(ApiEndpoint.ScrollIntoView);
3923
3924 if (elementOrelementId is GPALElement element)
3925 WithElements(new List<GPALElement>() { element });
3926 else if (elementOrelementId is List<GPALElement> elements)
3927 WithElements(elements);
3928 else
3929 WithElementId(elementOrelementId);
3931 return this;
3932 }
3933
3935
3938 public IAllowPuppeteerExecution WindowInnerHeight()
3939 {
3940 WithEndpoint(ApiEndpoint.WindowInnerHeight);
3941 return this;
3942 }
3943
3945
3948 public IAllowPuppeteerExecution WindowInnerWidth()
3949 {
3950 WithEndpoint(ApiEndpoint.WindowInnerWidth);
3951 return this;
3952 }
3953
3955
3958 public IAllowPuppeteerExecution WindowOuterHeight()
3959 {
3960 WithEndpoint(ApiEndpoint.WindowOuterHeight);
3961 return this;
3962 }
3963
3965
3968 public IAllowPuppeteerExecution WindowOuterWidth()
3969 {
3970 WithEndpoint(ApiEndpoint.WindowOuterWidth);
3971 return this;
3972 }
3973
3977
3978 public IAllowPuppeteerExecution WindowPageOffsetX()
3979 {
3980 WithEndpoint(ApiEndpoint.WindowPageOffsetX);
3981 return this;
3982 }
3983
3988 public IAllowPuppeteerExecution WindowPageOffsetY()
3989 {
3990 WithEndpoint(ApiEndpoint.WindowPageOffsetY);
3991 return this;
3992 }
3993
3998 public IAllowPuppeteerExecution WindowScreenLeft()
3999 {
4000 WithEndpoint(ApiEndpoint.WindowScreenLeft);
4001 return this;
4002 }
4003
4007
4008 public IAllowPuppeteerExecution WindowScreenTop()
4009 {
4010 WithEndpoint(ApiEndpoint.WindowScreenTop);
4011 return this;
4012 }
4019 public IAllowPuppeteerExecution Hover(string selector)
4020 {
4021 WithEndpoint(ApiEndpoint.Hover);
4022 WithElementId(selector);
4023 return this;
4024 }
4031
4033 private IAllowPuppeteerParametersOrExecution WithClickPoint(System.Drawing.Point point, ClickType clickType)
4034 {
4035 WithEndpoint(ApiEndpoint.ClickPoint);
4036 SetParameter("x", point.X);
4037 SetParameter("y", point.Y);
4038 SetParameter("clickType", (int)clickType);
4039 return this;
4040 }
4041
4043
4048 public IAllowPuppeteerExecution MoveTo(System.Drawing.Point point)
4049 {
4050 WithEndpoint(ApiEndpoint.MoveToPoint);
4051 SetParameter("x", point.X);
4052 SetParameter("y", point.Y);
4053 return this;
4054 }
4055
4057
4061 public IAllowPuppeteerDragAndDrop DragAndDrop(string selector)
4062 {
4063 WithEndpoint(ApiEndpoint.DragAndDrop);
4064 WithElementId(selector);
4065 return this;
4066 }
4067
4069
4073 public IAllowPuppeteerExecution QuerySelector(string css)
4074 {
4075 WithEndpoint(ApiEndpoint.QuerySelector);
4076 WithCss(css);
4077 return this;
4078 }
4079
4081
4085 public IAllowPuppeteerExecution QuerySelectors(string css)
4087 WithEndpoint(ApiEndpoint.QuerySelectors);
4088 WithCss(css);
4089 return this;
4090 }
4091
4097 /// <returns>Fluent interface to continue building the request</returns>
4098 public IAllowPuppeteerExecution ScrollWindow(int hPixels, int vPixels)
4099 {
4100 WithEndpoint(ApiEndpoint.ScrollWindow);
4101 WithHPixels(hPixels);
4102 WithVPixels(vPixels);
4103 return this;
4104 }
4105
4109 /// <param name="xpath">XPath expression</param>
4110 /// <returns>Fluent interface to continue building the request</returns>
4111 public IAllowPuppeteerExecution Evaluate(string xpath)
4112 {
4113 WithEndpoint(ApiEndpoint.Evaluate);
4114 WithXPath(xpath);
4115 return this;
4116 }
4117
4121 /// <param name="xpath">XPath expression</param>
4122 /// <returns>Fluent interface to continue building the request</returns>
4123 public IAllowPuppeteerExecution EvaluateAll(string xpath)
4124 {
4125 WithEndpoint(ApiEndpoint.EvaluateAll);
4126 WithXPath(xpath);
4127 return this;
4128 }
4129
4131 /// Configure this client to clear any overridden HTTP referrer.
4132 /// </summary>
4133 /// <returns>Fluent interface to continue building the request</returns>
4135 {
4136 WithEndpoint(ApiEndpoint.ClearReferrer);
4137 return this;
4138 }
4139
4144 /// <returns>Fluent interface to continue building the request</returns>
4145 public IAllowPuppeteerAttribute SetAttribute(string selector)
4146 {
4147 WithEndpoint(ApiEndpoint.SetAttribute);
4148 WithElementId(selector);
4149 return this;
4150 }
4151
4157 public IAllowPuppeteerRangeValue SetRange(string selector)
4158 {
4159 WithEndpoint(ApiEndpoint.SetRange);
4160 WithElementId(selector);
4161 return this;
4162 }
4163
4166
4169 public IAllowPuppeteerExecution WithRangeValue(int rangeValue)
4170 {
4171 SetParameter("rangeValue", rangeValue.ToString());
4172 return this;
4173 }
4176
4179 public IAllowPuppeteerScrollElement ScrollElement(string selector)
4180 {
4181 WithEndpoint(ApiEndpoint.ScrollElement);
4182 WithElementId(selector);
4183 return this;
4184 }
4185
4188
4192 public IAllowPuppeteerExecution ScrollElement(int hPixels, int vPixels)
4193 {
4194 WithEndpoint(ApiEndpoint.ScrollElement);
4195 _hPixels = hPixels;
4196 _vPixels = vPixels;
4197 return this;
4198 }
4199
4208 public IAllowPuppeteerExecution SetValueFrom(string destSelector)
4209 {
4210 WithEndpoint(ApiEndpoint.SetValueFromElement);
4211 SetParameter("destSelector", destSelector);
4212 return this;
4213 }
4214
4219 public IAllowPuppeteerExecution TopBrowser()
4220 {
4221 WithEndpoint(ApiEndpoint.TopBrowser);
4222 return this;
4223 }
4224
4227 /// </summary>
4228 /// <param name="selector">CSS selector of the element</param>
4229 /// <returns>Fluent interface to continue building the request</returns>
4230 public IAllowPuppeteerAttribute GetAttribute(string selector)
4231 {
4232 WithEndpoint(ApiEndpoint.GetAttribute);
4233 WithElementId(selector);
4234 return this;
4235 }
4236
4242 private object GetResultFromIndex(int index)
4243 {
4244 if (index < 0 || index >= _executionResults.Count) return null;
4245 return _executionResults[index].Result;
4246 }
4247
4254 private object GetResultFromName(string name)
4255 {
4256 return _executionResults.LastOrDefault(entry => entry.Name == name).Result;
4257 }
4258
4264 private void SetParameter(string key, object value)
4265 {
4266 if (_parameters == null) _parameters = new Dictionary<string, object>();
4267 ((Dictionary<string, object>)_parameters)[key] = value;
4268 }
4269
4275 public IAllowPuppeteerStorageOptions SetStorage(WebsiteStorageType storageType)
4276 {
4277 WithEndpoint(ApiEndpoint.SetStorage);
4278 SetParameter("storageType", storageType.ToString());
4279
4280 return this;
4281 }
4285 private void Reset()
4286 {
4287 _endpoint = "";
4288 _parameters = null;
4289 _customEndpoint = false;
4290 _nextResultName = null; // don't save results unless asked, potential memory leak
4291 _timeoutMs = 30_000;
4292 _pruneMs = 3_000;
4293 _maxConnections = 0;
4294 _hPixels = 0;
4295 _vPixels = 0;
4296 _attribute = null;
4297 _value = null;
4298 _text = null;
4299 _currentIteration = 1;
4300 _whileLoopTimeoutMs = 0;
4301 _whileLoopMaxIterations = 0;
4302 }
4303 // In PuppeteerClient.cs, updated ExecuteInternalAsync
4308 private async Task<object> ExecuteInternalAsync()
4309 {
4310 if (true == _puppeteerCommunicator._windowSessionsQueue.IsEmpty)
4311 {
4312 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"No browser windows open. Cannot call endpoint [{_endpoint}]", null, GPALObjectType.None);
4313 return null;
4314 }
4315
4316 if (string.IsNullOrEmpty(_endpoint))
4317 {
4318 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{_name}] No endpoint specified", null, GPALObjectType.None);
4319 return null;
4320 }
4321
4322 var parameters = _parameters as IDictionary<string, object> ??
4323 (_parameters?.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(_parameters)) ?? new Dictionary<string, object>());
4324
4325 if (!_customEndpoint && !restClient.ValidateParameters(_apiEndpoint, parameters))
4326 {
4327 return null;
4328 }
4329
4330 object result = await ExecuteEndpoint(_endpoint, parameters).ConfigureAwait(false);
4331
4332 if (!string.IsNullOrEmpty(_saveFile))
4333 {
4334 SaveResponseToFile(result?.ToString(), _saveFile);
4335 }
4336
4337 if (!string.IsNullOrEmpty(_nextResultName))
4338 {
4339 _executionResults.Add((_nextResultName, result, _executionResults.Where(r => r.Name == _nextResultName && r.TargetId == _currentTargetId).Select(r => r.Iteration).DefaultIfEmpty(0).Max() + 1, _currentTargetId));
4340 _workflowStates.Add((_endpoint, _parameters, _nextResultName));
4341
4342 }
4343
4344 string valueStr = (result as JObject)?["result"]?["value"]?.Value<string>();
4345 if (!string.IsNullOrEmpty(valueStr))
4346 {
4347 int statusCode = 0;
4348 try
4349 {
4350 statusCode = (int) JObject.Parse(valueStr)["status"]?.Value<int>();
4351 }
4352 catch { }
4353
4354 if (statusCode > 0)
4355 {
4356 _puppeteerCommunicator.ServerResponseCode = statusCode;
4357 if (statusCode < 200 || statusCode >= 300)
4358 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{_name}] Endpoint [{_endpoint}] returned HTTP [{statusCode}]", null, GPALObjectType.None);
4359 }
4361
4362 // Reset loop state
4363 _whileLoopTimeoutMs = 0;
4364 _whileLoopMaxIterations = 0;
4365
4366 return result;
4367 }
4368
4374 /// <returns>Task that resolves to the handler's result, or null if no handler is registered</returns>
4375 private async Task<object> ExecuteEndpoint(string endpoint, IDictionary<string, object> parameters)
4376 {
4377 if (endpointMap.TryGetValue(endpoint, out var handler))
4378 {
4379 // always use the most recent target/session
4380 var sessionId = _puppeteerCommunicator.GetEffectiveSessionId();
4381 if (sessionId == null)
4382 {
4383 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "No active session for execution. Using null.", this, GPALObjectType.Puppeteer);
4384 }
4385
4386 return await handler(_puppeteerCommunicator, parameters, sessionId).ConfigureAwait(false);
4387 }
4388
4389 return null;
4390 }
4391
4392 #region Helpers
4398 private void SaveResponseToFile(string response, string filePath)
4399 {
4400 RESTClient.SaveResponseToFile(response, filePath);
4401 }
4402
4408 public IAllowPuppeteerParametersOrExecution UploadFrom(GPALFile fileToUpload)
4409 {
4410 if (1 < fileToUpload.Count)
4411 SetParameter("uploadPaths", fileToUpload);
4412 else
4413 SetParameter("uploadPath", fileToUpload.Filename);
4414 return this;
4415 }
4416
4422 public IAllowPuppeteerParametersOrExecution LeftClickAndUpload(dynamic elementOrelementId)
4423 {
4424 WithEndpoint(ApiEndpoint.Upload);
4425
4426 if (elementOrelementId is GPALElement element)
4427 WithElements(new List<GPALElement>() { element });
4428 else if (elementOrelementId is List<GPALElement> elements)
4429 WithElements(elements);
4430 else
4431 WithElementId(elementOrelementId);
4432
4433 return this;
4434 }
4435 #endregion Helpers
4436 }
4437}
Represents a URL with optional pre-navigation storage cleanup / inspection actions....
Definition GPALUrl.cs:53
IAllowGPALUrlStorageType ForUrl(string url)
Changes (or sets) the target URL for this builder instance.
Definition GPALUrl.cs:106
string Url
Gets the target URL string.
Definition GPALUrl.cs:69
static int CalculateHash(Dictionary< string, object > attributes)
Calculates a consistent integer hash from a dictionary of attributes.
IAllowPuppeteerExecution FireChangeEvent(string selector)
Configure this client to fire a DOM change event on the given element.
IAllowPuppeteerExecution PreviousWindow()
Configure this client to switch to the previous browser window.
IAllowPuppeteerParametersOrExecution WithParameters(params object[] parameters)
Sets the request parameters as an ordinal array.
IAllowPuppeteerParametersOrExecution WithElementId(string selector)
Sets the elementId parameter for the request, resolving CSS selectors to an element handle if needed.
IAllowPuppeteerExecution WithTextFromResult(int resultIndex)
Sets the text parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution ClearResults()
Empties what WithResultName has stored, so GetExecutionResults answers about what has happened since ...
IAllowPuppeteerParametersOrExecution RightClick(dynamic elementOrelementId)
Configure this client to right-click the given element.
IAllowPuppeteerDragAndDrop WithDeltaX(int deltaX)
Sets the horizontal drag delta parameter for a drag and drop.
IAllowPuppeteerExecution ScrollWindowByVertical(int pixels)
Configure this client to scroll the browser window vertically by the given number of pixels.
IAllowPuppeteerExecution WithHeader(string name, string value)
Sets an extra HTTP header to send with the request.
IAllowPuppeteerExecution GoTo(GPALUrl url)
Configure this client to navigate the current tab to the given URL, resolving relative URLs and check...
IAllowPuppeteerParametersOrExecution WithCssFromResult(int resultIndex)
Sets the css selector parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution GetContentAndCss(string selector)
Configure this client to retrieve the text content and computed CSS of the given element.
IAllowPuppeteerExecution IsEnabled(string selector)
Configure this client to check whether the given element is enabled.
IAllowPuppeteerParametersOrExecution MiddleClick(dynamic elementOrelementId)
Configure this client to middle-click the given element.
IAllowPuppeteerParametersOrExecution WithParametersFromResult(int resultIndex)
Sets the request parameters from the result at the given index of a previous execution.
IAllowPuppeteerParametersOrExecution WithWindowId(int windowId)
Sets the window id parameter for the request.
IAllowPuppeteerExecution PreviousTab()
Configure this client to switch to the previous tab.
string Name
The display name assigned to this client via WithName.
IAllowPuppeteerAttribute SetAttribute(string selector)
Configure this client to set an attribute on the given element; combine with WithAttribute and WithVa...
IAllowPuppeteerParametersOrExecution LeftClick(dynamic elementOrelementId)
Configure this client to left-click the given element.
IAllowPuppeteerScrollElement WithVPixels(int vPixels)
Sets the vertical pixels parameter for the request.
IAllowPuppeteerExecution Evaluate(string xpath)
Configure this client to evaluate the given XPath expression and return the first matching node.
IAllowPuppeteerExecution IsVisibleInViewport(IGPALElement element)
Configure this client to check whether the given element is visible in the viewport.
IAllowPuppeteerParametersOrExecution WithKeyFromResult(int resultIndex)
Sets the key parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution CastDesktop(string deviceNameOrId)
Cast the desktop to the specified device name client.CastDesktop("living room").Execute();.
IAllowPuppeteerExecution WithSelectIndex(int index)
Sets the index of the option to choose from a Select menu.
IAllowPuppeteerCheckNetworkIdleOrExecution WithTimeoutMs(int timeoutMs)
Sets the timeout, in milliseconds, used by CheckNetworkIdle.
IAllowPuppeteerExecution WithText(string text)
Sets the text parameter for the request.
IAllowPuppeteerExecution GetCssAttributes(string selector)
Configure this client to retrieve the CSS attributes of the given element.
IAllowPuppeteerParametersOrExecution LeftDoubleClick(dynamic elementOrelementId)
Configure this client to double left-click the given element.
IAllowPuppeteerParametersOrExecution WithXPath(string xpath)
Sets the xpath parameter for the request.
IAllowPuppeteerExecution GetReadyStatus()
Configure this client to query the puppeteer server's ready status, optionally for a specific session...
IAllowPuppeteerExecution HideElement(string selector)
Configure this client to hide the given element.
IAllowPuppeteerInputText FillInAppend(dynamic elementOrelementId, int delayMs=0)
Configure this client to append text to the given input element.
IAllowPuppeteerParametersOrExecution WithElementIdFromResult(int resultIndex)
Sets the elementId parameter from the result at the given index of a previous execution,...
IAllowPuppeteerExecution InMainDom()
Configure this client to switch context back to the main document.
IAllowPuppeteerParametersOrExecution WithTabIdFromResult(int resultIndex)
Sets the tab id parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution InjectScript(string script)
Configure this client to inject the given JavaScript into the page.
IAllowPuppeteerEndpointDetails WithName(string name)
Sets the display name for this client.
IAllowPuppeteerExecution CloseTab(GPALUrl url=null)
Configure this client to close the tab with the given URL, or the current tab if none is given.
IAllowPuppeteerParametersOrExecution CheckNetworkIdle(int maxConnections=0)
Configure this client to check whether network activity has gone idle.
IAllowPuppeteerParametersOrExecution WithHPixelsFromResult(int resultIndex)
Sets the horizontal pixels parameter from the result at the given index of a previous execution.
IAllowPuppeteerParametersOrExecution WithSessionToken(string sessionToken)
Sets the session token parameter for the request.
IAllowPuppeteerParametersOrExecution WithReferrer(string referrer)
Sets the referrer parameter for the request.
IAllowPuppeteerExecution OverrideReferrer(string referrer)
Configure this client to override the HTTP referrer sent with subsequent requests.
IAllowPuppeteerDragAndDrop WithOffsetY(int offsetY)
Sets the vertical grab offset within the element for a drag and drop.
IAllowPuppeteerExecution PressModifierKey(ModifierKeys modifierKeys)
Configure this client to press and hold the given modifier key(s).
IAllowPuppeteerWorkflow ClearWorkflows()
Returns a collection of all named results recorded so far across this client's executions.
IAllowPuppeteerParametersOrExecution Fetch(string url)
Configure this client to issue an API request from inside the page, so it carries the session the bro...
IAllowPuppeteerExecution WithDeviceName(string sinkName)
Sets the CSS parameter.
IAllowPuppeteerParametersOrExecution WithIteration(int iteration)
Sets the iteration for an endpoint to retrieve from the results when using WithXXXFromResults.
IAllowPuppeteerParametersOrExecution WithEndpoint(string endpoint)
Sets a custom endpoint path to call directly, bypassing the built-in ApiEndpoint enum.
IAllowPuppeteerParametersOrExecution WithBody(string body)
Body to send with the request.
int GetModifierBitmask(ModifierKeys modifierKeys)
Converts GPAL ModifierKeys flags to the equivalent Chrome DevTools Protocol modifiers bitmask.
IAllowPuppeteerParametersOrExecution WithHeaders(string[] headers)
Extra request headers, flattened to name, value, name, value.
IAllowPuppeteerExecution Minimize()
Configure this client to minimize the browser window.
IAllowPuppeteerExecution Focus(string selector)
Configure this client to set keyboard focus on the given element.
IAllowPuppeteerParametersOrExecution WithModifiers(ModifierKeys modifierKeys)
Sets the modifier keys parameter for the request.
IAllowPuppeteerExecution ClearReferrer()
Configure this client to clear any overridden HTTP referrer.
IAllowPuppeteerParametersOrExecution WithTabId(int tabId)
Sets the tab id parameter for the request.
IAllowPuppeteerParametersOrExecution DownloadTo(GPALFile file)
Where a file the browser downloads should end up. Not SaveTo, which is GPAL writing a file it alread...
IAllowPuppeteerExecution StopCasting()
Configure this client to stop any active screen casting.
IAllowPuppeteerAttribute WithValue(string value)
Sets the attribute value parameter for SetAttribute requests.
IAllowPuppeteerParametersOrExecution WithPixels(int pixels)
Sets the pixels parameter for the request.
IAllowPuppeteerExecution MoveTo(string selector)
Configure this client to move the mouse to the element matching the given selector.
IAllowPuppeteerParametersOrExecution WithPixelsFromResult(int resultIndex)
Sets the pixels parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution Back()
Configure this client to navigate the browser back one page in history.
IAllowPuppeteerCheckNetworkIdleOrExecution WithPruneMs(int pruneMs)
Sets the prune interval, in milliseconds, used by CheckNetworkIdle.
IAllowPuppeteerExecution GetElementAttributeHash(string selector)
Configure this client to compute a hash of the given element's attributes, useful for detecting eleme...
IAllowPuppeteerExecution Restore()
Configure this client to restore the browser window to its normal size.
IAllowPuppeteerExecution GoToTab(GPALUrl url)
Configure this client to switch to the tab whose URL matches the given URL.
IAllowPuppeteerExecution CastTab(string deviceNameOrId)
Cast the current tab to the specified device name client.CastTab("living room").Execute();.
IAllowPuppeteerExecution InElement(string selector)
Configure this client to switch context into the element matching the given selector.
IAllowPuppeteerExecution WithDestElementId(string destElementId)
Sets the destination element id parameter for a drag and drop.
IAllowPuppeteerParametersOrExecution WithKey(string key)
Sets the key parameter for the request.
IAllowPuppeteerExecution Hover(string selector)
Configure this client to hover the mouse over the given element.
IAllowPuppeteerExecution SetUserAgent(string userAgent)
Configure this client to override the browser's user agent string for subsequent requests.
IAllowPuppeteerExecution GetDomAttributes(string selector)
Configure this client to retrieve the DOM attributes of the given element.
IAllowPuppeteerExecution SwitchToDefaultContent()
Configure this client to switch context back to the main document.
IAllowPuppeteerDragAndDrop WithOffsetX(int offsetX)
Sets the horizontal grab offset within the element for a drag and drop.
IAllowPuppeteerExecution GetParentNode(string selector)
Configure this client to retrieve the parent node of the given element.
IAllowPuppeteerExecution ScrollIntoView(dynamic elementOrelementId)
Configure this client to scroll the given element into view.
IAllowPuppeteerExecution GetCurrentWindow()
Configure this client to retrieve information about the current browser window.
IAllowPuppeteerExecution GoToWindow(GPALUrl url)
Configure this client to switch to the window containing the tab whose URL matches the given URL.
IAllowPuppeteerScrollElement WithHPixels(int hPixels)
Sets the horizontal pixels parameter for the request.
ResultCollection GetExecutionResults()
Everything stored under a name so far, in the order it was stored. The target id a result was taken f...
IAllowPuppeteerParametersOrExecution WithReferrerFromResult(int resultIndex)
Sets the referrer parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution GetBoundingClientRect(string backendNodeId)
Configure this client to retrieve the bounding client rectangle of the given element.
IAllowPuppeteerExecution GetDomProperties(string selector)
Configure this client to retrieve the DOM properties of the given element.
IAllowPuppeteerExecution SwitchToFrame(string selector)
Configure this client to switch context into the iframe matching the given selector.
IAllowPuppeteerStorageOptions WithDeleteAcrossOrigins(bool trueOrFalse)
Sets whether storage should be deleted across all origins, not just the current one.
IAllowPuppeteerParametersOrExecution Until(Func< bool > condition)
Configures execution to repeat until the given condition returns true, subject to WhileLoopTimeout/Wh...
IAllowPuppeteerExecution PageDown(int pagesToScroll=1)
Configure this client to scroll the page down by the given number of page heights.
IAllowPuppeteerExecution Forward()
Configure this client to navigate the browser forward one page in history.
IAllowPuppeteerExecution GetCurrentUrl()
Configure this client to retrieve the current page URL.
IAllowPuppeteerParametersOrExecution WithElementFromResult(int resultIndex)
Sets the elementId parameter from the result at the given index of a previous execution,...
IAllowPuppeteerExecution Normal()
Configure this client to restore the browser window to its normal (non-maximized, non-minimized) stat...
IAllowPuppeteerParametersOrExecution WithElements(List< GPALElement > gpalElements)
Sets the gpalElements parameter, identifying the elements the request should act on.
IAllowPuppeteerExecution SwitchToElement(string selector)
Configure this client to switch context into the element matching the given selector.
IAllowPuppeteerExecution IsDisplayed(string selector)
Configure this client to check whether the given element is displayed.
IAllowPuppeteerParametersOrExecution WithXPathFromResult(int resultIndex)
Sets the xpath parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution SetGoogleReferrer()
Configure this client to override the HTTP referrer with https://www.google.com, to mimic arriving fr...
IAllowPuppeteerExecution SwitchToShadowRoot(string selector)
Configure this client to switch context into the shadow root of the element matching the given select...
IAllowPuppeteerExecution EvaluateAll(string xpath)
Configure this client to evaluate the given XPath expression and return all matching nodes.
IAllowPuppeteerExecution WithSelectValue(string value)
Set the value to choose from the Select menu.
IAllowPuppeteerExecution InShadowDom(string selector)
Configure this client to switch context into the shadow root of the element matching the given select...
IAllowPuppeteerExecution CaptureVisibleTab(ImageFormat imageFormat=ImageFormat.JPEG)
Configure this client to capture a screenshot of the visible tab in the given image format.
IAllowPuppeteerExecution GetPageSource()
Configure this client to retrieve the current page's HTML source.
IAllowPuppeteerExecution OpenWindow(GPALUrl url)
Configure this client to open a new browser window navigated to the given URL, resolving relative URL...
IAllowPuppeteerParameters ExecuteJavaScript(string script)
Configure this client to execute the given JavaScript in the page.
IAllowPuppeteerExecution GetShadowRoot(string css)
Configure this client to retrieve the shadow root of the element matching the given CSS selector.
IAllowPuppeteerParametersOrExecution WithCss(string css)
Sets the css selector parameter for the request.
async Task< string > ExecuteAsync()
Asynchronously executes the configured request and returns the result as a string.
IAllowPuppeteerCheckNetworkIdleOrExecution WithMaxConnections(int maxConnections)
Sets the maximum number of in-flight connections allowed before the network is considered busy,...
IAllowPuppeteerDragAndDrop WithDeltaY(int deltaY)
Sets the vertical drag delta parameter for a drag and drop.
IAllowPuppeteerParametersOrExecution WithUrl(string url)
Sets the url parameter for the request.
IAllowPuppeteerAttribute WithAttribute(string attribute)
Sets the attribute name parameter for SetAttribute/GetAttribute requests.
IAllowPuppeteerParametersOrExecution WithUrlFromResult(int resultIndex)
Sets the url parameter from the result at the given index of a previous execution.
IAllowPuppeteerParametersOrExecution WithVerb(string method)
HTTP method for the request. GET when nothing was said.
IAllowPuppeteerParametersOrExecution WithWindowIdFromResult(int resultIndex)
Sets the window id parameter from the result at the given index of a previous execution.
IAllowPuppeteerParametersOrExecution WithVPixelsFromResult(int resultIndex)
Sets the vertical pixels parameter from the result at the given index of a previous execution.
IAllowPuppeteerExecution PageUp(int pagesToScroll=1)
Configure this client to scroll the page up by the given number of page heights.
IAllowPuppeteerExecution SendKey(byte vkcode)
Configure this client to send a key press for the given virtual key code, translating it to the corre...
IAllowPuppeteerExecution ScrollWindow(int hPixels, int vPixels)
Configure this client to scroll the browser window by the given horizontal and vertical pixel amounts...
IAllowPuppeteerExecution IsClickable(string selector)
Configure this client to check whether the given element is clickable.
IAllowPuppeteerAttribute GetAttribute(string selector)
Configure this client to retrieve an attribute from the given element; combine with WithAttribute to ...
Pseudo element used in Applications and Browser workflows for image matching and unified automation....
void MiddleClick(ModifierKeys modifierKeys=ModifierKeys.NONE)
Performs a middle mouse button click.
bool Displayed
Whether the element is visible on the page.
bool IsClickable(bool publishEvent=true)
Determines whether the element appears clickable based on visibility, disabled state,...
bool IsDisabled()
Checks if the element is disabled via attributes or properties.
Dictionary< string, object > Attributes
All raw attributes returned from the automation backend.
string GetAttribute(string attributeName)
Gets an attribute value with fallback to internal dictionary.
string Value
value attribute (inputs, textareas, etc.).
ISearchContext GetShadowRoot()
Returns the ShadowRoot if available.
string Css
CSS selector used to locate this element.
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
int Count
The number of filenames in Filenames.
Definition GPALFile.cs:720
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 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
Fluent REST client for making API calls with a chained interface. Supports defining workflows,...
Definition RESTClient.cs:56
static bool TryConvertVkCodeToDomKey(byte vkCode, out string key, out string code)
Converts a Windows VK code to DOM KeyboardEvent key and code values.