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;
29using static OpenCvSharp.XImgProc.CvXImgProc;
30
32{
33 public class PuppeteerClient : IPuppeteerClient
34 {
38 private readonly List<Action<PuppeteerClient>> _workflows = new List<Action<PuppeteerClient>>();
42 private List<(string Endpoint, object Parameters, string NextResultName)?> _workflowStates { get; set; } = new List<(string, object, string)?>();
46 private bool _customEndpoint { get; set; } = false;
50 private ApiEndpoint _apiEndpoint;
54 private string _endpoint { get; set; }
58 private object _parameters { get; set; }
62 internal ContentEncoding _contentEncoding = ContentEncoding.Json;
66 private string _name { get; set; }
70 private string _nextResultName { get; set; } // = "results"; .. do not save results unless asked to, this is a potential memory leak, it just keeps accumulating
74 private string _saveFile { get; set; }
75
79 private readonly List<(string Name, object Result, int Iteration, string TargetId)> _executionResults = new List<(string Name, object Result, int Iteration, string TargetId)>();
83 private PuppeteerCommunicator _puppeteerCommunicator { get; set; }
87 private int _currentIteration { get; set; }
91 internal Dictionary<string, string> _headers = new Dictionary<string, string>();
95 private int _timeoutMs = 30000;
99 private int _pruneMs = 3_000;
103 private int _maxConnections { get; set; }
107 private int _hPixels { get; set; }
111 private int _vPixels { get; set; }
115 private string _attribute { get; set; }
119 private string _value { get; set; }
123 private string _text { get; set; }
127 private string _apiBase { get; set; } = null;
131 private RESTClient restClient { get; set; } // for validation
135 internal string _currentTargetId { get; set; }
139 private Dictionary<string, Func<PuppeteerCommunicator, IDictionary<string, object>, string, Task<object>>> endpointMap { get; set; }
140
144 private Func<bool> _loopCondition { get; set; }
148 private string _loopType { get; set; } // "While" or "Until"
152 private int _whileLoopTimeoutMs { get; set; } // loop timeout
156 private int _whileLoopMaxIterations { get; set; } // loop max iterations
157
163 internal PuppeteerClient()
164 {
165 restClient = new RESTClient();
166 init();
167 }
168
172 private void init()
173 {
174 endpointMap = new Dictionary<string, Func<PuppeteerCommunicator, IDictionary<string, object>, string, Task<object>>>
175 {
176 [RESTHelper.Endpoints[ApiEndpoint.Back]] = async (c, p, s) =>
177 {
178 return await c.Back(s).ConfigureAwait(false);
179 },
180 [RESTHelper.Endpoints[ApiEndpoint.CaptureVisibleTab]] = async (c, p, s) =>
181 {
182 var parameters = new Dictionary<string, object> { { "format", "jpeg" } };
183 if (p.ContainsKey("clip"))
184 {
185 dynamic format = p.ContainsKey("imageFormat") ? p["imageFormat"] : "JPEG";
186 var clip = p["clip"] as IDictionary<string, object>;
187 if (clip != null)
188 {
189 double x = clip.ContainsKey("x") ? Convert.ToDouble(clip["x"]) : 0;
190 double y = clip.ContainsKey("y") ? Convert.ToDouble(clip["y"]) : 0;
191 double width = clip.ContainsKey("width") ? Convert.ToDouble(clip["width"]) : Puppeteer.GetScreenWidthInt();
192 double height = clip.ContainsKey("height") ? Convert.ToDouble(clip["height"]) : Puppeteer.GetScreenHeightInt();
193 double scale = clip.ContainsKey("scale") ? Convert.ToDouble(clip["scale"]) : 1.0;
194
195 if (width > 0 && height > 0 && x >= 0 && y >= 0)
196 {
197 parameters = new Dictionary<string, object>
198 {
199 { "format", format },
200 {
201 "clip", new Dictionary<string, object>
202 {
203 { "x", x },
204 { "y", y },
205 { "width", width },
206 { "height", height },
207 { "scale", scale }
208 }
209 }
210 };
211 }
212 else
213 {
214 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid clip parameters: x=[{x}], y=[{y}], width=[{width}], height=[{height}]. Using viewport.", this, GPALObjectType.PuppeteerClient);
215 }
216 }
217 }
218 JObject retData = await c.SendCommand(DevToolsMethods.PageCaptureScreenshot, parameters, s).ConfigureAwait(false);
219 string base64 = retData["data"]?.ToString();
220
221 return base64;
222 },
223 [RESTHelper.Endpoints[ApiEndpoint.CastDesktop]] = async (c, p, s) =>
224 {
225 string sinkName = p.ContainsKey("sinkName") ? p["sinkName"]?.ToString() : null;
226 await c.CastDesktop(sinkName, s).ConfigureAwait(false);
227 return true;
228 },
229 [RESTHelper.Endpoints[ApiEndpoint.CastTab]] = async (c, p, s) =>
230 {
231 string sinkName = p.ContainsKey("sinkName") ? p["sinkName"]?.ToString() : null;
232 await c.CastTab(sinkName, s).ConfigureAwait(false);
233 return true;
234 },
235 [RESTHelper.Endpoints[ApiEndpoint.CheckNetworkIdle]] = async (c, p, s) =>
236 {
237 string sessionToken = Guid.NewGuid().ToString();
238 return await c.CheckNetworkIdle(s, _maxConnections, _timeoutMs, _pruneMs, sessionToken).ConfigureAwait(false);
239 },
240 [RESTHelper.Endpoints[ApiEndpoint.ClearReferrer]] = async (c, p, s) =>
241 {
242 return await c.SendCommand(DevToolsMethods.NetworkSetExtraHTTPHeaders, new Dictionary<string, object> { { "headers", new Dictionary<string, object> { { "Referer", "" } } } }, s).ConfigureAwait(false);
243 },
244 [RESTHelper.Endpoints[ApiEndpoint.CloseTab]] = async (c, p, s) =>
245 {
246 string targetId = null;
247 if (p.ContainsKey("tabId"))
248 {
249 int tabId = int.Parse(p["tabId"]?.ToString());
250 var queueArray = c.CurrentSessions.ToArray();
251 if (tabId >= 0 && tabId < queueArray.Length)
252 {
253 targetId = queueArray[tabId].Key;
254 }
255 }
256 else
257 {
258 targetId = c.GetCurrentTargetId(); // Default to active
259 }
260
261 if (targetId != null)
262 {
263 await c.CloseTab(targetId, s).ConfigureAwait(false);
264 return true;
265 }
266 return false;
267 },
268 [RESTHelper.Endpoints[ApiEndpoint.CloseWindow]] = async (c, p, s) =>
269 {
270 string targetId = null;
271 string windowId = p.ContainsKey("windowId") ? p["windowId"]?.ToString() : null;
272 string url = p.ContainsKey("url") ? p["url"]?.ToString() : null;
273
274 if (!string.IsNullOrEmpty(windowId))
275 {
276 targetId = windowId; // Direct browserContextId
277 }
278 else if (!string.IsNullOrEmpty(url))
279 {
280 targetId = await c.GetTargetWindowIdByUrl(url).ConfigureAwait(false);
281 if (targetId != null)
282 {
283 // Set the window as active to ensure correct context
284 int windowIndex = c.GetCurrentWindowIndex(targetId);
285 if (windowIndex >= 0)
286 {
287 c.SetActiveWindowIndex(windowIndex);
288 }
289 }
290 }
291 else
292 {
293 targetId = c.GetCurrentWindowTargetId(); // Current window's BrowserContextId
294 }
295
296 if (!string.IsNullOrEmpty(targetId))
297 {
298 await c.CloseWindow(targetId, s).ConfigureAwait(false);
299 return true;
300 }
301
302 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"Unable to find window for targetId [{targetId}] or URL [{url}]", c, GPALObjectType.Puppeteer);
303 return false;
304 },
305 [RESTHelper.Endpoints[ApiEndpoint.DeleteStorage]] = async (c, p, s) =>
306 {
307 var storageType = p.ContainsKey("storageType") ? p["storageType"]?.ToString() : "";
308 var domain = p.ContainsKey("domain") ? p["domain"]?.ToString() : "";
309 var path = p.ContainsKey("path") ? p["path"]?.ToString() : "";
310 var key = p.ContainsKey("key") ? p["key"]?.ToString() : "";
311 var storeName = p.ContainsKey("storeName") ? p["storeName"]?.ToString() : "";
312
313 return await c.DeleteStorage(storageType, s, domain, storeName, path, key).ConfigureAwait(false);
314 },
315 [RESTHelper.Endpoints[ApiEndpoint.DragAndDrop]] = async (c, p, s) =>
316 {
317 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
318 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
319 var deltaX = p.ContainsKey("deltaX") ? Convert.ToInt32(p["deltaX"]) : 0;
320 var deltaY = p.ContainsKey("deltaY") ? Convert.ToInt32(p["deltaY"]) : 0;
321 var offsetX = p.ContainsKey("offsetX") ? Convert.ToInt32(p["offsetX"]) : 0;
322 var offsetY = p.ContainsKey("offsetY") ? Convert.ToInt32(p["offsetY"]) : 0;
323 List<dynamic> responses = new List<dynamic>();
324
325 foreach (GPALElement elem in elems)
326 {
327 await c.ScrollIntoView(elem, s).ConfigureAwait(false);
328 await c.DragAndDrop(elem, s, responses, deltaX, deltaY, offsetX, offsetY).ConfigureAwait(false);
329 }
330
331 return (responses, elems);
332 },
333 [RESTHelper.Endpoints[ApiEndpoint.Evaluate]] = async (c, p, s) =>
334 {
335 var xpath = p.ContainsKey("xpath") ? p["xpath"]?.ToString() : "";
336 xpath = xpath.Replace("'", "\\'");
337 List<GPALElement> elems = await _puppeteerCommunicator.EvaluateSelector(xpath, s).ConfigureAwait(false);
338
339 if (0 < elems.Count)
340 return elems[0];
341 else
342 return null;
343 // return await c.Evaluate(xpath, s).ConfigureAwait(false);
344 },
345 [RESTHelper.Endpoints[ApiEndpoint.EvaluateAll]] = async (c, p, s) =>
346 {
347 var xpath = p.ContainsKey("xpath") ? p["xpath"]?.ToString() : "";
348
349 xpath = xpath.Replace("'", "\\'");
350 return await _puppeteerCommunicator.EvaluateSelector(xpath, s).ConfigureAwait(false);
351 /*
352 var expression = $@"(function() {{
353 const result = document.evaluate('{xpath}', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
354 const elementsArray = [];
355 for (let i = 0; i < result.snapshotLength; i++) {{
356 elementsArray.push(result.snapshotItem(i));
357 }}
358 console.log('Nodes matched:', result.snapshotLength);
359 return elementsArray;
360 }})()";
361
362 // Step 1: Evaluate to get the array of nodes
363 var evalResponse = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
364 {
365 expression,
366 returnByValue = false
367 }, s).ConfigureAwait(false);
368
369 var arrayObjectId = evalResponse["result"]?["objectId"]?.ToString();
370 if (string.IsNullOrEmpty(arrayObjectId))
371 {
372 throw new InvalidOperationException("EvaluateAll failed to return an array objectId.");
373 }
374
375 // Step 2: Fetch array elements' properties
376 var propsResponse = await c.SendCommand<object>(DevToolsMethods.RuntimeGetProperties, new
377 {
378 objectId = arrayObjectId,
379 ownProperties = true
380 }, s).ConfigureAwait(false);
381
382 // Step 3: Extract node references and serialize to JSON strings
383 var nodeReferences = new List<string>();
384 foreach (var prop in propsResponse["result"])
385 {
386 int dummy;
387 var name = prop["name"]?.ToString();
388 if (int.TryParse(name, out dummy)) // Numeric indices
389 {
390 var value = prop["value"];
391 if (value != null)
392 {
393 nodeReferences.Add(value.ToString()); // Serialize to JSON string
394 }
395 }
396 }
397
398 // Step 4: Return as a value array
399 var response = new JObject
400 {
401 ["result"] = new JObject
402 {
403 ["type"] = "object",
404 ["subtype"] = "array",
405 ["className"] = "Array",
406 ["value"] = new JArray(nodeReferences)
407 }
408 };
409
410 return response;
411 */
412 },
413 [RESTHelper.Endpoints[ApiEndpoint.Fetch]] = async (c, p, s) =>
414 {
415 return await c.Fetch(
416 p.ContainsKey("url") ? p["url"]?.ToString() : null,
417 p.ContainsKey("method") ? p["method"]?.ToString() : null,
418 p.ContainsKey("body") ? p["body"]?.ToString() : null,
419 p.ContainsKey("contentType") ? p["contentType"]?.ToString() : null,
420 p.ContainsKey("headers") ? p["headers"] as string[] : null,
421 p.ContainsKey("bytes") && true == Convert.ToBoolean(p["bytes"]),
422 s).ConfigureAwait(false);
423 },
424 [RESTHelper.Endpoints[ApiEndpoint.ExecuteJavaScript]] = async (c, p, s) =>
425 {
426 var expression = p.ContainsKey("expression") ? p["expression"]?.ToString() : p["script"]?.ToString();
427 return await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
428 {
429 expression,
430 returnByValue = true,
431 awaitPromise = true
432 }, s).ConfigureAwait(false);
433 },
434 [RESTHelper.Endpoints[ApiEndpoint.FillInAppend]] = async (c, p, s) =>
435 {
436 List<dynamic> responses = new List<dynamic>();
437
438 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
439 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
440 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
441
442 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
443
444 if (null == elems && null != parm)
445 elems = await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
446
447 foreach (GPALElement element in elems)
448 {
449 await c.ScrollIntoView(element, s).ConfigureAwait(false);
450
451 // focus, not a click. Input.insertText goes to whatever has focus, and a click only ever
452 // got us that as a side effect while costing a coordinate the element may not be at
453 await c.FocusElement(element.ElementHandle, s).ConfigureAwait(false);
454 responses.Add(await c.FillIn(element.ElementHandle, element.Value + text, typingDelay, s).ConfigureAwait(false));
455 /*
456 string selector = !string.IsNullOrEmpty(element.Css) ? element.Css : element.Xpath; ;
457 string expression = $@"
458 (function() {{
459 var el = null;
460 try {{
461 el = document.querySelector('{selector.Replace("'", "\\'")}');
462 }} catch(e) {{}}
463 if (!el) {{
464 try {{
465 el = document.evaluate(
466 '{selector.Replace("'", "\\'")}',
467 document,
468 null,
469 XPathResult.FIRST_ORDERED_NODE_TYPE,
470 null
471 ).singleNodeValue;
472 }} catch(e) {{}}
473 }}
474 if (el) {{
475 el.value += '{text.Replace("'", "\\'")}';
476 }}
477 }})();
478 ";
479
480 responses.Add(await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
481 {
482 expression,
483 returnByValue = true
484 }, s));
485 */
486 }
487 return new { responses, elems };
488 },
489 [RESTHelper.Endpoints[ApiEndpoint.FillInInsert]] = async (c, p, s) =>
490 {
491 List<dynamic> responses = new List<dynamic>();
492 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
493 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
494 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
495
496 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
497
498 if (null == elems && null != parm)
499 elems = await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
500
501 foreach (GPALElement element in elems)
502 {
503 await c.ScrollIntoView(element, s).ConfigureAwait(false);
504
505 // focus, not a click. Input.insertText goes to whatever has focus, and a click only ever
506 // got us that as a side effect while costing a coordinate the element may not be at
507 await c.FocusElement(element.ElementHandle, s).ConfigureAwait(false);
508 responses.Add(await c.FillIn(element.ElementHandle, text + element.Value, typingDelay, s).ConfigureAwait(false));
509 /*
510 string selector = !string.IsNullOrEmpty(element.Css) ? element.Css : element.Xpath; ;
511 string expression = $@"
512 (function() {{
513 var el = null;
514 try {{
515 el = document.querySelector('{selector.Replace("'", "\\'")}');
516 }} catch(e) {{}}
517 if (!el) {{
518 try {{
519 el = document.evaluate(
520 '{selector.Replace("'", "\\'")}',
521 document,
522 null,
523 XPathResult.FIRST_ORDERED_NODE_TYPE,
524 null
525 ).singleNodeValue;
526 }} catch(e) {{}}
527 }}
528 if (el) {{
529 el.value = '{text.Replace("'", "\\'")}' + el.value;
530 }}
531 }})();
532 ";
533
534 await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
535 {
536 expression,
537 returnByValue = true
538 }, s);
539 */
540 }
541 return new { responses, elems };
542 },
543 [RESTHelper.Endpoints[ApiEndpoint.FillInOverwrite]] = async (c, p, s) =>
544 {
545 List<dynamic> responses = new List<dynamic>();
546 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
547 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
548 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
549
550 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
551
552 if (null == elems && null != parm)
553 elems = await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
554
555 foreach (GPALElement element in elems)
556 {
557 await c.ScrollIntoView(element, s).ConfigureAwait(false);
558
559 // focus, not a click. Input.insertText goes to whatever has focus, and a click only ever
560 // got us that as a side effect while costing a coordinate the element may not be at
561 await c.FocusElement(element.ElementHandle, s).ConfigureAwait(false);
562 responses.Add(await c.FillIn(element.ElementHandle, text, typingDelay, s).ConfigureAwait(false));
563 }
564 return new { responses, elems };
565 },
566 [RESTHelper.Endpoints[ApiEndpoint.FireChangeEvent]] = async (c, p, s) =>
567 {
568 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
569 return await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
570 {
571 expression = $"document.querySelector('{selector.Replace("'", "\\'")}').dispatchEvent(new Event('change'))",
572 returnByValue = true
573 }, s).ConfigureAwait(false);
574 },
575 [RESTHelper.Endpoints[ApiEndpoint.Focus]] = async (c, p, s) =>
576 {
577 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
578 //var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
579 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
580
581 foreach (GPALElement elem in elems)
582 {
583 var requestNodeResult = await c.SendCommand<object>(DevToolsMethods.DOMRequestNode, new { objectId = elem.ElementHandle }, s).ConfigureAwait(false);
584
585 if (requestNodeResult?.nodeId != null)
586 {
587 return await c.SendCommand<object>(DevToolsMethods.DOMFocus, new { nodeId = (int)requestNodeResult.nodeId }, s).ConfigureAwait(false);
588 }
589
590 //var doc = await c.SendCommand<object>(DevToolsMethods.DOMGetDocument, new { depth = 0 }, s);
591 //var node = await c.SendCommand<object>(DevToolsMethods.DOMQuerySelector, new { nodeId = (int)doc.root.nodeId, selector }, s);
592 //if (node?.nodeId != null)
593 //{
594 // return await c.SendCommand<object>(DevToolsMethods.DOMFocus, new { nodeId = (int)node.nodeId }, s);
595 //}
596 }
597 return null;
598 },
599 [RESTHelper.Endpoints[ApiEndpoint.Forward]] = async (c, p, s) =>
600 {
601 return await c.Forward(s).ConfigureAwait(false);
602 },
603 [RESTHelper.Endpoints[ApiEndpoint.FullScreen]] = async (c, p, s) =>
604 {
605 var windowId = await c.GetCurrentWindow(s).ConfigureAwait(false);
606 return await c.SendCommand(DevToolsMethods.BrowserSetWindowBounds, new
607 {
608 windowId = windowId,
609 bounds = new { windowState = "fullscreen" }
610 }, s).ConfigureAwait(false);
611 },
612 [RESTHelper.Endpoints[ApiEndpoint.GetAttribute]] = async (c, p, s) =>
613 {
614 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
615 var attribute = p.ContainsKey("attribute") ? p["attribute"]?.ToString() : "";
616 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
617
618 foreach (GPALElement elem in elems)
619 return elem.GetAttribute(attribute);
620
621 return null;
622 },
623 [RESTHelper.Endpoints[ApiEndpoint.GetBoundingClientRect]] = async (c, p, s) =>
624 {
625 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null; // (string) backendnodeid
626
627 return await c.GetBoundingClientRect(Int32.Parse(parm)).ConfigureAwait(false);
628 },
629 [RESTHelper.Endpoints[ApiEndpoint.GetContentAndCss]] = async (c, p, s) =>
630 {
631 var selector = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : "";
632 return await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
633 {
634 expression = $"(() => {{ const el = document.querySelector('{selector.Replace("'", "\\'")}'); return {{ text: el.textContent, css: window.getComputedStyle(el).cssText }}; }})()",
635 returnByValue = true
636 }, s).ConfigureAwait(false);
637 },
638 [RESTHelper.Endpoints[ApiEndpoint.GetCssAttributes]] = async (c, p, s) =>
639 {
640 string backendNodeId = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : null;
641
642 return await c.GetCssAttributes(Int32.Parse(backendNodeId), s).ConfigureAwait(false);
643 },
644 [RESTHelper.Endpoints[ApiEndpoint.GetCurrentUrl]] = async (c, p, s) =>
645 {
646 return await c.GetCurrentUrl(s).ConfigureAwait(false);
647 },
648 [RESTHelper.Endpoints[ApiEndpoint.GetCurrentWindow]] = async (c, p, s) =>
649 {
650 return await c.GetCurrentWindow(s).ConfigureAwait(false);
651 },
652 [RESTHelper.Endpoints[ApiEndpoint.GetDomAttributes]] = async (c, p, s) =>
653 {
654 string backendNodeId = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : null;
655
656 return await c.GetDomAttributes(Int32.Parse(backendNodeId), s).ConfigureAwait(false);
657 },
658 [RESTHelper.Endpoints[ApiEndpoint.GetDomProperties]] = async (c, p, s) =>
659 {
660 string backendNodeId = p.ContainsKey("elementId") ? p["elementId"]?.ToString() : null;
661
662 return await c.GetDomProperties(Int32.Parse(backendNodeId), s).ConfigureAwait(false);
663 },
664 [RESTHelper.Endpoints[ApiEndpoint.GetElementAttributeHash]] = async (c, p, s) =>
665 {
666 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
667 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
668 var attribute = p.ContainsKey("attribute") ? p["attribute"]?.ToString() : "";
669 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
670
671 foreach (GPALElement elem in elems)
673
674 return null;
675 },
676 [RESTHelper.Endpoints[ApiEndpoint.GetPageSource]] = async (c, p, s) =>
677 {
678 return await c.GetPageSource(s).ConfigureAwait(false);
679 },
680 [RESTHelper.Endpoints[ApiEndpoint.GetParentNode]] = async (c, p, s) =>
681 {
682 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
683 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
684
685 foreach (GPALElement elem in elems)
686 {
687 // elem.ElementHandle is the Runtime.objectId of the <option>
688
689 if (!string.IsNullOrEmpty(elem.ElementHandle))
690 {
691 // Call parentElement on the remote object
692 var parentResult = await c.SendCommand<object>(
693 DevToolsMethods.RuntimeCallFunctionOn,
694 new
695 {
696 objectId = elem.ElementHandle,
697 functionDeclaration = "function() { return this.parentElement; }",
698 returnByValue = false, // We want a RemoteObject, not a value
699 awaitPromise = false
700 },
701 s).ConfigureAwait(false);
702
703 if (parentResult?.result?.objectId != null)
704 {
705 string parentObjectId = parentResult.result.objectId;
706
707 // Now use your existing EvaluateSelector method (or whatever you use to turn objectId > element)
708 var parentElems = await c.EvaluateSelector(parentObjectId).ConfigureAwait(false);
709
710 return parentElems[0]; // This is now your parent element (e.g., the <select>)
711 }
712 }
713 }
714 return null;
715 },
716 [RESTHelper.Endpoints[ApiEndpoint.GetReadyStatus]] = async (c, p, s) =>
717 {
718 dynamic sessionToken = p.ContainsKey("sessionToken") ? p["sessionToken"] : null;
719 return await c.GetReadyStatus(s, sessionToken).ConfigureAwait(false);
720 },
721 [RESTHelper.Endpoints[ApiEndpoint.GetShadowRoot]] = async (c, p, s) =>
722 {
723 // NOTE: CAVEAT: this may not make any sense
724 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
725 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
726
727 foreach (GPALElement element in elems)
728 return element.GetShadowRoot();
729 return null;
730 },
731 [RESTHelper.Endpoints[ApiEndpoint.GetStorage]] = async (c, p, s) =>
732 {
733 var storageType = p.ContainsKey("storageType") ? p["storageType"]?.ToString() : null;
734 var domain = p.ContainsKey("domain") ? p["domain"]?.ToString() : null;
735 var path = p.ContainsKey("path") ? p["path"]?.ToString() : null;
736 var key = p.ContainsKey("key") ? p["key"]?.ToString() : null;
737 var storeName = p.ContainsKey("storeName") ? p["storeName"]?.ToString() : null;
738
739 return await c.GetStorage(storageType, s, domain, storeName, path, key).ConfigureAwait(false);
740 },
741 [RESTHelper.Endpoints[ApiEndpoint.GetLanguages]] = async (c, p, s) =>
742 {
743 var languages = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
744 {
745 expression = "navigator.languages.join(',')",
746 returnByValue = true
747 }, s).ConfigureAwait(false);
748 return (string)languages.result.value;
749 },
750 [RESTHelper.Endpoints[ApiEndpoint.GetUserAgent]] = async (c, p, s) =>
751 {
752 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
753 {
754 expression = "navigator.userAgent",
755 returnByValue = true
756 }, s).ConfigureAwait(false);
757 return (string)result.result.value;
758 },
759 [RESTHelper.Endpoints[ApiEndpoint.GoTo]] = async (c, p, s) =>
760 {
761 /*
762 * public class PageNavigateResponse
763 {
764 public string frameId { get; set; }
765 public string loaderId { get; set; }
766 public string errorText { get; set; }
767 }
768 */
769 var url = p.ContainsKey("url") ? p["url"]?.ToString() : "about:blank";
770 return await c.GoTo(url, s).ConfigureAwait(false);
771 },
772 [RESTHelper.Endpoints[ApiEndpoint.GoToTab]] = async (c, p, s) =>
773 {
774 string tabId = p.ContainsKey("tabId") ? p["tabId"]?.ToString() : null;
775 string url = p.ContainsKey("url") ? p["url"]?.ToString() : null;
776 int? index = p.TryGetValue("index", out object value) && int.TryParse(value?.ToString(), out int parsed) ? (int?)parsed : null;
777
778 object tabIdOrUrlOrIndex = tabId ?? url ?? (object)index;
779 if (tabIdOrUrlOrIndex == null)
780 {
781 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "No valid input provided for GoToTab (tabId, url, or index required)", c, GPALObjectType.PuppeteerCommunicator);
782 return null;
783 }
784
785 string targetId = await c.GoToTab(tabIdOrUrlOrIndex, s).ConfigureAwait(false);
786 if (targetId == null)
787 {
788 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Failed to switch to tab for input [{tabIdOrUrlOrIndex}]", c, GPALObjectType.PuppeteerCommunicator);
789 return null;
790 }
791
792 return new { targetId };
793 },
794 [RESTHelper.Endpoints[ApiEndpoint.GoToWindow]] = async (c, p, s) =>
795 {
796 var tabIdOrUrl = p.ContainsKey("tabId") ? (object)Convert.ToInt32(p["tabId"])
797 : (object)(p.ContainsKey("url") ? p["url"]?.ToString() : null);
798
799 if (tabIdOrUrl == null)
800 return null;
801
802 var targetId = await c.GoToWindow(tabIdOrUrl, s).ConfigureAwait(false);
803
804 if (!string.IsNullOrEmpty(targetId))
805 {
806 _currentTargetId = targetId;
807 return new { targetId };
808 }
809
810 return new { targetId };
811 },
812 [RESTHelper.Endpoints[ApiEndpoint.HideElement]] = async (c, p, s) =>
813 {
814 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] :
815 p.ContainsKey("css") ? p["css"] :
816 p.ContainsKey("xpath") ? p["xpath"] : null;
817 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] as List<GPALElement> :
818 await c.EvaluateSelector(parm).ConfigureAwait(false);
819 if (elems == null || !elems.Any())
820 {
821 GPAL.PublishSimpleEvent(GPALEventType.WARNING, "No elements resolved for HideElement", c, GPALObjectType.Puppeteer);
822 return new { };
823 }
824
825 List<dynamic> responses = new List<dynamic>();
826 foreach (GPALElement elem in elems)
827 {
828 responses.Add(await c.HideElement(elem.ElementBackendNodeId, s).ConfigureAwait(false));
829 }
830
831 return (responses, elems);
832 },
833 [RESTHelper.Endpoints[ApiEndpoint.Hover]] = async (c, p, s) =>
834 {
835 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
836 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
837 List<dynamic> responses = new List<dynamic>();
838
839 await c.MoveTo(elems, s, responses).ConfigureAwait(false);
840
841 return (responses, elems);
842 },
843 [RESTHelper.Endpoints[ApiEndpoint.ClickPoint]] = async (c, p, s) =>
844 {
845 var x = p.ContainsKey("x") ? Convert.ToInt32(p["x"]) : 0;
846 var y = p.ContainsKey("y") ? Convert.ToInt32(p["y"]) : 0;
847 var clickType = p.ContainsKey("clickType") ? (ClickType)Convert.ToInt32(p["clickType"]) : ClickType.LeftClick;
848 var modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
849 List<dynamic> responses = new List<dynamic>();
850
851 await c.ClickElement(x, y, s, responses, clickType, modifiers).ConfigureAwait(false);
852 return responses;
853 },
854 [RESTHelper.Endpoints[ApiEndpoint.MoveToPoint]] = async (c, p, s) =>
855 {
856 var x = p.ContainsKey("x") ? Convert.ToInt32(p["x"]) : 0;
857 var y = p.ContainsKey("y") ? Convert.ToInt32(p["y"]) : 0;
858
859 await c.MoveTo(x, y, s).ConfigureAwait(false);
860 return true;
861 },
862 [RESTHelper.Endpoints[ApiEndpoint.InjectScript]] = async (c, p, s) =>
863 {
864 var script = p.ContainsKey("script") ? p["script"]?.ToString() : "";
865 await c.InjectScript(script, s).ConfigureAwait(false);
866 return true;
867 },
868 [RESTHelper.Endpoints[ApiEndpoint.ClearInjectedScripts]] = async (c, p, s) =>
869 {
870 await c.ClearInjectedScripts(s).ConfigureAwait(false);
871 return true;
872 },
873 [RESTHelper.Endpoints[ApiEndpoint.SwitchToDefaultContent]] = async (c, p, s) =>
874 {
875 // one implementation, so the bring-to-front only happens when there was a frame to leave
876 await c.SwitchToDefaultContent(s).ConfigureAwait(false);
877 return null;
878 },
879 [RESTHelper.Endpoints[ApiEndpoint.SwitchToShadowRoot]] = (c, p, s) =>
880 {
881 // this means nothing in terms of puppeteer, only for gpal tracking
882 return null;
883 },
884 [RESTHelper.Endpoints[ApiEndpoint.IsClickable]] = async (c, p, s) =>
885 {
886 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
887 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
888 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
889 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
890 List<bool> isClickables = new List<bool>();
891
892 foreach (GPALElement element in elems)
893 {
894 isClickables.Add(element.IsClickable());
895 }
896 return isClickables.All(isClickAble => true == isClickAble);
897 },
898 [RESTHelper.Endpoints[ApiEndpoint.IsDisplayed]] = async (c, p, s) =>
899 {
900 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
901 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
902 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
903 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
904
905 bool isDisplayed = true;
906 foreach (GPALElement element in elems)
907 {
908 isDisplayed &= element.Displayed;
909 }
910 return isDisplayed;
911 },
912 [RESTHelper.Endpoints[ApiEndpoint.IsEnabled]] = async (c, p, s) =>
913 {
914 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
915 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
916 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
917 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
918
919 bool isDisabled = false;
920 foreach (GPALElement element in elems)
921 {
922 isDisabled |= element.IsDisabled();
923 }
924 return !isDisabled;
925 },
926 [RESTHelper.Endpoints[ApiEndpoint.IsEndOfPage]] = async (c, p, s) =>
927 {
928 return await c.IsEndOfPage(s).ConfigureAwait(false);
929 },
930 [RESTHelper.Endpoints[ApiEndpoint.IsVisibleInViewport]] = async (c, p, s) =>
931 {
932 // NOTE: when called from GPAL, we will always provide one element or selector that retrieves one element
933 // someone rolling their own and calling this endpoint outside gpal could result in multiple elements, so return the composite result
934 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
935 List<GPALElement> elems = (List<GPALElement>)(p.ContainsKey("gpalElements") ? p["gpalElements"] : null);
936
937 if (null != elems)
938 return await c.IsVisibleInViewport(elems, s).ConfigureAwait(false);
939 else
940 return await c.IsVisibleInViewport(parm, s).ConfigureAwait(false);
941 },
942 [RESTHelper.Endpoints[ApiEndpoint.LeftClick]] = async (c, p, s) =>
943 {
944 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
945 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
946 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
947 List<dynamic> responses = new List<dynamic>();
948
949 foreach (GPALElement element in elems)
950 {
951 await c.ScrollIntoView(element, s).ConfigureAwait(false);
952 await c.ClickElement(element, s, responses, ClickType.LeftClick, modifiers).ConfigureAwait(false);
953 }
954
955 return (responses, elems);
956 },
957 [RESTHelper.Endpoints[ApiEndpoint.LeftDoubleClick]] = async (c, p, s) =>
958 {
959 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
960 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
961 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
962 List<dynamic> responses = new List<dynamic>();
963
964 foreach (GPALElement element in elems)
965 {
966 await c.ScrollIntoView(element, s).ConfigureAwait(false);
967 await c.ClickElement(element, s, responses, ClickType.LeftDoubleClick, modifiers).ConfigureAwait(false);
968 }
969
970 return (responses, elems);
971 },
972 [RESTHelper.Endpoints[ApiEndpoint.LeftClickAndDownload]] = async (c, p, s) =>
973 {
974 string downloadPath = p.ContainsKey("downloadPath") ? p["downloadPath"]?.ToString() : "";
975 dynamic elementId = p.ContainsKey("elementId") ? p["elementId"] : null;
976 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
977
978 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(elementId).ConfigureAwait(false);
979 List<dynamic> responses = new List<dynamic>();
980
981 // we can set the download location, but not the filename
982 // se we still require a filewatcher to rename to the users filename
983 await c.LeftClickAndDownload(elems, downloadPath, modifiers, s, responses).ConfigureAwait(false);
984
985 // 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
986 //await c.SendCommand<object>(DevToolsMethods.BrowserSetDownloadBehavior, new { behavior = "deny" }, s);
987
988 return (responses, elems);
989 },
990 [RESTHelper.Endpoints[ApiEndpoint.Upload]] = async (c, p, s) =>
991 {
992 GPALFile uploadPaths = p.ContainsKey("uploadPaths") ? (GPALFile)p["uploadPaths"] : null;
993 string uploadPath = p.ContainsKey("uploadPath") ? p["uploadPath"]?.ToString() : "";
994 dynamic elementId = p.ContainsKey("elementId") ? p["elementId"] : null;
995 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0; // ???
996
997 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(elementId).ConfigureAwait(false);
998 List<dynamic> responses = new List<dynamic>();
999
1000 // we can set the download location, but not the filename
1001 // se we still require a filewatcher to rename to the users filename
1002 if (0 < uploadPaths?.Count)
1003 await c.LeftClickAndUpload(elems, uploadPaths, modifiers, s, responses).ConfigureAwait(false);
1004 else
1005 await c.LeftClickAndUpload(elems, uploadPath, modifiers, s, responses).ConfigureAwait(false);
1006
1007 // 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
1008 //await c.SendCommand<object>(DevToolsMethods.BrowserSetDownloadBehavior, new { behavior = "deny" }, s);
1009
1010 return (responses, elems);
1011 },
1012 [RESTHelper.Endpoints[ApiEndpoint.LeftDoubleClick]] = async (c, p, s) =>
1013 {
1014 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1015 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1016 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
1017
1018 List<dynamic> responses = new List<dynamic>();
1019
1020 foreach (GPALElement element in elems)
1021 {
1022 await c.ScrollIntoView(element, s).ConfigureAwait(false);
1023 await c.ClickElement(element, s, responses, ClickType.LeftDoubleClick, modifiers).ConfigureAwait(false);
1024 }
1025 return (responses, elems);
1026 },
1027 [RESTHelper.Endpoints[ApiEndpoint.MiddleClick]] = async (c, p, s) =>
1028 {
1029 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1030 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1031 int modifiers = p.ContainsKey("modifiers") ? Convert.ToInt32(p["modifiers"]) : 0;
1032 List<dynamic> responses = new List<dynamic>();
1033
1034 foreach (GPALElement element in elems)
1035 {
1036 await c.ScrollIntoView(element, s).ConfigureAwait(false);
1037 await c.ClickElement(element, s, responses, ClickType.MiddleClick, modifiers).ConfigureAwait(false);
1038 }
1039
1040 return (responses, elems);
1041 },
1042 [RESTHelper.Endpoints[ApiEndpoint.Maximize]] = async (c, p, s) =>
1043 {
1044 return await c.Maximize(s).ConfigureAwait(false);
1045 },
1046 [RESTHelper.Endpoints[ApiEndpoint.Minimize]] = async (c, p, s) =>
1047 {
1048 return await c.Minimize(s).ConfigureAwait(false);
1049 },
1050 [RESTHelper.Endpoints[ApiEndpoint.MoveTo]] = async (c, p, s) =>
1051 {
1052 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1053 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1054 List<dynamic> responses = new List<dynamic>();
1055
1056 return await c.MoveTo(elems, s, responses).ConfigureAwait(false);
1057 },
1058 [RESTHelper.Endpoints[ApiEndpoint.NewTab]] = async (c, p, s) =>
1059 {
1060 var url = p.ContainsKey("url") ? p["url"]?.ToString() : "https://google.com";
1061 var targetId = await c.NewTab(url, s).ConfigureAwait(false);
1062
1063 if (!string.IsNullOrEmpty(targetId))
1064 _currentTargetId = targetId;
1065 else
1066 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
1067 "NewTab: Failed to open a new tab",
1068 this, GPALObjectType.Puppeteer);
1069
1070 return _currentTargetId;
1071 },
1072 [RESTHelper.Endpoints[ApiEndpoint.NextTab]] = async (c, p, s) =>
1073 {
1074 var tmp = _currentTargetId;
1075
1076 _currentTargetId = await c.NextTab(_currentTargetId).ConfigureAwait(false);
1077
1078 return _currentTargetId;
1079 },
1080 [RESTHelper.Endpoints[ApiEndpoint.NextWindow]] = async (c, p, s) =>
1081 {
1082 _currentTargetId = await c.NextWindow(_currentTargetId).ConfigureAwait(false);
1083
1084 return _currentTargetId;
1085 },
1086 [RESTHelper.Endpoints[ApiEndpoint.Normal]] = async (c, p, s) =>
1087 {
1088 return await c.Normal(s).ConfigureAwait(false);
1089 },
1090 [RESTHelper.Endpoints[ApiEndpoint.OpenWindow]] = async (c, p, s) =>
1091 {
1092 var url = p.ContainsKey("url") ? p["url"]?.ToString() : "about:blank";
1093
1094 _currentTargetId = await c.OpenWindow(url, s).ConfigureAwait(false);
1095
1096 return _currentTargetId;
1097 },
1098 [RESTHelper.Endpoints[ApiEndpoint.OverrideReferrer]] = async (c, p, s) =>
1099 {
1100 var referrer = p.ContainsKey("referrer") ? p["referrer"]?.ToString() : "";
1101 return await c.SendCommand<object>(DevToolsMethods.NetworkSetExtraHTTPHeaders, new
1102 {
1103 headers = new Dictionary<string, object> { { "Referer", referrer } }
1104 }, s).ConfigureAwait(false);
1105 },
1106 [RESTHelper.Endpoints[ApiEndpoint.SetUserAgent]] = async (c, p, s) =>
1107 {
1108 var userAgent = p.ContainsKey("userAgent") ? p["userAgent"]?.ToString() : "";
1109 return await c.SetUserAgent(userAgent, s).ConfigureAwait(false);
1110 },
1111 [RESTHelper.Endpoints[ApiEndpoint.PageDown]] = async (c, p, s) =>
1112 {
1113 int pagesToScroll = p.ContainsKey("pagesToScroll") ? Convert.ToInt32(p["pagesToScroll"]) : 1;
1114 return await c.ScrollPageAsync("down", pagesToScroll, s).ConfigureAwait(false);
1115 },
1116 [RESTHelper.Endpoints[ApiEndpoint.PageEnd]] = async (c, p, s) =>
1117 {
1118 return await c.ScrollToPositionAsync("end", s).ConfigureAwait(false);
1119 },
1120 [RESTHelper.Endpoints[ApiEndpoint.PageTop]] = async (c, p, s) =>
1121 {
1122 return await c.ScrollToPositionAsync("top", s).ConfigureAwait(false);
1123 },
1124 [RESTHelper.Endpoints[ApiEndpoint.PageUp]] = async (c, p, s) =>
1125 {
1126 int pagesToScroll = p.ContainsKey("pagesToScroll") ? Convert.ToInt32(p["pagesToScroll"]) : 1;
1127 return await c.ScrollPageAsync("up", pagesToScroll, s).ConfigureAwait(false);
1128 },
1129 [RESTHelper.Endpoints[ApiEndpoint.PressModifierKey]] = async (c, p, s) =>
1130 {
1131 var modifier = p.ContainsKey("modifier") ? Convert.ToInt32(p["modifier"]) : 0;
1132 return await c.SendCommand<object>(DevToolsMethods.InputDispatchKeyEvent, new
1133 {
1134 type = "keyDown",
1135 modifiers = modifier
1136 }, s).ConfigureAwait(false);
1137 },
1138 [RESTHelper.Endpoints[ApiEndpoint.PreviousTab]] = async (c, p, s) =>
1139 {
1140 _currentTargetId = await c.PreviousTab(_currentTargetId).ConfigureAwait(false);
1141
1142 return _currentTargetId;
1143 },
1144 [RESTHelper.Endpoints[ApiEndpoint.PreviousWindow]] = async (c, p, s) =>
1145 {
1146 _currentTargetId = await c.PreviousWindow(_currentTargetId).ConfigureAwait(false);
1147
1148 return _currentTargetId;
1149 },
1150 [RESTHelper.Endpoints[ApiEndpoint.QuerySelector]] = async (c, p, s) =>
1151 {
1152 var selector = p.ContainsKey("css") ? p["css"]?.ToString() : "";
1153 List<GPALElement> elems = await _puppeteerCommunicator.EvaluateSelector(selector, s).ConfigureAwait(false);
1154
1155 if (0 < elems.Count)
1156 return elems[0];
1157 else
1158 return null;
1159 //var doc = await c.SendCommand<object>(DevToolsMethods.DOMGetDocument, new { depth = 0 }, s).ConfigureAwait(false);
1160 //return await c.SendCommand<object>(DevToolsMethods.DOMQuerySelector, new { nodeId = (int)doc.root.nodeId, selector }, s).ConfigureAwait(false);
1161 },
1162 [RESTHelper.Endpoints[ApiEndpoint.QuerySelectors]] = async (c, p, s) =>
1163 {
1164 var selector = p.ContainsKey("css") ? p["css"]?.ToString() : "";
1165 return await _puppeteerCommunicator.EvaluateSelector(selector, s).ConfigureAwait(false);
1166 },
1167 [RESTHelper.Endpoints[ApiEndpoint.Refresh]] = async (c, p, s) =>
1168 {
1169 return await c.SendCommand(DevToolsMethods.PageReload, new { }, s).ConfigureAwait(false);
1170 },
1171 [RESTHelper.Endpoints[ApiEndpoint.ReleaseModifierKey]] = async (c, p, s) =>
1172 {
1173 var modifier = p.ContainsKey("modifier") ? Convert.ToInt32(p["modifier"]) : 0;
1174 return await c.SendCommand<object>(DevToolsMethods.InputDispatchKeyEvent, new
1175 {
1176 type = "keyUp",
1177 modifiers = modifier
1178 }, s).ConfigureAwait(false);
1179 },
1180 [RESTHelper.Endpoints[ApiEndpoint.Restore]] = async (c, p, s) =>
1181 {
1182 return await c.Restore(s).ConfigureAwait(false);
1183 },
1184 [RESTHelper.Endpoints[ApiEndpoint.RightClick]] = async (c, p, s) =>
1185 {
1186 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1187 List<dynamic> responses = new List<dynamic>();
1188 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1189
1190 foreach (GPALElement element in elems)
1191 {
1192 await c.ScrollIntoView(element, s).ConfigureAwait(false);
1193 await c.ClickElement(element, s, responses, ClickType.RightClick).ConfigureAwait(false);
1194 }
1195 return elems;
1196 },
1197 [RESTHelper.Endpoints[ApiEndpoint.RightClickAndDownload]] = async (c, p, s) =>
1198 {
1199 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1200 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1201 var filename = p.ContainsKey("downloadPath") ? p["downloadPath"]?.ToString() : "";
1202
1203 await c.SendCommand<object>(DevToolsMethods.PageSetDownloadBehavior, new { behavior = "allow", downloadPath = filename }, s).ConfigureAwait(false);
1204 foreach (GPALElement element in elems)
1205 {
1206 // Randomize click within 90% of the element's border
1207 var random = new Random();
1208 var margin = 0.05f; // 5% margin on each side (90% clickable area)
1209 var clickAreaWidth = (float)element.BoundingRect.Width * (1f - 2 * margin);
1210 var clickAreaHeight = (float)element.BoundingRect.Height * (1f - 2 * margin);
1211 var x = (float)element.BoundingRect.X + (float)element.BoundingRect.Width * margin + (float)random.NextDouble() * clickAreaWidth;
1212 var y = (float)element.BoundingRect.Y + (float)element.BoundingRect.Height * margin + (float)random.NextDouble() * clickAreaHeight;
1213
1214 if (true == "A".Equals(element.TagName)) // click in dead center, don't miss it, altho width could be a bit more tolerant
1215 {
1216 x = (float)element.BoundingRect.X + (float)element.BoundingRect.Width / 2;
1217 y = (float)element.BoundingRect.Y + (float)element.BoundingRect.Height / 2;
1218 }
1219
1220 // Mouse press
1221 var pressResult = await c.SendCommand<object>(DevToolsMethods.InputDispatchMouseEvent, new
1222 {
1223 type = "mousePressed",
1224 button = "left",
1225 clickCount = 1,
1226 x = x + new Random().Next(-2, 3),
1227 y = y + new Random().Next(-2, 3),
1228 modifiers = 0,
1229 timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
1230 }, s).ConfigureAwait(false);
1231
1232 // Small random delay to mimic human behavior
1233 await Task.Delay(new Random().Next(50, 150)).ConfigureAwait(false);
1234
1235 // Mouse release
1236 var releaseResult = await c.SendCommand<object>(DevToolsMethods.InputDispatchMouseEvent, new
1237 {
1238 type = "mouseReleased",
1239 button = "left",
1240 clickCount = 1,
1241 x = x + new Random().Next(-2, 3), // simulat ehuman mouse jitter while performing a click
1242 y = x + new Random().Next(-2, 3), // simulat ehuman mouse jitter while performing a click
1243 modifiers = 0,
1244 timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
1245 }, s).ConfigureAwait(false);
1246 break;
1247 }
1248 await c.SendCommand<object>(DevToolsMethods.PageSetDownloadBehavior, new
1249 {
1250 behavior = "deny"
1251 }, s).ConfigureAwait(false);
1252 return elems;
1253 },
1254 [RESTHelper.Endpoints[ApiEndpoint.ScrollElement]] = async (c, p, s) =>
1255 {
1256 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1257 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1258 bool isObjectId = System.Text.RegularExpressions.Regex.IsMatch(parm, @"^-?\d+\.\d+\.\d+$");
1259 var hPixels = p.ContainsKey("hPixels") ? Convert.ToInt32(p["hPixels"]) : 0;
1260 var vPixels = p.ContainsKey("vPixels") ? Convert.ToInt32(p["vPixels"]) : 0;
1261 List<dynamic> responses = new List<dynamic>();
1262
1263 foreach (GPALElement elem in elems)
1264 {
1265 string jsFunc = $@"
1266 (function(selector, hPixels, vPixels) {{
1267 function evaluateSelector(selector) {{
1268 let nodes = [];
1269 let type = 'unknown';
1270 try {{
1271 nodes = Array.from(document.querySelectorAll(selector));
1272 if (nodes.length > 0) return nodes;
1273 }} catch {{}}
1274
1275 try {{
1276 const result = document.evaluate(selector, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1277 nodes = Array.from({{ length: result.snapshotLength }}, (_, i) => result.snapshotItem(i));
1278 if (nodes.length > 0) return nodes;
1279 }} catch {{}}
1280
1281 return [];
1282 }}
1283
1284 const elements = evaluateSelector(selector);
1285 elements.forEach(el => {{
1286 if (el.scrollBy) el.scrollBy(hPixels, vPixels);
1287 }});
1288 return elements.length; // optional: return number of elements scrolled
1289 }})('{(true == isObjectId ? (elem.Css ?? elem.Xpath) : parm).Replace("'", "\\'")}', {hPixels}, {vPixels})";
1290
1291 responses.Add(await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1292 {
1293 expression = jsFunc,
1294 returnByValue = true
1295 }, s).ConfigureAwait(false));
1296 }
1297 return (responses, elems);
1298 },
1299 [RESTHelper.Endpoints[ApiEndpoint.ScrollIntoView]] = async (c, p, s) =>
1300 {
1301 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1302 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1303 List<dynamic> responses = new List<dynamic>();
1304
1305 if (null != elems)
1306 foreach (GPALElement elem in elems)
1307 responses.Add(await c.ScrollIntoView(elem, s).ConfigureAwait(false));
1308
1309 return (responses, elems);
1310 },
1311 [RESTHelper.Endpoints[ApiEndpoint.ScrollWindow]] = async (c, p, s) =>
1312 {
1313 var hPixels = p.ContainsKey("hPixels") ? Convert.ToInt32(p["hPixels"]) : 0;
1314 var vPixels = p.ContainsKey("vPixels") ? Convert.ToInt32(p["vPixels"]) : 0;
1315 return await c.ScrollWindowByPixelsAsync(hPixels, vPixels, s).ConfigureAwait(false);
1316 },
1317 // SelectClick - selects an option in a <select> element
1318 // elementId = CSS selector of the <select>
1319 // value = string: option value or visible text (optional)
1320 // index = int: 0-based index of option (optional)
1321 // Exactly one of value/index must be provided
1322 [RESTHelper.Endpoints[ApiEndpoint.SelectClick]] = async (c, p, s) =>
1323 {
1324 // this is the select selector
1325 dynamic elementHandle = p.ContainsKey("elementId") ? p["elementId"] : null; // objectid
1326 string selectValue = p.ContainsKey("selectValue") ? p["selectValue"]?.ToString() : null;
1327 int? selectIndex = p.ContainsKey("selectIndex") && int.TryParse(p["selectIndex"]?.ToString(), out int idx) ? idx : (int?)null;
1328 bool result = false;
1329
1330 if (selectIndex.HasValue)
1331 {
1332 result = await c.SelectByIndex(elementHandle, selectIndex.Value, s).ConfigureAwait(false);
1333 }
1334 else // value is not null
1335 {
1336 string safeValue = selectValue.Replace("'", "\\'"); // not sure about this...
1337 result = await c.SelectByValue(elementHandle, safeValue, s).ConfigureAwait(false);
1338 }
1339
1340 return result;
1341 },
1342 [RESTHelper.Endpoints[ApiEndpoint.SendKey]] = async (c, p, s) =>
1343 {
1344 var key = p.ContainsKey("key") ? p["key"]?.ToString() : "Unidentified";
1345 var code = p.ContainsKey("code") ? p["code"]?.ToString() : "Unidentified";
1346 var vk = p.ContainsKey("vk") ? Convert.ToInt32(p["vk"]) : 0;
1347
1348 return await c.SendKey(key, code, vk).ConfigureAwait(false);
1349 },
1350
1351 [RESTHelper.Endpoints[ApiEndpoint.SendString]] = async (c, p, s) =>
1352 {
1353 var text = p.ContainsKey("text") ? p["text"]?.ToString() : "";
1354 var typingDelay = p.ContainsKey("typingDelay") ? Convert.ToInt32(p["typingDelay"]) : 0;
1355 await c.SendString(text, typingDelay, s).ConfigureAwait(false);
1356 return true;
1357 },
1358 [RESTHelper.Endpoints[ApiEndpoint.SetAttribute]] = async (c, p, s) =>
1359 {
1360 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1361 var value = p.ContainsKey("value") ? p["value"]?.ToString() : p["text"]?.ToString();
1362 var attribute = p.ContainsKey("attribute") ? p["attribute"] : "";
1363 bool isObjectId = System.Text.RegularExpressions.Regex.IsMatch(parm, @"^-?\d+\.\d+\.\d+$");
1364 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1365 List<dynamic> responses = new List<dynamic>();
1366
1367 foreach (GPALElement elem in elems)
1368 {
1369 string jsFunc = $@"
1370 (function(selector, attribute, value) {{
1371 function evaluateSelector(selector) {{
1372 let nodes = [];
1373 let type = 'unknown';
1374 try {{
1375 nodes = Array.from(document.querySelectorAll(selector));
1376 if (nodes.length > 0) return nodes;
1377 }} catch {{}}
1378
1379 try {{
1380 const result = document.evaluate(selector, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1381 nodes = Array.from({{ length: result.snapshotLength }}, (_, i) => result.snapshotItem(i));
1382 if (nodes.length > 0) return nodes;
1383 }} catch {{}}
1384
1385 return [];
1386 }}
1387
1388 const elements = evaluateSelector(selector);
1389 elements.forEach(el => {{
1390 el.setAttribute(attribute, value);
1391 }});
1392 return elements.length; // optional: return number of elements scrolled
1393 }})('{(true == isObjectId ? (elem.Css ?? elem.Xpath) : parm).Replace("'", "\\'")}', {attribute}, {value})";
1394
1395 responses.Add(await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1396 {
1397 expression = jsFunc,
1398 returnByValue = true
1399 }, s).ConfigureAwait(false));
1400 elem.Attributes[attribute.ToString()] = value;
1401 }
1402 return (responses, elems);
1403 },
1404 [RESTHelper.Endpoints[ApiEndpoint.SetDownloadFilename]] = async (c, p, s) =>
1405 {
1406 var filename = p.ContainsKey("filename") ? p["filename"]?.ToString() : "";
1407 return await c.SendCommand<object>(DevToolsMethods.PageSetDownloadBehavior, new
1408 {
1409 behavior = "allow",
1410 downloadPath = filename
1411 }, s).ConfigureAwait(false);
1412 },
1413 [RESTHelper.Endpoints[ApiEndpoint.SetRange]] = async (c, p, s) =>
1414 {
1415 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1416 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1417 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
1418 List<dynamic> responses = new List<dynamic>();
1419
1420 if ("0xdeadbeef" != rangeValue)
1421 foreach (GPALElement elem in elems)
1422 responses.Add(await c.SetRange(elem.Css, rangeValue, s).ConfigureAwait(false));
1423
1424 return responses;
1425 },
1426 [RESTHelper.Endpoints[ApiEndpoint.SetValueFromElement]] = async (c, p, s) =>
1427 {
1428 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1429 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1430 string destSelector = p.ContainsKey("destSelector") ? p["destSelector"]?.ToString() : null;
1431 List<dynamic> responses = new List<dynamic>();
1432
1433 if (false == string.IsNullOrEmpty(destSelector))
1434 foreach (GPALElement elem in elems)
1435 responses.Add(await c.SetValueFromElement(elem.Css, destSelector, s).ConfigureAwait(false));
1436
1437 return responses;
1438 },
1439 [RESTHelper.Endpoints[ApiEndpoint.SetStorage]] = async (c, p, s) =>
1440 {
1441 var storageType = p.ContainsKey("storageType") ? p["storageType"]?.ToString() : "";
1442 var domain = p.ContainsKey("domain") ? p["domain"]?.ToString() : "";
1443 var storeName = p.ContainsKey("storeName") ? p["storeName"]?.ToString() : null;
1444 var path = p.ContainsKey("path") ? p["path"]?.ToString() : "";
1445 var key = p.ContainsKey("key") ? p["key"]?.ToString() : "";
1446 var data = p.ContainsKey("data") ? p["data"]?.ToString() : "";
1447
1448 return await c.SetStorage(storageType, s, domain, key, data, storeName, path).ConfigureAwait(false);
1449 },
1450 [RESTHelper.Endpoints[ApiEndpoint.StealthOverrideReferrer]] = async (c, p, s) =>
1451 {
1452 return await c.SendCommand<object>(DevToolsMethods.NetworkSetExtraHTTPHeaders, new
1453 {
1454 headers = new Dictionary<string, object> { { "Referer", "https://www.google.com" } }
1455 }, s).ConfigureAwait(false);
1456 },
1457 [RESTHelper.Endpoints[ApiEndpoint.StopCasting]] = async (c, p, s) =>
1458 {
1459 return await c.StopCasting(s).ConfigureAwait(false);
1460 },
1461 [RESTHelper.Endpoints[ApiEndpoint.SubmitForm]] = async (c, p, s) =>
1462 {
1463 dynamic parm = p.ContainsKey("elementId") ? p["elementId"] : null;
1464 List<GPALElement> elems = p.ContainsKey("gpalElements") ? p["gpalElements"] : await _puppeteerCommunicator.EvaluateSelector(parm).ConfigureAwait(false);
1465 List<dynamic> responses = new List<dynamic>();
1466
1467 foreach (GPALElement elem in elems)
1468 {
1469 responses.Add(await c.SubmitForm(elem.ElementHandle, s).ConfigureAwait(false));
1470 /*
1471 string jsFunc = $@"
1472 (function(selector, attribute, value) {{
1473 function evaluateSelector(selector) {{
1474 let nodes = [];
1475 let type = 'unknown';
1476 try {{
1477 nodes = Array.from(document.querySelectorAll(selector));
1478 if (nodes.length > 0) return nodes;
1479 }} catch {{}}
1480
1481 try {{
1482 const result = document.evaluate(selector, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1483 nodes = Array.from({{ length: result.snapshotLength }}, (_, i) => result.snapshotItem(i));
1484 if (nodes.length > 0) return nodes;
1485 }} catch {{}}
1486
1487 return [];
1488 }}
1489
1490 const elements = evaluateSelector(selector);
1491 elements.forEach(el => {{
1492 el.submit();
1493 }});
1494 return elements.length; // optional: return number of elements scrolled
1495 }})()";
1496
1497 responses.Add(await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1498 {
1499 expression = jsFunc,
1500 returnByValue = true
1501 }, s));
1502 elem.Attributes[attribute.ToString()] = value;
1503 */
1504 }
1505 return (responses, elems);
1506 },
1507 [RESTHelper.Endpoints[ApiEndpoint.SwitchToDefaultContent]] = async (c, p, s) =>
1508 {
1509 // doesn't mean anything, useful for gpal tracking
1510 await c.SwitchToDefaultContent(s).ConfigureAwait(false);
1511 return null;
1512 },
1513 [RESTHelper.Endpoints[ApiEndpoint.SwitchToElement]] = async (c, p, s) =>
1514 {
1515 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
1516 await c.SwitchToElement(selector).ConfigureAwait(false);
1517 return null;
1518 },
1519 [RESTHelper.Endpoints[ApiEndpoint.SwitchToFrame]] = async (c, p, s) =>
1520 {
1521 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
1522 await c.SwitchToFrame(selector).ConfigureAwait(false);
1523 return null;
1524 },
1525 [RESTHelper.Endpoints[ApiEndpoint.SwitchToShadowRoot]] = async (c, p, s) =>
1526 {
1527 dynamic selector = p.ContainsKey("elementId") ? p["elementId"] : null;
1528 await c.SwitchToShadowDom(selector).ConfigureAwait(false);
1529 return null;
1530 },
1531 [RESTHelper.Endpoints[ApiEndpoint.TopBrowser]] = async (c, p, s) =>
1532 {
1533 await c.TopBrowser().ConfigureAwait(false);
1534 return null;
1535 },
1536 [RESTHelper.Endpoints[ApiEndpoint.WindowInnerHeight]] = async (c, p, s) =>
1537 {
1538 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1539 {
1540 expression = "window.innerHeight",
1541 returnByValue = true
1542 // always the top level window - inside an iframe this would report the frame's own viewport
1543 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1544 return (int)result.result.value;
1545 },
1546 [RESTHelper.Endpoints[ApiEndpoint.WindowInnerWidth]] = async (c, p, s) =>
1547 {
1548 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1549 {
1550 expression = "window.innerWidth",
1551 returnByValue = true
1552 // always the top level window - inside an iframe this would report the frame's own viewport
1553 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1554 return (int)result.result.value;
1555 },
1556 [RESTHelper.Endpoints[ApiEndpoint.WindowOuterHeight]] = async (c, p, s) =>
1557 {
1558 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1559 {
1560 expression = "window.outerHeight",
1561 returnByValue = true
1562 // always the top level window - the browser frame is not a property of any iframe
1563 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1564 return (int)result.result.value;
1565 },
1566 [RESTHelper.Endpoints[ApiEndpoint.WindowOuterWidth]] = async (c, p, s) =>
1567 {
1568 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1569 {
1570 expression = "window.outerWidth",
1571 returnByValue = true
1572 // always the top level window - the browser frame is not a property of any iframe
1573 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1574 return (int)result.result.value;
1575 },
1576 [RESTHelper.Endpoints[ApiEndpoint.WindowPageOffsetX]] = async (c, p, s) =>
1577 {
1578 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1579 {
1580 expression = "window.pageXOffset",
1581 returnByValue = true
1582 }, s).ConfigureAwait(false);
1583 return (int)result.result.value;
1584 },
1585 [RESTHelper.Endpoints[ApiEndpoint.WindowPageOffsetY]] = async (c, p, s) =>
1586 {
1587 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1588 {
1589 expression = "window.pageYOffset",
1590 returnByValue = true
1591 }, s).ConfigureAwait(false);
1592 return (int)result.result.value;
1593 },
1594 [RESTHelper.Endpoints[ApiEndpoint.WindowScreenLeft]] = async (c, p, s) =>
1595 {
1596 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1597 {
1598 expression = "window.screenLeft",
1599 returnByValue = true
1600 // always the top level window - this is where the browser sits on the desktop
1601 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1602 return (int)result.result.value;
1603 },
1604 [RESTHelper.Endpoints[ApiEndpoint.WindowScreenTop]] = async (c, p, s) =>
1605 {
1606 var result = await c.SendCommand<object>(DevToolsMethods.RuntimeEvaluate, new
1607 {
1608 expression = "window.screenTop",
1609 returnByValue = true
1610 // always the top level window - this is where the browser sits on the desktop
1611 }, c.GetCurrentSessionId()).ConfigureAwait(false);
1612 return (int)result.result.value;
1613 }
1614 };
1615 }
1616
1617
1621 public IPuppeteerClient ToGPALObject()
1623 return this;
1624 }
1625
1629 public string Name
1630 {
1631 get => _name;
1632 }
1633
1635 /// Sets a custom REST API base URL to use instead of the default, for connecting to a non-default puppeteer server.
1636 /// </summary>
1637 /// <param name="url">Base URL of the REST API</param>
1638 /// <returns>Fluent interface to continue building the request</returns>
1639 public IAllowPuppeteerCommunicator WithAPIBase(string url)
1640 {
1641 _apiBase = url;
1642 return this;
1643 }
1644
1648 /// <returns>Fluent interface to continue building the request</returns>
1650 {
1651 _apiBase = null;
1652 return this;
1653 }
1659 public IAllowPuppeteerEndpointDetails WithPuppeteerCommunicator(PuppeteerCommunicator communicator)
1660 {
1661 _puppeteerCommunicator = communicator;
1662 //_currentTargetId = _puppeteerCommunicator.GetInitialTargetId().Result; // Fetch initial target ID
1663 return this;
1664 }
1665 /// <summary>
1666 /// Resets the client and sets a custom endpoint path to call directly, bypassing the built-in ApiEndpoint enum.
1667 /// </summary>
1668 /// <param name="endpoint">The endpoint path to call</param>
1669 /// <returns>Fluent interface to continue building the request</returns>
1671 {
1672 Reset();
1673 _endpoint = endpoint;
1674 _customEndpoint = true;
1675 return this;
1677
1681
1683 public IAllowPuppeteerParametersOrExecution WithEndpoint(ApiEndpoint endpoint)
1684 {
1685 Reset();
1686 _apiEndpoint = endpoint;
1687 _endpoint = RESTHelper.Endpoints.TryGetValue(endpoint, out var ep) ? ep : "";
1688 return this;
1689 }
1690
1692
1696 public IAllowPuppeteerParametersOrExecution WithResultName(string name)
1697 {
1698 _nextResultName = name;
1699 return this;
1700 }
1701
1702 public IAllowPuppeteerEndpointDetails WithSaveFile(GPALFile file)
1703 {
1704 _saveFile = file?.Filename;
1705 return this;
1706 }
1707
1709 /// Sets the session token parameter for the request.
1710 /// </summary>
1711 /// <param name="sessionToken">The session token to send</param>
1712 /// <returns>Fluent interface to continue building the request</returns>
1713 public IAllowPuppeteerParametersOrExecution WithSessionToken(string sessionToken)
1714 {
1715 SetParameter("sessionToken", sessionToken);
1716 return this;
1717 }
1718
1720 /// Sets the display name for this client.
1721 /// </summary>
1722 /// <param name="name">The name to assign</param>
1723 /// <returns>Fluent interface to continue building the request</returns>
1724 public IAllowPuppeteerEndpointDetails WithName(string name)
1725 {
1726 _name = name;
1727 return this;
1728 }
1729
1731 /// Sets the url parameter for the request.
1732 /// </summary>
1733 /// <param name="url">The URL</param>
1734 /// <returns>Fluent interface to continue building the request</returns>
1736 {
1737 SetParameter("url", url);
1738 return this;
1739 }
1740
1742 /// Sets the image format parameter for the request (e.g. for CaptureVisibleTab).
1743 /// </summary>
1744 /// <param name="imageFormat">The image format to request</param>
1745 /// <returns>Fluent interface to continue building the request</returns>
1746 internal IAllowPuppeteerParametersOrExecution WithImageFormat(ImageFormat imageFormat)
1747 {
1748 SetParameter("imageFormat", imageFormat.ToString());
1749 return this;
1750 }
1751
1753 /// Sets the url parameter from the result at the given index of a previous execution.
1754 /// </summary>
1755 /// <param name="resultIndex">Index into the execution results</param>
1756 /// <returns>Fluent interface to continue building the request</returns>
1758 {
1759 var url = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1760 return WithUrl(url);
1761 }
1762
1764 /// Sets the url parameter from the named result of a previous execution.
1765 /// </summary>
1766 /// <param name="name">Name of a previous execution result</param>
1767 /// <returns>Fluent interface to continue building the request</returns>
1769 {
1770 var url = GetResultFromName(name)?.ToString() ?? "";
1771 return WithUrl(url);
1772 }
1773
1775 /// Sets the tab id parameter for the request.
1776 /// </summary>
1777 /// <param name="tabId">The tab id</param>
1778 /// <returns>Fluent interface to continue building the request</returns>
1780 {
1781 SetParameter("tabId", tabId);
1782 return this;
1783 }
1784
1786 /// Sets the tab id 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 tabId = GetResultFromIndex(resultIndex) is int i ? i : 0;
1793 return WithTabId(tabId);
1794 }
1795
1797 /// Sets the tab id 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 tabId = GetResultFromName(name) is int i ? i : 0;
1804 return WithTabId(tabId);
1805 }
1806
1807 /// <summary>
1808 /// Sets the window id parameter for the request.
1809 /// </summary>
1810 /// <param name="windowId">The window id</param>
1811 /// <returns>Fluent interface to continue building the request</returns>
1813 {
1814 SetParameter("windowId", windowId);
1815 return this;
1816 }
1822
1824 {
1825 var windowId = GetResultFromIndex(resultIndex) is int i ? i : 0;
1826 return WithWindowId(windowId);
1828
1832
1834 public IAllowPuppeteerParametersOrExecution WithWindowIdFromResult(string name)
1835 {
1836 var windowId = GetResultFromName(name) is int i ? i : 0;
1837 return WithWindowId(windowId);
1838 }
1839
1842
1845 public IAllowPuppeteerStorageOptions WithStorageDomain(string domain)
1846 {
1847 SetParameter("domain", domain);
1848 return this;
1849 }
1852
1855 public IAllowPuppeteerStorageOptions WithStoragePath(string path)
1856 {
1857 SetParameter("path", path);
1858 return this;
1859 }
1862
1865 public IAllowPuppeteerStorageOptions WithStorageKey(string key)
1866 {
1867 SetParameter("key", key);
1868 return this;
1869 }
1873
1875 public IAllowPuppeteerStorageOptions WithStorageStoreName(string storeName)
1876 {
1877 SetParameter("storeName", storeName);
1878 return this;
1884
1885 public IAllowPuppeteerStorageOptions WithStorageData(string data)
1886 {
1887 SetParameter("data", data);
1888 return this;
1889 }
1890 /// <summary>
1891 /// Sets whether storage should be deleted across all origins, not just the current one.
1892 /// </summary>
1893 /// <param name="trueOrFalse">True to delete storage across all origins</param>
1894 /// <returns>Fluent interface to continue building the request</returns>
1896 {
1897 SetParameter("crossOrigin", trueOrFalse);
1898 return this;
1899 }
1901 /// Sets the key parameter for the request.
1902 /// </summary>
1903 /// <param name="key">The key</param>
1904 /// <returns>Fluent interface to continue building the request</returns>
1906 {
1907 SetParameter("key", key);
1908 return this;
1909 }
1910
1912 /// Sets the key parameter from the result at the given index of a previous execution.
1913 /// </summary>
1914 /// <param name="resultIndex">Index into the execution results</param>
1915 /// <returns>Fluent interface to continue building the request</returns>
1917 {
1918 var key = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1919 return WithKey(key);
1920 }
1921
1923 /// Sets the key parameter from the named result of a previous execution.
1924 /// </summary>
1925 /// <param name="name">Name of a previous execution result</param>
1926 /// <returns>Fluent interface to continue building the request</returns>
1928 {
1929 var key = GetResultFromName(name)?.ToString() ?? "";
1930 return WithKey(key);
1931 }
1932
1934 /// Sets the css selector parameter for the request.
1935 /// </summary>
1936 /// <param name="css">CSS selector</param>
1937 /// <returns>Fluent interface to continue building the request</returns>
1939 {
1940 SetParameter("css", css);
1941 return this;
1942 }
1943
1945 /// Sets the css selector parameter from the result at the given index of a previous execution.
1946 /// </summary>
1947 /// <param name="resultIndex">Index into the execution results</param>
1948 /// <returns>Fluent interface to continue building the request</returns>
1950 {
1951 var css = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1952 return WithCss(css);
1953 }
1954
1955 /// <summary>
1956 /// Sets the css selector parameter from the named result of a previous execution.
1957 /// </summary>
1958 /// <param name="name">Name of a previous execution result</param>
1959 /// <returns>Fluent interface to continue building the request</returns>
1961 {
1962 var css = GetResultFromName(name)?.ToString() ?? "";
1963 return WithCss(css);
1964 }
1970
1971 public IAllowPuppeteerParametersOrExecution WithElements(List<GPALElement> gpalElements)
1972 {
1973 SetParameter("gpalElements", gpalElements);
1974 return this;
1975 }
1981
1983 {
1984 var elementId = GetResultFromIndex(resultIndex)?.ToString() ?? "";
1985 return WithElementId(elementId);
1986 }
1992
1994 {
1995 var elementId = GetResultFromName(name)?.ToString() ?? "";
1996 return WithElementId(elementId);
1997 }
1998 /// <summary>
1999 /// Sets the iteration for an endpoint to retrieve from the results when using WithXXXFromResults
2000 /// </summary>
2001 /// <param name="iteration"></param>
2002 /// <returns></returns>
2004 {
2005 _currentIteration = iteration > 0 ? iteration : 1;
2006 return this;
2007 }
2009 /// Sets the xpath parameter for the request.
2010 /// </summary>
2011 /// <param name="xpath">XPath expression</param>
2012 /// <returns>Fluent interface to continue building the request</returns>
2014 {
2015 SetParameter("xpath", xpath);
2016 return this;
2017 }
2018
2020 /// Sets the xpath parameter from the result at the given index of a previous execution.
2021 /// </summary>
2022 /// <param name="resultIndex">Index into the execution results</param>
2023 /// <returns>Fluent interface to continue building the request</returns>
2025 {
2026 var xpath = GetResultFromIndex(resultIndex)?.ToString() ?? "";
2027 return WithXPath(xpath);
2028 }
2029
2031 /// Sets the xpath parameter from the named result of a previous execution.
2032 /// </summary>
2033 /// <param name="name">Name of a previous execution result</param>
2034 /// <returns>Fluent interface to continue building the request</returns>
2036 {
2037 var xpath = GetResultFromName(name)?.ToString() ?? "";
2038 return WithXPath(xpath);
2039 }
2040
2042 /// Sets the pixels parameter for the request.
2043 /// </summary>
2044 /// <param name="pixels">Number of pixels</param>
2045 /// <returns>Fluent interface to continue building the request</returns>
2047 {
2048 SetParameter("pixels", pixels);
2049 return this;
2050 }
2051
2053 /// Sets the pixels parameter from the result at the given index of a previous execution.
2054 /// </summary>
2055 /// <param name="resultIndex">Index into the execution results</param>
2056 /// <returns>Fluent interface to continue building the request</returns>
2058 {
2059 var pixels = GetResultFromIndex(resultIndex) is int i ? i : 0;
2060 return WithPixels(pixels);
2061 }
2062
2064 /// Sets the pixels parameter from the named result of a previous execution.
2065 /// </summary>
2066 /// <param name="name">Name of a previous execution result</param>
2067 /// <returns>Fluent interface to continue building the request</returns>
2069 {
2070 var pixels = GetResultFromName(name) is int i ? i : 0;
2071 return WithPixels(pixels);
2072 }
2073
2076 /// </summary>
2077 /// <param name="referrer">The referrer URL</param>
2078 /// <returns>Fluent interface to continue building the request</returns>
2080 {
2081 SetParameter("referrer", referrer);
2082 return this;
2083 }
2084
2087 /// </summary>
2088 /// <param name="resultIndex">Index into the execution results</param>
2089 /// <returns>Fluent interface to continue building the request</returns>
2091 {
2092 var referrer = GetResultFromIndex(resultIndex)?.ToString() ?? "";
2093 return WithReferrer(referrer);
2094 }
2095
2102 {
2103 var referrer = GetResultFromName(name)?.ToString() ?? "";
2104 return WithReferrer(referrer);
2105 }
2106
2112 public IAllowPuppeteerExecution WithText(string text)
2113 {
2114 _text = text;
2115 SetParameter("text", text);
2116 return this;
2117 }
2118
2120 /// Sets the text parameter from the result at the given index of a previous execution.
2121 /// </summary>
2122 /// <param name="resultIndex">Index into the execution results</param>
2123 /// <returns>Fluent interface to continue building the request</returns>
2124 public IAllowPuppeteerExecution WithTextFromResult(int resultIndex)
2125 {
2126 var text = GetResultFromIndex(resultIndex)?.ToString() ?? "";
2127 return WithText(text);
2128 }
2129
2135 public IAllowPuppeteerExecution WithTextFromResult(string name)
2136 {
2137 var text = GetResultFromName(name)?.ToString() ?? "";
2138 return WithText(text);
2139 }
2140
2141 /// <summary>
2142 /// Where a file the browser downloads should end up.<br/>
2143 /// Not SaveTo, which is GPAL writing a file it already has. Only the directory of <paramref name="file"/>
2144 /// reaches the browser, since CDP sets where a download lands and not what it is called; the download
2145 /// watcher gives it the name that was asked for.
2146 /// </summary>
2147 /// <param name="file">Where the downloaded file is to end up</param>
2148 /// <returns>Fluent interface to continue building the request</returns>
2150 {
2151 SetParameter("downloadPath", file.Filename);
2152 return this;
2153 }
2157
2159 private List<GPALElement> GetElements(string selector)
2160 {
2161 return _puppeteerCommunicator.EvaluateSelector(selector).Result;
2162 }
2163 /// <summary>
2164 /// Sets the elementId parameter for the request, resolving CSS selectors to an element handle if needed.
2165 /// </summary>
2166 /// <param name="selector">An element id/object id, or a CSS selector to resolve</param>
2167 /// <returns>Fluent interface to continue building the request</returns>
2169 {
2170 bool isObjectId = System.Text.RegularExpressions.Regex.IsMatch(selector, @"^-?\d+\.\d+\.\d+$");
2171
2172 if (true/* == isObjectId*/)
2173 {
2174 SetParameter("elementId", selector);
2176 //else // we have to switch to css when interfacing with gpal, so insert it as it was called
2177 //{
2178 // List<GPALElement> queryResponse = GetElements(selector);
2179 // SetParameter("elementId", queryResponse?[0]?.ElementHandle);
2180 //}
2181 return this;
2182 }
2183
2187 /// <param name="resultIndex">Index into the execution results</param>
2188 /// <returns>Fluent interface to continue building the request</returns>
2190 {
2191 var elementId = GetResultFromIndex(resultIndex)?.ToString() ?? "";
2192 return WithElementId(elementId);
2193 }
2194
2199 /// <returns>Fluent interface to continue building the request</returns>
2201 {
2202 var elementId = GetResultFromName(name)?.ToString() ?? "";
2203 return WithElementId(elementId);
2204 }
2205
2210 /// <returns>Fluent interface to continue building the request</returns>
2212 {
2213 _hPixels = GetResultFromIndex(resultIndex) is int i ? i : 0;
2214 SetParameter("hPixels", _hPixels);
2215 return this;
2216 }
2217
2223 public IAllowPuppeteerParametersOrExecution WithHPixelsFromResult(string name)
2224 {
2225 _hPixels = GetResultFromName(name) is int i ? i : 0;
2226 SetParameter("hPixels", _hPixels);
2227 return this;
2228 }
2229
2235 public IAllowPuppeteerParametersOrExecution WithVPixelsFromResult(int resultIndex)
2236 {
2237 _vPixels = GetResultFromIndex(resultIndex) is int i ? i : 0;
2238 SetParameter("vPixels", _vPixels);
2239 return this;
2240 }
2241
2243 /// Sets the vertical pixels parameter from the named result of a previous execution.
2244 /// </summary>
2245 /// <param name="name">Name of a previous execution result</param>
2246 /// <returns>Fluent interface to continue building the request</returns>
2248 {
2249 _vPixels = GetResultFromName(name) is int i ? i : 0;
2250 SetParameter("vPixels", _vPixels);
2251 return this;
2252 }
2253
2258 public string Execute()
2260 return Task.Run(async () => await ExecuteAsync().ConfigureAwait(false)).GetAwaiter().GetResult();
2261 }
2262
2263 public T Execute<T>()
2264 {
2265 try
2266 {
2267 Task<T> task = ExecuteAsync<T>();
2268
2269 // WinForms compatibilty
2270 // This keeps the UI thread blocked, but allows the internal awaits
2271 // to complete successfully on a background thread.
2272 return Task.Run(async () => await task.ConfigureAwait(false)).GetAwaiter().GetResult();
2273 }
2274 catch (GPALException)
2275 {
2276 // the comms layer already decided the browser is gone, and default(T) reads to the caller
2277 // as an answer rather than as the browser being unreachable
2278 throw;
2279 }
2280 catch (Exception ex)
2281 {
2282 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to execute synchronously for type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, ex);
2283 return default(T);
2284 }
2285 }
2286
2291 public IAllowPuppeteerParametersOrExecution AndThen()
2292 {
2293 Execute();
2294 return this;
2295 }
2296
2297 public IAllowPuppeteerParametersOrExecution AndThen<T>()
2298 {
2299 Execute<T>();
2300 return this;
2301 }
2302
2307 public async Task<string> ExecuteAsync()
2308 {
2309 var result = await ExecuteInternalAsync().ConfigureAwait(false);
2310 return result?.ToString() ?? "";
2311 }
2312
2313 public async Task<T> ExecuteAsync<T>()
2314 {
2315 dynamic result = await ExecuteInternalAsync().ConfigureAwait(false); // Returns Task<dynamic>
2316
2317 if (result == null)
2318 {
2319 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "ExecuteInternalAsync returned null.", this, GPALObjectType.PuppeteerClient, null);
2320 return default(T);
2321 }
2322
2323 if (typeof(T) == typeof(JObject))
2324 {
2325 if (result is JObject)
2326 {
2327 return (T)(object)result;
2328 }
2329 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Expected JObject but got [{result.GetType().Name}].", this, GPALObjectType.PuppeteerClient, null);
2330 return default(T);
2331 }
2332
2333 // Try direct deserialization for non-JObject types (e.g., bool, string)
2334 try
2335 {
2336 if (result is T)
2337 {
2338 return (T)result;
2339 }
2340 if (result is JToken)
2341 {
2342 return JsonConvert.DeserializeObject<T>(result.ToString());
2343 }
2344 }
2345 catch //(Exception ex)
2346 {
2347 //GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize [{result}] to type [{typeof(T).Name}]. [{ex.Message}]", this, GPALObjectType.PuppeteerClient, ex);
2348 // Continue to try CDP structure
2349 }
2350
2351 // Try CDP structure (result.value or result)
2352 if (result is JObject jObject)
2353 {
2354 JToken resultToken = jObject["result"];
2355 if (resultToken == null)
2356 {
2357 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, "Result object is missing 'result' property.", this, GPALObjectType.PuppeteerClient, null);
2358 return default(T);
2359 }
2360
2361 JToken valueToken = resultToken["value"];
2362 if (valueToken != null)
2363 {
2364 // Special handling for string
2365 if (typeof(T) == typeof(string) && valueToken.Type == JTokenType.String)
2366 {
2367 return (T)(object)valueToken.ToString();
2369
2370 try
2371 {
2372 return JsonConvert.DeserializeObject<T>(valueToken.ToString());
2373 }
2374 catch (Exception ex)
2375 {
2376 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize result.value [{valueToken.ToString()}] to type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, ex);
2377 return default(T);
2378 }
2380
2381 try
2382 {
2383 return JsonConvert.DeserializeObject<T>(resultToken.ToString());
2384 }
2385 catch (Exception ex)
2386 {
2387 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Failed to deserialize result [{resultToken.ToString()}] to type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, ex);
2388 return default(T);
2389 }
2391
2392 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unsupported result type [{result.GetType().Name}] for type [{typeof(T).Name}].", this, GPALObjectType.PuppeteerClient, null);
2393 return default(T);
2394 }
2395
2400 public ResultCollection GetExecutionResults()
2402 var results = new ResultCollection();
2403 foreach (var entry in _executionResults)
2404 {
2405 results[entry.Name] = entry.Result;
2406 }
2407 return results;
2408 }
2409
2412 /// </summary>
2413 /// <param name="name">Header name</param>
2414 /// <param name="value">Header value</param>
2415 /// <returns>Fluent interface to continue building the request</returns>
2416 public IAllowPuppeteerExecution WithHeader(string name, string value)
2417 {
2418 _headers[name] = value;
2419 return this;
2420 }
2421
2423 /// Sets the request parameters as an ordinal array.
2424 /// </summary>
2425 /// <param name="parameters">Ordinal parameter values</param>
2426 /// <returns>Fluent interface to continue building the request</returns>
2427 public IAllowPuppeteerParametersOrExecution WithParameters(params object[] parameters)
2428 {
2429 SetParameter("parameters", parameters); // ordinal array
2430 return this;
2431 }
2432
2434 /// Sets the request parameters from an object with named fields.
2435 /// </summary>
2436 /// <param name="parameters">Object whose properties are the named parameters</param>
2437 /// <returns>Fluent interface to continue building the request</returns>
2439 {
2440 SetParameter("parameters", parameters); // object with named fields
2441 return this;
2442 }
2443
2445 /// Sets the request parameters from the result at the given index of a previous execution.
2446 /// </summary>
2447 /// <param name="resultIndex">Index into the execution results</param>
2448 /// <returns>Fluent interface to continue building the request</returns>
2450 {
2451 SetParameter("parameters", GetResultFromIndex(resultIndex));
2452 return this;
2453 }
2454
2456 /// Sets the request parameters from the named result of a previous execution.
2457 /// </summary>
2458 /// <param name="name">Name of a previous execution result</param>
2459 /// <returns>Fluent interface to continue building the request</returns>
2461 {
2462 SetParameter("parameters", GetResultFromName(name));
2463 return this;
2464 }
2465
2467 /// Sets the content encoding used for requests sent to the puppeteer communicator.
2468 /// </summary>
2469 /// <param name="encoding">The content encoding to use</param>
2470 /// <returns>Fluent interface to continue building the request</returns>
2471 public IAllowPuppeteerParametersOrExecution WithEncoding(ContentEncoding encoding)
2472 {
2473 _contentEncoding = encoding;
2474 return this;
2475 }
2476
2479 /// </summary>
2480 /// <param name="workflow">Action to run as a workflow step</param>
2481 /// <returns>Fluent interface to continue building the request</returns>
2482 public IAllowPuppeteerWorkflow WithWorkflow(Action<RESTClient> workflow)
2483 {
2484 _workflows.Add(w => workflow((RESTClient)(object)this));
2485 return this;
2486 }
2487
2491 /// <param name="timeoutMs">Timeout in milliseconds</param>
2492 /// <returns>Fluent interface to continue building the request</returns>
2493 public IAllowPuppeteerWorkflow WhileLoopTimeout(int timeoutMs)
2494 {
2495 _whileLoopTimeoutMs = timeoutMs;
2496 return this;
2497 }
2498
2502 /// <param name="maxIterations">Maximum number of iterations</param>
2503 /// <returns>Fluent interface to continue building the request</returns>
2504 public IAllowPuppeteerWorkflow WhileLoopMaxIterations(int maxIterations)
2505 {
2506 _whileLoopMaxIterations = maxIterations;
2507 return this;
2508 }
2509
2513 /// <param name="condition">Predicate evaluated each iteration</param>
2514 /// <returns>Fluent interface to continue building the request</returns>
2515 public IAllowPuppeteerParametersOrExecution While(Func<bool> condition)
2516 {
2517 _loopCondition = condition;
2518 _loopType = "While";
2519 return this;
2520 }
2521
2524 /// </summary>
2525 /// <param name="condition">Predicate evaluated each iteration</param>
2526 /// <returns>Fluent interface to continue building the request</returns>
2527 public IAllowPuppeteerParametersOrExecution Until(Func<bool> condition)
2528 {
2529 _loopCondition = condition;
2530 _loopType = "Until";
2531 return this;
2532 }
2533
2535 /// Sets the timeout, in milliseconds, used by CheckNetworkIdle.
2536 /// </summary>
2537 /// <param name="timeoutMs">Timeout in milliseconds</param>
2538 /// <returns>Fluent interface to continue building the request</returns>
2540 {
2541 _timeoutMs = timeoutMs;
2542 return this;
2543 }
2544
2546 /// Sets the prune interval, in milliseconds, used by CheckNetworkIdle.
2547 /// </summary>
2548 /// <param name="pruneMs">Prune interval in milliseconds</param>
2549 /// <returns>Fluent interface to continue building the request</returns>
2551 {
2552 _pruneMs = pruneMs;
2553 return this;
2554 }
2555
2557 /// Sets the maximum number of in-flight connections allowed before the network is considered busy, used by CheckNetworkIdle.
2558 /// </summary>
2559 /// <param name="maxConnections">Maximum number of in-flight connections</param>
2560 /// <returns>Fluent interface to continue building the request</returns>
2562 {
2563 _maxConnections = maxConnections;
2564 return this;
2565 }
2566
2568 /// Sets the modifier keys parameter for the request.
2569 /// </summary>
2570 /// <param name="modifierKeys">Modifier keys to hold during the action</param>
2571 /// <returns>Fluent interface to continue building the request</returns>
2572 public IAllowPuppeteerParametersOrExecution WithModifiers(ModifierKeys modifierKeys)
2573 {
2574 SetParameter("modifiers", GetModifierBitmask(modifierKeys).ToString());
2575 return this;
2576 }
2577
2579 /// Sets the horizontal pixels parameter for the request.
2580 /// </summary>
2581 /// <param name="hPixels">Number of horizontal pixels</param>
2582 /// <returns>Fluent interface to continue building the request</returns>
2583 public IAllowPuppeteerScrollElement WithHPixels(int hPixels)
2584 {
2585 SetParameter("hPixels", hPixels);
2586 return this;
2587 }
2588
2590 /// Sets the vertical pixels parameter for the request.
2591 /// </summary>
2592 /// <param name="vPixels">Number of vertical pixels</param>
2593 /// <returns>Fluent interface to continue building the request</returns>
2594 public IAllowPuppeteerScrollElement WithVPixels(int vPixels)
2595 {
2596 SetParameter("vPixels", vPixels);
2597 return this;
2598 }
2599
2601 /// Sets the horizontal drag delta parameter for a drag and drop.
2602 /// </summary>
2603 /// <param name="deltaX">Horizontal drag distance, in pixels</param>
2604 /// <returns>Fluent interface to continue building the request</returns>
2605 public IAllowPuppeteerDragAndDrop WithDeltaX(int deltaX)
2606 {
2607 SetParameter("deltaX", deltaX);
2608 return this;
2609 }
2610
2613 /// </summary>
2614 /// <param name="deltaY">Vertical drag distance, in pixels</param>
2615 /// <returns>Fluent interface to continue building the request</returns>
2616 public IAllowPuppeteerDragAndDrop WithDeltaY(int deltaY)
2617 {
2618 SetParameter("deltaY", deltaY);
2619 return this;
2620 }
2621
2625 /// <param name="offsetX">Horizontal grab offset, in pixels, within the element</param>
2626 /// <returns>Fluent interface to continue building the request</returns>
2627 public IAllowPuppeteerDragAndDrop WithOffsetX(int offsetX)
2628 {
2629 SetParameter("offsetX", offsetX);
2630 return this;
2631 }
2632
2636 /// <param name="offsetY">Vertical grab offset, in pixels, within the element</param>
2637 /// <returns>Fluent interface to continue building the request</returns>
2638 public IAllowPuppeteerDragAndDrop WithOffsetY(int offsetY)
2639 {
2640 SetParameter("offsetY", offsetY);
2641 return this;
2642 }
2643
2646 /// </summary>
2647 /// <param name="attribute">The attribute name</param>
2648 /// <returns>Fluent interface to continue building the request</returns>
2649 public IAllowPuppeteerAttribute WithAttribute(string attribute)
2650 {
2651 _attribute = attribute;
2652 SetParameter("attribute", attribute);
2653 return this;
2654 }
2655
2657 /// Sets the attribute value parameter for SetAttribute requests.
2658 /// </summary>
2659 /// <param name="value">The value to set</param>
2660 /// <returns>Fluent interface to continue building the request</returns>
2661 public IAllowPuppeteerAttribute WithValue(string value)
2662 {
2663 _value = value;
2664 SetParameter("value", value);
2665 return this;
2666 }
2667
2669 /// Sets the destination element id parameter for a drag and drop.
2670 /// </summary>
2671 /// <param name="destElementId">Element id of the drop target</param>
2672 /// <returns>Fluent interface to continue building the request</returns>
2673 public IAllowPuppeteerExecution WithDestElementId(string destElementId)
2674 {
2675 SetParameter("destElementId", destElementId);
2676 return this;
2677 }
2678
2680 /// Sets the CSS parameter
2681 /// </summary>
2682 /// <param name="css">The CSS selector</param>
2683 /// <returns></returns>
2684 public IAllowPuppeteerExecution WithDeviceName(string sinkName)
2685 {
2686 SetParameter("sinkName", sinkName);
2687 return this;
2688 }
2689
2691 /// Configure this client to navigate the browser back one page in history.
2692 /// </summary>
2693 /// <returns>Fluent interface to continue building the request</returns>
2695 {
2696 WithEndpoint(ApiEndpoint.Back);
2697 return this;
2698 }
2699
2703 /// <param name="imageFormat">The image format to capture</param>
2704 /// <returns>Fluent interface to continue building the request</returns>
2705 public IAllowPuppeteerExecution CaptureVisibleTab(ImageFormat imageFormat = ImageFormat.JPEG)
2706 {
2707 WithImageFormat(imageFormat);
2708 WithEndpoint(ApiEndpoint.CaptureVisibleTab);
2709 return this;
2710 }
2715 /// <param name="deviceNameOrId">The Cast Device Name or ID</param>
2716 /// <returns></returns>
2717 public IAllowPuppeteerExecution CastDesktop(string deviceNameOrId)
2718 {
2719 WithEndpoint(ApiEndpoint.CastDesktop).WithDeviceName(deviceNameOrId);
2720 return this;
2721 }
2727 /// <returns></returns>
2728 public IAllowPuppeteerExecution CastTab(string deviceNameOrId)
2729 {
2730 WithEndpoint(ApiEndpoint.CastTab).WithDeviceName(deviceNameOrId);
2731 return this;
2732 }
2733
2740 {
2741 WithEndpoint(ApiEndpoint.CloseTab);
2742 if (url != null) WithUrl(url.Url);
2743 return this;
2744 }
2745
2746
2751 public IAllowPuppeteerExecution CloseTab(int tabId)
2753 WithEndpoint(ApiEndpoint.CloseTab);
2754 WithTabId(tabId);
2755 return this;
2756 }
2757
2758
2763 public IAllowPuppeteerExecution CloseWindow(GPALUrl url = null)
2764 {
2765 WithEndpoint(ApiEndpoint.CloseWindow);
2766 if (url != null) WithUrl(url.Url);
2767 return this;
2768 }
2769
2771
2775 public IAllowPuppeteerExecution CloseWindow(int tabId)
2776 {
2777 WithEndpoint(ApiEndpoint.CloseWindow);
2778 WithTabId(tabId);
2779 return this;
2780 }
2781
2782
2787 public IAllowPuppeteerStorageOptions DeleteStorage(WebsiteStorageType storageType)
2789 WithEndpoint(ApiEndpoint.DeleteStorage);
2790 SetParameter("storageType", storageType.ToString());
2791
2792 return this;
2793 }
2794
2799 /// <returns>Fluent interface to continue building the request</returns>
2800 public IAllowPuppeteerParameters ExecuteJavaScript(string script)
2801 {
2802 WithEndpoint(ApiEndpoint.ExecuteJavaScript);
2803 SetParameter("script", script);
2804 return this;
2805 }
2806
2810 /// </summary>
2811 /// <param name="url">Url to request. A relative one resolves against wherever the browser currently is</param>
2812 /// <returns>Fluent interface to continue building the request</returns>
2814 {
2815 WithEndpoint(ApiEndpoint.Fetch);
2816 SetParameter("url", url);
2817 return this;
2818 }
2819
2822 /// </summary>
2823 /// <param name="method">HTTP method</param>
2824 /// <returns>Fluent interface to continue building the request</returns>
2826 {
2827 SetParameter("method", method);
2828 return this;
2829 }
2830
2834 /// <param name="body">Request body</param>
2835 /// <returns>Fluent interface to continue building the request</returns>
2837 {
2838 SetParameter("body", body);
2839 return this;
2840 }
2841
2847 public IAllowPuppeteerParametersOrExecution WithContentType(string contentType)
2848 {
2849 SetParameter("contentType", contentType);
2850 return this;
2851 }
2852
2855 /// </summary>
2856 /// <param name="headers">Header names and values in pairs</param>
2857 /// <returns>Fluent interface to continue building the request</returns>
2858 public IAllowPuppeteerParametersOrExecution WithHeaders(string[] headers)
2859 {
2860 SetParameter("headers", headers);
2861 return this;
2862 }
2863
2869
2870 public IAllowPuppeteerParametersOrExecution WithBytes(bool asBytes)
2871 {
2872 SetParameter("bytes", asBytes);
2873 return this;
2874 }
2875
2876 /// <summary>
2877 /// Configure this client to append text to the given input element.
2878 /// </summary>
2879 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the input</param>
2880 /// <param name="delayMs">Inter-character pacing delay in milliseconds for the per-character key-event fallback. 0 uses the fallback's default pacing.</param>
2881 /// <returns>Fluent interface to continue building the request</returns>
2882 public IAllowPuppeteerInputText FillInAppend(dynamic elementOrelementId, int delayMs = 0)
2883 {
2884 WithEndpoint(ApiEndpoint.FillInAppend);
2885
2886 if (elementOrelementId is GPALElement element)
2887 WithElements(new List<GPALElement>() { element });
2888 else if (elementOrelementId is List<GPALElement> elements)
2889 WithElements(elements);
2890 else
2891 WithElementId(elementOrelementId);
2892
2893 SetParameter("typingDelay", delayMs);
2894 return this;
2896
2900
2903 public IAllowPuppeteerInputText FillInInsert(dynamic elementOrelementId, int delayMs = 0)
2904 {
2905 WithEndpoint(ApiEndpoint.FillInInsert);
2906
2907 if (elementOrelementId is GPALElement element)
2908 WithElements(new List<GPALElement>() { element });
2909 else if (elementOrelementId is List<GPALElement> elements)
2910 WithElements(elements);
2911 else
2912 WithElementId(elementOrelementId);
2913
2914 SetParameter("typingDelay", delayMs);
2915 return this;
2917
2924 public IAllowPuppeteerInputText FillInOverwrite(dynamic elementOrelementId, int delayMs = 0)
2925 {
2926 WithEndpoint(ApiEndpoint.FillInOverwrite);
2927
2928 if (elementOrelementId is GPALElement element)
2929 WithElements(new List<GPALElement>() { element });
2930 else if (elementOrelementId is List<GPALElement> elements)
2931 WithElements(elements);
2932 else
2933 WithElementId(elementOrelementId);
2934
2935 SetParameter("typingDelay", delayMs);
2936 return this;
2937 }
2938
2944 {
2945 WithEndpoint(ApiEndpoint.Forward);
2946 return this;
2947 }
2948
2949
2953 public IAllowPuppeteerExecution FullScreen()
2955 WithEndpoint(ApiEndpoint.FullScreen);
2956 return this;
2957 }
2958
2959
2964 public IAllowPuppeteerExecution GetReadyStatus(string sessionToken = null)
2965 {
2966 WithEndpoint(ApiEndpoint.GetReadyStatus);
2967
2968 if (false == string.IsNullOrEmpty(sessionToken))
2969 WithSessionToken(sessionToken);
2970
2971 return this;
2972 }
2973
2975 /// Configure this client to check whether network activity has gone idle.
2976 /// </summary>
2977 /// <param name="maxConnections">Maximum number of in-flight connections still considered idle</param>
2978 /// <returns>Fluent interface to continue building the request</returns>
2979 public IAllowPuppeteerParametersOrExecution CheckNetworkIdle(int maxConnections = 0)
2980 {
2981 WithEndpoint(ApiEndpoint.CheckNetworkIdle);
2982 WithMaxConnections(maxConnections);
2983 return this;
2984 }
2985
2987 /// Configure this client to retrieve the shadow root of the element matching the given CSS selector.
2988 /// </summary>
2989 /// <param name="css">CSS selector of the element</param>
2990 /// <returns>Fluent interface to continue building the request</returns>
2991 public IAllowPuppeteerExecution GetShadowRoot(string css)
2992 {
2993 WithEndpoint(ApiEndpoint.GetShadowRoot);
2994 WithCss(css);
2995 return this;
2996 }
3003 {
3004 WithEndpoint(ApiEndpoint.GetCurrentUrl);
3005 return this;
3006 }
3007
3008 /// <summary>
3009 /// Configure this client to retrieve information about the current browser window.
3010 /// </summary>
3011 /// <returns>Fluent interface to continue building the request</returns>
3013 {
3014 WithEndpoint(ApiEndpoint.GetCurrentWindow);
3015 return this;
3016 }
3017
3021
3023 public IAllowPuppeteerStorageOptions GetStorage(WebsiteStorageType storageType)
3024 {
3025 WithEndpoint(ApiEndpoint.GetStorage);
3026 SetParameter("storageType", storageType.ToString());
3028 return this;
3029 }
3030
3033
3035 public IAllowPuppeteerExecution GetLanguages()
3036 {
3037 WithEndpoint(ApiEndpoint.GetLanguages);
3038 return this;
3040
3045 public IAllowPuppeteerExecution GetUserAgent()
3046 {
3047 WithEndpoint(ApiEndpoint.GetUserAgent);
3048 return this;
3049 }
3050
3051 /// <summary>
3052 /// Configure this client to navigate the current tab to the given URL, resolving relative URLs and checking robots.txt restrictions.
3053 /// </summary>
3054 /// <param name="url">The URL to navigate to</param>
3055 /// <returns>Fluent interface to continue building the request</returns>
3057 {
3058 url.ForUrl(MagicHelper.GetFullUrl(url?.Url, _puppeteerCommunicator.Browser, out _puppeteerCommunicator.Browser._areRobotsAllowed));
3059
3060 if (false == _puppeteerCommunicator.Browser.AreRobotsAllowed && true == _puppeteerCommunicator.Browser.BrowserSettings.ObeyRobotsTxt)
3061 {
3062 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);
3063 url.ForUrl("https://google.com");
3064 }
3065 WithEndpoint(ApiEndpoint.GoTo);
3066 WithUrl(url?.Url);
3067 return this;
3068 }
3069
3076 {
3077 WithEndpoint(ApiEndpoint.GoToTab);
3078 WithUrl(url?.Url);
3079 return this;
3080 }
3081
3086 /// <returns>Fluent interface to continue building the request</returns>
3087 public IAllowPuppeteerExecution GoToTab(int tabId)
3088 {
3089 WithEndpoint(ApiEndpoint.GoToTab);
3090 WithTabId(tabId);
3091 return this;
3092 }
3093
3097 /// <param name="url">The URL of the tab whose window to switch to</param>
3098 /// <returns>Fluent interface to continue building the request</returns>
3100 {
3101 WithEndpoint(ApiEndpoint.GoToWindow);
3102 WithUrl(url?.Url);
3103 return this;
3104 }
3105
3108 /// </summary>
3109 /// <param name="tabId">The tab id whose window to switch to</param>
3110 /// <returns>Fluent interface to continue building the request</returns>
3111 public IAllowPuppeteerExecution GoToWindow(int tabId)
3112 {
3113 WithEndpoint(ApiEndpoint.GoToWindow);
3114 WithTabId(tabId);
3115 return this;
3116 }
3117
3118 /// <summary>
3119 /// Configure this client to switch context into the element matching the given selector.
3120 /// </summary>
3121 /// <param name="selector">CSS selector of the element</param>
3122 /// <returns>Fluent interface to continue building the request</returns>
3123 public IAllowPuppeteerExecution InElement(string selector)
3124 {
3125 WithEndpoint(ApiEndpoint.SwitchToElement);
3126 WithElementId(selector);
3127 return this;
3128 }
3135 {
3136 WithEndpoint(ApiEndpoint.SwitchToDefaultContent);
3137 return this;
3138 }
3139
3141 /// Configure this client to switch context into the shadow root of the element matching the given selector.
3142 /// </summary>
3143 /// <param name="selector">CSS selector of the element</param>
3144 /// <returns>Fluent interface to continue building the request</returns>
3145 public IAllowPuppeteerExecution InShadowDom(string selector)
3146 {
3147 WithEndpoint(ApiEndpoint.SwitchToShadowRoot);
3148 WithElementId(selector);
3149 return this;
3150 }
3151
3156 public IAllowPuppeteerExecution Maximize()
3157 {
3158 WithEndpoint(ApiEndpoint.Maximize);
3159 return this;
3160 }
3167 {
3168 WithEndpoint(ApiEndpoint.Minimize);
3169 return this;
3170 }
3176
3177 public IAllowPuppeteerExecution MoveTo(string selector)
3178 {
3179 WithEndpoint(ApiEndpoint.MoveTo);
3180 WithElementId(selector);
3181 return this;
3182 }
3183
3186
3189 public IAllowPuppeteerExecution NewTab(GPALUrl url = null)
3190 {
3191 WithEndpoint(ApiEndpoint.NewTab);
3193 url?.ForUrl(MagicHelper.GetFullUrl(url?.Url, _puppeteerCommunicator.Browser, out _puppeteerCommunicator.Browser._areRobotsAllowed));
3194
3195 if (false == _puppeteerCommunicator.Browser.AreRobotsAllowed && true == _puppeteerCommunicator.Browser.BrowserSettings.ObeyRobotsTxt)
3196 {
3197 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);
3198 url.ForUrl("https://google.com");
3199 }
3200
3201 WithUrl(url?.Url);
3202 return this;
3203 }
3204
3207
3209 public IAllowPuppeteerExecution NextTab()
3210 {
3211 WithEndpoint(ApiEndpoint.NextTab);
3212 return this;
3214
3219 public IAllowPuppeteerExecution NextWindow()
3220 {
3221 WithEndpoint(ApiEndpoint.NextWindow);
3222 return this;
3223 }
3224
3225 /// <summary>
3226 /// Configure this client to restore the browser window to its normal (non-maximized, non-minimized) state.
3227 /// </summary>
3228 /// <returns>Fluent interface to continue building the request</returns>
3230 {
3231 WithEndpoint(ApiEndpoint.Normal);
3232 return this;
3233 }
3234
3237 /// </summary>
3238 /// <param name="url">The URL to navigate to in the new window</param>
3239 /// <returns>Fluent interface to continue building the request</returns>
3241 {
3242 WithEndpoint(ApiEndpoint.OpenWindow);
3243
3244 url.ForUrl(MagicHelper.GetFullUrl(url?.Url, _puppeteerCommunicator.Browser, out _puppeteerCommunicator.Browser._areRobotsAllowed));
3245
3246 if (false == _puppeteerCommunicator.Browser.AreRobotsAllowed && true == _puppeteerCommunicator.Browser.BrowserSettings.ObeyRobotsTxt)
3247 {
3248 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);
3249 url.ForUrl("https://google.com");
3250 }
3251
3252 WithUrl(url?.Url);
3253 return this;
3254 }
3255
3258 /// </summary>
3259 /// <param name="referrer">The referrer URL to send</param>
3260 /// <returns>Fluent interface to continue building the request</returns>
3261 public IAllowPuppeteerExecution OverrideReferrer(string referrer)
3262 {
3263 WithEndpoint(ApiEndpoint.OverrideReferrer);
3264 WithReferrer(referrer);
3265 return this;
3266 }
3267
3269 /// Configure this client to override the browser's user agent string for subsequent requests.
3270 /// </summary>
3271 /// <param name="userAgent">The user agent string to use</param>
3272 /// <returns>Fluent interface to continue building the request</returns>
3273 public IAllowPuppeteerExecution SetUserAgent(string userAgent)
3274 {
3275 WithEndpoint(ApiEndpoint.SetUserAgent);
3276 SetParameter("userAgent", userAgent);
3277 return this;
3278 }
3279
3280 /// <summary>
3281 /// Configure this client to scroll the page down by the given number of page heights.
3282 /// </summary>
3283 /// <param name="pagesToScroll">Number of page heights to scroll down</param>
3284 /// <returns>Fluent interface to continue building the request</returns>
3285 public IAllowPuppeteerExecution PageDown(int pagesToScroll = 1)
3286 {
3287 WithEndpoint(ApiEndpoint.PageDown);
3288 SetParameter("pagesToScroll", pagesToScroll);
3289 return this;
3291
3295
3296 public IAllowPuppeteerExecution PageEnd()
3297 {
3298 WithEndpoint(ApiEndpoint.PageEnd);
3299 return this;
3301
3305
3306 public IAllowPuppeteerExecution PageTop()
3307 {
3308 WithEndpoint(ApiEndpoint.PageTop);
3309 return this;
3310 }
3317 public IAllowPuppeteerExecution PageUp(int pagesToScroll = 1)
3318 {
3319 WithEndpoint(ApiEndpoint.PageUp);
3320 SetParameter("pagesToScroll", pagesToScroll);
3321 return this;
3322 }
3329 {
3330 WithEndpoint(ApiEndpoint.PreviousTab);
3331 return this;
3332 }
3333
3336 /// </summary>
3337 /// <returns>Fluent interface to continue building the request</returns>
3339 {
3340 WithEndpoint(ApiEndpoint.PreviousWindow);
3341 return this;
3342 }
3343
3348 public IAllowPuppeteerExecution Refresh()
3349 {
3350 WithEndpoint(ApiEndpoint.Refresh);
3351 return this;
3352 }
3353
3354 /// <summary>
3355 /// Configure this client to press and hold the given modifier key(s).
3356 /// </summary>
3357 /// <param name="modifierKeys">Modifier key(s) to press and hold</param>
3358 /// <returns>Fluent interface to continue building the request</returns>
3359 public IAllowPuppeteerExecution PressModifierKey(ModifierKeys modifierKeys)
3360 {
3361 WithEndpoint(ApiEndpoint.PressModifierKey);
3362 SetParameter("modifier", GetModifierBitmask(modifierKeys));
3363 return this;
3364 }
3365
3369
3371 public IAllowPuppeteerExecution ReleaseModifierKey(ModifierKeys modifierKeys)
3372 {
3373 WithEndpoint(ApiEndpoint.ReleaseModifierKey);
3374 SetParameter("modifier", GetModifierBitmask(modifierKeys));
3375 return this;
3376 }
3377
3378 // Helper method to convert ModifierKeys to CDP modifiers bitmask
3384 public int GetModifierBitmask(ModifierKeys modifierKeys)
3385 {
3386 int bitmask = 0;
3387 if (modifierKeys.HasFlag(ModifierKeys.Alt))
3388 bitmask |= 1; // CDP Alt
3389 if (modifierKeys.HasFlag(ModifierKeys.Control))
3390 bitmask |= 2; // CDP Ctrl
3391 if (modifierKeys.HasFlag(ModifierKeys.Windows)) // Maps to Meta/Command
3392 bitmask |= 4; // CDP Meta
3393 if (modifierKeys.HasFlag(ModifierKeys.Shift))
3394 bitmask |= 8; // CDP Shift
3395 return bitmask;
3402 public string GetModifiersString(int bitmask)
3403 {
3404 var modifiers = new List<string>();
3405
3406 if ((bitmask & 1) != 0) // Check for Alt
3407 modifiers.Add("Alt");
3408 if ((bitmask & 2) != 0) // Check for Control
3409 modifiers.Add("Control");
3410 if ((bitmask & 4) != 0) // Check for Windows/Meta
3411 modifiers.Add("Windows");
3412 if ((bitmask & 8) != 0) // Check for Shift
3413 modifiers.Add("Shift");
3414
3415 return modifiers.Count > 0 ? string.Join("+", modifiers) : "None";
3416 }
3420 /// <returns>Fluent interface to continue building the request</returns>
3422 {
3423 WithEndpoint(ApiEndpoint.Restore);
3424 return this;
3425 }
3426
3432 public IAllowPuppeteerParametersOrExecution RightClickAndDownload(string selector)
3434 WithEndpoint(ApiEndpoint.RightClickAndDownload);
3435 WithElementId(selector);
3436 return this;
3437 }
3438
3440
3444 public IAllowPuppeteerParametersOrExecution LeftClickAndDownload(string selector)
3445 {
3446 WithEndpoint(ApiEndpoint.LeftClickAndDownload);
3447 WithElementId(selector);
3448 return this;
3449 }
3450
3456 public IAllowPuppeteerExecution ScrollWindowByHorizontal(int pixels)
3457 {
3458 WithEndpoint(ApiEndpoint.ScrollWindowByHorizontal);
3459 SetParameter("hPixels", pixels);
3460 return this;
3461 }
3467
3469 {
3470 WithEndpoint(ApiEndpoint.ScrollWindowByVertical);
3471 SetParameter("vPixels", pixels);
3472 return this;
3473 }
3474
3477
3481 public IAllowPuppeteerExecution SendString(string text, int delayMs = 0)
3482 {
3483 WithEndpoint(ApiEndpoint.SendString);
3484 WithText(text);
3485 SetParameter("typingDelay", delayMs);
3486 return this;
3487 }
3488
3489
3495 {
3496 VkCodeToDomKeyConverter.TryConvertVkCodeToDomKey(vkcode, out string key, out string code);
3497
3498 WithEndpoint(ApiEndpoint.SendKey);
3499 SetParameter("key", key);
3500 SetParameter("code", code);
3501 SetParameter("vk", (int)vkcode);
3502 return this;
3503 }
3504
3511 {
3512 WithEndpoint(ApiEndpoint.OverrideReferrer).WithReferrer("https://www.google.com");
3513 return this;
3514 }
3515
3517 /// Configure this client to stop any active screen casting.
3518 /// </summary>
3519 /// <returns>Fluent interface to continue building the request</returns>
3521 {
3522 WithEndpoint(ApiEndpoint.StopCasting);
3523 return this;
3524 }
3525
3529 /// <param name="selector">CSS selector of an element within the form</param>
3530 /// <returns>Fluent interface to continue building the request</returns>
3531 public IAllowPuppeteerExecution SubmitForm(string selector)
3532 {
3533 WithEndpoint(ApiEndpoint.SubmitForm);
3534 WithElementId(selector);
3535 return this;
3536 }
3537
3541 /// <returns>Fluent interface to continue building the request</returns>
3543 {
3544 WithEndpoint(ApiEndpoint.SwitchToDefaultContent);
3545 return this;
3546 }
3547
3552 /// <returns>Fluent interface to continue building the request</returns>
3553 public IAllowPuppeteerExecution SwitchToElement(string selector)
3554 {
3555 WithEndpoint(ApiEndpoint.SwitchToElement);
3556 WithElementId(selector);
3557 return this;
3558 }
3559
3564 /// <returns>Fluent interface to continue building the request</returns>
3565 public IAllowPuppeteerExecution SwitchToFrame(string selector)
3566 {
3567 WithEndpoint(ApiEndpoint.SwitchToFrame);
3568 WithElementId(selector);
3569 return this;
3570 }
3571
3578 {
3579 WithEndpoint(ApiEndpoint.SwitchToShadowRoot);
3580 WithElementId(selector);
3581 return this;
3582 }
3583
3590 {
3591 WithEndpoint(ApiEndpoint.InjectScript);
3592 SetParameter("script", script);
3593 return this;
3594 }
3595
3600 public IAllowPuppeteerExecution ClearInjectedScripts()
3602 WithEndpoint(ApiEndpoint.ClearInjectedScripts);
3603 return this;
3604 }
3605
3607
3612 public IAllowPuppeteerExecution DownloadTo(string downloadPath)
3614 // the parameter only. setting the endpoint here overwrote whatever it was chained onto, so
3615 // LeftClickAndDownload(handle).DownloadTo(path) called set-download-filename and never clicked
3616 SetParameter("downloadPath", downloadPath);
3617 return this;
3618 }
3619
3624 /// <returns>Fluent interface to continue building the request</returns>
3625 public IAllowPuppeteerExecution FireChangeEvent(string selector)
3626 {
3627 WithEndpoint(ApiEndpoint.FireChangeEvent);
3628 WithElementId(selector);
3629 return this;
3630 }
3631
3635 /// <param name="selector">CSS selector of the element</param>
3636 /// <returns>Fluent interface to continue building the request</returns>
3637 public IAllowPuppeteerExecution Focus(string selector)
3638 {
3639 WithEndpoint(ApiEndpoint.Focus);
3640 WithElementId(selector);
3641 return this;
3642 }
3643
3647 /// <param name="backendNodeId">Backend node id of the element</param>
3648 /// <returns>Fluent interface to continue building the request</returns>
3649 public IAllowPuppeteerExecution GetBoundingClientRect(string backendNodeId)
3650 {
3651 WithEndpoint(ApiEndpoint.GetBoundingClientRect);
3652 WithElementId(backendNodeId);
3653 return this;
3654 }
3655
3659 /// <param name="selector">CSS selector of the element</param>
3660 /// <returns>Fluent interface to continue building the request</returns>
3662 {
3663 WithEndpoint(ApiEndpoint.GetElementAttributeHash);
3664 WithElementId(selector);
3665 return this;
3666 }
3667
3671 /// <returns>Fluent interface to continue building the request</returns>
3673 {
3674 WithEndpoint(ApiEndpoint.GetPageSource);
3675 return this;
3676 }
3677
3684 {
3685 WithEndpoint(ApiEndpoint.GetParentNode);
3686 WithElementId(selector);
3687 return this;
3688 }
3689
3696 {
3697 WithEndpoint(ApiEndpoint.GetContentAndCss);
3698 WithElementId(selector);
3699 return this;
3700 }
3701
3708 {
3709 WithEndpoint(ApiEndpoint.GetCssAttributes);
3710 WithElementId(selector);
3711 return this;
3712 }
3713
3720 {
3721 WithEndpoint(ApiEndpoint.GetDomAttributes);
3722 WithElementId(selector);
3723 return this;
3724 }
3725
3732 {
3733 WithEndpoint(ApiEndpoint.GetDomProperties);
3734 WithElementId(selector);
3735 return this;
3736 }
3737
3742 /// <returns>Fluent interface to continue building the request</returns>
3743 public IAllowPuppeteerExecution HideElement(string selector)
3744 {
3745 WithEndpoint(ApiEndpoint.HideElement);
3746 WithElementId(selector);
3747 return this;
3748 }
3749
3753 /// <param name="selector">CSS selector of the element</param>
3754 /// <returns>Fluent interface to continue building the request</returns>
3755 public IAllowPuppeteerExecution IsClickable(string selector)
3756 {
3757 WithEndpoint(ApiEndpoint.IsClickable);
3758 WithElementId(selector);
3759 return this;
3760 }
3761
3765 /// <param name="selector">CSS selector of the element</param>
3766 /// <returns>Fluent interface to continue building the request</returns>
3767 public IAllowPuppeteerExecution IsDisplayed(string selector)
3768 {
3769 WithEndpoint(ApiEndpoint.IsDisplayed);
3770 WithElementId(selector);
3771 return this;
3772 }
3773
3777 /// <param name="selector">CSS selector of the element</param>
3778 /// <returns>Fluent interface to continue building the request</returns>
3779 public IAllowPuppeteerExecution IsEnabled(string selector)
3780 {
3781 WithEndpoint(ApiEndpoint.IsEnabled);
3782 WithElementId(selector);
3783 return this;
3784 }
3785
3790 public IAllowPuppeteerExecution IsEndOfPage()
3791 {
3792 WithEndpoint(ApiEndpoint.IsEndOfPage);
3793 return this;
3794 }
3795
3799 /// <param name="element">The element to check</param>
3800 /// <returns>Fluent interface to continue building the request</returns>
3802 {
3803 WithEndpoint(ApiEndpoint.IsVisibleInViewport);
3804 SetParameter("gpalElements", new List<GPALElement>() { (GPALElement)element });
3805 return this;
3806 }
3807
3813 public IAllowPuppeteerExecution IsVisibleInViewport(string backendNodeId)
3814 {
3815 WithEndpoint(ApiEndpoint.IsVisibleInViewport);
3816 WithElementId(backendNodeId);
3817 return this;
3818 }
3819
3821 /// Configure this client to left-click the given element.
3822 /// </summary>
3823 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, element id, or a viewport Point for a target with no DOM handle</param>
3824 /// <returns>Fluent interface to continue building the request</returns>
3825 public IAllowPuppeteerParametersOrExecution LeftClick(dynamic elementOrelementId)
3826 {
3827 if (elementOrelementId is System.Drawing.Point point)
3828 return WithClickPoint(point, ClickType.LeftClick);
3829
3830 WithEndpoint(ApiEndpoint.LeftClick);
3831
3832 if (elementOrelementId is GPALElement element)
3833 WithElements(new List<GPALElement>() { element });
3834 else if (elementOrelementId is List<GPALElement> elements)
3835 WithElements(elements);
3836 else
3837 WithElementId(elementOrelementId);
3838
3839 return this;
3840 }
3841
3843 /// Configure this client to middle-click the given element.
3844 /// </summary>
3845 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the target</param>
3846 /// <returns>Fluent interface to continue building the request</returns>
3847 public IAllowPuppeteerParametersOrExecution MiddleClick(dynamic elementOrelementId)
3848 {
3849 if (elementOrelementId is System.Drawing.Point point)
3850 return WithClickPoint(point, ClickType.MiddleClick);
3851
3852 WithEndpoint(ApiEndpoint.MiddleClick);
3853
3854 if (elementOrelementId is GPALElement element)
3855 WithElements(new List<GPALElement>() { element });
3856 else if (elementOrelementId is List<GPALElement> elements)
3857 WithElements(elements);
3858 else
3859 WithElementId(elementOrelementId);
3860
3861 return this;
3862 }
3863
3865 /// Configure this client to double left-click the given element.
3866 /// </summary>
3867 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the target</param>
3868 /// <returns>Fluent interface to continue building the request</returns>
3869 public IAllowPuppeteerParametersOrExecution LeftDoubleClick(dynamic elementOrelementId)
3870 {
3871 if (elementOrelementId is System.Drawing.Point point)
3872 return WithClickPoint(point, ClickType.LeftDoubleClick);
3873
3874 WithEndpoint(ApiEndpoint.LeftDoubleClick);
3875
3876 if (elementOrelementId is GPALElement element)
3877 WithElements(new List<GPALElement>() { element });
3878 else if (elementOrelementId is List<GPALElement> elements)
3879 WithElements(elements);
3880 else
3881 WithElementId(elementOrelementId);
3882
3883 return this;
3884 }
3885
3887 /// Configure this client to right-click the given element.
3888 /// </summary>
3889 /// <param name="elementOrelementId">A GPALElement, list of GPALElements, or element id identifying the target</param>
3890 /// <returns>Fluent interface to continue building the request</returns>
3891 public IAllowPuppeteerParametersOrExecution RightClick(dynamic elementOrelementId)
3892 {
3893 if (elementOrelementId is System.Drawing.Point point)
3894 return WithClickPoint(point, ClickType.RightClick);
3895
3896 WithEndpoint(ApiEndpoint.RightClick);
3898 if (elementOrelementId is GPALElement element)
3899 WithElements(new List<GPALElement>() { element });
3900 else if (elementOrelementId is List<GPALElement> elements)
3901 WithElements(elements);
3902 else
3903 WithElementId(elementOrelementId);
3904
3905 return this;
3906 }
3907
3910
3913 public IAllowPuppeteerSelectOptions SelectClick(string elementHandle) // objectid
3914 {
3915 WithEndpoint(ApiEndpoint.SelectClick);
3916 WithElementId(elementHandle);
3917
3918 return this;
3919 }
3920
3926 {
3927 SetParameter("selectValue", value);
3928 return this;
3929 }
3930
3936 {
3937 SetParameter("selectIndex", index);
3938 return this;
3939 }
3940
3945 public IAllowPuppeteerExecution ScrollIntoView(dynamic elementOrelementId)
3946 {
3947 WithEndpoint(ApiEndpoint.ScrollIntoView);
3948
3949 if (elementOrelementId is GPALElement element)
3950 WithElements(new List<GPALElement>() { element });
3951 else if (elementOrelementId is List<GPALElement> elements)
3952 WithElements(elements);
3953 else
3954 WithElementId(elementOrelementId);
3956 return this;
3957 }
3958
3960
3963 public IAllowPuppeteerExecution WindowInnerHeight()
3964 {
3965 WithEndpoint(ApiEndpoint.WindowInnerHeight);
3966 return this;
3967 }
3968
3970
3973 public IAllowPuppeteerExecution WindowInnerWidth()
3974 {
3975 WithEndpoint(ApiEndpoint.WindowInnerWidth);
3976 return this;
3977 }
3978
3980
3983 public IAllowPuppeteerExecution WindowOuterHeight()
3984 {
3985 WithEndpoint(ApiEndpoint.WindowOuterHeight);
3986 return this;
3987 }
3988
3990
3993 public IAllowPuppeteerExecution WindowOuterWidth()
3994 {
3995 WithEndpoint(ApiEndpoint.WindowOuterWidth);
3996 return this;
3997 }
3998
4002
4003 public IAllowPuppeteerExecution WindowPageOffsetX()
4004 {
4005 WithEndpoint(ApiEndpoint.WindowPageOffsetX);
4006 return this;
4007 }
4008
4013 public IAllowPuppeteerExecution WindowPageOffsetY()
4014 {
4015 WithEndpoint(ApiEndpoint.WindowPageOffsetY);
4016 return this;
4017 }
4018
4023 public IAllowPuppeteerExecution WindowScreenLeft()
4024 {
4025 WithEndpoint(ApiEndpoint.WindowScreenLeft);
4026 return this;
4027 }
4028
4032
4033 public IAllowPuppeteerExecution WindowScreenTop()
4034 {
4035 WithEndpoint(ApiEndpoint.WindowScreenTop);
4036 return this;
4037 }
4044 public IAllowPuppeteerExecution Hover(string selector)
4045 {
4046 WithEndpoint(ApiEndpoint.Hover);
4047 WithElementId(selector);
4048 return this;
4049 }
4056
4058 private IAllowPuppeteerParametersOrExecution WithClickPoint(System.Drawing.Point point, ClickType clickType)
4059 {
4060 WithEndpoint(ApiEndpoint.ClickPoint);
4061 SetParameter("x", point.X);
4062 SetParameter("y", point.Y);
4063 SetParameter("clickType", (int)clickType);
4064 return this;
4065 }
4066
4068
4073 public IAllowPuppeteerExecution MoveTo(System.Drawing.Point point)
4074 {
4075 WithEndpoint(ApiEndpoint.MoveToPoint);
4076 SetParameter("x", point.X);
4077 SetParameter("y", point.Y);
4078 return this;
4079 }
4080
4082
4086 public IAllowPuppeteerDragAndDrop DragAndDrop(string selector)
4087 {
4088 WithEndpoint(ApiEndpoint.DragAndDrop);
4089 WithElementId(selector);
4090 return this;
4091 }
4092
4094
4098 public IAllowPuppeteerExecution QuerySelector(string css)
4099 {
4100 WithEndpoint(ApiEndpoint.QuerySelector);
4101 WithCss(css);
4102 return this;
4103 }
4104
4106
4110 public IAllowPuppeteerExecution QuerySelectors(string css)
4112 WithEndpoint(ApiEndpoint.QuerySelectors);
4113 WithCss(css);
4114 return this;
4115 }
4116
4122 /// <returns>Fluent interface to continue building the request</returns>
4123 public IAllowPuppeteerExecution ScrollWindow(int hPixels, int vPixels)
4124 {
4125 WithEndpoint(ApiEndpoint.ScrollWindow);
4126 WithHPixels(hPixels);
4127 WithVPixels(vPixels);
4128 return this;
4129 }
4130
4134 /// <param name="xpath">XPath expression</param>
4135 /// <returns>Fluent interface to continue building the request</returns>
4136 public IAllowPuppeteerExecution Evaluate(string xpath)
4137 {
4138 WithEndpoint(ApiEndpoint.Evaluate);
4139 WithXPath(xpath);
4140 return this;
4141 }
4142
4146 /// <param name="xpath">XPath expression</param>
4147 /// <returns>Fluent interface to continue building the request</returns>
4148 public IAllowPuppeteerExecution EvaluateAll(string xpath)
4149 {
4150 WithEndpoint(ApiEndpoint.EvaluateAll);
4151 WithXPath(xpath);
4152 return this;
4153 }
4154
4156 /// Configure this client to clear any overridden HTTP referrer.
4157 /// </summary>
4158 /// <returns>Fluent interface to continue building the request</returns>
4160 {
4161 WithEndpoint(ApiEndpoint.ClearReferrer);
4162 return this;
4163 }
4164
4169 /// <returns>Fluent interface to continue building the request</returns>
4170 public IAllowPuppeteerAttribute SetAttribute(string selector)
4171 {
4172 WithEndpoint(ApiEndpoint.SetAttribute);
4173 WithElementId(selector);
4174 return this;
4175 }
4176
4182 public IAllowPuppeteerRangeValue SetRange(string selector)
4183 {
4184 WithEndpoint(ApiEndpoint.SetRange);
4185 WithElementId(selector);
4186 return this;
4187 }
4188
4191
4194 public IAllowPuppeteerExecution WithRangeValue(int rangeValue)
4195 {
4196 SetParameter("rangeValue", rangeValue.ToString());
4197 return this;
4198 }
4201
4204 public IAllowPuppeteerScrollElement ScrollElement(string selector)
4205 {
4206 WithEndpoint(ApiEndpoint.ScrollElement);
4207 WithElementId(selector);
4208 return this;
4209 }
4210
4213
4217 public IAllowPuppeteerExecution ScrollElement(int hPixels, int vPixels)
4218 {
4219 WithEndpoint(ApiEndpoint.ScrollElement);
4220 _hPixels = hPixels;
4221 _vPixels = vPixels;
4222 return this;
4223 }
4224
4233 public IAllowPuppeteerExecution SetValueFrom(string destSelector)
4234 {
4235 WithEndpoint(ApiEndpoint.SetValueFromElement);
4236 SetParameter("destSelector", destSelector);
4237 return this;
4238 }
4239
4244 public IAllowPuppeteerExecution TopBrowser()
4245 {
4246 WithEndpoint(ApiEndpoint.TopBrowser);
4247 return this;
4248 }
4249
4251 /// Configure this client to retrieve an attribute from the given element; combine with WithAttribute to specify which attribute.
4252 /// </summary>
4253 /// <param name="selector">CSS selector of the element</param>
4254 /// <returns>Fluent interface to continue building the request</returns>
4255 public IAllowPuppeteerAttribute GetAttribute(string selector)
4256 {
4257 WithEndpoint(ApiEndpoint.GetAttribute);
4258 WithElementId(selector);
4259 return this;
4260 }
4261
4267 private object GetResultFromIndex(int index)
4268 {
4269 if (index < 0 || index >= _executionResults.Count) return null;
4270 return _executionResults[index].Result;
4271 }
4272
4278 private object GetResultFromName(string name)
4279 {
4280 return _executionResults.FirstOrDefault(entry => entry.Name == name && entry.Iteration == _currentIteration && entry.TargetId == _currentTargetId).Result;
4281 }
4282
4288 private void SetParameter(string key, object value)
4289 {
4290 if (_parameters == null) _parameters = new Dictionary<string, object>();
4291 ((Dictionary<string, object>)_parameters)[key] = value;
4292 }
4293
4299 public IAllowPuppeteerStorageOptions SetStorage(WebsiteStorageType storageType)
4300 {
4301 WithEndpoint(ApiEndpoint.SetStorage);
4302 SetParameter("storageType", storageType.ToString());
4303
4304 return this;
4305 }
4309 private void Reset()
4310 {
4311 _endpoint = "";
4312 _parameters = null;
4313 _customEndpoint = false;
4314 _nextResultName = null; // don't save results unless asked, potential memory leak
4315 _timeoutMs = 30_000;
4316 _pruneMs = 3_000;
4317 _maxConnections = 0;
4318 _hPixels = 0;
4319 _vPixels = 0;
4320 _attribute = null;
4321 _value = null;
4322 _text = null;
4323 _currentIteration = 1;
4324 _loopCondition = null;
4325 _loopType = null;
4326 _whileLoopTimeoutMs = 0;
4327 _whileLoopMaxIterations = 0;
4328 }
4329 // In PuppeteerClient.cs, updated ExecuteInternalAsync
4334 private async Task<object> ExecuteInternalAsync()
4335 {
4336 if (true == _puppeteerCommunicator._windowSessionsQueue.IsEmpty)
4337 {
4338 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"No browser windows open. Cannot call endpoint [{_endpoint}]", null, GPALObjectType.None);
4339 return null;
4340 }
4341
4342 if (string.IsNullOrEmpty(_endpoint))
4343 {
4344 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{_name}] No endpoint specified", null, GPALObjectType.None);
4345 return null;
4346 }
4347
4348 var parameters = _parameters as IDictionary<string, object> ??
4349 (_parameters?.GetType().GetProperties().ToDictionary(p => p.Name, p => p.GetValue(_parameters)) ?? new Dictionary<string, object>());
4350
4351 if (!_customEndpoint && !restClient.ValidateParameters(_apiEndpoint, parameters))
4352 {
4353 return null;
4354 }
4355
4356 object result = null;
4357
4358 if (_loopCondition != null && !string.IsNullOrEmpty(_loopType))
4359 {
4360 int maxIterations = _whileLoopMaxIterations > 0 ? _whileLoopMaxIterations : 1000;
4361 int iteration = 0;
4362 var startTime = DateTime.UtcNow;
4363 int timeoutMs = _whileLoopTimeoutMs > 0 ? _whileLoopTimeoutMs : int.MaxValue;
4364
4365 if (_loopType == "While")
4366 {
4367 // CRITICAL: If _loopCondition checks UI elements, this WILL deadlock if the UI thread is blocked.
4368 while (_loopCondition() && iteration < maxIterations && (DateTime.UtcNow - startTime).TotalMilliseconds < timeoutMs)
4369 {
4370 // Fix: Added ConfigureAwait(false)
4371 result = await ExecuteEndpoint(_endpoint, parameters).ConfigureAwait(false);
4372 iteration++;
4373 // Fix: Added ConfigureAwait(false)
4374 await Task.Delay(100).ConfigureAwait(false);
4375 }
4376 }
4377 else if (_loopType == "Until")
4378 {
4379 do
4380 {
4381 // Fix: Added ConfigureAwait(false)
4382 result = await ExecuteEndpoint(_endpoint, parameters).ConfigureAwait(false);
4383 iteration++;
4384 // Fix: Added ConfigureAwait(false)
4385 await Task.Delay(100).ConfigureAwait(false);
4386 } while (!_loopCondition() && iteration < maxIterations && (DateTime.UtcNow - startTime).TotalMilliseconds < timeoutMs);
4387 }
4388
4389 if (iteration >= maxIterations)
4390 {
4391 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{_name}] Loop exceeded max iterations [{maxIterations}]", null, GPALObjectType.None);
4392 }
4393 else if ((DateTime.UtcNow - startTime).TotalMilliseconds >= timeoutMs)
4394 {
4395 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{_name}] Loop exceeded timeout [{timeoutMs}ms]", null, GPALObjectType.None);
4396 }
4397 }
4398 else
4399 {
4400 // Fix: Added ConfigureAwait(false)
4401 result = await ExecuteEndpoint(_endpoint, parameters).ConfigureAwait(false);
4402 }
4403
4404 if (!string.IsNullOrEmpty(_saveFile))
4405 {
4406 SaveResponseToFile(result?.ToString(), _saveFile);
4407 }
4408
4409 if (!string.IsNullOrEmpty(_nextResultName))
4410 {
4411 _executionResults.Add((_nextResultName, result, _executionResults.Where(r => r.Name == _nextResultName && r.TargetId == _currentTargetId).Select(r => r.Iteration).DefaultIfEmpty(0).Max() + 1, _currentTargetId));
4412 _workflowStates.Add((_endpoint, _parameters, _nextResultName));
4413
4414 }
4415
4416 string valueStr = (result as JObject)?["result"]?["value"]?.Value<string>();
4417 if (!string.IsNullOrEmpty(valueStr))
4418 {
4419 int statusCode = 0;
4420 try
4421 {
4422 statusCode = (int) JObject.Parse(valueStr)["status"]?.Value<int>();
4423 }
4424 catch { }
4425
4426 if (statusCode > 0)
4427 {
4428 _puppeteerCommunicator.ServerResponseCode = statusCode;
4429 if (statusCode < 200 || statusCode >= 300)
4430 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"[{_name}] Endpoint [{_endpoint}] returned HTTP [{statusCode}]", null, GPALObjectType.None);
4431 }
4432 }
4433
4434 // Reset loop state
4435 _loopCondition = null;
4436 _loopType = null;
4437 _whileLoopTimeoutMs = 0;
4438 _whileLoopMaxIterations = 0;
4439
4440 return result;
4441 }
4442
4448 /// <returns>Task that resolves to the handler's result, or null if no handler is registered</returns>
4449 private async Task<object> ExecuteEndpoint(string endpoint, IDictionary<string, object> parameters)
4450 {
4451 if (endpointMap.TryGetValue(endpoint, out var handler))
4452 {
4453 // always use the most recent target/session
4454 var sessionId = _puppeteerCommunicator.GetEffectiveSessionId();
4455 if (sessionId == null)
4456 {
4457 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "No active session for execution. Using null.", this, GPALObjectType.Puppeteer);
4458 }
4459
4460 return await handler(_puppeteerCommunicator, parameters, sessionId).ConfigureAwait(false);
4461 }
4462
4463 return null;
4464 }
4465
4466 #region Helpers
4472 private void SaveResponseToFile(string response, string filePath)
4473 {
4474 RESTClient.SaveResponseToFile(response, filePath);
4475 }
4476
4482 public IAllowPuppeteerParametersOrExecution UploadFrom(GPALFile fileToUpload)
4483 {
4484 if (1 < fileToUpload.Count)
4485 SetParameter("uploadPaths", fileToUpload);
4486 else
4487 SetParameter("uploadPath", fileToUpload.Filename);
4488 return this;
4489 }
4490
4496 public IAllowPuppeteerParametersOrExecution LeftClickAndUpload(dynamic elementOrelementId)
4497 {
4498 WithEndpoint(ApiEndpoint.Upload);
4499
4500 if (elementOrelementId is GPALElement element)
4501 WithElements(new List<GPALElement>() { element });
4502 else if (elementOrelementId is List<GPALElement> elements)
4503 WithElements(elements);
4504 else
4505 WithElementId(elementOrelementId);
4506
4507 return this;
4508 }
4509 #endregion Helpers
4510 }
4511}
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.
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.
IAllowPuppeteerWorkflow WithWorkflow(Action< RESTClient > workflow)
Adds a workflow step that invokes the given action against this client.
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.
IAllowPuppeteerParametersOrExecution While(Func< bool > condition)
Configures execution to repeat while the given condition returns true, subject to WhileLoopTimeout/Wh...
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).
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)
Resets the client and sets a custom endpoint path to call directly, bypassing the built-in ApiEndpoin...
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 StealthOverrideReferrer()
Configure this client to override the HTTP referrer with https://www.google.com, to mimic arriving fr...
IAllowPuppeteerExecution StopCasting()
Configure this client to stop any active screen casting.
IAllowPuppeteerExecution GetReadyStatus(string sessionToken=null)
Configure this client to query the puppeteer server's ready status, optionally for a specific session...
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.
IAllowPuppeteerWorkflow WhileLoopMaxIterations(int maxIterations)
Sets the maximum number of iterations a subsequent While/Until loop may run.
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.
IAllowPuppeteerCommunicator WithAPIBase(string url)
Sets a custom REST API base URL to use instead of the default, for connecting to a non-default puppet...
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.
IAllowPuppeteerCommunicator WithUsePipes()
Clears any custom API base URL so the puppeteer communicator connects via pipes instead of HTTP.
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 WithEncoding(ContentEncoding encoding)
Sets the content encoding used for requests sent to the puppeteer communicator.
IAllowPuppeteerWorkflow WhileLoopTimeout(int timeoutMs)
Sets the maximum time a subsequent While/Until loop may run before timing out.
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 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.
IAllowPuppeteerExecution SubmitForm(string selector)
Configure this client to submit the form containing the given element.
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 ...
float Y
Gets or sets the Y coordinate.
float Height
Gets or sets the height.
float Width
Gets or sets the width.
float X
Gets or sets the X coordinate.
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 TagName
HTML tag name of the element.
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.
ClientRectangle BoundingRect
Client bounding rectangle with detailed coordinates.
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:716
string Filename
We have only one file, accessing it.
Definition GPALFile.cs:474
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:2406
Fluent REST client for making API calls with a chained interface. Supports defining workflows,...
Definition RESTClient.cs:57
static bool TryConvertVkCodeToDomKey(byte vkCode, out string key, out string code)
Converts a Windows VK code to DOM KeyboardEvent key and code values.